Skip to content

Commit e8ee463

Browse files
Add Assert, AssertIfTrue and AssertIfFalse attributes
1 parent b012631 commit e8ee463

5 files changed

+271
-3
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"require": {
2626
"php": ">=8.0",
2727
"nikic/php-parser": "^4 || ^5",
28-
"php-static-analysis/attributes": "^0.3.0 || dev-main"
28+
"php-static-analysis/attributes": "^0.3.1 || dev-main"
2929
},
3030
"require-dev": {
3131
"php-static-analysis/phpstan-extension": "dev-main",

src/AttributeNodeVisitor.php

+45-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
use PhpParser\Node\Scalar\String_;
1212
use PhpParser\Node\Stmt;
1313
use PhpParser\NodeVisitorAbstract;
14+
use PhpStaticAnalysis\Attributes\Assert;
15+
use PhpStaticAnalysis\Attributes\AssertIfFalse;
16+
use PhpStaticAnalysis\Attributes\AssertIfTrue;
1417
use PhpStaticAnalysis\Attributes\DefineType;
1518
use PhpStaticAnalysis\Attributes\Deprecated;
1619
use PhpStaticAnalysis\Attributes\Immutable;
@@ -92,6 +95,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
9295
Type::class,
9396
],
9497
Stmt\ClassMethod::class => [
98+
Assert::class,
99+
AssertIfFalse::class,
100+
AssertIfTrue::class,
95101
Deprecated::class,
96102
Impure::class,
97103
Internal::class,
@@ -105,6 +111,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
105111
Type::class,
106112
],
107113
Stmt\Function_::class => [
114+
Assert::class,
115+
AssertIfFalse::class,
116+
AssertIfTrue::class,
108117
Deprecated::class,
109118
Impure::class,
110119
Internal::class,
@@ -160,6 +169,9 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
160169
];
161170

162171
private const SHORT_NAME_TO_FQN = [
172+
'Assert' => Assert::class,
173+
'AssertIfFalse' => AssertIfFalse::class,
174+
'AssertIfTrue' => AssertIfTrue::class,
163175
'DefineType' => DefineType::class,
164176
'Deprecated' => Deprecated::class,
165177
'Immutable' => Immutable::class,
@@ -190,6 +202,15 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
190202
];
191203

192204
private const ANNOTATION_PER_ATTRIBUTE = [
205+
Assert::class => [
206+
'all' => 'assert',
207+
],
208+
AssertIfFalse::class => [
209+
'all' => 'assert-if-false',
210+
],
211+
AssertIfTrue::class => [
212+
'all' => 'assert-if-true',
213+
],
193214
DefineType::class => [
194215
'all' => 'type',
195216
],
@@ -279,6 +300,15 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
279300
];
280301

281302
private const ARGUMENTS_PER_ATTRIBUTE = [
303+
Assert::class => [
304+
'all' => self::ARGS_MANY_WITH_NAME,
305+
],
306+
AssertIfFalse::class => [
307+
'all' => self::ARGS_MANY_WITH_NAME,
308+
],
309+
AssertIfTrue::class => [
310+
'all' => self::ARGS_MANY_WITH_NAME,
311+
],
282312
DefineType::class => [
283313
'all' => self::ARGS_MANY_IN_TYPE,
284314
],
@@ -379,7 +409,15 @@ public function __construct(
379409
public function enterNode(Node $node)
380410
{
381411
if (in_array($node::class, self::ALLOWED_NODE_TYPES)) {
382-
/** @var Stmt\Class_|Stmt\ClassConst|Stmt\ClassMethod|Stmt\Function_|Stmt\Interface_|Stmt\Property|Stmt\Trait_ $node */
412+
assert(
413+
$node instanceof Stmt\Class_ ||
414+
$node instanceof Stmt\ClassConst ||
415+
$node instanceof Stmt\ClassMethod ||
416+
$node instanceof Stmt\Function_ ||
417+
$node instanceof Stmt\Interface_ ||
418+
$node instanceof Stmt\Property ||
419+
$node instanceof Stmt\Trait_
420+
);
383421
$tagsToAdd = [];
384422
$useTagsToAdd = [];
385423
$attributeGroups = $node->attrGroups;
@@ -584,7 +622,12 @@ private function getParamTagsFromParams(Stmt\ClassMethod|Stmt\Function_ $node):
584622
foreach ($attributes as $attribute) {
585623
$attributeName = $attribute->name->toString();
586624
$attributeName = self::SHORT_NAME_TO_FQN[$attributeName] ?? $attributeName;
587-
if ($attributeName === Param::class || $attributeName === ParamOut::class) {
625+
if ($attributeName === Param::class ||
626+
$attributeName === ParamOut::class ||
627+
$attributeName === Assert::class ||
628+
$attributeName === AssertIfFalse::class ||
629+
$attributeName === AssertIfTrue::class
630+
) {
588631
$args = $attribute->args;
589632
$tagCreated = false;
590633
if (isset($args[0])) {
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\NodeVisitor;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Attribute;
7+
use PhpParser\Node\AttributeGroup;
8+
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Name\FullyQualified;
10+
use PhpStaticAnalysis\Attributes\Assert;
11+
12+
class AssertAttributeNodeVisitorTest extends AttributeNodeVisitorTestBase
13+
{
14+
public function testAddsAssertPHPDoc(): void
15+
{
16+
$node = new Node\Stmt\ClassMethod('Test');
17+
$this->addAssertAttributesToNode($node);
18+
$this->nodeVisitor->enterNode($node);
19+
$docText = $this->getDocText($node);
20+
$this->assertEquals("/**\n * @assert string \$param\n */", $docText);
21+
}
22+
23+
public function testAddsSeveralAssertPHPDocs(): void
24+
{
25+
$node = new Node\Stmt\ClassMethod('Test');
26+
$this->addAssertAttributesToNode($node, 2);
27+
$this->nodeVisitor->enterNode($node);
28+
$docText = $this->getDocText($node);
29+
$this->assertEquals("/**\n * @assert string \$param\n * @assert string \$param\n */", $docText);
30+
}
31+
32+
public function testAddsMultipleAssertPHPDocs(): void
33+
{
34+
$node = new Node\Stmt\ClassMethod('Test');
35+
$this->addAssertAttributesToNode($node);
36+
$this->addAssertAttributesToNode($node);
37+
$this->nodeVisitor->enterNode($node);
38+
$docText = $this->getDocText($node);
39+
$this->assertEquals("/**\n * @assert string \$param\n * @assert string \$param\n */", $docText);
40+
}
41+
42+
public function testAddsAssertPHPDocToParam(): void
43+
{
44+
$node = new Node\Stmt\ClassMethod('Test');
45+
$this->addAssertAttributeToParamNode($node);
46+
$this->nodeVisitor->enterNode($node);
47+
$docText = $this->getDocText($node);
48+
$this->assertEquals("/**\n * @assert string \$param\n */", $docText);
49+
}
50+
51+
private function addAssertAttributesToNode(Node\Stmt\ClassMethod $node, int $num = 1): void
52+
{
53+
$name = new Identifier('param');
54+
$value = new Node\Scalar\String_('string');
55+
$args = [];
56+
for ($i = 0; $i < $num; $i++) {
57+
$args[] = new Node\Arg($value, name: $name);
58+
}
59+
$attributeName = new FullyQualified(Assert::class);
60+
$attribute = new Attribute($attributeName, $args);
61+
$node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
62+
}
63+
64+
private function addAssertAttributeToParamNode(Node\Stmt\ClassMethod $node): void
65+
{
66+
$var = new Node\Expr\Variable('param');
67+
$parameter = new Node\Param($var);
68+
$value = new Node\Scalar\String_('string');
69+
$args = [new Node\Arg($value)];
70+
$attributeName = new FullyQualified(Assert::class);
71+
$attribute = new Attribute($attributeName, $args);
72+
$parameter->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
73+
$node->params = [$parameter];
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\NodeVisitor;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Attribute;
7+
use PhpParser\Node\AttributeGroup;
8+
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Name\FullyQualified;
10+
use PhpStaticAnalysis\Attributes\AssertIfFalse;
11+
12+
class AssertIfFalseAttributeNodeVisitorTest extends AttributeNodeVisitorTestBase
13+
{
14+
public function testAddsAssertIfFalsePHPDoc(): void
15+
{
16+
$node = new Node\Stmt\ClassMethod('Test');
17+
$this->addAssertIfFalseAttributesToNode($node);
18+
$this->nodeVisitor->enterNode($node);
19+
$docText = $this->getDocText($node);
20+
$this->assertEquals("/**\n * @assert-if-false string \$param\n */", $docText);
21+
}
22+
23+
public function testAddsSeveralAssertIfFalsePHPDocs(): void
24+
{
25+
$node = new Node\Stmt\ClassMethod('Test');
26+
$this->addAssertIfFalseAttributesToNode($node, 2);
27+
$this->nodeVisitor->enterNode($node);
28+
$docText = $this->getDocText($node);
29+
$this->assertEquals("/**\n * @assert-if-false string \$param\n * @assert-if-false string \$param\n */", $docText);
30+
}
31+
32+
public function testAddsMultipleAssertIfFalsePHPDocs(): void
33+
{
34+
$node = new Node\Stmt\ClassMethod('Test');
35+
$this->addAssertIfFalseAttributesToNode($node);
36+
$this->addAssertIfFalseAttributesToNode($node);
37+
$this->nodeVisitor->enterNode($node);
38+
$docText = $this->getDocText($node);
39+
$this->assertEquals("/**\n * @assert-if-false string \$param\n * @assert-if-false string \$param\n */", $docText);
40+
}
41+
42+
public function testAddsAssertIfFalsePHPDocToParam(): void
43+
{
44+
$node = new Node\Stmt\ClassMethod('Test');
45+
$this->addAssertIfFalseAttributeToParamNode($node);
46+
$this->nodeVisitor->enterNode($node);
47+
$docText = $this->getDocText($node);
48+
$this->assertEquals("/**\n * @assert-if-false string \$param\n */", $docText);
49+
}
50+
51+
private function addAssertIfFalseAttributesToNode(Node\Stmt\ClassMethod $node, int $num = 1): void
52+
{
53+
$name = new Identifier('param');
54+
$value = new Node\Scalar\String_('string');
55+
$args = [];
56+
for ($i = 0; $i < $num; $i++) {
57+
$args[] = new Node\Arg($value, name: $name);
58+
}
59+
$attributeName = new FullyQualified(AssertIfFalse::class);
60+
$attribute = new Attribute($attributeName, $args);
61+
$node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
62+
}
63+
64+
private function addAssertIfFalseAttributeToParamNode(Node\Stmt\ClassMethod $node): void
65+
{
66+
$var = new Node\Expr\Variable('param');
67+
$parameter = new Node\Param($var);
68+
$value = new Node\Scalar\String_('string');
69+
$args = [new Node\Arg($value)];
70+
$attributeName = new FullyQualified(AssertIfFalse::class);
71+
$attribute = new Attribute($attributeName, $args);
72+
$parameter->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
73+
$node->params = [$parameter];
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\NodeVisitor;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Attribute;
7+
use PhpParser\Node\AttributeGroup;
8+
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Name\FullyQualified;
10+
use PhpStaticAnalysis\Attributes\AssertIfTrue;
11+
12+
class AssertIfTrueAttributeNodeVisitorTest extends AttributeNodeVisitorTestBase
13+
{
14+
public function testAddsAssertIfTruePHPDoc(): void
15+
{
16+
$node = new Node\Stmt\ClassMethod('Test');
17+
$this->addAssertIfTrueAttributesToNode($node);
18+
$this->nodeVisitor->enterNode($node);
19+
$docText = $this->getDocText($node);
20+
$this->assertEquals("/**\n * @assert-if-true string \$param\n */", $docText);
21+
}
22+
23+
public function testAddsSeveralAssertIfTruePHPDocs(): void
24+
{
25+
$node = new Node\Stmt\ClassMethod('Test');
26+
$this->addAssertIfTrueAttributesToNode($node, 2);
27+
$this->nodeVisitor->enterNode($node);
28+
$docText = $this->getDocText($node);
29+
$this->assertEquals("/**\n * @assert-if-true string \$param\n * @assert-if-true string \$param\n */", $docText);
30+
}
31+
32+
public function testAddsMultipleAssertIfTruePHPDocs(): void
33+
{
34+
$node = new Node\Stmt\ClassMethod('Test');
35+
$this->addAssertIfTrueAttributesToNode($node);
36+
$this->addAssertIfTrueAttributesToNode($node);
37+
$this->nodeVisitor->enterNode($node);
38+
$docText = $this->getDocText($node);
39+
$this->assertEquals("/**\n * @assert-if-true string \$param\n * @assert-if-true string \$param\n */", $docText);
40+
}
41+
42+
public function testAddsAssertIfTruePHPDocToParam(): void
43+
{
44+
$node = new Node\Stmt\ClassMethod('Test');
45+
$this->addAssertIfTrueAttributeToParamNode($node);
46+
$this->nodeVisitor->enterNode($node);
47+
$docText = $this->getDocText($node);
48+
$this->assertEquals("/**\n * @assert-if-true string \$param\n */", $docText);
49+
}
50+
51+
private function addAssertIfTrueAttributesToNode(Node\Stmt\ClassMethod $node, int $num = 1): void
52+
{
53+
$name = new Identifier('param');
54+
$value = new Node\Scalar\String_('string');
55+
$args = [];
56+
for ($i = 0; $i < $num; $i++) {
57+
$args[] = new Node\Arg($value, name: $name);
58+
}
59+
$attributeName = new FullyQualified(AssertIfTrue::class);
60+
$attribute = new Attribute($attributeName, $args);
61+
$node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
62+
}
63+
64+
private function addAssertIfTrueAttributeToParamNode(Node\Stmt\ClassMethod $node): void
65+
{
66+
$var = new Node\Expr\Variable('param');
67+
$parameter = new Node\Param($var);
68+
$value = new Node\Scalar\String_('string');
69+
$args = [new Node\Arg($value)];
70+
$attributeName = new FullyQualified(AssertIfTrue::class);
71+
$attribute = new Attribute($attributeName, $args);
72+
$parameter->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
73+
$node->params = [$parameter];
74+
}
75+
}

0 commit comments

Comments
 (0)