1<?php 2 3set_error_handler(function($_, $msg) { 4 throw new Exception($msg); 5}); 6 7if ($argc > 1) { 8 $dir = $argv[1]; 9} else { 10 $dir = __DIR__ . '/../..'; 11} 12if (!is_dir($dir)) { 13 echo "Directory $dir does not exist.\n"; 14 exit(1); 15} 16 17$it = new RecursiveIteratorIterator( 18 new RecursiveDirectoryIterator($dir), 19 RecursiveIteratorIterator::LEAVES_ONLY 20); 21 22$excludes = [ 23 // Bundled libraries / files. 24 'ext/bcmath/libbcmath/', 25 'ext/date/lib/', 26 'ext/fileinfo/data_file.c', 27 'ext/fileinfo/libmagic/', 28 'ext/gd/libgd/', 29 'ext/hash/sha3/', 30 'ext/hash/hash_whirlpool.c', 31 'ext/hash/php_hash_whirlpool_tables.h', 32 'ext/mbstring/libmbfl/', 33 'ext/mbstring/unicode_data.h', 34 'ext/opcache/jit/dynasm', 35 'ext/opcache/jit/libudis86', 36 'ext/opcache/jit/vtune', 37 'ext/pcre/pcre2lib/', 38 'ext/standard/html_tables/html_table_gen.php', 39 'sapi/cli/php_http_parser.c', 40 'sapi/cli/php_http_parser.h', 41 'sapi/litespeed/', 42 // Not a PHP file. 43 'ext/zlib/tests/data.inc', 44 // Flexible HEREDOC/NOWDOC tests are likely whitespace sensitive. 45 // TODO: Properly classify them. 46 'Zend/tests/flexible-', 47]; 48 49foreach ($it as $file) { 50 if (!$file->isFile()) { 51 continue; 52 } 53 54 $path = $file->getPathName(); 55 foreach ($excludes as $exclude) { 56 if (strpos($path, $exclude) !== false) { 57 continue 2; 58 } 59 } 60 61 $lang = getLanguageFromExtension($file->getExtension()); 62 if ($lang === null) { 63 continue; 64 } 65 66 $origCode = $code = file_get_contents($path); 67 68 if ($lang === 'c') { 69 $code = stripTrailingWhitespace($code); 70 // TODO: Avoid this for now. 71 // $code = reindentToTabs($code); 72 } else if ($lang === 'php') { 73 $code = stripTrailingWhitespace($code); 74 $code = reindentToSpaces($code); 75 } else if ($lang === 'phpt') { 76 $code = transformTestCode($code, function(string $code) { 77 $code = stripTrailingWhitespace($code); 78 $code = reindentToSpaces($code); 79 return $code; 80 }); 81 } 82 83 if ($origCode !== $code) { 84 file_put_contents($path, $code); 85 } 86} 87 88function stripTrailingWhitespace(string $code): string { 89 return preg_replace('/\h+$/m', '', $code); 90} 91 92function reindentToTabs(string $code): string { 93 return preg_replace_callback('/^ +/m', function(array $matches) { 94 $tabSize = 4; 95 $spaces = strlen($matches[0]); 96 $tabs = intdiv($spaces, $tabSize); 97 $spaces -= $tabs * $tabSize; 98 return str_repeat("\t", $tabs) . str_repeat(" ", $spaces); 99 }, $code); 100} 101 102function reindentToSpaces(string $code): string { 103 return preg_replace_callback('/^[ \t]+/m', function(array $matches) { 104 $tabSize = 4; 105 $indent = 0; 106 foreach (str_split($matches[0]) as $char) { 107 if ($char === ' ') { 108 $indent++; 109 } else { 110 $partialIndent = $indent % $tabSize; 111 if ($partialIndent === 0) { 112 $indent += $tabSize; 113 } else { 114 $indent += $tabSize - $partialIndent; 115 } 116 } 117 } 118 return str_repeat(" ", $indent); 119 }, $code); 120} 121 122function transformTestCode(string $code, callable $transformer): string { 123 // Don't transform whitespace-sensitive tests. 124 if (strpos($code, '--WHITESPACE_SENSITIVE--') !== false) { 125 return $code; 126 } 127 128 return preg_replace_callback( 129 '/(--(?:FILE|SKIPIF|CLEAN)--)(.+?)(?=--[A-Z_]+--)/s', 130 function(array $matches) use($transformer) { 131 return $matches[1] . $transformer($matches[2]); 132 }, 133 $code 134 ); 135} 136 137function getLanguageFromExtension(string $ext): ?string { 138 switch ($ext) { 139 case 'c': 140 case 'h': 141 case 'cpp': 142 case 'y': 143 case 'l': 144 case 're': 145 return 'c'; 146 case 'php': 147 case 'inc': 148 return 'php'; 149 case 'phpt': 150 return 'phpt'; 151 default: 152 return null; 153 } 154} 155