From 0e38fba9757c7577fb7e726b4a7dffb8a7d14897 Mon Sep 17 00:00:00 2001 From: Sander Muller Date: Sat, 31 Oct 2026 20:38:07 +0100 Subject: [PATCH] Run post rectors after the rules reach a fixed point, so no rule sees an imported short name --- src/Application/FileProcessor.php | 28 +++++++++---- .../Fixture/nullable_enum_param.php.inc | 39 +++++++++++++++++++ .../nullable_final_class_param.php.inc | 39 +++++++++++++++++++ .../skip_written_short_instanceof.php.inc | 17 ++++++++ .../ImportedNameInNextPassTest.php | 28 +++++++++++++ .../Source/ArrayObject.php | 9 +++++ .../Source/Directory.php | 10 +++++ .../config/configured_rule.php | 11 ++++++ 8 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 tests/Issues/ImportedNameInNextPass/Fixture/nullable_enum_param.php.inc create mode 100644 tests/Issues/ImportedNameInNextPass/Fixture/nullable_final_class_param.php.inc create mode 100644 tests/Issues/ImportedNameInNextPass/Fixture/skip_written_short_instanceof.php.inc create mode 100644 tests/Issues/ImportedNameInNextPass/ImportedNameInNextPassTest.php create mode 100644 tests/Issues/ImportedNameInNextPass/Source/ArrayObject.php create mode 100644 tests/Issues/ImportedNameInNextPass/Source/Directory.php create mode 100644 tests/Issues/ImportedNameInNextPass/config/configured_rule.php diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 2d65ae111ad..7043b8af2f5 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -58,6 +58,7 @@ public function processFile(File $file, Configuration $configuration): FileProce $fileHasChanged = false; $filePath = $file->getFilePath(); + $rulesReachedFixedPoint = false; do { $file->changeHasChanged(false); @@ -65,14 +66,22 @@ public function processFile(File $file, Configuration $configuration): FileProce // every rule and post rector records a line change when it mutates the AST $changeCountBeforePass = count($file->getRectorWithLineChanges()); - // 1. change nodes with Rector Rules - $newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts(), $file); + // 1. change nodes with Rector Rules, until they reach a fixed point + if ($rulesReachedFixedPoint) { + $newStmts = $file->getNewStmts(); + } else { + $newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts(), $file); + $rulesReachedFixedPoint = count($file->getRectorWithLineChanges()) === $changeCountBeforePass; + } - // 2. apply post rectors - $postNewStmts = $this->postFileProcessor->traverse($newStmts, $file); + // 2. apply post rectors after the rules: a rule in a next pass would resolve + // a short name they import against the global namespace + if ($rulesReachedFixedPoint) { + $newStmts = $this->postFileProcessor->traverse($newStmts, $file); + } // 3. this is needed for new tokens added in "afterTraverse()" - $file->changeNewStmts($postNewStmts); + $file->changeNewStmts($newStmts); // file never changed and this pass changed nothing: the print would just reproduce the // original content, so skip the expensive format-preserving print entirely. @@ -86,9 +95,14 @@ public function processFile(File $file, Configuration $configuration): FileProce // important to detect if file has changed $this->printFile($file, $configuration, $filePath); - // no change in current iteration, stop + // no change in current iteration: stop, once the post rectors ran if (! $file->hasChanged()) { - break; + if ($rulesReachedFixedPoint) { + break; + } + + $rulesReachedFixedPoint = true; + continue; } $fileHasChanged = true; diff --git a/tests/Issues/ImportedNameInNextPass/Fixture/nullable_enum_param.php.inc b/tests/Issues/ImportedNameInNextPass/Fixture/nullable_enum_param.php.inc new file mode 100644 index 00000000000..bf55f3cd2c7 --- /dev/null +++ b/tests/Issues/ImportedNameInNextPass/Fixture/nullable_enum_param.php.inc @@ -0,0 +1,39 @@ +value; + } +} + +?> +----- +value; + } +} + +?> diff --git a/tests/Issues/ImportedNameInNextPass/Fixture/nullable_final_class_param.php.inc b/tests/Issues/ImportedNameInNextPass/Fixture/nullable_final_class_param.php.inc new file mode 100644 index 00000000000..69ecfbc0f6c --- /dev/null +++ b/tests/Issues/ImportedNameInNextPass/Fixture/nullable_final_class_param.php.inc @@ -0,0 +1,39 @@ + +----- + diff --git a/tests/Issues/ImportedNameInNextPass/Fixture/skip_written_short_instanceof.php.inc b/tests/Issues/ImportedNameInNextPass/Fixture/skip_written_short_instanceof.php.inc new file mode 100644 index 00000000000..ef6724ede92 --- /dev/null +++ b/tests/Issues/ImportedNameInNextPass/Fixture/skip_written_short_instanceof.php.inc @@ -0,0 +1,17 @@ +value; + } +} diff --git a/tests/Issues/ImportedNameInNextPass/ImportedNameInNextPassTest.php b/tests/Issues/ImportedNameInNextPass/ImportedNameInNextPassTest.php new file mode 100644 index 00000000000..17dab5f3413 --- /dev/null +++ b/tests/Issues/ImportedNameInNextPass/ImportedNameInNextPassTest.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/ImportedNameInNextPass/Source/ArrayObject.php b/tests/Issues/ImportedNameInNextPass/Source/ArrayObject.php new file mode 100644 index 00000000000..c7d6b391f0c --- /dev/null +++ b/tests/Issues/ImportedNameInNextPass/Source/ArrayObject.php @@ -0,0 +1,9 @@ +withRules([ExplicitNullableObjectInstanceRector::class, RemoveAlwaysTrueIfConditionRector::class]) + ->withImportNames();