Skip to content

Commit c603060

Browse files
committed
detect override of deprecated method
1 parent c8009bd commit c603060

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed

Diff for: rules.neon

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ services:
1717
- phpstan.deprecations.deprecatedScopeResolver
1818
-
1919
class: PHPStan\Rules\Deprecations\OverrideDeprecatedPropertyRule
20+
-
21+
class: PHPStan\Rules\Deprecations\OverrideDeprecatedMethodRule
2022

2123
rules:
2224
- PHPStan\Rules\Deprecations\AccessDeprecatedPropertyRule
@@ -39,3 +41,5 @@ rules:
3941
conditionalTags:
4042
PHPStan\Rules\Deprecations\OverrideDeprecatedPropertyRule:
4143
phpstan.rules.rule: %featureToggles.bleedingEdge%
44+
PHPStan\Rules\Deprecations\OverrideDeprecatedMethodRule:
45+
phpstan.rules.rule: %featureToggles.bleedingEdge%
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Deprecations;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt\ClassMethod;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use function sprintf;
11+
12+
/**
13+
* @implements Rule<ClassMethod>
14+
*/
15+
class OverrideDeprecatedMethodRule implements Rule
16+
{
17+
18+
/** @var DeprecatedScopeHelper */
19+
private $deprecatedScopeHelper;
20+
21+
public function __construct(DeprecatedScopeHelper $deprecatedScopeHelper)
22+
{
23+
$this->deprecatedScopeHelper = $deprecatedScopeHelper;
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return ClassMethod::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
if ($this->deprecatedScopeHelper->isScopeDeprecated($scope)) {
34+
return [];
35+
}
36+
37+
if (!$scope->isInClass()) {
38+
return [];
39+
}
40+
41+
if ($node->isPrivate()) {
42+
return [];
43+
}
44+
45+
$class = $scope->getClassReflection();
46+
47+
$ancestors = $class->getAncestors();
48+
49+
$methodName = (string) $node->name;
50+
51+
$method = $class->getMethod($methodName, $scope);
52+
53+
if ($method->isDeprecated()->no()) {
54+
return [];
55+
}
56+
57+
foreach ($ancestors as $ancestor) {
58+
if ($ancestor === $class) {
59+
continue;
60+
}
61+
62+
if (!$ancestor->hasMethod($methodName)) {
63+
continue;
64+
}
65+
66+
$ancestorMethod = $ancestor->getMethod($methodName, $scope);
67+
68+
if (!$ancestorMethod->isDeprecated()->yes()) {
69+
return [];
70+
}
71+
72+
return [RuleErrorBuilder::message(sprintf(
73+
'Class %s overrides deprecated method %s of %s %s.',
74+
$class->getName(),
75+
$methodName,
76+
$ancestor->isInterface() ? 'interface' : 'class',
77+
$ancestor->getName()
78+
))->identifier('method.deprecated')->build()];
79+
}
80+
81+
return [];
82+
}
83+
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Deprecations;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<OverrideDeprecatedMethodRule>
10+
*/
11+
class OverrideDeprecatedMethodRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new OverrideDeprecatedMethodRule(new DeprecatedScopeHelper([new DefaultDeprecatedScopeResolver()]));
17+
}
18+
19+
public function testDeprecatedMethodOverride(): void
20+
{
21+
$this->analyse(
22+
[__DIR__ . '/data/override-deprecated-method.php'],
23+
[
24+
[
25+
'Class OverrideDeprecatedMethod\Child overrides deprecated method deprecatedMethod of class OverrideDeprecatedMethod\Ancestor.',
26+
36,
27+
],
28+
29+
[
30+
'Class OverrideDeprecatedMethod\Child overrides deprecated method deprecatedInInterface of interface OverrideDeprecatedMethod\Deprecated.',
31+
48,
32+
],
33+
]
34+
);
35+
}
36+
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace OverrideDeprecatedMethod;
4+
5+
class Ancestor
6+
{
7+
/**
8+
* @deprecated
9+
*/
10+
public function deprecatedMethod(): void
11+
{}
12+
13+
/**
14+
* @deprecated
15+
*/
16+
private function privateDeprecatedMethod(): void
17+
{}
18+
19+
/**
20+
* @deprecated
21+
*/
22+
public function explicitlyNotDeprecatedMethod(): void
23+
{}
24+
}
25+
26+
interface Deprecated
27+
{
28+
/**
29+
* @deprecated
30+
*/
31+
public function deprecatedInInterface(): void;
32+
}
33+
34+
class Child extends Ancestor implements Deprecated
35+
{
36+
public function deprecatedMethod(): void
37+
{}
38+
39+
private function privateDeprecatedMethod(): void
40+
{}
41+
42+
/**
43+
* @not-deprecated
44+
*/
45+
public function explicitlyNotDeprecatedMethod(): void
46+
{}
47+
48+
public function deprecatedInInterface(): void
49+
{}
50+
}
51+
52+
class GrandChild extends Child
53+
{
54+
public function explicitlyNotDeprecatedMethod(): void
55+
{}
56+
}

0 commit comments

Comments
 (0)