diff --git a/docs/bootstrap-inventory.md b/docs/bootstrap-inventory.md index 66ba038e..a1a0f920 100644 --- a/docs/bootstrap-inventory.md +++ b/docs/bootstrap-inventory.md @@ -8,9 +8,9 @@ Regenerate: `php script/bootstrap-inventory.php` | Metric | Count | |--------|------:| -| PHP files on vm.php path | 195 | +| PHP files on vm.php path | 196 | | Source constructs flagged (blockers) | 10 | -| Source constructs flagged (warnings) | 518 | +| Source constructs flagged (warnings) | 523 | ## Compiler CFG gaps (`lib/Compiler.php`) @@ -197,9 +197,10 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: | `lib/JIT/SuperglobalInit.php` | 0 | 3 | | `lib/JIT/ValueEchoHelper.php` | 0 | 1 | | `lib/JIT/Variable.php` | 0 | 18 | +| `lib/Lint/IncrementDetector.php` | 0 | 4 | | `lib/Lint/Issue.php` | 0 | 2 | | `lib/Lint/LintCompiler.php` | 0 | 6 | -| `lib/Lint/Linter.php` | 0 | 4 | +| `lib/Lint/Linter.php` | 0 | 5 | | `lib/Lint/UnsupportedRegistry.php` | 0 | 1 | | `lib/Module.php` | 0 | 1 | | `lib/ModuleAbstract.php` | 0 | 1 | @@ -1318,6 +1319,14 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: - new Variable (line 473) - 15 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler +### `lib/Lint/IncrementDetector.php` + +**Warnings** (review for bootstrap subset): +- new ParserFactory (line 34) +- new NodeTraverser (line 56) +- new Issue (line 63) +- 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler + ### `lib/Lint/Issue.php` **Warnings** (review for bootstrap subset): @@ -1338,8 +1347,9 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: **Warnings** (review for bootstrap subset): - new Runtime (line 24) -- new State (line 83) -- new LintCompiler (line 98) +- new IncrementDetector (line 44) +- new State (line 86) +- new LintCompiler (line 101) - 9 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler ### `lib/Lint/UnsupportedRegistry.php` diff --git a/docs/unsupported-syntax.md b/docs/unsupported-syntax.md index 7349f48a..b2e28723 100644 --- a/docs/unsupported-syntax.md +++ b/docs/unsupported-syntax.md @@ -27,6 +27,7 @@ Exit code `0` when the entry (and best-effort `include`/`require` targets with s | `Expr_Yield`, `Expr_YieldFrom` | [#167](https://github.com/PurHur/php-compiler/issues/167) | | `Expr_Closure` | [#72](https://github.com/PurHur/php-compiler/issues/72) | | `Expr_ArrowFunction` | [#142](https://github.com/PurHur/php-compiler/issues/142) | +| `Expr_PreInc`, `Expr_PostInc`, `Expr_PreDec`, `Expr_PostDec` (`++`/`--`) | [#137](https://github.com/PurHur/php-compiler/issues/137) | | `Expr_New` (non-trivial) | [#136](https://github.com/PurHur/php-compiler/issues/136) | | Named arguments, traits, enums | [#168](https://github.com/PurHur/php-compiler/issues/168), [#169](https://github.com/PurHur/php-compiler/issues/169) | diff --git a/lib/Lint/IncrementDetector.php b/lib/Lint/IncrementDetector.php new file mode 100644 index 00000000..965263ae --- /dev/null +++ b/lib/Lint/IncrementDetector.php @@ -0,0 +1,74 @@ +, string> */ + public const NODE_TO_KIND = [ + PreInc::class => 'Expr_PreInc', + PostInc::class => 'Expr_PostInc', + PreDec::class => 'Expr_PreDec', + PostDec::class => 'Expr_PostDec', + ]; + + /** + * @return list + */ + public function detect(string $code, string $filename): array + { + $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); + try { + $ast = $parser->parse($code); + } catch (\PhpParser\Error $e) { + return []; + } + if (!is_array($ast)) { + return []; + } + + $visitor = new class extends NodeVisitorAbstract { + /** @var list */ + public array $hits = []; + + public function enterNode(Node $node) + { + if (isset(IncrementDetector::NODE_TO_KIND[get_class($node)])) { + $this->hits[] = [$node->getStartLine(), IncrementDetector::NODE_TO_KIND[get_class($node)]]; + } + } + }; + + $traverser = new NodeTraverser(); + $traverser->addVisitor($visitor); + $traverser->traverse($ast); + + $issues = []; + foreach ($visitor->hits as [$line, $kind]) { + $tracking = UnsupportedRegistry::trackingIssueForKind($kind); + $issues[] = new Issue( + $filename, + $line, + $kind, + "Unsupported expression: {$kind}", + $tracking + ); + } + + return $issues; + } +} diff --git a/lib/Lint/Linter.php b/lib/Lint/Linter.php index 5557d4e1..417da85a 100644 --- a/lib/Lint/Linter.php +++ b/lib/Lint/Linter.php @@ -41,8 +41,11 @@ public function lintFile(string $filename): array */ public function lintSource(string $code, string $filename): array { + $issues = (new IncrementDetector())->detect($code, $filename); $script = $this->parseForLint($code, $filename); - $issues = $this->lintScript($script); + foreach ($this->lintScript($script) as $issue) { + $issues[] = $issue; + } $queue = [$filename => $script]; $seenFiles = [$filename => true]; diff --git a/lib/Lint/UnsupportedRegistry.php b/lib/Lint/UnsupportedRegistry.php index 39f51499..2f5a02d4 100644 --- a/lib/Lint/UnsupportedRegistry.php +++ b/lib/Lint/UnsupportedRegistry.php @@ -44,6 +44,10 @@ final class UnsupportedRegistry 'Expr_Closure' => 72, 'Stmt_Trait' => 168, 'Expr_NamedArgument' => 168, + 'Expr_PreInc' => 137, + 'Expr_PostInc' => 137, + 'Expr_PreDec' => 137, + 'Expr_PostDec' => 137, ]; public static function trackingIssueForKind(string $kind): ?int diff --git a/test/unit/LintTest.php b/test/unit/LintTest.php index 8774395a..80588996 100644 --- a/test/unit/LintTest.php +++ b/test/unit/LintTest.php @@ -52,6 +52,22 @@ public function testLintYieldReportsIssue167(): void $this->assertStringNotContainsString('#114', $exit['stdout']); } + public function testLintPreIncReportsIssue137(): void + { + $exit = $this->runLint(['-r', '++$i;']); + $this->assertSame(1, $exit['code']); + $this->assertStringContainsString('#137', $exit['stdout']); + $this->assertStringContainsString('Expr_PreInc', $exit['stdout']); + } + + public function testLintPostIncReportsIssue137(): void + { + $exit = $this->runLint(['-r', '$i++;']); + $this->assertSame(1, $exit['code']); + $this->assertStringContainsString('#137', $exit['stdout']); + $this->assertStringContainsString('Expr_PostInc', $exit['stdout']); + } + public function testLintCleanScriptExitsZero(): void { $code = ' ['Expr_YieldFrom', 167], 'closure' => ['Expr_Closure', 72], 'arrow function' => ['Expr_ArrowFunction', 142], + 'pre inc' => ['Expr_PreInc', 137], + 'post inc' => ['Expr_PostInc', 137], + 'pre dec' => ['Expr_PreDec', 137], + 'post dec' => ['Expr_PostDec', 137], ]; }