diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 9018578135..b941085e3f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -8,6 +8,7 @@ use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\Node\Arg; +use PhpParser\Node\AttributeGroup; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayDimFetch; @@ -395,13 +396,7 @@ private function processStmtNode( } elseif ($stmt instanceof Node\Stmt\Function_) { $hasYield = false; $throwPoints = []; - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - } - } + $this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt); foreach ($stmt->params as $param) { @@ -456,13 +451,7 @@ private function processStmtNode( } elseif ($stmt instanceof Node\Stmt\ClassMethod) { $hasYield = false; $throwPoints = []; - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - } - } + $this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt); foreach ($stmt->params as $param) { @@ -633,13 +622,7 @@ private function processStmtNode( } $classStatementsGatherer = new ClassStatementsGatherer($classReflection, $nodeCallback); - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $this->processExprNode($arg->value, $classScope, $classStatementsGatherer, ExpressionContext::createDeep()); - } - } - } + $this->processAttributeGroups($stmt->attrGroups, $classScope, $classStatementsGatherer); $this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer); $nodeCallback(new ClassPropertiesNode($stmt, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getTraitProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls()), $classScope); @@ -649,13 +632,8 @@ private function processStmtNode( } elseif ($stmt instanceof Node\Stmt\Property) { $hasYield = false; $throwPoints = []; - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - } - } + $this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); + foreach ($stmt->props as $prop) { $this->processStmtNode($prop, $scope, $nodeCallback); [,,,,,,,,,$isReadOnly, $docComment] = $this->getPhpDocs($scope, $stmt); @@ -1406,13 +1384,7 @@ private function processStmtNode( $hasYield = false; $throwPoints = []; if ($stmt instanceof Node\Stmt\ClassConst) { - foreach ($stmt->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - } - } + $this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback); } foreach ($stmt->consts as $const) { $nodeCallback($const, $scope); @@ -3108,13 +3080,7 @@ private function processParamNode( callable $nodeCallback, ): void { - foreach ($param->attrGroups as $attrGroup) { - foreach ($attrGroup->attrs as $attr) { - foreach ($attr->args as $arg) { - $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); - } - } - } + $this->processAttributeGroups($param->attrGroups, $scope, $nodeCallback); $nodeCallback($param, $scope); if ($param->type !== null) { $nodeCallback($param->type, $scope); @@ -3126,6 +3092,28 @@ private function processParamNode( $this->processExprNode($param->default, $scope, $nodeCallback, ExpressionContext::createDeep()); } + /** + * @param AttributeGroup[] $attrGroups + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + private function processAttributeGroups( + array $attrGroups, + MutatingScope $scope, + callable $nodeCallback, + ): void + { + foreach ($attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + foreach ($attr->args as $arg) { + $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); + $nodeCallback($arg, $scope); + } + $nodeCallback($attr, $scope); + } + $nodeCallback($attrGroup, $scope); + } + } + /** * @param MethodReflection|FunctionReflection|null $calleeReflection * @param Node\Arg[] $args diff --git a/tests/PHPStan/Node/AttributeArgRule.php b/tests/PHPStan/Node/AttributeArgRule.php new file mode 100644 index 0000000000..90865da170 --- /dev/null +++ b/tests/PHPStan/Node/AttributeArgRule.php @@ -0,0 +1,31 @@ + + */ +class AttributeArgRule implements Rule +{ + + public const ERROR_MESSAGE = 'Found Arg'; + + public function getNodeType(): string + { + return Node\Arg::class; + } + + /** + * @param Node\Arg $node + * @return string[] + */ + public function processNode(Node $node, Scope $scope): array + { + return [self::ERROR_MESSAGE]; + } + +} diff --git a/tests/PHPStan/Node/AttributeArgRuleTest.php b/tests/PHPStan/Node/AttributeArgRuleTest.php new file mode 100644 index 0000000000..29773859d8 --- /dev/null +++ b/tests/PHPStan/Node/AttributeArgRuleTest.php @@ -0,0 +1,45 @@ + + */ +class AttributeArgRuleTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new AttributeArgRule(); + } + + public function dataRule(): iterable + { + yield [ + __DIR__ . '/data/attributes.php', + AttributeArgRule::ERROR_MESSAGE, + [8, 16, 20, 23, 26, 27, 34, 40], + ]; + } + + /** + * @param int[] $lines + * @dataProvider dataRule + */ + public function testRule(string $file, string $expectedError, array $lines): void + { + $errors = []; + foreach ($lines as $line) { + $errors[] = [$expectedError, $line]; + } + $this->analyse([$file], $errors); + } + +} diff --git a/tests/PHPStan/Node/AttributeGroupRule.php b/tests/PHPStan/Node/AttributeGroupRule.php new file mode 100644 index 0000000000..127c4f1f62 --- /dev/null +++ b/tests/PHPStan/Node/AttributeGroupRule.php @@ -0,0 +1,31 @@ + + */ +class AttributeGroupRule implements Rule +{ + + public const ERROR_MESSAGE = 'Found AttributeGroup'; + + public function getNodeType(): string + { + return Node\AttributeGroup::class; + } + + /** + * @param Node\AttributeGroup $node + * @return string[] + */ + public function processNode(Node $node, Scope $scope): array + { + return [self::ERROR_MESSAGE]; + } + +} diff --git a/tests/PHPStan/Node/AttributeGroupRuleTest.php b/tests/PHPStan/Node/AttributeGroupRuleTest.php new file mode 100644 index 0000000000..d1e3044f44 --- /dev/null +++ b/tests/PHPStan/Node/AttributeGroupRuleTest.php @@ -0,0 +1,45 @@ + + */ +class AttributeGroupRuleTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new AttributeGroupRule(); + } + + public function dataRule(): iterable + { + yield [ + __DIR__ . '/data/attributes.php', + AttributeGroupRule::ERROR_MESSAGE, + [8, 16, 20, 23, 26, 27, 34, 40], + ]; + } + + /** + * @param int[] $lines + * @dataProvider dataRule + */ + public function testRule(string $file, string $expectedError, array $lines): void + { + $errors = []; + foreach ($lines as $line) { + $errors[] = [$expectedError, $line]; + } + $this->analyse([$file], $errors); + } + +} diff --git a/tests/PHPStan/Node/AttributeRule.php b/tests/PHPStan/Node/AttributeRule.php new file mode 100644 index 0000000000..9b7519bce8 --- /dev/null +++ b/tests/PHPStan/Node/AttributeRule.php @@ -0,0 +1,31 @@ + + */ +class AttributeRule implements Rule +{ + + public const ERROR_MESSAGE = 'Found Attribute'; + + public function getNodeType(): string + { + return Node\Attribute::class; + } + + /** + * @param Node\Attribute $node + * @return string[] + */ + public function processNode(Node $node, Scope $scope): array + { + return [self::ERROR_MESSAGE]; + } + +} diff --git a/tests/PHPStan/Node/AttributeRuleTest.php b/tests/PHPStan/Node/AttributeRuleTest.php new file mode 100644 index 0000000000..3b189a210d --- /dev/null +++ b/tests/PHPStan/Node/AttributeRuleTest.php @@ -0,0 +1,45 @@ + + */ +class AttributeRuleTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new AttributeRule(); + } + + public function dataRule(): iterable + { + yield [ + __DIR__ . '/data/attributes.php', + AttributeRule::ERROR_MESSAGE, + [8, 16, 20, 23, 26, 27, 34, 40], + ]; + } + + /** + * @param int[] $lines + * @dataProvider dataRule + */ + public function testRule(string $file, string $expectedError, array $lines): void + { + $errors = []; + foreach ($lines as $line) { + $errors[] = [$expectedError, $line]; + } + $this->analyse([$file], $errors); + } + +} diff --git a/tests/PHPStan/Node/data/attributes.php b/tests/PHPStan/Node/data/attributes.php new file mode 100644 index 0000000000..0103b343c4 --- /dev/null +++ b/tests/PHPStan/Node/data/attributes.php @@ -0,0 +1,41 @@ += 8.0 + +namespace NodeCallbackCalled; + +use ClassAttributes\AttributeWithConstructor; +use FunctionAttributes\Baz; + +#[\Attribute(flags: \Attribute::TARGET_ALL)] +class UniversalAttribute +{ + public function __construct(int $foo) + { + } +} + +#[UniversalAttribute(1)] +class MyClass +{ + + #[UniversalAttribute(2)] + private const MY_CONST = 'const'; + + #[UniversalAttribute(3)] + private string $myProperty; + + #[UniversalAttribute(4)] + public function myMethod(#[UniversalAttribute(5)] string $arg): void + { + + } + +} + +#[UniversalAttribute(6)] +interface MyInterface {} + +#[UniversalAttribute(7)] +trait MyTrait {} + +#[UniversalAttribute(8)] +function myFunction() {}