diff --git a/conf/config.neon b/conf/config.neon index 5799bb1169..29dbaf60f1 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -974,6 +974,9 @@ services: - class: PHPStan\Rules\Constants\LazyAlwaysUsedClassConstantsExtensionProvider + - + class: PHPStan\Rules\Methods\LazyAlwaysUsedMethodExtensionProvider + - class: PHPStan\Rules\PhpDoc\ConditionalReturnTypeRuleHelper diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index df805f3731..938778fe4d 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -7,6 +7,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\ClassMethodsNode; use PHPStan\Reflection\MethodReflection; +use PHPStan\Rules\Methods\AlwaysUsedMethodExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantStringType; @@ -22,6 +23,10 @@ class UnusedPrivateMethodRule implements Rule { + public function __construct(private AlwaysUsedMethodExtensionProvider $extensionProvider) + { + } + public function getNodeType(): string { return ClassMethodsNode::class; @@ -54,6 +59,14 @@ public function processNode(Node $node, Scope $scope): array if (strtolower($methodName) === '__clone') { continue; } + + $methodReflection = $classType->getMethod($methodName, $scope); + foreach ($this->extensionProvider->getExtensions() as $extension) { + if ($extension->isAlwaysUsed($methodReflection)) { + continue 2; + } + } + $methods[strtolower($methodName)] = $method; } diff --git a/src/Rules/Methods/AlwaysUsedMethodExtension.php b/src/Rules/Methods/AlwaysUsedMethodExtension.php new file mode 100644 index 0000000000..cfccf5b972 --- /dev/null +++ b/src/Rules/Methods/AlwaysUsedMethodExtension.php @@ -0,0 +1,27 @@ +extensions; + } + +} diff --git a/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php b/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php new file mode 100644 index 0000000000..cbd397ee94 --- /dev/null +++ b/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php @@ -0,0 +1,22 @@ +extensions ??= $this->container->getServicesByTag(static::EXTENSION_TAG); + } + +} diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php index 83a1e8ef83..246237f97f 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php @@ -2,6 +2,9 @@ namespace PHPStan\Rules\DeadCode; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Rules\Methods\AlwaysUsedMethodExtension; +use PHPStan\Rules\Methods\DirectAlwaysUsedMethodExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_VERSION_ID; @@ -14,7 +17,19 @@ class UnusedPrivateMethodRuleTest extends RuleTestCase protected function getRule(): Rule { - return new UnusedPrivateMethodRule(); + return new UnusedPrivateMethodRule( + new DirectAlwaysUsedMethodExtensionProvider([ + new class() implements AlwaysUsedMethodExtension { + + public function isAlwaysUsed(MethodReflection $methodReflection): bool + { + return $methodReflection->getDeclaringClass()->is('UnusedPrivateMethod\IgnoredByExtension') + && $methodReflection->getName() === 'foo'; + } + + }, + ]), + ); } public function testRule(): void @@ -40,6 +55,10 @@ public function testRule(): void 'Method UnusedPrivateMethod\Lorem::doBaz() is unused.', 99, ], + [ + 'Method UnusedPrivateMethod\IgnoredByExtension::bar() is unused.', + 181, + ], ]); } diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php b/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php index 3bdf538e6b..ce43e40a90 100644 --- a/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php +++ b/tests/PHPStan/Rules/DeadCode/data/unused-private-method.php @@ -171,3 +171,14 @@ public function doTest(): void } } + +class IgnoredByExtension +{ + private function foo(): void + { + } + + private function bar(): void + { + } +}