diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 71bb059712..4f896f2995 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -80,6 +80,7 @@ rules: - PHPStan\Rules\Functions\VariadicParametersDeclarationRule - PHPStan\Rules\Keywords\ContinueBreakInLoopRule - PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule + - PHPStan\Rules\Methods\AbstractPrivateMethodRule - PHPStan\Rules\Methods\CallMethodsRule - PHPStan\Rules\Methods\CallStaticMethodsRule - PHPStan\Rules\Methods\ConstructorReturnTypeRule diff --git a/src/Rules/Methods/AbstractPrivateMethodRule.php b/src/Rules/Methods/AbstractPrivateMethodRule.php new file mode 100644 index 0000000000..de779287e7 --- /dev/null +++ b/src/Rules/Methods/AbstractPrivateMethodRule.php @@ -0,0 +1,55 @@ + */ +class AbstractPrivateMethodRule implements Rule +{ + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $method = $node->getMethodReflection(); + + if (!$method->isPrivate()) { + return []; + } + + if (!$method->isAbstract()->yes()) { + return []; + } + + if ($scope->isInTrait()) { + return []; + } + + $classReflection = $scope->getClassReflection(); + if ($classReflection === null) { + return []; + } + + if (!$classReflection->isAbstract() && !$classReflection->isInterface()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Private method %s::%s() cannot be abstract.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + ))->nonIgnorable()->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/AbstractPrivateMethodRuleTest.php b/tests/PHPStan/Rules/Methods/AbstractPrivateMethodRuleTest.php new file mode 100644 index 0000000000..8154e0b77d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/AbstractPrivateMethodRuleTest.php @@ -0,0 +1,31 @@ + */ +class AbstractPrivateMethodRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new AbstractPrivateMethodRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/abstract-private-method.php'], [ + [ + 'Private method PrivateAbstractMethod\HelloWorld::sayPrivate() cannot be abstract.', + 12, + ], + [ + 'Private method PrivateAbstractMethod\fooInterface::sayPrivate() cannot be abstract.', + 24, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/abstract-private-method.php b/tests/PHPStan/Rules/Methods/data/abstract-private-method.php new file mode 100644 index 0000000000..afb7d91eba --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/abstract-private-method.php @@ -0,0 +1,27 @@ +sayHello(); + $this->sayWorld(); + } + + abstract private function sayPrivate() : void; + abstract protected function sayProtected() : void; + abstract public function sayPublic() : void; +} + +trait fooTrait{ + abstract private function sayPrivate() : void; + abstract protected function sayProtected() : void; + abstract public function sayPublic() : void; +} + +interface fooInterface { + abstract private function sayPrivate() : void; + abstract protected function sayProtected() : void; + abstract public function sayPublic() : void; +}