xref: /PHP-Parser/bin/php-parse (revision 4b497045)
1#!/usr/bin/env php
2<?php
3
4foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
5    if (file_exists($file)) {
6        require $file;
7        break;
8    }
9}
10
11ini_set('xdebug.max_nesting_level', 3000);
12
13// Disable Xdebug var_dump() output truncation
14ini_set('xdebug.var_display_max_children', -1);
15ini_set('xdebug.var_display_max_data', -1);
16ini_set('xdebug.var_display_max_depth', -1);
17
18list($operations, $files, $attributes) = parseArgs($argv);
19
20/* Dump nodes by default */
21if (empty($operations)) {
22    $operations[] = 'dump';
23}
24
25if (empty($files)) {
26    showHelp("Must specify at least one file.");
27}
28
29$parser = (new PhpParser\ParserFactory())->createForVersion($attributes['version']);
30$dumper = new PhpParser\NodeDumper([
31    'dumpComments' => true,
32    'dumpPositions' => $attributes['with-positions'],
33]);
34$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
35
36$traverser = new PhpParser\NodeTraverser();
37$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
38
39foreach ($files as $file) {
40    if ($file === '-') {
41        $code = file_get_contents('php://stdin');
42        fwrite(STDERR, "====> Stdin:\n");
43    } else if (strpos($file, '<?php') === 0) {
44        $code = $file;
45        fwrite(STDERR, "====> Code $code\n");
46    } else {
47        if (!file_exists($file)) {
48            fwrite(STDERR, "File $file does not exist.\n");
49            exit(1);
50        }
51
52        $code = file_get_contents($file);
53        fwrite(STDERR, "====> File $file:\n");
54    }
55
56    if ($attributes['with-recovery']) {
57        $errorHandler = new PhpParser\ErrorHandler\Collecting;
58        $stmts = $parser->parse($code, $errorHandler);
59        foreach ($errorHandler->getErrors() as $error) {
60            $message = formatErrorMessage($error, $code, $attributes['with-column-info']);
61            fwrite(STDERR, $message . "\n");
62        }
63        if (null === $stmts) {
64            continue;
65        }
66    } else {
67        try {
68            $stmts = $parser->parse($code);
69        } catch (PhpParser\Error $error) {
70            $message = formatErrorMessage($error, $code, $attributes['with-column-info']);
71            fwrite(STDERR, $message . "\n");
72            exit(1);
73        }
74    }
75
76    foreach ($operations as $operation) {
77        if ('dump' === $operation) {
78            fwrite(STDERR, "==> Node dump:\n");
79            echo $dumper->dump($stmts, $code), "\n";
80        } elseif ('pretty-print' === $operation) {
81            fwrite(STDERR, "==> Pretty print:\n");
82            echo $prettyPrinter->prettyPrintFile($stmts), "\n";
83        } elseif ('json-dump' === $operation) {
84            fwrite(STDERR, "==> JSON dump:\n");
85            echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
86        } elseif ('var-dump' === $operation) {
87            fwrite(STDERR, "==> var_dump():\n");
88            var_dump($stmts);
89        } elseif ('resolve-names' === $operation) {
90            fwrite(STDERR, "==> Resolved names.\n");
91            $stmts = $traverser->traverse($stmts);
92        }
93    }
94}
95
96function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) {
97    if ($withColumnInfo && $e->hasColumnInfo()) {
98        return $e->getMessageWithColumnInfo($code);
99    } else {
100        return $e->getMessage();
101    }
102}
103
104function showHelp($error = '') {
105    if ($error) {
106        fwrite(STDERR, $error . "\n\n");
107    }
108    fwrite($error ? STDERR : STDOUT, <<<'OUTPUT'
109Usage: php-parse [operations] file1.php [file2.php ...]
110   or: php-parse [operations] "<?php code"
111Turn PHP source code into an abstract syntax tree.
112
113Operations is a list of the following options (--dump by default):
114
115    -d, --dump              Dump nodes using NodeDumper
116    -p, --pretty-print      Pretty print file using PrettyPrinter\Standard
117    -j, --json-dump         Print json_encode() result
118        --var-dump          var_dump() nodes (for exact structure)
119    -N, --resolve-names     Resolve names using NodeVisitor\NameResolver
120    -c, --with-column-info  Show column-numbers for errors (if available)
121    -P, --with-positions    Show positions in node dumps
122    -r, --with-recovery     Use parsing with error recovery
123        --version=VERSION   Target specific PHP version (default: newest)
124    -h, --help              Display this page
125
126Example:
127    php-parse -d -p -N -d file.php
128
129    Dumps nodes, pretty prints them, then resolves names and dumps them again.
130
131
132OUTPUT
133    );
134    exit($error ? 1 : 0);
135}
136
137function parseArgs($args) {
138    $operations = [];
139    $files = [];
140    $attributes = [
141        'with-column-info' => false,
142        'with-positions' => false,
143        'with-recovery' => false,
144        'version' => PhpParser\PhpVersion::getNewestSupported(),
145    ];
146
147    array_shift($args);
148    $parseOptions = true;
149    foreach ($args as $arg) {
150        if (!$parseOptions) {
151            $files[] = $arg;
152            continue;
153        }
154
155        switch ($arg) {
156            case '--dump':
157            case '-d':
158                $operations[] = 'dump';
159                break;
160            case '--pretty-print':
161            case '-p':
162                $operations[] = 'pretty-print';
163                break;
164            case '--json-dump':
165            case '-j':
166                $operations[] = 'json-dump';
167                break;
168            case '--var-dump':
169                $operations[] = 'var-dump';
170                break;
171            case '--resolve-names':
172            case '-N';
173                $operations[] = 'resolve-names';
174                break;
175            case '--with-column-info':
176            case '-c';
177                $attributes['with-column-info'] = true;
178                break;
179            case '--with-positions':
180            case '-P':
181                $attributes['with-positions'] = true;
182                break;
183            case '--with-recovery':
184            case '-r':
185                $attributes['with-recovery'] = true;
186                break;
187            case '--help':
188            case '-h';
189                showHelp();
190                break;
191            case '--':
192                $parseOptions = false;
193                break;
194            default:
195                if (preg_match('/^--version=(.*)$/', $arg, $matches)) {
196                    $attributes['version'] = PhpParser\PhpVersion::fromString($matches[1]);
197                } elseif ($arg[0] === '-' && \strlen($arg[0]) > 1) {
198                    showHelp("Invalid operation $arg.");
199                } else {
200                    $files[] = $arg;
201                }
202        }
203    }
204
205    return [$operations, $files, $attributes];
206}
207