diff --git a/packages/NodeTypeResolver/Node/AttributeKey.php b/packages/NodeTypeResolver/Node/AttributeKey.php index 5d616322ad5..296d3054b37 100644 --- a/packages/NodeTypeResolver/Node/AttributeKey.php +++ b/packages/NodeTypeResolver/Node/AttributeKey.php @@ -133,6 +133,12 @@ final class AttributeKey */ public const CREATED_BY_RULE = 'created_by_rule'; + /** + * Helps with skipped below node + * @var string + */ + public const SKIPPED_BY_RECTOR_RULE = 'skipped_rector_rule'; + /** * @var string */ diff --git a/src/ProcessAnalyzer/RectifiedAnalyzer.php b/src/ProcessAnalyzer/RectifiedAnalyzer.php index b1762915a93..f3cccbcffc7 100644 --- a/src/ProcessAnalyzer/RectifiedAnalyzer.php +++ b/src/ProcessAnalyzer/RectifiedAnalyzer.php @@ -27,7 +27,11 @@ public function hasRectified(string $rectorClass, Node $node): bool return true; } - return $this->isJustReprintedOverlappedTokenStart($node, $originalNode); + if ($this->isJustReprintedOverlappedTokenStart($node, $originalNode)) { + return true; + } + + return $node->getAttribute(AttributeKey::SKIPPED_BY_RECTOR_RULE) === $rectorClass; } /** diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php index 444e0ee6048..e5e5c0ecc6e 100644 --- a/src/Rector/AbstractRector.php +++ b/src/Rector/AbstractRector.php @@ -195,11 +195,16 @@ final public function enterNode(Node $node): int|Node|null if (is_int($refactoredNode)) { $this->createdByRuleDecorator->decorate($node, $originalNode, static::class); - // notify this rule changing code - $rectorWithLineChange = new RectorWithLineChange(static::class, $originalNode->getLine()); - $this->file->addRectorClassWithLine($rectorWithLineChange); + if (! in_array($refactoredNode, [NodeTraverser::DONT_TRAVERSE_CHILDREN, NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN], true)) { + // notify this rule changing code + $rectorWithLineChange = new RectorWithLineChange(static::class, $originalNode->getLine()); + $this->file->addRectorClassWithLine($rectorWithLineChange); - return $refactoredNode; + return $refactoredNode; + } + + $this->decorateCurrentAndChildren($node); + return null; } // nothing to change → continue @@ -215,6 +220,26 @@ final public function enterNode(Node $node): int|Node|null return $this->postRefactorProcess($originalNode, $node, $refactoredNode, $filePath); } + private function decorateCurrentAndChildren(Node $node): void + { + // filter only types that + // 1. registered in getNodesTypes() method + // 2. different with current node type, as already decorated above + // + $types = array_filter( + $this->getNodeTypes(), + static fn (string $nodeType): bool => $nodeType !== $node::class + ); + $this->traverseNodesWithCallable($node, static function (Node $subNode) use ($types) { + if (in_array($subNode::class, $types, true)) { + $subNode->setAttribute(AttributeKey::SKIPPED_BY_RECTOR_RULE, static::class); + $subNode->setAttribute(AttributeKey::SKIPPED_BY_RECTOR_RULE, static::class); + } + + return null; + }); + } + /** * Replacing nodes in leaveNode() method avoids infinite recursion * see"infinite recursion" in https://github.com/nikic/PHP-Parser/blob/master/doc/component/Walking_the_AST.markdown diff --git a/tests/Issues/RenameString/Fixture/rename_string.php.inc b/tests/Issues/RenameString/Fixture/rename_string.php.inc new file mode 100644 index 00000000000..7d8b26bad4b --- /dev/null +++ b/tests/Issues/RenameString/Fixture/rename_string.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/tests/Issues/RenameString/RenameStringTest.php b/tests/Issues/RenameString/RenameStringTest.php new file mode 100644 index 00000000000..a34e4728d0a --- /dev/null +++ b/tests/Issues/RenameString/RenameStringTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Issues/RenameString/config/configured_rule.php b/tests/Issues/RenameString/config/configured_rule.php new file mode 100644 index 00000000000..00197eb620c --- /dev/null +++ b/tests/Issues/RenameString/config/configured_rule.php @@ -0,0 +1,18 @@ +rule(StringClassNameToClassConstantRector::class); + + $rectorConfig->ruleWithConfiguration( + RenameStringRector::class, + [ + 'Rector\Core\Tests\Issues\DoubleRun\Fixture\RenameString' => 'new test', + ] + ); +};