diff --git a/rules/DeadCode/Rector/ClassConst/RemoveUnusedPrivateClassConstantRector.php b/rules/DeadCode/Rector/ClassConst/RemoveUnusedPrivateClassConstantRector.php index dbcd0ff4f8d..07ad4e1a5e8 100644 --- a/rules/DeadCode/Rector/ClassConst/RemoveUnusedPrivateClassConstantRector.php +++ b/rules/DeadCode/Rector/ClassConst/RemoveUnusedPrivateClassConstantRector.php @@ -5,8 +5,9 @@ namespace Rector\DeadCode\Rector\ClassConst; use PhpParser\Node; +use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassConst; -use PhpParser\NodeVisitor; +use PhpParser\Node\Stmt\Enum_; use PHPStan\Reflection\ClassReflection; use Rector\NodeManipulator\ClassConstManipulator; use Rector\PHPStan\ScopeFetcher; @@ -58,28 +59,46 @@ public function run() */ public function getNodeTypes(): array { - return [ClassConst::class]; + return [Class_::class, Enum_::class]; } /** - * @param ClassConst $node + * @param Class_|Enum_ $node */ - public function refactor(Node $node): ?int + public function refactor(Node $node): ?Node { - if ($this->shouldSkipClassConst($node)) { - return null; + $unusedClassConstKeys = []; + foreach ($node->stmts as $key => $stmt) { + if (! $stmt instanceof ClassConst) { + continue; + } + + if ($this->shouldSkipClassConst($stmt)) { + continue; + } + + $classReflection = $this->reflectionResolver->resolveClassReflection($stmt); + if (! $classReflection instanceof ClassReflection) { + continue; + } + + if ($this->classConstManipulator->hasClassConstFetch($stmt, $classReflection, $node)) { + continue; + } + + $unusedClassConstKeys[] = $key; } - $classReflection = $this->reflectionResolver->resolveClassReflection($node); - if (! $classReflection instanceof ClassReflection) { + if ($unusedClassConstKeys === []) { return null; } - if ($this->classConstManipulator->hasClassConstFetch($node, $classReflection)) { - return null; + // remove only after all constants are checked, so a constant used by another removed one is kept + foreach ($unusedClassConstKeys as $unusedClassConstKey) { + unset($node->stmts[$unusedClassConstKey]); } - return NodeVisitor::REMOVE_NODE; + return $node; } private function shouldSkipClassConst(ClassConst $classConst): bool diff --git a/src/NodeManipulator/ClassConstManipulator.php b/src/NodeManipulator/ClassConstManipulator.php index 5a88103c032..bed99fd8a6b 100644 --- a/src/NodeManipulator/ClassConstManipulator.php +++ b/src/NodeManipulator/ClassConstManipulator.php @@ -26,23 +26,26 @@ public function __construct( ) { } - public function hasClassConstFetch(ClassConst $classConst, ClassReflection $classReflection): bool - { + public function hasClassConstFetch( + ClassConst $classConst, + ClassReflection $classReflection, + ClassLike $classLike + ): bool { if (! $classReflection->isClass() && ! $classReflection->isEnum()) { return true; } $className = $classReflection->getName(); $objectType = new ObjectType($className); - foreach ($classReflection->getAncestors() as $ancestorClassReflection) { - $ancestorClass = $this->astResolver->resolveClassFromClassReflection($ancestorClassReflection); - if (! $ancestorClass instanceof ClassLike) { - continue; - } + // private constant can be fetched only in the class itself, or in a trait it uses + $classLikes = [$classLike]; + if ($classLike->getTraitUses() !== []) { + $classLikes = [...$classLikes, ...$this->astResolver->parseClassReflectionTraits($classReflection)]; + } - // has in class? - $isClassConstFetchFound = (bool) $this->betterNodeFinder->findFirst($ancestorClass, function (Node $node) use ( + foreach ($classLikes as $currentClassLike) { + $isClassConstFetchFound = (bool) $this->betterNodeFinder->findFirst($currentClassLike, function (Node $node) use ( $classConst, $className, $objectType