Skip to content

Commit

Permalink
Detect wrong case in name of an inherited method
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Apr 16, 2018
1 parent b80ae7a commit 5c524c0
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* 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.
* Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`/`checkAlwaysTrueCheckTypeFunctionCall`/`checkAlwaysTrueStrictComparison` to false.
* Correct case for referenced and called function names.
* Correct case for inherited and implemented method names.

Additional rules are coming in subsequent releases!

Expand Down
18 changes: 18 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ rules:
- PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule
- PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule
- PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule
- PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule
- PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule
- PHPStan\Rules\StrictCalls\StrictFunctionCallsRule
- PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule

services:

This comment has been minimized.

Copy link
@lcobucci

lcobucci Apr 17, 2018

@ondrejmirtes is this definition correct? I know I'm living on the edge but I'm testing things with phpstan v0.9 and v0.10@dev and after the addition of these services the dev version is crashing because PHPStan\Build\ScopeIsInClassTypeSpecifyingExtension can't be found (this is not really an issue for me, it's just to make you aware of it 😄)

This comment has been minimized.

Copy link
@lcobucci

lcobucci Apr 17, 2018

I can send a PR, but didn't want to bother you too much (idk if you're refactoring things and this class is being created)

This comment has been minimized.

Copy link
@ondrejmirtes

ondrejmirtes via email Apr 17, 2018

Author Member

This comment has been minimized.

Copy link
@ondrejmirtes

ondrejmirtes Apr 17, 2018

Author Member

Fixed: 8a269c8

scopeIsInClass:
class: PHPStan\Build\ScopeIsInClassTypeSpecifyingExtension
arguments:
isInMethodName: isInClass
removeNullMethodName: getClassReflection
tags:
- phpstan.typeSpecifier.methodTypeSpecifyingExtension

scopeIsInTrait:
class: PHPStan\Build\ScopeIsInClassTypeSpecifyingExtension
arguments:
isInMethodName: isInTrait
removeNullMethodName: getTraitReflection
tags:
- phpstan.typeSpecifier.methodTypeSpecifyingExtension
85 changes: 85 additions & 0 deletions src/Rules/Methods/WrongCaseOfInheritedMethodRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;

class WrongCaseOfInheritedMethodRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\Node\Stmt\ClassMethod::class;
}

/**
* @param \PhpParser\Node\Stmt\ClassMethod $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(
\PhpParser\Node $node,
Scope $scope
): array
{
if (!$scope->isInClass()) {
throw new \PHPStan\ShouldNotHappenException();
}

$methodReflection = $scope->getClassReflection()->getNativeMethod($node->name);
$declaringClass = $methodReflection->getDeclaringClass();

$messages = [];
if ($declaringClass->getParentClass() !== false) {
$parentMessage = $this->findMethod(
$declaringClass,
$declaringClass->getParentClass(),
$node->name
);
if ($parentMessage !== null) {
$messages[] = $parentMessage;
}
}

foreach ($declaringClass->getInterfaces() as $interface) {
$interfaceMessage = $this->findMethod(
$declaringClass,
$interface,
$node->name
);
if ($interfaceMessage === null) {
continue;
}
$messages[] = $interfaceMessage;
}

return $messages;
}

private function findMethod(
ClassReflection $declaringClass,
ClassReflection $classReflection,
string $methodName
): ?string
{
if (!$classReflection->hasNativeMethod($methodName)) {
return null;
}

$parentMethod = $classReflection->getNativeMethod($methodName);
if ($parentMethod->getName() === $methodName) {
return null;
}

return sprintf(
'Method %s::%s() does not match %s method name: %s::%s()',
$declaringClass->getName(),
$methodName,
$classReflection->isInterface() ? 'interface' : 'parent',
$classReflection->getName(),
$parentMethod->getName()
);
}

}
27 changes: 27 additions & 0 deletions tests/Rules/Methods/WrongCaseOfInheritedMethodRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

class WrongCaseOfInheritedMethodRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
return new WrongCaseOfInheritedMethodRule();
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/wrong-case.php'], [
[
'Method WrongCase\Foo::GETfoo() does not match interface method name: WrongCase\FooInterface::getFoo()',
25,
],
[
'Method WrongCase\Foo::GETbar() does not match parent method name: WrongCase\FooParent::getBar()',
30,
],
]);
}

}
40 changes: 40 additions & 0 deletions tests/Rules/Methods/data/wrong-case.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace WrongCase;

interface FooInterface
{

public function getFoo();

}

class FooParent
{

public function getBar()
{

}

}

class Foo extends FooParent implements FooInterface
{

public function GETfoo()
{

}

public function GETbar()
{

}

public function getBaz()
{

}

}

0 comments on commit 5c524c0

Please sign in to comment.