diff --git a/packages/DeadCode/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php b/packages/DeadCode/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php index 217b953fad0..19a3d52463d 100644 --- a/packages/DeadCode/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php +++ b/packages/DeadCode/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Class_; use Rector\NodeContainer\ParsedNodesByType; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\PhpParser\Node\Resolver\NameResolver; +use ReflectionMethod; final class ClassUnusedPrivateClassMethodResolver { @@ -61,7 +62,9 @@ final class ClassUnusedPrivateClassMethodResolver $unusedMethods = $this->filterOutSystemMethods($unusedMethods); - return $this->filterOutInterfaceRequiredMethods($class, $unusedMethods); + $unusedMethods = $this->filterOutInterfaceRequiredMethods($class, $unusedMethods); + + return $this->filterOutParentAbstractMethods($class, $unusedMethods); } /** @@ -103,4 +106,43 @@ final class ClassUnusedPrivateClassMethodResolver return array_diff($unusedMethods, $interfaceMethods); } + + /** + * @param string[] $unusedMethods + * @return string[] + */ + private function filterOutParentAbstractMethods(Class_ $class, array $unusedMethods): array + { + if ($class->extends === null) { + return $unusedMethods; + } + + $parentClasses = class_parents($class); + + $parentAbstractMethods = []; + + foreach ($parentClasses as $parentClass) { + foreach ($unusedMethods as $unusedMethod) { + if (in_array($unusedMethod, $parentAbstractMethods, true)) { + continue; + } + + if ($this->isMethodAbstract($parentClass, $unusedMethod)) { + $parentAbstractMethods[] = $unusedMethod; + } + } + } + + return array_diff($unusedMethods, $parentAbstractMethods); + } + + private function isMethodAbstract(string $class, string $method): bool + { + if (! method_exists($class, $method)) { + return false; + } + $methodReflection = new ReflectionMethod($class, $method); + + return $methodReflection->isAbstract(); + } } diff --git a/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/Fixture/skip_abstract_parent.php.inc b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/Fixture/skip_abstract_parent.php.inc new file mode 100644 index 00000000000..1fb886f1e2d --- /dev/null +++ b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/Fixture/skip_abstract_parent.php.inc @@ -0,0 +1,23 @@ +<?php + +namespace Rector\DeadCode\Tests\Rector\Class_\RemoveUnusedDoctrineEntityMethodAndPropertyRector\Fixture; + +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity() + */ +class Product extends Service +{ + public const TYPE = 'product'; + + public function getType(): string + { + return self::TYPE; + } +} + +abstract class Service +{ + public abstract function getType(); +} diff --git a/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php index 3f523f51f34..7404399718b 100644 --- a/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php +++ b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php @@ -19,6 +19,7 @@ final class RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest extends Abstra __DIR__ . '/Fixture/skip_trait_called_method.php.inc', __DIR__ . '/Fixture/skip_trait_doc_typed.php.inc', __DIR__ . '/Fixture/skip_trait_complex.php.inc', + __DIR__ . '/Fixture/skip_abstract_parent.php.inc', ]); }