xref: /PHP-8.0/scripts/dev/tidy.php (revision 367f8452)
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