Skip to content

Commit

Permalink
Add Throws attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Feb 26, 2024
1 parent cae6644 commit e16bdc9
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 12 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
26 changes: 25 additions & 1 deletion src/AttributeNodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -89,6 +93,7 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
Returns::class,
SelfOut::class,
Template::class,
Throws::class,
Type::class,
],
Stmt\Function_::class => [
Expand All @@ -100,6 +105,7 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
Pure::class,
Returns::class,
Template::class,
Throws::class,
Type::class,
],
Stmt\Interface_::class => [
Expand Down Expand Up @@ -160,6 +166,7 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
'TemplateExtends' => TemplateExtends::class,
'TemplateImplements' => TemplateImplements::class,
'TemplateUse' => TemplateUse::class,
'Throws' => Throws::class,
'Type' => Type::class,
];

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
],
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
16 changes: 12 additions & 4 deletions tests/MixinAttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace test\PhpStaticAnalysis\NodeVisitor;

use other\A;
use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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
{
}
12 changes: 10 additions & 2 deletions tests/RequireExtendsAttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace test\PhpStaticAnalysis\NodeVisitor;

use other\RequireClass;
use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
Expand All @@ -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
{
}
16 changes: 12 additions & 4 deletions tests/RequireImplementsAttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace test\PhpStaticAnalysis\NodeVisitor;

use other\RequireInterface;
use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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
{
}
54 changes: 54 additions & 0 deletions tests/ThrowsAttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace test\PhpStaticAnalysis\NodeVisitor;

use Exception;
use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Name\FullyQualified;
use PhpStaticAnalysis\Attributes\Throws;

class ThrowsAttributeNodeVisitorTest extends AttributeNodeVisitorTestBase
{
public function testAddsThrowsPHPDoc(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->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])]);
}
}

0 comments on commit e16bdc9

Please sign in to comment.