diff --git a/src/Rules/Classes/DuplicateTraitDeclarationRule.php b/src/Rules/Classes/DuplicateTraitDeclarationRule.php new file mode 100644 index 0000000000..a4165c0796 --- /dev/null +++ b/src/Rules/Classes/DuplicateTraitDeclarationRule.php @@ -0,0 +1,120 @@ + + */ +#[RegisteredRule(level: 0)] +final class DuplicateTraitDeclarationRule implements Rule +{ + + public function getNodeType(): string + { + return InTraitNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $traitReflection = $node->getTraitReflection(); + + $errors = []; + + $declaredClassConstants = []; + foreach ($node->getOriginalNode()->stmts as $stmtNode) { + if (!($stmtNode instanceof ClassConst)) { + continue; + } + foreach ($stmtNode->consts as $classConstNode) { + if (array_key_exists($classConstNode->name->name, $declaredClassConstants)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare constant %s::%s.', + $traitReflection->getDisplayName(), + $classConstNode->name->name, + ))->identifier('trait.duplicateConstant') + ->line($classConstNode->getStartLine()) + ->nonIgnorable() + ->build(); + } else { + $declaredClassConstants[$classConstNode->name->name] = true; + } + } + } + + $declaredProperties = []; + foreach ($node->getOriginalNode()->getProperties() as $propertyDecl) { + foreach ($propertyDecl->props as $property) { + if (array_key_exists($property->name->name, $declaredProperties)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare property %s::$%s.', + $traitReflection->getDisplayName(), + $property->name->name, + ))->identifier('trait.duplicateProperty') + ->line($property->getStartLine()) + ->nonIgnorable() + ->build(); + } else { + $declaredProperties[$property->name->name] = true; + } + } + } + + $declaredFunctions = []; + foreach ($node->getOriginalNode()->getMethods() as $method) { + if ($method->name->toLowerString() === '__construct') { + foreach ($method->params as $param) { + if ($param->flags === 0) { + continue; + } + + if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) { + throw new ShouldNotHappenException(); + } + + $propertyName = $param->var->name; + + if (array_key_exists($propertyName, $declaredProperties)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare property %s::$%s.', + $traitReflection->getDisplayName(), + $propertyName, + ))->identifier('trait.duplicateProperty') + ->line($param->getStartLine()) + ->nonIgnorable() + ->build(); + } else { + $declaredProperties[$propertyName] = true; + } + } + } + if (array_key_exists(strtolower($method->name->name), $declaredFunctions)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare method %s::%s().', + $traitReflection->getDisplayName(), + $method->name->name, + ))->identifier('trait.duplicateMethod') + ->line($method->getStartLine()) + ->nonIgnorable() + ->build(); + } else { + $declaredFunctions[strtolower($method->name->name)] = true; + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Classes/DuplicateTraitDeclarationRuleTest.php b/tests/PHPStan/Rules/Classes/DuplicateTraitDeclarationRuleTest.php new file mode 100644 index 0000000000..b3cdd5f197 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/DuplicateTraitDeclarationRuleTest.php @@ -0,0 +1,29 @@ + + */ +class DuplicateTraitDeclarationRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new DuplicateTraitDeclarationRule(); + } + + public function testBug14250(): void + { + $this->analyse([__DIR__ . '/data/bug-14250.php'], [ + [ + 'Cannot redeclare method Bug14250\MyTrait::doSomething().', + 11, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/bug-14250.php b/tests/PHPStan/Rules/Classes/data/bug-14250.php new file mode 100644 index 0000000000..9a23ef836b --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-14250.php @@ -0,0 +1,19 @@ +