From 778beac8eb9ff95025079ebf1d67464bfd538024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Tue, 7 Jan 2025 16:50:39 +0100 Subject: [PATCH] Fix: Do not require methods with phpunit/phpunit annotations to be private --- CHANGELOG.md | 2 + composer-require-checker.json | 1 + src/Methods/PrivateInFinalClassRule.php | 49 +++++++++++++++++++ ...ProtectedMethodsWithPhpUnitAnnotations.php | 38 ++++++++++++++ .../Methods/PrivateInFinalClassRuleTest.php | 5 +- 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/Fixture/Methods/PrivateInFinalClassRule/FinalClassWithProtectedMethodsWithPhpUnitAnnotations.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b75b3e17..342b86e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ For a full diff see [`2.5.0...main`][2.5.0...main]. - Adjusted `Methods\PrivateInFinalClassRule` to use more appropriate message when detecting a `protected` method in an anonymous class ([#890]), by [@localheinz] - Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods from traits ([#891]), by [@localheinz] - Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` attributes requiring methods to be `protected` ([#863]), by [@cosmastech] +- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` annotations requiring methods to be `protected` ([#895]), by [@cosmastech] ## [`2.5.0`][2.5.0] @@ -558,6 +559,7 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0]. [#889]: https://github.com/ergebnis/phpstan-rules/pull/889 [#890]: https://github.com/ergebnis/phpstan-rules/pull/890 [#891]: https://github.com/ergebnis/phpstan-rules/pull/891 +[#895]: https://github.com/ergebnis/phpstan-rules/pull/895 [@enumag]: https://github.com/enumag [@cosmastech]: https://github.com/cosmastech diff --git a/composer-require-checker.json b/composer-require-checker.json index c1fa2577..dbbbecfd 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -33,6 +33,7 @@ "PHPStan\\Rules\\Rule", "PHPStan\\Rules\\RuleError", "PHPStan\\Rules\\RuleErrorBuilder", + "PHPStan\\Type\\FileTypeMapper", "PHPUnit\\Framework\\Attributes\\After", "PHPUnit\\Framework\\Attributes\\Before", "PHPUnit\\Framework\\Attributes\\PostCondition", diff --git a/src/Methods/PrivateInFinalClassRule.php b/src/Methods/PrivateInFinalClassRule.php index c63329e8..8f574265 100644 --- a/src/Methods/PrivateInFinalClassRule.php +++ b/src/Methods/PrivateInFinalClassRule.php @@ -14,10 +14,12 @@ namespace Ergebnis\PHPStan\Rules\Methods; use Ergebnis\PHPStan\Rules\ErrorIdentifier; +use PhpParser\Comment; use PhpParser\Node; use PHPStan\Analyser; use PHPStan\Reflection; use PHPStan\Rules; +use PHPStan\Type; use PHPUnit\Framework; /** @@ -25,6 +27,13 @@ */ final class PrivateInFinalClassRule implements Rules\Rule { + private Type\FileTypeMapper $fileTypeMapper; + + public function __construct(Type\FileTypeMapper $fileTypeMapper) + { + $this->fileTypeMapper = $fileTypeMapper; + } + public function getNodeType(): string { return Node\Stmt\ClassMethod::class; @@ -49,6 +58,10 @@ public function processNode( return []; } + if ($this->methodHasPhpUnitAnnotationWhichRequiresProtectedVisibility($node, $containingClass)) { + return []; + } + if (self::methodHasPhpUnitAttributeWhichRequiresProtectedVisibility($node)) { return []; } @@ -92,6 +105,42 @@ public function processNode( ]; } + private function methodHasPhpUnitAnnotationWhichRequiresProtectedVisibility( + Node\Stmt\ClassMethod $node, + Reflection\ClassReflection $containingClass + ): bool { + $docComment = $node->getDocComment(); + + if (!$docComment instanceof Comment\Doc) { + return false; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + null, + $containingClass->getName(), + null, + null, + $docComment->getText(), + ); + + $annotations = [ + '@after', + '@before', + '@postCondition', + '@preCondition', + ]; + + foreach ($resolvedPhpDoc->getPhpDocNodes() as $phpDocNode) { + foreach ($phpDocNode->getTags() as $tag) { + if (\in_array($tag->name, $annotations, true)) { + return true; + } + } + } + + return false; + } + private static function methodHasPhpUnitAttributeWhichRequiresProtectedVisibility(Node\Stmt\ClassMethod $node): bool { $attributes = [ diff --git a/test/Fixture/Methods/PrivateInFinalClassRule/FinalClassWithProtectedMethodsWithPhpUnitAnnotations.php b/test/Fixture/Methods/PrivateInFinalClassRule/FinalClassWithProtectedMethodsWithPhpUnitAnnotations.php new file mode 100644 index 00000000..deb1ae93 --- /dev/null +++ b/test/Fixture/Methods/PrivateInFinalClassRule/FinalClassWithProtectedMethodsWithPhpUnitAnnotations.php @@ -0,0 +1,38 @@ +getByType(Type\FileTypeMapper::class); + + return new Methods\PrivateInFinalClassRule($fileTypeMapper); } }