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