From 26eaa9d05a72ee1c9efe32cb1a73afdf35edf149 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 25 Oct 2026 22:39:37 +0100 Subject: [PATCH 1/2] make RenameMethodRector use ClassReflection --- .../Rector/MethodCall/RenameMethodRector.php | 10 ++---- .../NodeTraverser/RectorNodeTraverser.php | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/rules/Renaming/Rector/MethodCall/RenameMethodRector.php b/rules/Renaming/Rector/MethodCall/RenameMethodRector.php index 9d77fc9f192..4a1d19d3906 100644 --- a/rules/Renaming/Rector/MethodCall/RenameMethodRector.php +++ b/rules/Renaming/Rector/MethodCall/RenameMethodRector.php @@ -15,12 +15,10 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Trait_; -use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\NodeManipulator\ClassManipulator; -use Rector\PHPStan\ScopeFetcher; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\Renaming\Contract\MethodCallRenameInterface; @@ -87,8 +85,8 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { if ($node instanceof Class_ || $node instanceof Trait_ || $node instanceof Interface_) { - $scope = ScopeFetcher::fetch($node); - return $this->refactorClass($node, $scope); + $classReflection = $this->getClassReflection($node); + return $this->refactorClass($node, $classReflection); } return $this->refactorMethodCallAndStaticCall($node); @@ -169,10 +167,8 @@ private function shouldKeepForParentInterface( */ private function refactorClass( Class_|Trait_|Interface_ $classOrInterface, - Scope $scope + ClassReflection $classReflection ): Class_|Trait_|Interface_|null { - $classReflection = $scope->getClassReflection(); - $hasChanged = false; foreach ($classOrInterface->getMethods() as $classMethod) { diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index f45faa2dcdf..0ee66735bd5 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -8,6 +8,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Stmt; +use PhpParser\Node\Stmt\Class_; use PhpParser\NodeVisitor; use Rector\Application\RectorRegistry; use Rector\Rector\RectorRunner; @@ -77,6 +78,10 @@ private function traverseNode(Node $node, File $file): void $this->traverseNode($subNode, $file); + // a descendant removal (e.g. an emptied method body) can make a Class_ rule + // newly apply; re-offer the class once, now that its children are settled + $node->{$name} = $subNode = $this->reRunClassRectors($subNode, $file); + if ($this->stopTraversal) { break; } @@ -126,6 +131,10 @@ private function traverseArray(array $nodes, File $file): array } $this->traverseNode($node, $file); + + // re-offer a Class_ after descendant removals settle (see traverseNode) + $nodes[$i] = $node = $this->reRunClassRectors($node, $file); + if ($this->stopTraversal) { break; } @@ -140,6 +149,29 @@ private function traverseArray(array $nodes, File $file): array return $nodes; } + /** + * Re-run Class_ rules once, after its children are traversed, so a rule whose + * precondition is only met by a descendant removal still fires in a single pass. + */ + private function reRunClassRectors(Node $node, File $file): Node + { + if (! $node instanceof Class_) { + return $node; + } + + foreach ($this->rectorRegistry->forNodeAndFile($node, $file) as $currentNodeVisitor) { + $return = $this->rectorRunner->run($currentNodeVisitor, $node); + if (! $return instanceof Node) { + continue; + } + + $this->ensureReplacementReasonable($node, $return); + $node = $return; + } + + return $node; + } + private function ensureReplacementReasonable(Node $old, Node $new): void { if ($old instanceof Stmt) { From 19be93b82360dafd39073aa247e16dbdf7dbf210 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 25 Oct 2026 21:41:24 +0000 Subject: [PATCH 2/2] [ci-review] Rector Rectify --- src/PhpParser/NodeTraverser/RectorNodeTraverser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index 0ee66735bd5..3f1f29943f9 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -77,10 +77,10 @@ private function traverseNode(Node $node, File $file): void } $this->traverseNode($subNode, $file); - // a descendant removal (e.g. an emptied method body) can make a Class_ rule // newly apply; re-offer the class once, now that its children are settled - $node->{$name} = $subNode = $this->reRunClassRectors($subNode, $file); + $node->{$name} = $this->reRunClassRectors($subNode, $file); + $subNode = $node->{$name}; if ($this->stopTraversal) { break;