diff --git a/composer.json b/composer.json index de2bcee..e82ae76 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require": { "php": ">=8.0", "nikic/php-parser": "^4 || ^5", - "php-static-analysis/attributes": "^0.1.13 || dev-main" + "php-static-analysis/attributes": "^0.1.14 || dev-main" }, "require-dev": { "php-static-analysis/phpstan-extension": "dev-main", diff --git a/src/AttributeNodeVisitor.php b/src/AttributeNodeVisitor.php index f35f9bf..5e9fada 100644 --- a/src/AttributeNodeVisitor.php +++ b/src/AttributeNodeVisitor.php @@ -21,6 +21,8 @@ use PhpStaticAnalysis\Attributes\Property; use PhpStaticAnalysis\Attributes\PropertyRead; use PhpStaticAnalysis\Attributes\PropertyWrite; +use PhpStaticAnalysis\Attributes\RequireExtends; +use PhpStaticAnalysis\Attributes\RequireImplements; use PhpStaticAnalysis\Attributes\Returns; use PhpStaticAnalysis\Attributes\SelfOut; use PhpStaticAnalysis\Attributes\Template; @@ -41,6 +43,7 @@ class AttributeNodeVisitor extends NodeVisitorAbstract private const ARGS_MANY_IN_USE = "many in use"; private const ARGS_MANY_WITH_NAME = "many with name"; private const ARGS_MANY_WITHOUT_NAME = "many without name"; + private const ARGS_MANY_WITHOUT_NAME_AND_PREFIX = "many without name and prexif"; private const ALLOWED_NODE_TYPES = [ Stmt\Class_::class, @@ -119,6 +122,8 @@ class AttributeNodeVisitor extends NodeVisitorAbstract Property::class, PropertyRead::class, PropertyWrite::class, + RequireExtends::class, + RequireImplements::class, Template::class, TemplateContravariant::class, TemplateCovariant::class, @@ -136,6 +141,8 @@ class AttributeNodeVisitor extends NodeVisitorAbstract 'Property' => Property::class, 'PropertyRead' => PropertyRead::class, 'PropertyWrite' => PropertyWrite::class, + 'RequireExtends' => RequireExtends::class, + 'RequireImplements' => RequireImplements::class, 'Returns' => Returns::class, 'SelfOut' => SelfOut::class, 'Template' => Template::class, @@ -179,6 +186,12 @@ class AttributeNodeVisitor extends NodeVisitorAbstract PropertyWrite::class => [ 'all' => 'property-write', ], + RequireExtends::class => [ + 'all' => 'require-extends', + ], + RequireImplements::class => [ + 'all' => 'require-implements', + ], Returns::class => [ 'all' => 'return', ], @@ -243,6 +256,12 @@ class AttributeNodeVisitor extends NodeVisitorAbstract PropertyWrite::class => [ 'all' => self::ARGS_MANY_WITH_NAME, ], + RequireExtends::class => [ + 'all' => self::ARGS_ONE_WITH_PREFIX, + ], + RequireImplements::class => [ + 'all' => self::ARGS_MANY_WITHOUT_NAME_AND_PREFIX, + ], Returns::class => [ 'all' => self::ARGS_ONE, ], @@ -365,6 +384,12 @@ public function enterNode(Node $node) $tagCreated = true; } break; + case self::ARGS_MANY_WITHOUT_NAME_AND_PREFIX: + foreach ($args as $arg) { + $tagsToAdd[] = $this->createTag($nodeType, $attributeName, $arg, prefix: $this->toolType); + $tagCreated = true; + } + break; case self::ARGS_MANY_IN_USE: foreach ($args as $arg) { if ($arg->value instanceof String_) { diff --git a/tests/RequireExtendsAttributeNodeVisitorTest.php b/tests/RequireExtendsAttributeNodeVisitorTest.php new file mode 100644 index 0000000..92d66a1 --- /dev/null +++ b/tests/RequireExtendsAttributeNodeVisitorTest.php @@ -0,0 +1,31 @@ +addRequireExtendsAttributeToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @require-extends RequireClass\n */", $docText); + } + + private function addRequireExtendsAttributeToNode(Node\Stmt\Trait_ $node): void + { + $args = [ + new Node\Arg(new Node\Scalar\String_('RequireClass')) + ]; + $attributeName = new FullyQualified(RequireExtends::class); + $attribute = new Attribute($attributeName, $args); + $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + } +} diff --git a/tests/RequireImplementsAttributeNodeVisitorTest.php b/tests/RequireImplementsAttributeNodeVisitorTest.php new file mode 100644 index 0000000..8c2e244 --- /dev/null +++ b/tests/RequireImplementsAttributeNodeVisitorTest.php @@ -0,0 +1,52 @@ +addRequireImplementsAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @require-implements RequireInterface\n */", $docText); + } + + public function testAddsSeveralRequireImplementsPHPDocs(): void + { + $node = new Node\Stmt\Trait_('Test'); + $this->addRequireImplementsAttributesToNode($node, 2); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @require-implements RequireInterface\n * @require-implements RequireInterface\n */", $docText); + } + + public function testAddsMultipleRequireImplementsPHPDocs(): void + { + $node = new Node\Stmt\Trait_('Test'); + $this->addRequireImplementsAttributesToNode($node); + $this->addRequireImplementsAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @require-implements RequireInterface\n * @require-implements RequireInterface\n */", $docText); + } + + private function addRequireImplementsAttributesToNode(Node\Stmt\Trait_ $node, int $num = 1): void + { + $value = new Node\Scalar\String_('RequireInterface'); + $args = []; + for ($i = 0; $i < $num; $i++) { + $args[] = new Node\Arg($value); + } + $attributeName = new FullyQualified(RequireImplements::class); + $attribute = new Attribute($attributeName, $args); + $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + } +} diff --git a/tests/TemplateExtendsAttributeNodeVisitorTest.php b/tests/TemplateExtendsAttributeNodeVisitorTest.php index 58f9170..9e061e2 100644 --- a/tests/TemplateExtendsAttributeNodeVisitorTest.php +++ b/tests/TemplateExtendsAttributeNodeVisitorTest.php @@ -2,7 +2,6 @@ namespace test\PhpStaticAnalysis\NodeVisitor; -use Exception; use PhpParser\Node; use PhpParser\Node\Attribute; use PhpParser\Node\AttributeGroup;