From 865dc9f373f794284ffc16810a399ded6bf2e06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Ols=CC=8Cavsky=CC=81?= Date: Wed, 15 Jun 2022 18:37:12 +0200 Subject: [PATCH] Add support for attributes --- .../ExportedAttributeArgumentNode.php | 76 +++++++++++++++ .../ExportedNode/ExportedAttributeNode.php | 92 +++++++++++++++++++ .../ExportedClassConstantNode.php | 46 ++++++++-- .../ExportedNode/ExportedClassNode.php | 37 ++++++-- .../ExportedNode/ExportedFunctionNode.php | 33 ++++++- .../ExportedNode/ExportedMethodNode.php | 33 ++++++- .../ExportedNode/ExportedParameterNode.php | 37 +++++++- .../ExportedNode/ExportedPropertyNode.php | 41 +++++++-- src/Dependency/ExportedNodeResolver.php | 58 +++++++++--- 9 files changed, 410 insertions(+), 43 deletions(-) create mode 100644 src/Dependency/ExportedNode/ExportedAttributeArgumentNode.php create mode 100644 src/Dependency/ExportedNode/ExportedAttributeNode.php diff --git a/src/Dependency/ExportedNode/ExportedAttributeArgumentNode.php b/src/Dependency/ExportedNode/ExportedAttributeArgumentNode.php new file mode 100644 index 00000000000..11678bfda9a --- /dev/null +++ b/src/Dependency/ExportedNode/ExportedAttributeArgumentNode.php @@ -0,0 +1,76 @@ +name = $name; + $this->value = $value; + $this->byRef = $byRef; + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + return $this->name === $node->name + && $this->value === $node->value + && $this->byRef === $node->byRef; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['value'], + $properties['byRef'], + ); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'value' => $this->value, + 'byRef' => $this->byRef, + ], + ]; + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['value'], + $data['byRef'], + ); + } + +} diff --git a/src/Dependency/ExportedNode/ExportedAttributeNode.php b/src/Dependency/ExportedNode/ExportedAttributeNode.php new file mode 100644 index 00000000000..3c2a851bd4b --- /dev/null +++ b/src/Dependency/ExportedNode/ExportedAttributeNode.php @@ -0,0 +1,92 @@ +name = $name; + $this->args = $args; + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + if (count($this->args) !== count($node->args)) { + return false; + } + + foreach ($this->args as $i => $ourAttribute) { + $theirAttribute = $node->args[$i]; + if (!$ourAttribute->equals($theirAttribute)) { + return false; + } + } + + + return $this->name === $node->name; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['name'], + $properties['args'], + ); + } + + /** + * @return mixed + */ + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'args' => $this->args, + ], + ]; + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + array_map(static function (array $parameterData): ExportedAttributeArgumentNode { + if ($parameterData['type'] !== ExportedAttributeArgumentNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedAttributeArgumentNode::decode($parameterData['data']); + }, $data['args']) + ); + } + +} diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index e9c0523abf8..bb6a00ffc43 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -20,7 +20,21 @@ class ExportedClassConstantNode implements ExportedNode, JsonSerializable private ?ExportedPhpDocNode $phpDoc; - public function __construct(string $name, string $value, bool $public, bool $private, bool $final, ?ExportedPhpDocNode $phpDoc) + /** @var ExportedAttributeNode[] */ + private array $attributes; + + /** + * @param ExportedAttributeNode[] $attributes + */ + public function __construct( + string $name, + string $value, + bool $public, + bool $private, + bool $final, + ?ExportedPhpDocNode $phpDoc, + array $attributes + ) { $this->name = $name; $this->value = $value; @@ -28,7 +42,8 @@ public function __construct(string $name, string $value, bool $public, bool $pri $this->private = $private; $this->final = $final; $this->phpDoc = $phpDoc; - } + $this->attributes = $attributes; + } public function equals(ExportedNode $node): bool { @@ -48,6 +63,17 @@ public function equals(ExportedNode $node): bool return false; } + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $ourAttribute) { + $theirAttribute = $node->attributes[$i]; + if (!$ourAttribute->equals($theirAttribute)) { + return false; + } + } + return $this->name === $node->name && $this->value === $node->value && $this->public === $node->public @@ -67,8 +93,9 @@ public static function __set_state(array $properties): ExportedNode $properties['public'], $properties['private'], $properties['final'], - $properties['phpDoc'] - ); + $properties['phpDoc'], + $properties['attributes'] + ); } /** @@ -83,7 +110,13 @@ public static function decode(array $data): ExportedNode $data['public'], $data['private'], $data['final'], - $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + array_map(static function (array $parameterData): ExportedAttributeNode { + if ($parameterData['type'] !== ExportedAttributeNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($parameterData['data']); + }, $data['attributes']) ); } @@ -101,7 +134,8 @@ public function jsonSerialize() 'private' => $this->private, 'final' => $this->final, 'phpDoc' => $this->phpDoc, - ], + 'attributes' => $this->attributes, + ], ]; } diff --git a/src/Dependency/ExportedNode/ExportedClassNode.php b/src/Dependency/ExportedNode/ExportedClassNode.php index 4977a476436..cf5619636c6 100644 --- a/src/Dependency/ExportedNode/ExportedClassNode.php +++ b/src/Dependency/ExportedNode/ExportedClassNode.php @@ -27,6 +27,9 @@ class ExportedClassNode implements ExportedNode, JsonSerializable /** @var ExportedTraitUseAdaptation[] */ private array $traitUseAdaptations; + /** @var ExportedAttributeNode[] */ + private array $attributes; + /** * @param string $name * @param ExportedPhpDocNode|null $phpDoc @@ -36,6 +39,7 @@ class ExportedClassNode implements ExportedNode, JsonSerializable * @param string[] $implements * @param string[] $usedTraits * @param ExportedTraitUseAdaptation[] $traitUseAdaptations + * @param ExportedAttributeNode[] $attributes */ public function __construct( string $name, @@ -45,7 +49,8 @@ public function __construct( ?string $extends, array $implements, array $usedTraits, - array $traitUseAdaptations + array $traitUseAdaptations, + array $attributes ) { $this->name = $name; @@ -56,7 +61,8 @@ public function __construct( $this->implements = $implements; $this->usedTraits = $usedTraits; $this->traitUseAdaptations = $traitUseAdaptations; - } + $this->attributes = $attributes; + } public function equals(ExportedNode $node): bool { @@ -76,6 +82,17 @@ public function equals(ExportedNode $node): bool return false; } + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $ourAttribute) { + $theirAttribute = $node->attributes[$i]; + if (!$ourAttribute->equals($theirAttribute)) { + return false; + } + } + if (count($this->traitUseAdaptations) !== count($node->traitUseAdaptations)) { return false; } @@ -109,8 +126,9 @@ public static function __set_state(array $properties): ExportedNode $properties['extends'], $properties['implements'], $properties['usedTraits'], - $properties['traitUseAdaptations'] - ); + $properties['traitUseAdaptations'], + $properties['attributes'] + ); } /** @@ -129,7 +147,8 @@ public function jsonSerialize() 'implements' => $this->implements, 'usedTraits' => $this->usedTraits, 'traitUseAdaptations' => $this->traitUseAdaptations, - ], + 'attributes' => $this->attributes, + ], ]; } @@ -152,7 +171,13 @@ public static function decode(array $data): ExportedNode throw new \PHPStan\ShouldNotHappenException(); } return ExportedTraitUseAdaptation::decode($traitUseAdaptationData['data']); - }, $data['traitUseAdaptations']) + }, $data['traitUseAdaptations']), + array_map(static function (array $parameterData): ExportedAttributeNode { + if ($parameterData['type'] !== ExportedAttributeNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($parameterData['data']); + }, $data['attributes']) ); } diff --git a/src/Dependency/ExportedNode/ExportedFunctionNode.php b/src/Dependency/ExportedNode/ExportedFunctionNode.php index 6c4382b1b08..759166e2950 100644 --- a/src/Dependency/ExportedNode/ExportedFunctionNode.php +++ b/src/Dependency/ExportedNode/ExportedFunctionNode.php @@ -19,19 +19,24 @@ class ExportedFunctionNode implements ExportedNode, JsonSerializable /** @var ExportedParameterNode[] */ private array $parameters; + /** @var ExportedAttributeNode[] */ + private array $attributes; + /** * @param string $name * @param ExportedPhpDocNode|null $phpDoc * @param bool $byRef * @param string|null $returnType - * @param ExportedParameterNode[] $parameters + * @param ExportedParameterNode[] $parameters + * @param ExportedAttributeNode[] $attributes */ public function __construct( string $name, ?ExportedPhpDocNode $phpDoc, bool $byRef, ?string $returnType, - array $parameters + array $parameters, + array $attributes ) { $this->name = $name; @@ -39,6 +44,7 @@ public function __construct( $this->byRef = $byRef; $this->returnType = $returnType; $this->parameters = $parameters; + $this->attributes = $attributes; } public function equals(ExportedNode $node): bool @@ -70,6 +76,17 @@ public function equals(ExportedNode $node): bool return false; } + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $ourAttribute) { + $theirAttribute = $node->attributes[$i]; + if (!$ourAttribute->equals($theirAttribute)) { + return false; + } + } + return $this->name === $node->name && $this->byRef === $node->byRef && $this->returnType === $node->returnType; @@ -86,7 +103,8 @@ public static function __set_state(array $properties): ExportedNode $properties['phpDoc'], $properties['byRef'], $properties['returnType'], - $properties['parameters'] + $properties['parameters'], + $properties['attributes'], ); } @@ -103,6 +121,7 @@ public function jsonSerialize() 'byRef' => $this->byRef, 'returnType' => $this->returnType, 'parameters' => $this->parameters, + 'attributes' => $this->attributes, ], ]; } @@ -123,7 +142,13 @@ public static function decode(array $data): ExportedNode throw new \PHPStan\ShouldNotHappenException(); } return ExportedParameterNode::decode($parameterData['data']); - }, $data['parameters']) + }, $data['parameters']), + array_map(static function (array $parameterData): ExportedAttributeNode { + if ($parameterData['type'] !== ExportedAttributeNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($parameterData['data']); + }, $data['attributes']) ); } diff --git a/src/Dependency/ExportedNode/ExportedMethodNode.php b/src/Dependency/ExportedNode/ExportedMethodNode.php index a59a40c25a7..e11c957a1db 100644 --- a/src/Dependency/ExportedNode/ExportedMethodNode.php +++ b/src/Dependency/ExportedNode/ExportedMethodNode.php @@ -29,6 +29,9 @@ class ExportedMethodNode implements ExportedNode, JsonSerializable /** @var ExportedParameterNode[] */ private array $parameters; + /** @var ExportedAttributeNode[] */ + private array $attributes; + /** * @param string $name * @param ExportedPhpDocNode|null $phpDoc @@ -39,7 +42,8 @@ class ExportedMethodNode implements ExportedNode, JsonSerializable * @param bool $final * @param bool $static * @param string|null $returnType - * @param ExportedParameterNode[] $parameters + * @param ExportedParameterNode[] $parameters + * @param ExportedAttributeNode[] $attributes */ public function __construct( string $name, @@ -51,7 +55,8 @@ public function __construct( bool $final, bool $static, ?string $returnType, - array $parameters + array $parameters, + array $attributes ) { $this->name = $name; @@ -64,6 +69,7 @@ public function __construct( $this->static = $static; $this->returnType = $returnType; $this->parameters = $parameters; + $this->attributes = $attributes; } public function equals(ExportedNode $node): bool @@ -95,6 +101,17 @@ public function equals(ExportedNode $node): bool return false; } + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $ourAttribute) { + $theirAttribute = $node->attributes[$i]; + if (!$ourAttribute->equals($theirAttribute)) { + return false; + } + } + return $this->name === $node->name && $this->byRef === $node->byRef && $this->public === $node->public @@ -121,7 +138,8 @@ public static function __set_state(array $properties): ExportedNode $properties['final'], $properties['static'], $properties['returnType'], - $properties['parameters'] + $properties['parameters'], + $properties['attributes'], ); } @@ -143,6 +161,7 @@ public function jsonSerialize() 'static' => $this->static, 'returnType' => $this->returnType, 'parameters' => $this->parameters, + 'attributes' => $this->attributes, ], ]; } @@ -168,7 +187,13 @@ public static function decode(array $data): ExportedNode throw new \PHPStan\ShouldNotHappenException(); } return ExportedParameterNode::decode($parameterData['data']); - }, $data['parameters']) + }, $data['parameters']), + array_map(static function (array $parameterData): ExportedAttributeNode { + if ($parameterData['type'] !== ExportedAttributeNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($parameterData['data']); + }, $data['attributes']) ); } diff --git a/src/Dependency/ExportedNode/ExportedParameterNode.php b/src/Dependency/ExportedNode/ExportedParameterNode.php index 5f171a442f5..d14e23271e0 100644 --- a/src/Dependency/ExportedNode/ExportedParameterNode.php +++ b/src/Dependency/ExportedNode/ExportedParameterNode.php @@ -18,12 +18,19 @@ class ExportedParameterNode implements ExportedNode, JsonSerializable private bool $hasDefault; - public function __construct( + /** @var ExportedAttributeNode[] */ + private array $attributes; + + /** + * @param ExportedAttributeNode[] $attributes + */ + public function __construct( string $name, ?string $type, bool $byRef, bool $variadic, - bool $hasDefault + bool $hasDefault, + array $attributes ) { $this->name = $name; @@ -31,7 +38,8 @@ public function __construct( $this->byRef = $byRef; $this->variadic = $variadic; $this->hasDefault = $hasDefault; - } + $this->attributes = $attributes; + } public function equals(ExportedNode $node): bool { @@ -39,6 +47,17 @@ public function equals(ExportedNode $node): bool return false; } + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $ourAttribute) { + $theirAttribute = $node->attributes[$i]; + if (!$ourAttribute->equals($theirAttribute)) { + return false; + } + } + return $this->name === $node->name && $this->type === $node->type && $this->byRef === $node->byRef @@ -57,7 +76,8 @@ public static function __set_state(array $properties): ExportedNode $properties['type'], $properties['byRef'], $properties['variadic'], - $properties['hasDefault'] + $properties['hasDefault'], + $properties['attributes'], ); } @@ -74,6 +94,7 @@ public function jsonSerialize() 'byRef' => $this->byRef, 'variadic' => $this->variadic, 'hasDefault' => $this->hasDefault, + 'attributes' => $this->attributes, ], ]; } @@ -89,7 +110,13 @@ public static function decode(array $data): ExportedNode $data['type'], $data['byRef'], $data['variadic'], - $data['hasDefault'] + $data['hasDefault'], + array_map(static function (array $parameterData): ExportedAttributeNode { + if ($parameterData['type'] !== ExportedAttributeNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($parameterData['data']); + }, $data['attributes']) ); } diff --git a/src/Dependency/ExportedNode/ExportedPropertyNode.php b/src/Dependency/ExportedNode/ExportedPropertyNode.php index b5bf696d471..5d752822165 100644 --- a/src/Dependency/ExportedNode/ExportedPropertyNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertyNode.php @@ -22,6 +22,12 @@ class ExportedPropertyNode implements JsonSerializable, ExportedNode private bool $readonly; + /** @var ExportedAttributeNode[] */ + private array $attributes; + + /** + * @param ExportedAttributeNode[] $attributes + */ public function __construct( string $name, ?ExportedPhpDocNode $phpDoc, @@ -29,8 +35,9 @@ public function __construct( bool $public, bool $private, bool $static, - bool $readonly - ) + bool $readonly, + array $attributes + ) { $this->name = $name; $this->phpDoc = $phpDoc; @@ -39,7 +46,8 @@ public function __construct( $this->private = $private; $this->static = $static; $this->readonly = $readonly; - } + $this->attributes = $attributes; + } public function equals(ExportedNode $node): bool { @@ -59,6 +67,17 @@ public function equals(ExportedNode $node): bool return false; } + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $ourAttribute) { + $theirAttribute = $node->attributes[$i]; + if (!$ourAttribute->equals($theirAttribute)) { + return false; + } + } + return $this->name === $node->name && $this->type === $node->type && $this->public === $node->public @@ -80,8 +99,9 @@ public static function __set_state(array $properties): ExportedNode $properties['public'], $properties['private'], $properties['static'], - $properties['readonly'] - ); + $properties['readonly'], + $properties['attributes'] + ); } /** @@ -97,7 +117,13 @@ public static function decode(array $data): ExportedNode $data['public'], $data['private'], $data['static'], - $data['readonly'] + $data['readonly'], + array_map(static function (array $parameterData): ExportedAttributeNode { + if ($parameterData['type'] !== ExportedAttributeNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($parameterData['data']); + }, $data['attributes']) ); } @@ -116,7 +142,8 @@ public function jsonSerialize() 'private' => $this->private, 'static' => $this->static, 'readonly' => $this->readonly, - ], + 'attributes' => $this->attributes, + ], ]; } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 0353437cac4..9a9b7fa08dc 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -9,6 +9,8 @@ use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; use PhpParser\PrettyPrinter\Standard; +use PHPStan\Dependency\ExportedNode\ExportedAttributeArgumentNode; +use PHPStan\Dependency\ExportedNode\ExportedAttributeNode; use PHPStan\Dependency\ExportedNode\ExportedClassConstantNode; use PHPStan\Dependency\ExportedNode\ExportedClassNode; use PHPStan\Dependency\ExportedNode\ExportedFunctionNode; @@ -95,8 +97,9 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode } throw new \PHPStan\ShouldNotHappenException(); - }, $adaptations) - ); + }, $adaptations), + $this->exportAttributeNodes($node->attrGroups) + ); } if ($node instanceof \PhpParser\Node\Stmt\Interface_ && isset($node->namespacedName)) { @@ -148,7 +151,8 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode $node->isFinal(), $node->isStatic(), $this->printType($node->returnType), - $this->exportParameterNodes($node->params) + $this->exportParameterNodes($node->params), + $this->exportAttributeNodes($node->attrGroups) ); } } @@ -181,8 +185,9 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode $parentNode->isPublic(), $parentNode->isPrivate(), $parentNode->isStatic(), - $parentNode->isReadonly() - ); + $parentNode->isReadonly(), + $this->exportAttributeNodes($parentNode->attrGroups) + ); } if ($node instanceof Node\Const_) { @@ -213,8 +218,9 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode $classNode->namespacedName->toString(), null, $docComment !== null ? $docComment->getText() : null - ) - ); + ), + $this->exportAttributeNodes($parentNode->attrGroups) + ); } if ($node instanceof Function_) { @@ -235,8 +241,9 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode ), $node->byRef, $this->printType($node->returnType), - $this->exportParameterNodes($node->params) - ); + $this->exportParameterNodes($node->params), + $this->exportAttributeNodes($node->attrGroups) + ); } return null; @@ -315,13 +322,42 @@ private function exportParameterNodes(array $params): array $this->printType($type), $param->byRef, $param->variadic, - $param->default !== null - ); + $param->default !== null, + $this->exportAttributeNodes($param->attrGroups) + ); } return $nodes; } + /** + * @param Node\AttributeGroup[] $attributeGroups + * @return ExportedAttributeNode[] + */ + private function exportAttributeNodes(array $attributeGroups): array + { + $nodes = []; + foreach ($attributeGroups as $attributeGroup) { + foreach ($attributeGroup->attrs as $attribute) { + $args = []; + foreach ($attribute->args as $arg) { + $args[] = new ExportedAttributeArgumentNode( + $arg->name !== null ? $arg->name->name : null, + $this->printer->prettyPrintExpr($arg->value), + $arg->byRef, + ); + } + + $nodes[] = new ExportedAttributeNode( + $attribute->name->toString(), + $args, + ); + } + } + + return $nodes; + } + private function exportPhpDocNode( string $file, ?string $className,