From 14c84f566b7d258276c50002a245c4ad184a4889 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Fri, 6 Nov 2026 14:16:25 +0000 Subject: [PATCH] Resolve names added by fixes only when a fix adds names Writing the added class names relative to the namespace and uses traverses the whole file. Skipping it when the fixed nodes contain no added names and no changed PHPDocs makes it cost nothing for fixes that only remove or change code, like removing #[\Override] from every method. Amp-Thread-ID: https://ampcode.com/threads/T-01a1b134-dc47-7297-bf0e-a2cadcd064cc --- src/Analyser/RuleErrorTransformer.php | 14 ++++++++------ src/Fixable/NewClassNamesVisitor.php | 27 +++++++++++++++++++++++++++ src/Fixable/ReplacingNodeVisitor.php | 15 +++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index ef730cac9f4..6f043096dc0 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -246,12 +246,14 @@ private function fix(string $fixingFile, array $fileNodes, array $fixes): ?Fixed return null; } - $nameResolver = new NameResolver(new Collecting(), ['replaceNodes' => false]); - /** @var Stmt[] $newStmts */ - $newStmts = (new NodeTraverser( - $nameResolver, - new NewClassNamesVisitor($nameResolver->getNameContext(), $this->importNames, $this->preferSelf), - ))->traverse($newStmts); + if (NewClassNamesVisitor::hasNewNames($replacingVisitor->getNewNodes())) { + $nameResolver = new NameResolver(new Collecting(), ['replaceNodes' => false]); + /** @var Stmt[] $newStmts */ + $newStmts = (new NodeTraverser( + $nameResolver, + new NewClassNamesVisitor($nameResolver->getNameContext(), $this->importNames, $this->preferSelf), + ))->traverse($newStmts); + } $newCode = $this->getPrinter($indent)->printFormatPreserving($newStmts, $fileNodes, $parsedFile['tokens']); if ($parsedFile['code'] === $newCode) { diff --git a/src/Fixable/NewClassNamesVisitor.php b/src/Fixable/NewClassNamesVisitor.php index e114f107c59..c27631354ce 100644 --- a/src/Fixable/NewClassNamesVisitor.php +++ b/src/Fixable/NewClassNamesVisitor.php @@ -95,6 +95,33 @@ public function __construct( $this->phpDocParser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser); } + /** + * Whether there is something to write in $newNodes, the nodes returned by fixes. + * The rest of the file is cloned unchanged, so the visitor would not change it. + * Traversing the whole file is skipped when this returns false, + * which matters in files with many fixable errors. + * + * @param Node[] $newNodes + */ + public static function hasNewNames(array $newNodes): bool + { + return (new NodeFinder())->findFirst($newNodes, static function (Node $node): bool { + if ($node instanceof FullyQualified && $node->getAttribute('origNode') === null) { + return true; + } + + $doc = $node->getDocComment(); + if ($doc === null) { + return false; + } + + $originalNode = $node->getAttribute('origNode'); + $originalDoc = $originalNode instanceof Node ? $originalNode->getDocComment() : null; + + return $originalDoc === null || $originalDoc->getText() !== $doc->getText(); + }) !== null; + } + #[Override] public function beforeTraverse(array $nodes): ?array { diff --git a/src/Fixable/ReplacingNodeVisitor.php b/src/Fixable/ReplacingNodeVisitor.php index 99081afc3b2..cf69a5f505e 100644 --- a/src/Fixable/ReplacingNodeVisitor.php +++ b/src/Fixable/ReplacingNodeVisitor.php @@ -33,6 +33,9 @@ final class ReplacingNodeVisitor extends NodeVisitorAbstract private int $appliedCount = 0; + /** @var list */ + private array $newNodes = []; + private bool $failed = false; /** @@ -115,9 +118,21 @@ public function leaveNode(Node $node): Node|int|null } } + $this->newNodes[] = $newNode; + return $newNode; } + /** + * The nodes returned by the fixes, the rest of the file is cloned unchanged. + * + * @return list + */ + public function getNewNodes(): array + { + return $this->newNodes; + } + /** * Whether every node was found and fixed. */