Skip to content

Commit 0e2968b

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents d9442ff + 594b7bd commit 0e2968b

File tree

9 files changed

+359
-107
lines changed

9 files changed

+359
-107
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ lint:
128128
--exclude tests/PHPStan/Rules/Properties/data/property-override-attr-missing.php \
129129
--exclude tests/PHPStan/Rules/Properties/data/override-attr-on-property.php \
130130
--exclude tests/PHPStan/Rules/Properties/data/property-override-attr.php \
131+
--exclude tests/PHPStan/Rules/Classes/data/bug-14250.php \
132+
--exclude tests/PHPStan/Rules/Classes/data/bug-14250-promoted-properties.php \
131133
--exclude tests/PHPStan/Rules/Operators/data/bug-3585.php \
132134
src tests
133135

src/PhpDoc/StubValidator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use PHPStan\Reflection\ReflectionProvider;
1919
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
2020
use PHPStan\Rules\Classes\DuplicateClassDeclarationRule;
21+
use PHPStan\Rules\Classes\DuplicateDeclarationHelper;
2122
use PHPStan\Rules\Classes\DuplicateDeclarationRule;
2223
use PHPStan\Rules\Classes\ExistingClassesInClassImplementsRule;
2324
use PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule;
@@ -228,7 +229,7 @@ private function getRuleRegistry(Container $container): RuleRegistry
228229
new MethodPrototypeFinder($phpVersion, $phpClassReflectionExtension),
229230
$container->getParameter('checkMissingOverrideMethodAttribute'),
230231
),
231-
new DuplicateDeclarationRule(),
232+
new DuplicateDeclarationRule(new DuplicateDeclarationHelper()),
232233
new LocalTypeAliasesRule($localTypeAliasesCheck),
233234
new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider),
234235
new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck),
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt\ClassConst;
7+
use PhpParser\Node\Stmt\ClassLike;
8+
use PhpParser\Node\Stmt\EnumCase;
9+
use PHPStan\DependencyInjection\AutowiredService;
10+
use PHPStan\Rules\IdentifierRuleError;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
use PHPStan\ShouldNotHappenException;
13+
use function array_key_exists;
14+
use function is_string;
15+
use function sprintf;
16+
use function strtolower;
17+
18+
#[AutowiredService]
19+
final class DuplicateDeclarationHelper
20+
{
21+
22+
/**
23+
* @param 'class'|'interface'|'trait'|'enum' $identifierType
24+
* @return list<IdentifierRuleError>
25+
*/
26+
public function checkClassLike(ClassLike $classLike, string $displayName, string $identifierType): array
27+
{
28+
$errors = [];
29+
30+
$declaredClassConstantsOrEnumCases = [];
31+
foreach ($classLike->stmts as $stmtNode) {
32+
if ($stmtNode instanceof EnumCase) {
33+
if (array_key_exists($stmtNode->name->name, $declaredClassConstantsOrEnumCases)) {
34+
$errors[] = RuleErrorBuilder::message(sprintf(
35+
'Cannot redeclare enum case %s::%s.',
36+
$displayName,
37+
$stmtNode->name->name,
38+
))->identifier(sprintf('%s.duplicateEnumCase', $identifierType))
39+
->line($stmtNode->getStartLine())
40+
->nonIgnorable()
41+
->build();
42+
} else {
43+
$declaredClassConstantsOrEnumCases[$stmtNode->name->name] = true;
44+
}
45+
} elseif ($stmtNode instanceof ClassConst) {
46+
foreach ($stmtNode->consts as $classConstNode) {
47+
if (array_key_exists($classConstNode->name->name, $declaredClassConstantsOrEnumCases)) {
48+
$errors[] = RuleErrorBuilder::message(sprintf(
49+
'Cannot redeclare constant %s::%s.',
50+
$displayName,
51+
$classConstNode->name->name,
52+
))->identifier(sprintf('%s.duplicateConstant', $identifierType))
53+
->line($classConstNode->getStartLine())
54+
->nonIgnorable()
55+
->build();
56+
} else {
57+
$declaredClassConstantsOrEnumCases[$classConstNode->name->name] = true;
58+
}
59+
}
60+
}
61+
}
62+
63+
$declaredProperties = [];
64+
foreach ($classLike->getProperties() as $propertyDecl) {
65+
foreach ($propertyDecl->props as $property) {
66+
if (array_key_exists($property->name->name, $declaredProperties)) {
67+
$errors[] = RuleErrorBuilder::message(sprintf(
68+
'Cannot redeclare property %s::$%s.',
69+
$displayName,
70+
$property->name->name,
71+
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
72+
->line($property->getStartLine())
73+
->nonIgnorable()
74+
->build();
75+
} else {
76+
$declaredProperties[$property->name->name] = true;
77+
}
78+
}
79+
}
80+
81+
$declaredFunctions = [];
82+
foreach ($classLike->getMethods() as $method) {
83+
if ($method->name->toLowerString() === '__construct') {
84+
foreach ($method->params as $param) {
85+
if ($param->flags === 0) {
86+
continue;
87+
}
88+
89+
if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) {
90+
throw new ShouldNotHappenException();
91+
}
92+
93+
$propertyName = $param->var->name;
94+
95+
if (array_key_exists($propertyName, $declaredProperties)) {
96+
$errors[] = RuleErrorBuilder::message(sprintf(
97+
'Cannot redeclare property %s::$%s.',
98+
$displayName,
99+
$propertyName,
100+
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
101+
->line($param->getStartLine())
102+
->nonIgnorable()
103+
->build();
104+
} else {
105+
$declaredProperties[$propertyName] = true;
106+
}
107+
}
108+
}
109+
if (array_key_exists(strtolower($method->name->name), $declaredFunctions)) {
110+
$errors[] = RuleErrorBuilder::message(sprintf(
111+
'Cannot redeclare method %s::%s().',
112+
$displayName,
113+
$method->name->name,
114+
))->identifier(sprintf('%s.duplicateMethod', $identifierType))
115+
->line($method->getStartLine())
116+
->nonIgnorable()
117+
->build();
118+
} else {
119+
$declaredFunctions[strtolower($method->name->name)] = true;
120+
}
121+
}
122+
123+
return $errors;
124+
}
125+
126+
}

src/Rules/Classes/DuplicateDeclarationRule.php

Lines changed: 9 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,10 @@
33
namespace PHPStan\Rules\Classes;
44

55
use PhpParser\Node;
6-
use PhpParser\Node\Stmt\ClassConst;
7-
use PhpParser\Node\Stmt\EnumCase;
86
use PHPStan\Analyser\Scope;
97
use PHPStan\DependencyInjection\RegisteredRule;
108
use PHPStan\Node\InClassNode;
119
use PHPStan\Rules\Rule;
12-
use PHPStan\Rules\RuleErrorBuilder;
13-
use PHPStan\ShouldNotHappenException;
14-
use function array_key_exists;
15-
use function is_string;
16-
use function sprintf;
1710
use function strtolower;
1811

1912
/**
@@ -23,6 +16,10 @@
2316
final class DuplicateDeclarationRule implements Rule
2417
{
2518

19+
public function __construct(private DuplicateDeclarationHelper $helper)
20+
{
21+
}
22+
2623
public function getNodeType(): string
2724
{
2825
return InClassNode::class;
@@ -32,104 +29,11 @@ public function processNode(Node $node, Scope $scope): array
3229
{
3330
$classReflection = $node->getClassReflection();
3431

35-
$identifierType = strtolower($classReflection->getClassTypeDescription());
36-
37-
$errors = [];
38-
39-
$declaredClassConstantsOrEnumCases = [];
40-
foreach ($node->getOriginalNode()->stmts as $stmtNode) {
41-
if ($stmtNode instanceof EnumCase) {
42-
if (array_key_exists($stmtNode->name->name, $declaredClassConstantsOrEnumCases)) {
43-
$errors[] = RuleErrorBuilder::message(sprintf(
44-
'Cannot redeclare enum case %s::%s.',
45-
$classReflection->getDisplayName(),
46-
$stmtNode->name->name,
47-
))->identifier(sprintf('%s.duplicateEnumCase', $identifierType))
48-
->line($stmtNode->getStartLine())
49-
->nonIgnorable()
50-
->build();
51-
} else {
52-
$declaredClassConstantsOrEnumCases[$stmtNode->name->name] = true;
53-
}
54-
} elseif ($stmtNode instanceof ClassConst) {
55-
foreach ($stmtNode->consts as $classConstNode) {
56-
if (array_key_exists($classConstNode->name->name, $declaredClassConstantsOrEnumCases)) {
57-
$errors[] = RuleErrorBuilder::message(sprintf(
58-
'Cannot redeclare constant %s::%s.',
59-
$classReflection->getDisplayName(),
60-
$classConstNode->name->name,
61-
))->identifier(sprintf('%s.duplicateConstant', $identifierType))
62-
->line($classConstNode->getStartLine())
63-
->nonIgnorable()
64-
->build();
65-
} else {
66-
$declaredClassConstantsOrEnumCases[$classConstNode->name->name] = true;
67-
}
68-
}
69-
}
70-
}
71-
72-
$declaredProperties = [];
73-
foreach ($node->getOriginalNode()->getProperties() as $propertyDecl) {
74-
foreach ($propertyDecl->props as $property) {
75-
if (array_key_exists($property->name->name, $declaredProperties)) {
76-
$errors[] = RuleErrorBuilder::message(sprintf(
77-
'Cannot redeclare property %s::$%s.',
78-
$classReflection->getDisplayName(),
79-
$property->name->name,
80-
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
81-
->line($property->getStartLine())
82-
->nonIgnorable()
83-
->build();
84-
} else {
85-
$declaredProperties[$property->name->name] = true;
86-
}
87-
}
88-
}
89-
90-
$declaredFunctions = [];
91-
foreach ($node->getOriginalNode()->getMethods() as $method) {
92-
if ($method->name->toLowerString() === '__construct') {
93-
foreach ($method->params as $param) {
94-
if ($param->flags === 0) {
95-
continue;
96-
}
97-
98-
if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) {
99-
throw new ShouldNotHappenException();
100-
}
101-
102-
$propertyName = $param->var->name;
103-
104-
if (array_key_exists($propertyName, $declaredProperties)) {
105-
$errors[] = RuleErrorBuilder::message(sprintf(
106-
'Cannot redeclare property %s::$%s.',
107-
$classReflection->getDisplayName(),
108-
$propertyName,
109-
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
110-
->line($param->getStartLine())
111-
->nonIgnorable()
112-
->build();
113-
} else {
114-
$declaredProperties[$propertyName] = true;
115-
}
116-
}
117-
}
118-
if (array_key_exists(strtolower($method->name->name), $declaredFunctions)) {
119-
$errors[] = RuleErrorBuilder::message(sprintf(
120-
'Cannot redeclare method %s::%s().',
121-
$classReflection->getDisplayName(),
122-
$method->name->name,
123-
))->identifier(sprintf('%s.duplicateMethod', $identifierType))
124-
->line($method->getStartLine())
125-
->nonIgnorable()
126-
->build();
127-
} else {
128-
$declaredFunctions[strtolower($method->name->name)] = true;
129-
}
130-
}
131-
132-
return $errors;
32+
return $this->helper->checkClassLike(
33+
$node->getOriginalNode(),
34+
$classReflection->getDisplayName(),
35+
strtolower($classReflection->getClassTypeDescription()),
36+
);
13337
}
13438

13539
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\RegisteredRule;
8+
use PHPStan\Node\InTraitNode;
9+
use PHPStan\Rules\Rule;
10+
11+
/**
12+
* @implements Rule<InTraitNode>
13+
*/
14+
#[RegisteredRule(level: 0)]
15+
final class DuplicateTraitDeclarationRule implements Rule
16+
{
17+
18+
public function __construct(private DuplicateDeclarationHelper $helper)
19+
{
20+
}
21+
22+
public function getNodeType(): string
23+
{
24+
return InTraitNode::class;
25+
}
26+
27+
public function processNode(Node $node, Scope $scope): array
28+
{
29+
return $this->helper->checkClassLike(
30+
$node->getOriginalNode(),
31+
$node->getTraitReflection()->getDisplayName(),
32+
'trait',
33+
);
34+
}
35+
36+
}

tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class DuplicateDeclarationRuleTest extends RuleTestCase
1414

1515
protected function getRule(): Rule
1616
{
17-
return new DuplicateDeclarationRule();
17+
return new DuplicateDeclarationRule(new DuplicateDeclarationHelper());
1818
}
1919

2020
public function testDuplicateDeclarations(): void

0 commit comments

Comments
 (0)