diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveEmptyClassMethodRector/Fixture/skip_unresolvable_interface.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveEmptyClassMethodRector/Fixture/skip_unresolvable_interface.php.inc new file mode 100644 index 00000000000..1eb02db29e7 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveEmptyClassMethodRector/Fixture/skip_unresolvable_interface.php.inc @@ -0,0 +1,10 @@ +hasUnresolvableParentOrInterface($class, $classMethod, $scope->getClassReflection())) { + return true; + } + if ($this->paramAnalyzer->hasPropertyPromotion($classMethod->params)) { return true; } @@ -184,6 +189,28 @@ private function shouldSkipClassMethod(Class_ $class, ClassMethod $classMethod): return $this->isAttributeMarkerConstructor($classMethod, $classReflection); } + private function hasUnresolvableParentOrInterface( + Class_ $class, + ClassMethod $classMethod, + ?ClassReflection $classReflection + ): bool { + if (! $classReflection instanceof ClassReflection) { + return false; + } + + // a protected empty method only exists to override/implement a parent's method; + // if the parent is out of scope, keep it to avoid breaking an abstract requirement + if ($classMethod->isProtected() && $class->extends instanceof Name && ! $classReflection->getParentClass() instanceof ClassReflection) { + return true; + } + + $interfaceNames = array_map( + static fn (ClassReflection $classReflection): string => $classReflection->getName(), + $classReflection->getInterfaces() + ); + return array_any($class->implements, fn (Name $name): bool => ! in_array($this->getName($name), $interfaceNames, true)); + } + private function hasDeprecatedAnnotation(ClassMethod $classMethod): bool { $phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod);