From e8ee463fbdc0a0c99a2fdce21a441d2dc273f945 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Thu, 12 Sep 2024 16:55:56 +0200 Subject: [PATCH] Add Assert, AssertIfTrue and AssertIfFalse attributes --- composer.json | 2 +- src/AttributeNodeVisitor.php | 47 +++++++++++- tests/AssertAttributeNodeVisitorTest.php | 75 +++++++++++++++++++ .../AssertIfFalseAttributeNodeVisitorTest.php | 75 +++++++++++++++++++ .../AssertIfTrueAttributeNodeVisitorTest.php | 75 +++++++++++++++++++ 5 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 tests/AssertAttributeNodeVisitorTest.php create mode 100644 tests/AssertIfFalseAttributeNodeVisitorTest.php create mode 100644 tests/AssertIfTrueAttributeNodeVisitorTest.php diff --git a/composer.json b/composer.json index 5e5e18d..ba01d82 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.3.0 || dev-main" + "php-static-analysis/attributes": "^0.3.1 || dev-main" }, "require-dev": { "php-static-analysis/phpstan-extension": "dev-main", diff --git a/src/AttributeNodeVisitor.php b/src/AttributeNodeVisitor.php index e3d0de3..d29002f 100644 --- a/src/AttributeNodeVisitor.php +++ b/src/AttributeNodeVisitor.php @@ -11,6 +11,9 @@ use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt; use PhpParser\NodeVisitorAbstract; +use PhpStaticAnalysis\Attributes\Assert; +use PhpStaticAnalysis\Attributes\AssertIfFalse; +use PhpStaticAnalysis\Attributes\AssertIfTrue; use PhpStaticAnalysis\Attributes\DefineType; use PhpStaticAnalysis\Attributes\Deprecated; use PhpStaticAnalysis\Attributes\Immutable; @@ -92,6 +95,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract Type::class, ], Stmt\ClassMethod::class => [ + Assert::class, + AssertIfFalse::class, + AssertIfTrue::class, Deprecated::class, Impure::class, Internal::class, @@ -105,6 +111,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract Type::class, ], Stmt\Function_::class => [ + Assert::class, + AssertIfFalse::class, + AssertIfTrue::class, Deprecated::class, Impure::class, Internal::class, @@ -160,6 +169,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract ]; private const SHORT_NAME_TO_FQN = [ + 'Assert' => Assert::class, + 'AssertIfFalse' => AssertIfFalse::class, + 'AssertIfTrue' => AssertIfTrue::class, 'DefineType' => DefineType::class, 'Deprecated' => Deprecated::class, 'Immutable' => Immutable::class, @@ -190,6 +202,15 @@ class AttributeNodeVisitor extends NodeVisitorAbstract ]; private const ANNOTATION_PER_ATTRIBUTE = [ + Assert::class => [ + 'all' => 'assert', + ], + AssertIfFalse::class => [ + 'all' => 'assert-if-false', + ], + AssertIfTrue::class => [ + 'all' => 'assert-if-true', + ], DefineType::class => [ 'all' => 'type', ], @@ -279,6 +300,15 @@ class AttributeNodeVisitor extends NodeVisitorAbstract ]; private const ARGUMENTS_PER_ATTRIBUTE = [ + Assert::class => [ + 'all' => self::ARGS_MANY_WITH_NAME, + ], + AssertIfFalse::class => [ + 'all' => self::ARGS_MANY_WITH_NAME, + ], + AssertIfTrue::class => [ + 'all' => self::ARGS_MANY_WITH_NAME, + ], DefineType::class => [ 'all' => self::ARGS_MANY_IN_TYPE, ], @@ -379,7 +409,15 @@ public function __construct( public function enterNode(Node $node) { if (in_array($node::class, self::ALLOWED_NODE_TYPES)) { - /** @var Stmt\Class_|Stmt\ClassConst|Stmt\ClassMethod|Stmt\Function_|Stmt\Interface_|Stmt\Property|Stmt\Trait_ $node */ + assert( + $node instanceof Stmt\Class_ || + $node instanceof Stmt\ClassConst || + $node instanceof Stmt\ClassMethod || + $node instanceof Stmt\Function_ || + $node instanceof Stmt\Interface_ || + $node instanceof Stmt\Property || + $node instanceof Stmt\Trait_ + ); $tagsToAdd = []; $useTagsToAdd = []; $attributeGroups = $node->attrGroups; @@ -584,7 +622,12 @@ private function getParamTagsFromParams(Stmt\ClassMethod|Stmt\Function_ $node): foreach ($attributes as $attribute) { $attributeName = $attribute->name->toString(); $attributeName = self::SHORT_NAME_TO_FQN[$attributeName] ?? $attributeName; - if ($attributeName === Param::class || $attributeName === ParamOut::class) { + if ($attributeName === Param::class || + $attributeName === ParamOut::class || + $attributeName === Assert::class || + $attributeName === AssertIfFalse::class || + $attributeName === AssertIfTrue::class + ) { $args = $attribute->args; $tagCreated = false; if (isset($args[0])) { diff --git a/tests/AssertAttributeNodeVisitorTest.php b/tests/AssertAttributeNodeVisitorTest.php new file mode 100644 index 0000000..0bf9008 --- /dev/null +++ b/tests/AssertAttributeNodeVisitorTest.php @@ -0,0 +1,75 @@ +addAssertAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert string \$param\n */", $docText); + } + + public function testAddsSeveralAssertPHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertAttributesToNode($node, 2); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert string \$param\n * @assert string \$param\n */", $docText); + } + + public function testAddsMultipleAssertPHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertAttributesToNode($node); + $this->addAssertAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert string \$param\n * @assert string \$param\n */", $docText); + } + + public function testAddsAssertPHPDocToParam(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertAttributeToParamNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert string \$param\n */", $docText); + } + + private function addAssertAttributesToNode(Node\Stmt\ClassMethod $node, int $num = 1): void + { + $name = new Identifier('param'); + $value = new Node\Scalar\String_('string'); + $args = []; + for ($i = 0; $i < $num; $i++) { + $args[] = new Node\Arg($value, name: $name); + } + $attributeName = new FullyQualified(Assert::class); + $attribute = new Attribute($attributeName, $args); + $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + } + + private function addAssertAttributeToParamNode(Node\Stmt\ClassMethod $node): void + { + $var = new Node\Expr\Variable('param'); + $parameter = new Node\Param($var); + $value = new Node\Scalar\String_('string'); + $args = [new Node\Arg($value)]; + $attributeName = new FullyQualified(Assert::class); + $attribute = new Attribute($attributeName, $args); + $parameter->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + $node->params = [$parameter]; + } +} diff --git a/tests/AssertIfFalseAttributeNodeVisitorTest.php b/tests/AssertIfFalseAttributeNodeVisitorTest.php new file mode 100644 index 0000000..2d817f5 --- /dev/null +++ b/tests/AssertIfFalseAttributeNodeVisitorTest.php @@ -0,0 +1,75 @@ +addAssertIfFalseAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-false string \$param\n */", $docText); + } + + public function testAddsSeveralAssertIfFalsePHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertIfFalseAttributesToNode($node, 2); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-false string \$param\n * @assert-if-false string \$param\n */", $docText); + } + + public function testAddsMultipleAssertIfFalsePHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertIfFalseAttributesToNode($node); + $this->addAssertIfFalseAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-false string \$param\n * @assert-if-false string \$param\n */", $docText); + } + + public function testAddsAssertIfFalsePHPDocToParam(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertIfFalseAttributeToParamNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-false string \$param\n */", $docText); + } + + private function addAssertIfFalseAttributesToNode(Node\Stmt\ClassMethod $node, int $num = 1): void + { + $name = new Identifier('param'); + $value = new Node\Scalar\String_('string'); + $args = []; + for ($i = 0; $i < $num; $i++) { + $args[] = new Node\Arg($value, name: $name); + } + $attributeName = new FullyQualified(AssertIfFalse::class); + $attribute = new Attribute($attributeName, $args); + $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + } + + private function addAssertIfFalseAttributeToParamNode(Node\Stmt\ClassMethod $node): void + { + $var = new Node\Expr\Variable('param'); + $parameter = new Node\Param($var); + $value = new Node\Scalar\String_('string'); + $args = [new Node\Arg($value)]; + $attributeName = new FullyQualified(AssertIfFalse::class); + $attribute = new Attribute($attributeName, $args); + $parameter->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + $node->params = [$parameter]; + } +} diff --git a/tests/AssertIfTrueAttributeNodeVisitorTest.php b/tests/AssertIfTrueAttributeNodeVisitorTest.php new file mode 100644 index 0000000..ab1969c --- /dev/null +++ b/tests/AssertIfTrueAttributeNodeVisitorTest.php @@ -0,0 +1,75 @@ +addAssertIfTrueAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-true string \$param\n */", $docText); + } + + public function testAddsSeveralAssertIfTruePHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertIfTrueAttributesToNode($node, 2); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-true string \$param\n * @assert-if-true string \$param\n */", $docText); + } + + public function testAddsMultipleAssertIfTruePHPDocs(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertIfTrueAttributesToNode($node); + $this->addAssertIfTrueAttributesToNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-true string \$param\n * @assert-if-true string \$param\n */", $docText); + } + + public function testAddsAssertIfTruePHPDocToParam(): void + { + $node = new Node\Stmt\ClassMethod('Test'); + $this->addAssertIfTrueAttributeToParamNode($node); + $this->nodeVisitor->enterNode($node); + $docText = $this->getDocText($node); + $this->assertEquals("/**\n * @assert-if-true string \$param\n */", $docText); + } + + private function addAssertIfTrueAttributesToNode(Node\Stmt\ClassMethod $node, int $num = 1): void + { + $name = new Identifier('param'); + $value = new Node\Scalar\String_('string'); + $args = []; + for ($i = 0; $i < $num; $i++) { + $args[] = new Node\Arg($value, name: $name); + } + $attributeName = new FullyQualified(AssertIfTrue::class); + $attribute = new Attribute($attributeName, $args); + $node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + } + + private function addAssertIfTrueAttributeToParamNode(Node\Stmt\ClassMethod $node): void + { + $var = new Node\Expr\Variable('param'); + $parameter = new Node\Param($var); + $value = new Node\Scalar\String_('string'); + $args = [new Node\Arg($value)]; + $attributeName = new FullyQualified(AssertIfTrue::class); + $attribute = new Attribute($attributeName, $args); + $parameter->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]); + $node->params = [$parameter]; + } +}