Skip to content

Commit

Permalink
Add Assert, AssertIfTrue and AssertIfFalse attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Sep 12, 2024
1 parent b012631 commit e8ee463
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 3 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.3.0 || dev-main"
"php-static-analysis/attributes": "^0.3.1 || dev-main"
},
"require-dev": {
"php-static-analysis/phpstan-extension": "dev-main",
Expand Down
47 changes: 45 additions & 2 deletions src/AttributeNodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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',
],
Expand Down Expand Up @@ -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,
],
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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])) {
Expand Down
75 changes: 75 additions & 0 deletions tests/AssertAttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace test\PhpStaticAnalysis\NodeVisitor;

use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpStaticAnalysis\Attributes\Assert;

class AssertAttributeNodeVisitorTest extends AttributeNodeVisitorTestBase
{
public function testAddsAssertPHPDoc(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->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];
}
}
75 changes: 75 additions & 0 deletions tests/AssertIfFalseAttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace test\PhpStaticAnalysis\NodeVisitor;

use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpStaticAnalysis\Attributes\AssertIfFalse;

class AssertIfFalseAttributeNodeVisitorTest extends AttributeNodeVisitorTestBase
{
public function testAddsAssertIfFalsePHPDoc(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->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];
}
}
75 changes: 75 additions & 0 deletions tests/AssertIfTrueAttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace test\PhpStaticAnalysis\NodeVisitor;

use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpStaticAnalysis\Attributes\AssertIfTrue;

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

0 comments on commit e8ee463

Please sign in to comment.