Skip to content

Commit

Permalink
Add the PropertyRead and PropertyWrite attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Feb 16, 2024
1 parent 71405e0 commit dd76af5
Show file tree
Hide file tree
Showing 12 changed files with 491 additions and 276 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.3 || dev-main"
"php-static-analysis/attributes": "^0.1.4 || dev-main"
},
"require-dev": {
"php-static-analysis/phpstan-extension": "dev-main",
Expand Down
18 changes: 18 additions & 0 deletions src/AttributeNodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use PhpStaticAnalysis\Attributes\IsReadOnly;
use PhpStaticAnalysis\Attributes\Param;
use PhpStaticAnalysis\Attributes\Property;
use PhpStaticAnalysis\Attributes\PropertyRead;
use PhpStaticAnalysis\Attributes\PropertyWrite;
use PhpStaticAnalysis\Attributes\Returns;
use PhpStaticAnalysis\Attributes\Template;
use PhpStaticAnalysis\Attributes\Type;
Expand All @@ -38,6 +40,8 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
private const ALLOWED_ATTRIBUTES_PER_NODE_TYPE = [
Stmt\Class_::class => [
Property::class,
PropertyRead::class,
PropertyWrite::class,
Template::class,
],
Stmt\ClassConst::class => [
Expand Down Expand Up @@ -72,6 +76,8 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
'IsReadOnly' => IsReadOnly::class,
'Param' => Param::class,
'Property' => Property::class,
'PropertyRead' => PropertyRead::class,
'PropertyWrite' => PropertyWrite::class,
'Returns' => Returns::class,
'Template' => Template::class,
'Type' => Type::class,
Expand All @@ -88,6 +94,12 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
Stmt\Class_::class => 'property',
Stmt\Property::class => 'var',
],
PropertyRead::class => [
'all' => 'property-read',
],
PropertyWrite::class => [
'all' => 'property-write',
],
Returns::class => [
'all' => 'return',
],
Expand All @@ -113,6 +125,12 @@ class AttributeNodeVisitor extends NodeVisitorAbstract
Stmt\Class_::class => self::ARGS_MANY_WITH_NAME,
Stmt\Property::class => self::ARGS_ONE,
],
PropertyRead::class => [
Stmt\Class_::class => self::ARGS_MANY_WITH_NAME,
],
PropertyWrite::class => [
Stmt\Class_::class => self::ARGS_MANY_WITH_NAME,
],
Returns::class => [
'all' => self::ARGS_ONE,
],
Expand Down
276 changes: 1 addition & 275 deletions tests/AttributeNodeVisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,14 @@

namespace test\PhpStaticAnalysis\NodeVisitor;

use Exception;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpStaticAnalysis\Attributes\IsReadOnly;
use PhpStaticAnalysis\Attributes\Param;
use PhpStaticAnalysis\Attributes\Property;
use PhpStaticAnalysis\Attributes\Returns;
use PhpStaticAnalysis\Attributes\Template;
use PhpStaticAnalysis\Attributes\Type;
use PhpStaticAnalysis\NodeVisitor\AttributeNodeVisitor;
use PHPUnit\Framework\TestCase;

class AttributeNodeVisitorTest extends TestCase
class AttributeNodeVisitorTest extends AttributeNodeVisitorTestBase
{
private const UNTOUCHED = "/**\n * untouched\n */";

private AttributeNodeVisitor $nodeVisitor;

public function setUp(): void
{
$this->nodeVisitor = new AttributeNodeVisitor();
}

public function testDoesNotProcessUnknownNodes(): void
{
$node = new Node\Stmt\Use_([]);
Expand Down Expand Up @@ -56,259 +37,4 @@ public function testDoesNotProcessAttributeNotAvailableForStmt(): void
$docText = $this->getDocText($node);
$this->assertEquals(self::UNTOUCHED, $docText);
}

public function testAddsReadOnlyPHPDoc(): void
{
$node = new Node\Stmt\Property(0, []);
$this->addIsReadOnlyAttributeToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @readonly\n */", $docText);
}

public function testAddsReadOnlyPHPDocMaintainingExistingPHPDoc(): void
{
$node = new Node\Stmt\Property(0, []);
$this->setDocComment($node, self::UNTOUCHED);
$this->addIsReadOnlyAttributeToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * untouched\n * @readonly\n */", $docText);
}

public function testAddsReturnPHPDoc(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->addReturnsAttributeToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @return string\n */", $docText);
}

public function testAddsVarPHPDoc(): void
{
$node = new Node\Stmt\Property(0, []);
$this->addTypeAttributeToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @var string\n */", $docText);
}

public function testAddsReturnPHPDocWithTypeAttribute(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->addTypeAttributeToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @return string\n */", $docText);
}

public function testAddsTemplatePHPDoc(): void
{
$node = new Node\Stmt\Class_('Test');
$this->addTemplateAttributeToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @template T\n */", $docText);
}

public function testAddsTemplateWithTypePHPDoc(): void
{
$node = new Node\Stmt\Class_('Test');
$this->addTemplateAttributeToNode($node, true);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @template T of Exception\n */", $docText);
}

public function testAddsMultipleTemplatePHPDocs(): void
{
$node = new Node\Stmt\Class_('Test');
$this->addTemplateAttributeToNode($node);
$this->addTemplateAttributeToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @template T\n * @template T\n */", $docText);
}

public function testAddsParamPHPDoc(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->addParamAttributesToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @param string \$param\n */", $docText);
}

public function testAddsSeveralParamPHPDocs(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->addParamAttributesToNode($node, 2);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @param string \$param\n * @param string \$param\n */", $docText);
}

public function testAddsMultipleParamPHPDocs(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->addParamAttributesToNode($node);
$this->addParamAttributesToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @param string \$param\n * @param string \$param\n */", $docText);
}

public function testAddsParamPHPDocToParam(): void
{
$node = new Node\Stmt\ClassMethod('Test');
$this->addParamAttributeToParamNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @param string \$param\n */", $docText);
}

public function testAddsPropertyPHPDoc(): void
{
$node = new Node\Stmt\Class_('Test');
$this->addPropertyAttributesToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @property string \$param\n */", $docText);
}

public function testAddsSeveralPropertyPHPDocs(): void
{
$node = new Node\Stmt\Class_('Test');
$this->addPropertyAttributesToNode($node, 2);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @property string \$param\n * @property string \$param\n */", $docText);
}

public function testAddsMultiplePropertyPHPDocs(): void
{
$node = new Node\Stmt\Class_('Test');
$this->addPropertyAttributesToNode($node);
$this->addPropertyAttributesToNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @property string \$param\n * @property string \$param\n */", $docText);
}

public function testAddsVarPHPDocForPropertyAttribute(): void
{
$node = new Node\Stmt\Property(0, []);
$this->addPropertyAttributeToPropertyNode($node);
$this->nodeVisitor->enterNode($node);
$docText = $this->getDocText($node);
$this->assertEquals("/**\n * @var string\n */", $docText);
}

private function setDocComment(Node $node, string $text): void
{
$docComment = new Doc(
$text,
);
$node->setDocComment($docComment);
}

private function addIsReadOnlyAttributeToNode(
Node\Stmt\ClassMethod | Node\Stmt\Property $node
): void {
$attributeName = new FullyQualified(IsReadOnly::class);
$attribute = new Attribute($attributeName);
$node->attrGroups = [new AttributeGroup([$attribute])];
}

private function addReturnsAttributeToNode(Node\Stmt\ClassMethod $node): void
{
$args = [
new Node\Arg(new Node\Scalar\String_('string'))
];
$attributeName = new FullyQualified(Returns::class);
$attribute = new Attribute($attributeName, $args);
$node->attrGroups = [new AttributeGroup([$attribute])];
}

private function addTypeAttributeToNode(Node\Stmt\Property|Node\Stmt\ClassMethod $node): void
{
$args = [
new Node\Arg(new Node\Scalar\String_('string'))
];
$attributeName = new FullyQualified(Type::class);
$attribute = new Attribute($attributeName, $args);
$node->attrGroups = [new AttributeGroup([$attribute])];
}

private function addTemplateAttributeToNode(Node\Stmt\Class_ $node, bool $addType = false): void
{
$args = [
new Node\Arg(new Node\Scalar\String_('T'))
];
if ($addType) {
$args[] = new Node\Arg(new Node\Scalar\String_(Exception::class));
}
$attributeName = new FullyQualified(Template::class);
$attribute = new Attribute($attributeName, $args);
$node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
}

private function addParamAttributesToNode(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(Param::class);
$attribute = new Attribute($attributeName, $args);
$node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
}

private function addParamAttributeToParamNode(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(Param::class);
$attribute = new Attribute($attributeName, $args);
$parameter->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
$node->params = [$parameter];
}

private function addPropertyAttributesToNode(Node\Stmt\Class_ $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(Property::class);
$attribute = new Attribute($attributeName, $args);
$node->attrGroups = array_merge($node->attrGroups, [new AttributeGroup([$attribute])]);
}

private function addPropertyAttributeToPropertyNode(Node\Stmt\Property $node): void
{
$args = [
new Node\Arg(new Node\Scalar\String_('string'))
];
$attributeName = new FullyQualified(Property::class);
$attribute = new Attribute($attributeName, $args);
$node->attrGroups = [new AttributeGroup([$attribute])];
}

private function getDocText(Node $node): string
{
$docComment = $node->getDocComment();
$docText = '';
if ($docComment instanceof Doc) {
$docText = $docComment->getText();
}
return $docText;
}
}
Loading

0 comments on commit dd76af5

Please sign in to comment.