From ac4d3de8df284308b4879a5a01168a10c18c0bea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Nov 2023 11:24:01 +0100 Subject: [PATCH] New parameter `checkMissingOverrideMethodAttribute` --- conf/config.level0.neon | 1 + conf/config.neon | 1 + conf/parametersSchema.neon | 1 + src/PhpDoc/StubValidator.php | 2 +- src/Rules/Methods/OverridingMethodRule.php | 14 ++++++++++ .../Rules/Methods/MethodSignatureRuleTest.php | 1 + .../Methods/OverridingMethodRuleTest.php | 27 +++++++++++++++++++ .../data/check-missing-override-attr.php | 23 ++++++++++++++++ 8 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/check-missing-override-attr.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index f4fd3dd9da..1060295132 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -154,6 +154,7 @@ services: checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% genericPrototypeMessage: %featureToggles.genericPrototypeMessage% finalByPhpDoc: %featureToggles.finalByPhpDoc% + checkMissingOverrideMethodAttribute: %checkMissingOverrideMethodAttribute% tags: - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 9cbbed5bee..8bb239c694 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -116,6 +116,7 @@ parameters: reportMaybesInPropertyPhpDocTypes: false reportStaticMethodSignatures: false reportWrongPhpDocTypeInVarTag: false + checkMissingOverrideMethodAttribute: false mixinExcludeClasses: [] scanFiles: [] scanDirectories: [] diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index e7579611c1..8ea97a1aa3 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -114,6 +114,7 @@ parametersSchema: reportMaybesInPropertyPhpDocTypes: bool() reportStaticMethodSignatures: bool() reportWrongPhpDocTypeInVarTag: bool() + checkMissingOverrideMethodAttribute: bool() parallel: structure([ jobSize: int(), processTimeout: float(), diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index adaf2e9718..f3b80a5143 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -164,7 +164,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, $unresolvableTypeHelper, $phpVersion, true, false), - new OverridingMethodRule($phpVersion, new MethodSignatureRule(true, true, $container->getParameter('featureToggles')['abstractTraitMethod']), true, new MethodParameterComparisonHelper($phpVersion, $container->getParameter('featureToggles')['genericPrototypeMessage']), $container->getParameter('featureToggles')['genericPrototypeMessage'], $container->getParameter('featureToggles')['finalByPhpDoc']), + new OverridingMethodRule($phpVersion, new MethodSignatureRule(true, true, $container->getParameter('featureToggles')['abstractTraitMethod']), true, new MethodParameterComparisonHelper($phpVersion, $container->getParameter('featureToggles')['genericPrototypeMessage']), $container->getParameter('featureToggles')['genericPrototypeMessage'], $container->getParameter('featureToggles')['finalByPhpDoc'], $container->getParameter('checkMissingOverrideMethodAttribute')), new DuplicateDeclarationRule(), new LocalTypeAliasesRule($localTypeAliasesCheck), new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider), diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index c42b686de2..16558b4c71 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -37,6 +37,7 @@ public function __construct( private MethodParameterComparisonHelper $methodParameterComparisonHelper, private bool $genericPrototypeMessage, private bool $finalByPhpDoc, + private bool $checkMissingOverrideMethodAttribute, ) { } @@ -96,6 +97,19 @@ public function processNode(Node $node, Scope $scope): array [$prototype, $checkVisibility] = $prototypeData; $messages = []; + if ( + $this->phpVersion->supportsOverrideAttribute() + && $this->checkMissingOverrideMethodAttribute + && !$this->hasOverrideAttribute($node->getOriginalNode()) + ) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() overrides method %s::%s() but is missing the #[Override] attribute.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getDeclaringClass()->getDisplayName($this->genericPrototypeMessage), + $prototype->getName(), + ))->build(); + } if ($prototype->isFinalByKeyword()->yes()) { $messages[] = RuleErrorBuilder::message(sprintf( 'Method %s::%s() overrides final method %s::%s().', diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index a3a1f790a9..9274e08bb2 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -28,6 +28,7 @@ protected function getRule(): Rule new MethodParameterComparisonHelper($phpVersion, true), true, true, + false, ); } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 698313a878..9381114e8c 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -17,6 +17,8 @@ class OverridingMethodRuleTest extends RuleTestCase private int $phpVersionId; + private bool $checkMissingOverrideMethodAttribute = false; + protected function getRule(): Rule { $phpVersion = new PhpVersion($this->phpVersionId); @@ -28,6 +30,7 @@ protected function getRule(): Rule new MethodParameterComparisonHelper($phpVersion, true), true, true, + $this->checkMissingOverrideMethodAttribute, ); } @@ -751,4 +754,28 @@ public function testOverrideAttribute(): void ]); } + public function dataCheckMissingOverrideAttribute(): iterable + { + yield [false, 80000, []]; + yield [true, 80000, []]; + yield [false, 80300, []]; + yield [true, 80300, [ + [ + 'Method CheckMissingOverrideAttr\Bar::doFoo() overrides method CheckMissingOverrideAttr\Foo::doFoo() but is missing the #[Override] attribute.', + 18, + ], + ]]; + } + + /** + * @dataProvider dataCheckMissingOverrideAttribute + * @param list $errors + */ + public function testCheckMissingOverrideAttribute(bool $checkMissingOverrideMethodAttribute, int $phpVersionId, array $errors): void + { + $this->checkMissingOverrideMethodAttribute = $checkMissingOverrideMethodAttribute; + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/check-missing-override-attr.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/check-missing-override-attr.php b/tests/PHPStan/Rules/Methods/data/check-missing-override-attr.php new file mode 100644 index 0000000000..3e1cf69a07 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/check-missing-override-attr.php @@ -0,0 +1,23 @@ +