diff --git a/CHANGELOG.md b/CHANGELOG.md index cc62947..8a4d82b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 0.10.4 - 2021-08-27 + + +----- + +### Release Notes for [0.10.4](https://github.com/open-code-modeling/php-code-ast/milestone/24) + +0.10.x bugfix release (patch) + +### 0.10.4 + +- Total issues resolved: **1** +- Total pull requests resolved: **0** +- Total contributors: **1** + +#### bug + + - [82: Exists check in NodeVisitor/ClassImplements not working properly](https://github.com/open-code-modeling/php-code-ast/issues/82) thanks to @sandrokeil + ## 0.10.3 - 2021-01-29 diff --git a/composer.json b/composer.json index 331c975..529cbf9 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "phpstan/phpstan": "^0.12.33", "phpstan/phpstan-strict-rules": "^0.12.4", "phpunit/phpunit": "^9.5.0", - "prooph/php-cs-fixer-config": "^v0.3.1", + "prooph/php-cs-fixer-config": "^v0.4.0", "roave/security-advisories": "dev-master" }, "minimum-stability": "dev", diff --git a/src/NodeVisitor/ClassImplements.php b/src/NodeVisitor/ClassImplements.php index 243c679..54c234f 100644 --- a/src/NodeVisitor/ClassImplements.php +++ b/src/NodeVisitor/ClassImplements.php @@ -67,24 +67,33 @@ public function afterTraverse(array $nodes): ?array private function filterImplements(array $nodes): array { - $implements = $this->implements; - foreach ($nodes as $node) { if ($node instanceof Namespace_) { foreach ($node->stmts as $stmt) { if ($stmt instanceof Stmt\Class_) { - foreach ($stmt->implements as $implementName) { - $implements = \array_filter($implements, static function (string $implement) use ($implementName) { - return $implement !== ($implementName instanceof FullyQualified - ? '\\' . $implementName->toString() - : (string) $implementName); - }); - } + return $this->filterClassImplements($stmt); } } + } elseif ($node instanceof Stmt\Class_) { + return $this->filterClassImplements($node); } } + return $this->implements; + } + + private function filterClassImplements(Stmt\Class_ $node): array + { + $implements = $this->implements; + + foreach ($node->implements as $implementName) { + $implements = \array_filter($implements, static function (string $implement) use ($implementName) { + return $implement !== ($implementName instanceof FullyQualified + ? '\\' . $implementName->toString() + : (string) $implementName); + }); + } + return $implements; } } diff --git a/src/NodeVisitor/NamespaceUse.php b/src/NodeVisitor/NamespaceUse.php index df7b13c..f0c2991 100644 --- a/src/NodeVisitor/NamespaceUse.php +++ b/src/NodeVisitor/NamespaceUse.php @@ -57,7 +57,7 @@ public function afterTraverse(array $nodes): ?array } \array_unshift($stmts, $useNamespace->getNode()); } - $node->stmts = $stmts; // @phpstan-ignore-line + $node->stmts = $stmts; } } diff --git a/tests/NodeVisitor/ClassImplementsTest.php b/tests/NodeVisitor/ClassImplementsTest.php new file mode 100644 index 0000000..8454fbb --- /dev/null +++ b/tests/NodeVisitor/ClassImplementsTest.php @@ -0,0 +1,217 @@ +parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); + $this->printer = new Standard(['shortArraySyntax' => true]); + } + + /** + * Values are: interfaces + * + * @return Generator + */ + public function provideImplements(): Generator + { + yield '\\Awesome\\AcmeClass' => [['\\Awesome\\AcmeClass']]; + yield '\\Foo' => [['\\Foo']]; + + yield '\\Awesome\\AcmeClass, \\My\\OtherInterface' => [['\\Awesome\\AcmeClass', '\\My\\OtherInterface']]; + yield '\\Foo, \\Bar' => [['\\Foo', '\\Bar']]; + + yield 'FirstInterface, SecondInterface, ThirdInterface' => [['FirstInterface', 'SecondInterface', 'ThirdInterface']]; + } + + /** + * @test + * @dataProvider provideImplements + * @param array $interfaces + */ + public function it_generates_class_implements_for_empty_file(array $interfaces): void + { + $ast = $this->parser->parse(''); + + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor(new ClassFile(new ClassGenerator('TestClass'))); + $nodeTraverser->addVisitor(new ClassImplements(...$interfaces)); + + $extends = \implode(', ', $interfaces); + + $expected = <<assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + + $ast = $this->parser->parse($expected); + $this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + } + + /** + * @test + * @dataProvider provideImplements + * @param array $interfaces + */ + public function it_checks_class_implements_for_existing_file(array $interfaces): void + { + $ast = $this->parser->parse('addVisitor(new ClassFile(new ClassGenerator('TestClass'))); + $nodeTraverser->addVisitor(new ClassImplements(...$interfaces)); + + $extends = \implode(', ', $interfaces); + + $expected = <<assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + + $ast = $this->parser->parse($expected); + $this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + } + + /** + * @test + * @dataProvider provideImplements + * @param array $interfaces + */ + public function it_generates_class_implements_with_namespace_for_empty_file(array $interfaces): void + { + $ast = $this->parser->parse(''); + + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor(new ClassNamespace('My\\Awesome\\Service')); + $nodeTraverser->addVisitor(new ClassFile(new ClassGenerator('TestClass'))); + $nodeTraverser->addVisitor(new ClassImplements(...$interfaces)); + + $extends = \implode(', ', $interfaces); + + $expected = <<assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + + $ast = $this->parser->parse($expected); + $this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + } + + /** + * @test + * @dataProvider provideImplements + * @param array $interfaces + */ + public function it_generates_class_implements_for_namespace_file(array $interfaces): void + { + $code = <<parser->parse($code); + + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor(new ClassFile(new ClassGenerator('TestClass'))); + $nodeTraverser->addVisitor(new ClassImplements(...$interfaces)); + + $extends = \implode(', ', $interfaces); + + $expected = <<assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + + $ast = $this->parser->parse($expected); + $this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + } + + /** + * @test + * @dataProvider provideImplements + * @param array $interfaces + */ + public function it_checks_class_implements_with_namespace_for_existing_file(array $interfaces): void + { + $code = <<parser->parse($code); + + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor(new ClassFile(new ClassGenerator('TestClass'))); + $nodeTraverser->addVisitor(new ClassImplements(...$interfaces)); + + $extends = \implode(', ', $interfaces); + + $expected = <<assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + + $ast = $this->parser->parse($expected); + $this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast))); + } +}