#!/usr/bin/env php 1) { $dir = $argv[1]; } else { $dir = __DIR__ . '/../..'; } if (!is_dir($dir)) { echo "Directory $dir does not exist.\n"; exit(1); } $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY ); $excludes = [ // Bundled libraries / files. 'ext/date/lib/', 'ext/dom/lexbor/', 'ext/fileinfo/data_file.c', 'ext/fileinfo/libmagic/', 'ext/gd/libgd/', 'ext/hash/sha3/', 'ext/hash/hash_whirlpool.c', 'ext/hash/php_hash_whirlpool_tables.h', 'ext/mbstring/libmbfl/', 'ext/mbstring/unicode_data.h', 'ext/opcache/jit/ir', 'ext/pcre/pcre2lib/', 'ext/standard/html_tables/html_table_gen.php', 'sapi/cli/php_http_parser.c', 'sapi/cli/php_http_parser.h', 'sapi/litespeed/', // Not a PHP file. 'ext/zlib/tests/data.inc', // Flexible HEREDOC/NOWDOC tests are likely whitespace sensitive. // TODO: Properly classify them. 'Zend/tests/flexible-', ]; foreach ($it as $file) { if (!$file->isFile()) { continue; } $path = $file->getPathName(); foreach ($excludes as $exclude) { if (strpos($path, $exclude) !== false) { continue 2; } } $lang = getLanguageFromExtension($file->getExtension()); if ($lang === null) { continue; } $origCode = $code = file_get_contents($path); if ($lang === 'c') { $code = stripTrailingWhitespace($code); // TODO: Avoid this for now. // $code = reindentToTabs($code); } else if ($lang === 'php') { $code = stripTrailingWhitespace($code); $code = reindentToSpaces($code); } else if ($lang === 'phpt') { $code = transformTestCode($code, function(string $code) { $code = stripTrailingWhitespace($code); $code = reindentToSpaces($code); return $code; }); } if ($origCode !== $code) { file_put_contents($path, $code); } } function stripTrailingWhitespace(string $code): string { return preg_replace('/\h+$/m', '', $code); } function reindentToTabs(string $code): string { return preg_replace_callback('/^ +/m', function(array $matches) { $tabSize = 4; $spaces = strlen($matches[0]); $tabs = intdiv($spaces, $tabSize); $spaces -= $tabs * $tabSize; return str_repeat("\t", $tabs) . str_repeat(" ", $spaces); }, $code); } function reindentToSpaces(string $code): string { return preg_replace_callback('/^[ \t]+/m', function(array $matches) { $tabSize = 4; $indent = 0; foreach (str_split($matches[0]) as $char) { if ($char === ' ') { $indent++; } else { $partialIndent = $indent % $tabSize; if ($partialIndent === 0) { $indent += $tabSize; } else { $indent += $tabSize - $partialIndent; } } } return str_repeat(" ", $indent); }, $code); } function transformTestCode(string $code, callable $transformer): string { // Don't transform whitespace-sensitive tests. if (strpos($code, '--WHITESPACE_SENSITIVE--') !== false) { return $code; } return preg_replace_callback( '/(--(?:FILE|SKIPIF|CLEAN)--)(.+?)(?=--[A-Z_]+--)/s', function(array $matches) use($transformer) { return $matches[1] . $transformer($matches[2]); }, $code ); } function getLanguageFromExtension(string $ext): ?string { switch ($ext) { case 'c': case 'h': case 'cpp': case 'y': case 'l': case 're': return 'c'; case 'php': case 'inc': return 'php'; case 'phpt': return 'phpt'; default: return null; } }