diff --git a/composer.json b/composer.json index da3e0db..efd4603 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.15 || dev-main" + "php-static-analysis/attributes": "^0.1.16 || dev-main" }, "require-dev": { "php-static-analysis/phpstan-extension": "dev-main", diff --git a/src/AttributeNodeVisitor.php b/src/AttributeNodeVisitor.php index 2762030..61b7491 100644 --- a/src/AttributeNodeVisitor.php +++ b/src/AttributeNodeVisitor.php @@ -33,10 +33,14 @@ use PhpStaticAnalysis\Attributes\TemplateExtends; use PhpStaticAnalysis\Attributes\TemplateImplements; use PhpStaticAnalysis\Attributes\TemplateUse; +use PhpStaticAnalysis\Attributes\Throws; use PhpStaticAnalysis\Attributes\Type; class AttributeNodeVisitor extends NodeVisitorAbstract { + public const TOOL_PHPSTAN = 'phpstan'; + public const TOOL_PSALM = 'psalm'; + private const ARGS_NONE = 'none'; private const ARGS_NONE_WITH_PREFIX = 'none with prefix'; private const ARGS_ONE = 'one'; @@ -89,6 +93,7 @@ class AttributeNodeVisitor extends NodeVisitorAbstract Returns::class, SelfOut::class, Template::class, + Throws::class, Type::class, ], Stmt\Function_::class => [ @@ -100,6 +105,7 @@ class AttributeNodeVisitor extends NodeVisitorAbstract Pure::class, Returns::class, Template::class, + Throws::class, Type::class, ], Stmt\Interface_::class => [ @@ -160,6 +166,7 @@ class AttributeNodeVisitor extends NodeVisitorAbstract 'TemplateExtends' => TemplateExtends::class, 'TemplateImplements' => TemplateImplements::class, 'TemplateUse' => TemplateUse::class, + 'Throws' => Throws::class, 'Type' => Type::class, ]; @@ -231,6 +238,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract TemplateUse::class => [ 'all' => 'template-use', ], + Throws::class => [ + 'all' => 'throws', + ], Type::class => [ Stmt\ClassConst::class => 'var', Stmt\ClassMethod::class => 'return', @@ -307,6 +317,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract TemplateUse::class => [ 'all' => self::ARGS_MANY_IN_USE, ], + Throws::class => [ + 'all' => self::ARGS_MANY_WITHOUT_NAME, + ], Type::class => [ 'all' => self::ARGS_ONE ], @@ -384,7 +397,7 @@ public function enterNode(Node $node) $nodeType, $attributeName, $args[0], - prefix: $this->toolType === 'psalm' ? $this->toolType : null + prefix: $this->toolType === self::TOOL_PSALM ? $this->toolType : null ); } else { $tagsToAdd[] = $this->createTag($nodeType, $attributeName); @@ -468,6 +481,17 @@ private function createTag( $type = ''; if ($value instanceof String_) { $type = $value->value; + } elseif ($value instanceof Node\Expr\ClassConstFetch && + $value->class instanceof Node\Name && + $value->name instanceof Node\Identifier && + (string)$value->name == 'class' + ) { + $type = (string)$value->class; + if ($this->toolType === self::TOOL_PHPSTAN) { + $type = '\\' . $type; + } + } + if ($type !== '') { $tag .= ' ' . $type; } if ($of) { diff --git a/tests/MixinAttributeNodeVisitorTest.php b/tests/MixinAttributeNodeVisitorTest.php index 331fe35..554261f 100644 --- a/tests/MixinAttributeNodeVisitorTest.php +++ b/tests/MixinAttributeNodeVisitorTest.php @@ -2,6 +2,7 @@ namespace test\PhpStaticAnalysis\NodeVisitor; +use other\A; use PhpParser\Node; use PhpParser\Node\Attribute; use PhpParser\Node\AttributeGroup; @@ -16,7 +17,7 @@ public function testAddsMixinPHPDoc(): void $this->addMixinAttributesToNode($node); $this->nodeVisitor->enterNode($node); $docText = $this->getDocText($node); - $this->assertEquals("/**\n * @mixin A\n */", $docText); + $this->assertEquals("/**\n * @mixin other\A\n */", $docText); } public function testAddsSeveralMixinPHPDocs(): void @@ -25,7 +26,7 @@ public function testAddsSeveralMixinPHPDocs(): void $this->addMixinAttributesToNode($node, 2); $this->nodeVisitor->enterNode($node); $docText = $this->getDocText($node); - $this->assertEquals("/**\n * @mixin A\n * @mixin A\n */", $docText); + $this->assertEquals("/**\n * @mixin other\A\n * @mixin other\A\n */", $docText); } public function testAddsMultipleMixinPHPDocs(): void @@ -35,12 +36,13 @@ public function testAddsMultipleMixinPHPDocs(): void $this->addMixinAttributesToNode($node); $this->nodeVisitor->enterNode($node); $docText = $this->getDocText($node); - $this->assertEquals("/**\n * @mixin A\n * @mixin A\n */", $docText); + $this->assertEquals("/**\n * @mixin other\A\n * @mixin other\A\n */", $docText); } private function addMixinAttributesToNode(Node\Stmt\Class_ $node, int $num = 1): void { - $value = new Node\Scalar\String_('A'); + $class = new Node\Name(A::class); + $value = new Node\Expr\ClassConstFetch($class, 'class'); $args = []; for ($i = 0; $i < $num; $i++) { $args[] = new Node\Arg($value); @@ -50,3 +52,9 @@ private function addMixinAttributesToNode(Node\Stmt\Class_ $node, int $num = 1): $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); } } + +namespace other; + +class A +{ +} diff --git a/tests/RequireExtendsAttributeNodeVisitorTest.php b/tests/RequireExtendsAttributeNodeVisitorTest.php index 92d66a1..3dea468 100644 --- a/tests/RequireExtendsAttributeNodeVisitorTest.php +++ b/tests/RequireExtendsAttributeNodeVisitorTest.php @@ -2,6 +2,7 @@ namespace test\PhpStaticAnalysis\NodeVisitor; +use other\RequireClass; use PhpParser\Node; use PhpParser\Node\Attribute; use PhpParser\Node\AttributeGroup; @@ -16,16 +17,23 @@ public function testAddsRequireExtendsPHPDoc(): void $this->addRequireExtendsAttributeToNode($node); $this->nodeVisitor->enterNode($node); $docText = $this->getDocText($node); - $this->assertEquals("/**\n * @require-extends RequireClass\n */", $docText); + $this->assertEquals("/**\n * @require-extends other\RequireClass\n */", $docText); } private function addRequireExtendsAttributeToNode(Node\Stmt\Trait_ $node): void { + $class = new Node\Name(RequireClass::class); $args = [ - new Node\Arg(new Node\Scalar\String_('RequireClass')) + new Node\Arg(new Node\Expr\ClassConstFetch($class, 'class')) ]; $attributeName = new FullyQualified(RequireExtends::class); $attribute = new Attribute($attributeName, $args); $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); } } + +namespace other; + +class RequireClass +{ +} diff --git a/tests/RequireImplementsAttributeNodeVisitorTest.php b/tests/RequireImplementsAttributeNodeVisitorTest.php index 8c2e244..57d27c2 100644 --- a/tests/RequireImplementsAttributeNodeVisitorTest.php +++ b/tests/RequireImplementsAttributeNodeVisitorTest.php @@ -2,6 +2,7 @@ namespace test\PhpStaticAnalysis\NodeVisitor; +use other\RequireInterface; use PhpParser\Node; use PhpParser\Node\Attribute; use PhpParser\Node\AttributeGroup; @@ -16,7 +17,7 @@ public function testAddsRequireImplementsPHPDoc(): void $this->addRequireImplementsAttributesToNode($node); $this->nodeVisitor->enterNode($node); $docText = $this->getDocText($node); - $this->assertEquals("/**\n * @require-implements RequireInterface\n */", $docText); + $this->assertEquals("/**\n * @require-implements other\RequireInterface\n */", $docText); } public function testAddsSeveralRequireImplementsPHPDocs(): void @@ -25,7 +26,7 @@ public function testAddsSeveralRequireImplementsPHPDocs(): void $this->addRequireImplementsAttributesToNode($node, 2); $this->nodeVisitor->enterNode($node); $docText = $this->getDocText($node); - $this->assertEquals("/**\n * @require-implements RequireInterface\n * @require-implements RequireInterface\n */", $docText); + $this->assertEquals("/**\n * @require-implements other\RequireInterface\n * @require-implements other\RequireInterface\n */", $docText); } public function testAddsMultipleRequireImplementsPHPDocs(): void @@ -35,12 +36,13 @@ public function testAddsMultipleRequireImplementsPHPDocs(): void $this->addRequireImplementsAttributesToNode($node); $this->nodeVisitor->enterNode($node); $docText = $this->getDocText($node); - $this->assertEquals("/**\n * @require-implements RequireInterface\n * @require-implements RequireInterface\n */", $docText); + $this->assertEquals("/**\n * @require-implements other\RequireInterface\n * @require-implements other\RequireInterface\n */", $docText); } private function addRequireImplementsAttributesToNode(Node\Stmt\Trait_ $node, int $num = 1): void { - $value = new Node\Scalar\String_('RequireInterface'); + $interface = new Node\Name(RequireInterface::class); + $value = new Node\Expr\ClassConstFetch($interface, 'class'); $args = []; for ($i = 0; $i < $num; $i++) { $args[] = new Node\Arg($value); @@ -50,3 +52,9 @@ private function addRequireImplementsAttributesToNode(Node\Stmt\Trait_ $node, in $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); } } + +namespace other; + +interface RequireInterface +{ +} diff --git a/tests/ThrowsAttributeNodeVisitorTest.php b/tests/ThrowsAttributeNodeVisitorTest.php new file mode 100644 index 0000000..f6fbe65 --- /dev/null +++ b/tests/ThrowsAttributeNodeVisitorTest.php @@ -0,0 +1,54 @@ +addThrowsAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @throws Exception\n */", $docText); + } + + public function testAddsSeveralThrowsPHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addThrowsAttributesToNode($node, 2); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @throws Exception\n * @throws Exception\n */", $docText); + } + + public function testAddsMultipleThrowsPHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addThrowsAttributesToNode($node); + $this->addThrowsAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @throws Exception\n * @throws Exception\n */", $docText); + } + + private function addThrowsAttributesToNode(Node\Stmt\ClassMethod $node, int $num = 1): void + { + $exception = new Node\Name(Exception::class); + $value = new Node\Expr\ClassConstFetch($exception, 'class'); + $args = []; + for ($i = 0; $i < $num; $i++) { + $args[] = new Node\Arg($value); + } + $attributeName = new FullyQualified(Throws::class); + $attribute = new Attribute($attributeName, $args); + $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + } +}