From 429255cde887e5ac752da3bbf25210326c4f89d9 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 28 Jun 2026 16:52:25 +0200 Subject: [PATCH] [DeadCode] Keep private method called via self::class static call A private method invoked through a class-string static call, e.g. self::class::sampleClass(), is not detected by the usage analyzer and would be wrongly removed. Keep such methods. --- .../keep_self_class_static_call.php.inc | 18 +++++++++ .../RemoveUnusedPrivateMethodRector.php | 40 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc new file mode 100644 index 00000000000..660a8124339 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc @@ -0,0 +1,18 @@ +resolveDataProviderMethodNames($node); + // methods invoked via a class-string static call, e.g. self::class::sampleClass(), + // are not seen by the usage analyzer + $classStringCallMethodNames = $this->resolveClassStringStaticCallNames($node); + foreach ($node->stmts as $classStmtKey => $classStmt) { if (! $classStmt instanceof ClassMethod) { continue; @@ -118,6 +125,10 @@ public function refactor(Node $node): ?Node continue; } + if ($this->isNames($classMethod, $classStringCallMethodNames)) { + continue; + } + unset($node->stmts[$classStmtKey]); $hasChanged = true; } @@ -153,6 +164,35 @@ private function shouldSkip(ClassMethod $classMethod, ClassReflection $classRefl return $classReflection->hasMethod(MethodName::CALL); } + /** + * @return string[] + */ + private function resolveClassStringStaticCallNames(Class_ $class): array + { + $methodNames = []; + + /** @var StaticCall[] $staticCalls */ + $staticCalls = $this->betterNodeFinder->findInstanceOf($class->stmts, StaticCall::class); + foreach ($staticCalls as $staticCall) { + // e.g. self::class::sampleClass() - the called class is a ::class expression + if (! $staticCall->class instanceof ClassConstFetch) { + continue; + } + + if (! $this->isName($staticCall->class->name, 'class')) { + continue; + } + + if (! $staticCall->name instanceof Identifier) { + continue; + } + + $methodNames[] = $staticCall->name->toString(); + } + + return $methodNames; + } + private function hasDynamicMethodCallOnFetchThis(ClassMethod $classMethod): bool { return (bool) $this->betterNodeFinder->findFirst(