tidy.php 3.9 KB

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