Skip to content

Commit 98ed732

Browse files
committed
Internal PHPStan rule - add #[Override] for methods overriding 3rd party
1 parent fc60b19 commit 98ed732

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Build;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Attribute;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Node\InClassMethodNode;
9+
use PHPStan\Php\PhpVersion;
10+
use PHPStan\Rules\Methods\MethodPrototypeFinder;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use function str_starts_with;
14+
15+
/**
16+
* @implements Rule<InClassMethodNode>
17+
*/
18+
final class OverrideAttributeThirdPartyMethodRule implements Rule
19+
{
20+
21+
public function __construct(
22+
private PhpVersion $phpVersion,
23+
private MethodPrototypeFinder $methodPrototypeFinder,
24+
)
25+
{
26+
}
27+
28+
public function getNodeType(): string
29+
{
30+
return InClassMethodNode::class;
31+
}
32+
33+
public function processNode(Node $node, Scope $scope): array
34+
{
35+
$method = $node->getMethodReflection();
36+
$prototypeData = $this->methodPrototypeFinder->findPrototype($node->getClassReflection(), $method->getName());
37+
if ($prototypeData === null) {
38+
return [];
39+
}
40+
41+
[$prototype, $prototypeDeclaringClass] = $prototypeData;
42+
43+
if (str_starts_with($prototypeDeclaringClass->getName(), 'PHPStan\\')) {
44+
if (
45+
!str_starts_with($prototypeDeclaringClass->getName(), 'PHPStan\\PhpDocParser\\')
46+
&& !str_starts_with($prototypeDeclaringClass->getName(), 'PHPStan\\BetterReflection\\')
47+
) {
48+
return [];
49+
}
50+
}
51+
52+
$messages = [];
53+
if (
54+
$this->phpVersion->supportsOverrideAttribute()
55+
&& !$scope->isInTrait()
56+
&& !$this->hasOverrideAttribute($node->getOriginalNode())
57+
) {
58+
$messages[] = RuleErrorBuilder::message(sprintf(
59+
'Method %s::%s() overrides 3rd party method %s::%s() but is missing the #[\Override] attribute.',
60+
$method->getDeclaringClass()->getDisplayName(),
61+
$method->getName(),
62+
$prototypeDeclaringClass->getDisplayName(true),
63+
$prototype->getName(),
64+
))
65+
->identifier('phpstan.missing3rdPartyOverride')
66+
->fixNode($node->getOriginalNode(), static function (Node\Stmt\ClassMethod $method) {
67+
$method->attrGroups[] = new Node\AttributeGroup([
68+
new Attribute(new Node\Name\FullyQualified('Override')),
69+
]);
70+
71+
return $method;
72+
})
73+
->build();
74+
}
75+
76+
return $messages;
77+
}
78+
79+
private function hasOverrideAttribute(Node\Stmt\ClassMethod $method): bool
80+
{
81+
foreach ($method->attrGroups as $attrGroup) {
82+
foreach ($attrGroup->attrs as $attr) {
83+
if ($attr->name->toLowerString() === 'override') {
84+
return true;
85+
}
86+
}
87+
}
88+
89+
return false;
90+
}
91+
92+
}

build/phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ rules:
121121
- PHPStan\Build\FinalClassRule
122122
- PHPStan\Build\AttributeNamedArgumentsRule
123123
- PHPStan\Build\NamedArgumentsRule
124+
- PHPStan\Build\OverrideAttributeThirdPartyMethodRule
124125

125126
services:
126127
-

0 commit comments

Comments
 (0)