Skip to content

Commit 5c524c0

Browse files
committed
Detect wrong case in name of an inherited method
1 parent b80ae7a commit 5c524c0

File tree

5 files changed

+171
-0
lines changed

5 files changed

+171
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* Disallow `empty()` - it's a very loose comparison (see [manual](https://secure.php.net/manual/en/function.empty.php)), it's recommended to use more strict one.
1919
* Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`/`checkAlwaysTrueCheckTypeFunctionCall`/`checkAlwaysTrueStrictComparison` to false.
2020
* Correct case for referenced and called function names.
21+
* Correct case for inherited and implemented method names.
2122

2223
Additional rules are coming in subsequent releases!
2324

Diff for: rules.neon

+18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ rules:
1313
- PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule
1414
- PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule
1515
- PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule
16+
- PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule
1617
- PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule
1718
- PHPStan\Rules\StrictCalls\StrictFunctionCallsRule
1819
- PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule
20+
21+
services:
22+
scopeIsInClass:
23+
class: PHPStan\Build\ScopeIsInClassTypeSpecifyingExtension
24+
arguments:
25+
isInMethodName: isInClass
26+
removeNullMethodName: getClassReflection
27+
tags:
28+
- phpstan.typeSpecifier.methodTypeSpecifyingExtension
29+
30+
scopeIsInTrait:
31+
class: PHPStan\Build\ScopeIsInClassTypeSpecifyingExtension
32+
arguments:
33+
isInMethodName: isInTrait
34+
removeNullMethodName: getTraitReflection
35+
tags:
36+
- phpstan.typeSpecifier.methodTypeSpecifyingExtension

Diff for: src/Rules/Methods/WrongCaseOfInheritedMethodRule.php

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PHPStan\Analyser\Scope;
6+
use PHPStan\Reflection\ClassReflection;
7+
8+
class WrongCaseOfInheritedMethodRule implements \PHPStan\Rules\Rule
9+
{
10+
11+
public function getNodeType(): string
12+
{
13+
return \PhpParser\Node\Stmt\ClassMethod::class;
14+
}
15+
16+
/**
17+
* @param \PhpParser\Node\Stmt\ClassMethod $node
18+
* @param \PHPStan\Analyser\Scope $scope
19+
* @return string[]
20+
*/
21+
public function processNode(
22+
\PhpParser\Node $node,
23+
Scope $scope
24+
): array
25+
{
26+
if (!$scope->isInClass()) {
27+
throw new \PHPStan\ShouldNotHappenException();
28+
}
29+
30+
$methodReflection = $scope->getClassReflection()->getNativeMethod($node->name);
31+
$declaringClass = $methodReflection->getDeclaringClass();
32+
33+
$messages = [];
34+
if ($declaringClass->getParentClass() !== false) {
35+
$parentMessage = $this->findMethod(
36+
$declaringClass,
37+
$declaringClass->getParentClass(),
38+
$node->name
39+
);
40+
if ($parentMessage !== null) {
41+
$messages[] = $parentMessage;
42+
}
43+
}
44+
45+
foreach ($declaringClass->getInterfaces() as $interface) {
46+
$interfaceMessage = $this->findMethod(
47+
$declaringClass,
48+
$interface,
49+
$node->name
50+
);
51+
if ($interfaceMessage === null) {
52+
continue;
53+
}
54+
$messages[] = $interfaceMessage;
55+
}
56+
57+
return $messages;
58+
}
59+
60+
private function findMethod(
61+
ClassReflection $declaringClass,
62+
ClassReflection $classReflection,
63+
string $methodName
64+
): ?string
65+
{
66+
if (!$classReflection->hasNativeMethod($methodName)) {
67+
return null;
68+
}
69+
70+
$parentMethod = $classReflection->getNativeMethod($methodName);
71+
if ($parentMethod->getName() === $methodName) {
72+
return null;
73+
}
74+
75+
return sprintf(
76+
'Method %s::%s() does not match %s method name: %s::%s()',
77+
$declaringClass->getName(),
78+
$methodName,
79+
$classReflection->isInterface() ? 'interface' : 'parent',
80+
$classReflection->getName(),
81+
$parentMethod->getName()
82+
);
83+
}
84+
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
class WrongCaseOfInheritedMethodRuleTest extends \PHPStan\Testing\RuleTestCase
6+
{
7+
8+
protected function getRule(): \PHPStan\Rules\Rule
9+
{
10+
return new WrongCaseOfInheritedMethodRule();
11+
}
12+
13+
public function testRule(): void
14+
{
15+
$this->analyse([__DIR__ . '/data/wrong-case.php'], [
16+
[
17+
'Method WrongCase\Foo::GETfoo() does not match interface method name: WrongCase\FooInterface::getFoo()',
18+
25,
19+
],
20+
[
21+
'Method WrongCase\Foo::GETbar() does not match parent method name: WrongCase\FooParent::getBar()',
22+
30,
23+
],
24+
]);
25+
}
26+
27+
}

Diff for: tests/Rules/Methods/data/wrong-case.php

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace WrongCase;
4+
5+
interface FooInterface
6+
{
7+
8+
public function getFoo();
9+
10+
}
11+
12+
class FooParent
13+
{
14+
15+
public function getBar()
16+
{
17+
18+
}
19+
20+
}
21+
22+
class Foo extends FooParent implements FooInterface
23+
{
24+
25+
public function GETfoo()
26+
{
27+
28+
}
29+
30+
public function GETbar()
31+
{
32+
33+
}
34+
35+
public function getBaz()
36+
{
37+
38+
}
39+
40+
}

0 commit comments

Comments
 (0)