From d9d14736a622212e14b710b0cf4617ef01461b1a Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 11 Oct 2026 11:27:02 +0200 Subject: [PATCH 1/7] move rule selection to RectorRegistry --- src/Application/FileProcessor.php | 8 +- src/Application/RectorRegistry.php | 46 +++++- src/Configuration/ConfigurationRuleFilter.php | 2 + .../NodeTraverser/RectorNodeTraverser.php | 145 +++++++++--------- 4 files changed, 126 insertions(+), 75 deletions(-) diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 00303a8fc42..c4b9c09038c 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -60,14 +60,20 @@ public function processFile(File $file, Configuration $configuration): FileProce $fileHasChanged = false; $filePath = $file->getFilePath(); +<<<<<<< HEAD $currentRectors = $this->rectorRegistry->getRectors(); $this->rectorNodeTraverser->refreshPhpRectors($currentRectors); +======= + $fileRectors = $this->rectorRegistry->forFile($file); + + // $this->rectorNodeTraverser->refreshPhpRectors($fileRectors); +>>>>>>> 7fbb048d92 (move rule selection to RectorRegistry) do { $file->changeHasChanged(false); // 1. change nodes with Rector Rules - $newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts()); + $newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts(), $fileRectors); // 2. apply post rectors $postNewStmts = $this->postFileProcessor->traverse($newStmts, $file); diff --git a/src/Application/RectorRegistry.php b/src/Application/RectorRegistry.php index 0989c4892d7..49a77ac7c28 100644 --- a/src/Application/RectorRegistry.php +++ b/src/Application/RectorRegistry.php @@ -4,7 +4,24 @@ namespace Rector\Application; +<<<<<<< HEAD +<<<<<<< HEAD use Rector\Contract\Rector\RectorInterface; +======= +use PhpParser\Node; +======= +>>>>>>> 1babe5c467 (fixup! move rule selection to RectorRegistry) +use Rector\Configuration\ConfigurationRuleFilter; +use Rector\Contract\Rector\HTMLAverseRectorInterface; +use Rector\Contract\Rector\RectorInterface; +use Rector\ValueObject\Application\File; +use Rector\VersionBonding\ComposerPackageConstraintFilter; +use Rector\VersionBonding\PhpVersionedFilter; +<<<<<<< HEAD +use Rector\VersionBonding\ValueObject\ComposerPackageConstraint; +>>>>>>> 7fbb048d92 (move rule selection to RectorRegistry) +======= +>>>>>>> 1babe5c467 (fixup! move rule selection to RectorRegistry) /** * @see \Rector\Tests\Application\RectorRegistryTest @@ -15,10 +32,25 @@ final class RectorRegistry * @param RectorInterface[] $rectors */ public function __construct( + private PhpVersionedFilter $phpVersionedFilter, + private ComposerPackageConstraintFilter $composerPackageConstraintFilter, + private ConfigurationRuleFilter $configurationRuleFilter, private array $rectors, ) { } + /** + * @param array $rectors + * @return array + */ + private function filteredRectors(array $rectors): array + { + $rectors = $this->phpVersionedFilter->filter($rectors); + $rectors = $this->composerPackageConstraintFilter->filter($rectors); + + return $this->configurationRuleFilter->filter($rectors); + } + /** * @param RectorInterface[] $rectors * @api used in tests to update the active rules @@ -27,7 +59,7 @@ public function __construct( */ public function refreshRectors(array $rectors): void { - $this->rectors = $rectors; + $this->rectors = $this->filteredRectors($rectors); } /** @@ -35,6 +67,18 @@ public function refreshRectors(array $rectors): void */ public function getRectors(): array { +<<<<<<< HEAD return $this->rectors; +======= + $rectorsForFile = $this->filteredRectors($this->rectors); + + if ($file->containsHTML()) { + $rectorsForFile = array_filter($rectorsForFile, static function (RectorInterface $rector): bool { + return $rector instanceof HTMLAverseRectorInterface; + }); + } + + return $rectorsForFile; +>>>>>>> 7fbb048d92 (move rule selection to RectorRegistry) } } diff --git a/src/Configuration/ConfigurationRuleFilter.php b/src/Configuration/ConfigurationRuleFilter.php index 798a4fa188b..c8e993f2195 100644 --- a/src/Configuration/ConfigurationRuleFilter.php +++ b/src/Configuration/ConfigurationRuleFilter.php @@ -13,6 +13,8 @@ use Rector\VersionBonding\ValueObject\ComposerBoundRuleConfiguration; /** + * @todo merge to RectorRegistry, as it is only used there + * * Modify available rector rules based on the configuration options * @see \Rector\Tests\Configuration\ConfigurationRuleFilterTest */ diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index b26aa8a54b7..7345ac5ac80 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Stmt; use PhpParser\NodeVisitor; +use Rector\Application\RectorRegistry; use Rector\Configuration\ConfigurationRuleFilter; use Rector\Contract\Rector\RectorInterface; use Rector\Rector\RectorRunner; @@ -35,15 +36,13 @@ final class RectorNodeTraverser */ private array $visitorsPerNodeClass = []; - /** - * @param RectorInterface[] $rectors - */ public function __construct( - private array $rectors, - private readonly PhpVersionedFilter $phpVersionedFilter, - private readonly ComposerPackageConstraintFilter $composerPackageConstraintFilter, - private readonly ConfigurationRuleFilter $configurationRuleFilter, + // private array $rectors, + // private readonly PhpVersionedFilter $phpVersionedFilter, + // private readonly ComposerPackageConstraintFilter $composerPackageConstraintFilter, + // private readonly ConfigurationRuleFilter $configurationRuleFilter, private readonly RectorRunner $rectorRunner, + // private readonly RectorRegistry $rectorRegistry, ) { } @@ -53,56 +52,56 @@ public function __construct( */ public function traverse(array $nodes): array { - $this->prepareNodeVisitors(); + // $this->prepareNodeVisitors(); $this->stopTraversal = false; return $this->traverseArray($nodes); } - /** - * @param RectorInterface[] $rectors - * - * @internal Used only in Rector core, not supported outside. Might change any time. - */ - public function refreshPhpRectors(array $rectors): void - { - Assert::allIsInstanceOf($rectors, RectorInterface::class); - - $this->rectors = $rectors; - $this->visitors = []; - $this->visitorsPerNodeClass = []; - - $this->areNodeVisitorsPrepared = false; - - $this->prepareNodeVisitors(); - } - - /** - * @return RectorInterface[] - * - * @api used in tests - */ - public function getVisitorsForNode(Node $node): array - { - $nodeClass = $node::class; - - if (! isset($this->visitorsPerNodeClass[$nodeClass])) { - $this->visitorsPerNodeClass[$nodeClass] = []; - - /** @var RectorInterface $visitor */ - foreach ($this->visitors as $visitor) { - foreach ($visitor->getNodeTypes() as $nodeType) { - if (is_a($nodeClass, $nodeType, true)) { - $this->visitorsPerNodeClass[$nodeClass][] = $visitor; - continue 2; - } - } - } - } - - return $this->visitorsPerNodeClass[$nodeClass]; - } + // /** + // * @param RectorInterface[] $node + // * + // * @internal Used only in Rector core, not supported outside. Might change any time. + // */ + // public function refreshPhpRectors(array $rectors): void + // { + // Assert::allIsInstanceOf($rectors, RectorInterface::class); + // + // // $this->rectors = $rectors; + // // $this->visitors = []; + // // $this->visitorsPerNodeClass = []; + // + // $this->areNodeVisitorsPrepared = false; + // + // // $this->prepareNodeVisitors(); + // } + + // /** + // * @return RectorInterface[] + // * + // * @api used in tests + // */ + // public function getVisitorsForNode(Node $node): array + // { + // $nodeClass = $node::class; + // + // if (! isset($this->visitorsPerNodeClass[$nodeClass])) { + // $this->visitorsPerNodeClass[$nodeClass] = []; + // + // /** @var RectorInterface */ + // foreach ($this->visitors as $visitor) { + // foreach ($visitor->getNodeTypes() as $nodeType) { + // if (is_a($nodeClass, $nodeType, true)) { + // $this->visitorsPerNodeClass[$nodeClass][] = $visitor; + // continue 2; + // } + // } + // } + // } + // + // return $this->visitorsPerNodeClass[$nodeClass]; + // } private function traverseNode(Node $node): void { @@ -235,26 +234,26 @@ private function ensureReplacementReasonable(Node $old, Node $new): void } } - /** - * This must happen after $this->configuration is set after ProcessCommand::execute() is run, otherwise we get default false positives. - * - * This should be removed after https://github.com/rectorphp/rector/issues/5584 is resolved - */ - private function prepareNodeVisitors(): void - { - if ($this->areNodeVisitorsPrepared) { - return; - } - - // filter out by PHP version - $this->visitors = $this->phpVersionedFilter->filter($this->rectors); - - // filter out by composer package constraint - $this->visitors = $this->composerPackageConstraintFilter->filter($this->visitors); - - // filter by configuration - $this->visitors = $this->configurationRuleFilter->filter($this->visitors); - - $this->areNodeVisitorsPrepared = true; - } + // /** + // * This must happen after $this->configuration is set after ProcessCommand::execute() is run, otherwise we get default false positives. + // * + // * This should be removed after https://github.com/rectorphp/rector/issues/5584 is resolved + // */ + // private function prepareNodeVisitors(): void + // { + // if ($this->areNodeVisitorsPrepared) { + // return; + // } + // + // // filter out by PHP version + // $this->visitors = $this->phpVersionedFilter->filter($this->rectors); + // + // // filter out by composer package constraint + // $this->visitors = $this->composerPackageConstraintFilter->filter($this->visitors); + // + // // filter by configuration + // $this->visitors = $this->configurationRuleFilter->filter($this->visitors); + // + // $this->areNodeVisitorsPrepared = true; + // } } From cbe49f1d905aa125ef470d05dabae51bd40170f0 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 11 Oct 2026 11:34:13 +0200 Subject: [PATCH 2/7] misc --- .../TernaryFalseExpressionToIfRector.php | 3 ++ src/Application/RectorRegistry.php | 29 ++++++++++++------- .../NodeTraverser/RectorNodeTraverser.php | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php b/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php index 97beb61566d..e907f537430 100644 --- a/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php +++ b/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php @@ -69,10 +69,13 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { if ($this->getFile()->containsHTML()) { +<<<<<<< HEAD return null; } if (! $node->expr instanceof Ternary) { +======= +>>>>>>> 211cc67a7a (misc) return null; } diff --git a/src/Application/RectorRegistry.php b/src/Application/RectorRegistry.php index 49a77ac7c28..d522f2f1dc8 100644 --- a/src/Application/RectorRegistry.php +++ b/src/Application/RectorRegistry.php @@ -37,29 +37,32 @@ public function __construct( private ConfigurationRuleFilter $configurationRuleFilter, private array $rectors, ) { + $this->rectors = $this->filterRectors($rectors); } /** - * @param array $rectors - * @return array + * @param RectorInterface[] $rectors + * @api used in tests to update the active rules + * + * @internal Used only in Rector core, not supported outside. Might change any time. */ - private function filteredRectors(array $rectors): array + public function refreshRectors(array $rectors): void { - $rectors = $this->phpVersionedFilter->filter($rectors); - $rectors = $this->composerPackageConstraintFilter->filter($rectors); - - return $this->configurationRuleFilter->filter($rectors); + $this->rectors = $this->filteredRectors($rectors); } /** * @param RectorInterface[] $rectors - * @api used in tests to update the active rules + * @return RectorInterface[] * - * @internal Used only in Rector core, not supported outside. Might change any time. + * @api used in tests to update the active rules */ - public function refreshRectors(array $rectors): void + private function filterRectors(array $rectors): array { - $this->rectors = $this->filteredRectors($rectors); + $rectors = $this->phpVersionedFilter->filter($rectors); + $rectors = $this->composerPackageConstraintFilter->filter($rectors); + + return $this->configurationRuleFilter->filter($rectors); } /** @@ -67,10 +70,14 @@ public function refreshRectors(array $rectors): void */ public function getRectors(): array { +<<<<<<< HEAD <<<<<<< HEAD return $this->rectors; ======= $rectorsForFile = $this->filteredRectors($this->rectors); +======= + $rectorsForFile = $this->rectors; +>>>>>>> 211cc67a7a (misc) if ($file->containsHTML()) { $rectorsForFile = array_filter($rectorsForFile, static function (RectorInterface $rector): bool { diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index 7345ac5ac80..c57abc61f17 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -42,7 +42,7 @@ public function __construct( // private readonly ComposerPackageConstraintFilter $composerPackageConstraintFilter, // private readonly ConfigurationRuleFilter $configurationRuleFilter, private readonly RectorRunner $rectorRunner, - // private readonly RectorRegistry $rectorRegistry, + private readonly RectorRegistry $rectorRegistry, ) { } From b4fa2197360203f5ab333daac7b97016dee46901 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 11 Oct 2026 11:51:40 +0200 Subject: [PATCH 3/7] cleanup --- .../TernaryFalseExpressionToIfRector.php | 3 - src/Application/FileProcessor.php | 12 +-- src/Application/RectorRegistry.php | 69 ++++++-------- .../NodeTraverser/RectorNodeTraverser.php | 95 +------------------ tests/Application/RectorRegistryTest.php | 41 -------- 5 files changed, 34 insertions(+), 186 deletions(-) delete mode 100644 tests/Application/RectorRegistryTest.php diff --git a/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php b/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php index e907f537430..97beb61566d 100644 --- a/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php +++ b/rules/CodeQuality/Rector/Expression/TernaryFalseExpressionToIfRector.php @@ -69,13 +69,10 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { if ($this->getFile()->containsHTML()) { -<<<<<<< HEAD return null; } if (! $node->expr instanceof Ternary) { -======= ->>>>>>> 211cc67a7a (misc) return null; } diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index c4b9c09038c..1fc05375392 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -44,7 +44,6 @@ public function __construct( private RectorParser $rectorParser, private NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator, private UsedImportsResolver $usedImportsResolver, - private RectorRegistry $rectorRegistry, ) { } @@ -60,20 +59,11 @@ public function processFile(File $file, Configuration $configuration): FileProce $fileHasChanged = false; $filePath = $file->getFilePath(); -<<<<<<< HEAD - $currentRectors = $this->rectorRegistry->getRectors(); - $this->rectorNodeTraverser->refreshPhpRectors($currentRectors); -======= - $fileRectors = $this->rectorRegistry->forFile($file); - - // $this->rectorNodeTraverser->refreshPhpRectors($fileRectors); ->>>>>>> 7fbb048d92 (move rule selection to RectorRegistry) - do { $file->changeHasChanged(false); // 1. change nodes with Rector Rules - $newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts(), $fileRectors); + $newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts()); // 2. apply post rectors $postNewStmts = $this->postFileProcessor->traverse($newStmts, $file); diff --git a/src/Application/RectorRegistry.php b/src/Application/RectorRegistry.php index d522f2f1dc8..0dd1918c9d1 100644 --- a/src/Application/RectorRegistry.php +++ b/src/Application/RectorRegistry.php @@ -4,24 +4,11 @@ namespace Rector\Application; -<<<<<<< HEAD -<<<<<<< HEAD -use Rector\Contract\Rector\RectorInterface; -======= use PhpParser\Node; -======= ->>>>>>> 1babe5c467 (fixup! move rule selection to RectorRegistry) use Rector\Configuration\ConfigurationRuleFilter; -use Rector\Contract\Rector\HTMLAverseRectorInterface; use Rector\Contract\Rector\RectorInterface; -use Rector\ValueObject\Application\File; use Rector\VersionBonding\ComposerPackageConstraintFilter; use Rector\VersionBonding\PhpVersionedFilter; -<<<<<<< HEAD -use Rector\VersionBonding\ValueObject\ComposerPackageConstraint; ->>>>>>> 7fbb048d92 (move rule selection to RectorRegistry) -======= ->>>>>>> 1babe5c467 (fixup! move rule selection to RectorRegistry) /** * @see \Rector\Tests\Application\RectorRegistryTest @@ -48,44 +35,50 @@ public function __construct( */ public function refreshRectors(array $rectors): void { - $this->rectors = $this->filteredRectors($rectors); + $this->rectors = $this->filterRectors($rectors); } /** - * @param RectorInterface[] $rectors + * @var array, RectorInterface[]> + */ + private array $cache = []; + + /** * @return RectorInterface[] * - * @api used in tests to update the active rules + * @api used in tests */ - private function filterRectors(array $rectors): array + public function forNode(Node $node): array { - $rectors = $this->phpVersionedFilter->filter($rectors); - $rectors = $this->composerPackageConstraintFilter->filter($rectors); + $nodeClass = $node::class; - return $this->configurationRuleFilter->filter($rectors); + if (! isset($this->cache[$nodeClass])) { + $this->cache[$nodeClass] = []; + + foreach ($this->rectors as $rector) { + foreach ($rector->getNodeTypes() as $nodeType) { + if (is_a($nodeClass, $nodeType, true)) { + $this->cache[$nodeClass][] = $rector; + continue 2; + } + } + } + } + + return $this->cache[$nodeClass]; } /** - * @return array + * @param RectorInterface[] $rectors + * @return RectorInterface[] + * + * @api used in tests to update the active rules */ - public function getRectors(): array + private function filterRectors(array $rectors): array { -<<<<<<< HEAD -<<<<<<< HEAD - return $this->rectors; -======= - $rectorsForFile = $this->filteredRectors($this->rectors); -======= - $rectorsForFile = $this->rectors; ->>>>>>> 211cc67a7a (misc) - - if ($file->containsHTML()) { - $rectorsForFile = array_filter($rectorsForFile, static function (RectorInterface $rector): bool { - return $rector instanceof HTMLAverseRectorInterface; - }); - } + $rectors = $this->phpVersionedFilter->filter($rectors); + $rectors = $this->composerPackageConstraintFilter->filter($rectors); - return $rectorsForFile; ->>>>>>> 7fbb048d92 (move rule selection to RectorRegistry) + return $this->configurationRuleFilter->filter($rectors); } } diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index c57abc61f17..5863560b5b7 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -10,37 +10,16 @@ use PhpParser\Node\Stmt; use PhpParser\NodeVisitor; use Rector\Application\RectorRegistry; -use Rector\Configuration\ConfigurationRuleFilter; -use Rector\Contract\Rector\RectorInterface; use Rector\Rector\RectorRunner; -use Rector\VersionBonding\ComposerPackageConstraintFilter; -use Rector\VersionBonding\PhpVersionedFilter; -use Webmozart\Assert\Assert; /** * @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest */ final class RectorNodeTraverser { - /** - * @var RectorInterface[] - */ - private array $visitors = []; - private bool $stopTraversal; - private bool $areNodeVisitorsPrepared = false; - - /** - * @var array, RectorInterface[]> - */ - private array $visitorsPerNodeClass = []; - public function __construct( - // private array $rectors, - // private readonly PhpVersionedFilter $phpVersionedFilter, - // private readonly ComposerPackageConstraintFilter $composerPackageConstraintFilter, - // private readonly ConfigurationRuleFilter $configurationRuleFilter, private readonly RectorRunner $rectorRunner, private readonly RectorRegistry $rectorRegistry, ) { @@ -52,57 +31,11 @@ public function __construct( */ public function traverse(array $nodes): array { - // $this->prepareNodeVisitors(); - $this->stopTraversal = false; return $this->traverseArray($nodes); } - // /** - // * @param RectorInterface[] $node - // * - // * @internal Used only in Rector core, not supported outside. Might change any time. - // */ - // public function refreshPhpRectors(array $rectors): void - // { - // Assert::allIsInstanceOf($rectors, RectorInterface::class); - // - // // $this->rectors = $rectors; - // // $this->visitors = []; - // // $this->visitorsPerNodeClass = []; - // - // $this->areNodeVisitorsPrepared = false; - // - // // $this->prepareNodeVisitors(); - // } - - // /** - // * @return RectorInterface[] - // * - // * @api used in tests - // */ - // public function getVisitorsForNode(Node $node): array - // { - // $nodeClass = $node::class; - // - // if (! isset($this->visitorsPerNodeClass[$nodeClass])) { - // $this->visitorsPerNodeClass[$nodeClass] = []; - // - // /** @var RectorInterface */ - // foreach ($this->visitors as $visitor) { - // foreach ($visitor->getNodeTypes() as $nodeType) { - // if (is_a($nodeClass, $nodeType, true)) { - // $this->visitorsPerNodeClass[$nodeClass][] = $visitor; - // continue 2; - // } - // } - // } - // } - // - // return $this->visitorsPerNodeClass[$nodeClass]; - // } - private function traverseNode(Node $node): void { foreach ($node->getSubNodeNames() as $name) { @@ -120,7 +53,7 @@ private function traverseNode(Node $node): void continue; } - $currentNodeVisitors = $this->getVisitorsForNode($subNode); + $currentNodeVisitors = $this->rectorRegistry->forNode($subNode); foreach ($currentNodeVisitors as $currentNodeVisitor) { $return = $this->rectorRunner->run($currentNodeVisitor, $subNode); @@ -167,8 +100,7 @@ private function traverseArray(array $nodes): array continue; } - $traverseChildren = true; - $currentNodeVisitors = $this->getVisitorsForNode($node); + $currentNodeVisitors = $this->rectorRegistry->forNode($node); foreach ($currentNodeVisitors as $currentNodeVisitor) { $return = $this->rectorRunner->run($currentNodeVisitor, $node); @@ -233,27 +165,4 @@ private function ensureReplacementReasonable(Node $old, Node $new): void ); } } - - // /** - // * This must happen after $this->configuration is set after ProcessCommand::execute() is run, otherwise we get default false positives. - // * - // * This should be removed after https://github.com/rectorphp/rector/issues/5584 is resolved - // */ - // private function prepareNodeVisitors(): void - // { - // if ($this->areNodeVisitorsPrepared) { - // return; - // } - // - // // filter out by PHP version - // $this->visitors = $this->phpVersionedFilter->filter($this->rectors); - // - // // filter out by composer package constraint - // $this->visitors = $this->composerPackageConstraintFilter->filter($this->visitors); - // - // // filter by configuration - // $this->visitors = $this->configurationRuleFilter->filter($this->visitors); - // - // $this->areNodeVisitorsPrepared = true; - // } } diff --git a/tests/Application/RectorRegistryTest.php b/tests/Application/RectorRegistryTest.php deleted file mode 100644 index 51b2628a64e..00000000000 --- a/tests/Application/RectorRegistryTest.php +++ /dev/null @@ -1,41 +0,0 @@ - ['*/skipped_directory/*'], - ]); - - $this->rectorRegistry = $this->make(RectorRegistry::class); - $this->rectorRegistry->refreshRectors([new FifthElement()]); - } - - protected function tearDown(): void - { - SimpleParameterProvider::setParameter(Option::SKIP, []); - } - - public function testGetRectors(): void - { - $rectors = $this->rectorRegistry->getRectors(); - - $this->assertCount(1, $rectors); - $this->assertInstanceOf(FifthElement::class, $rectors[0]); - } -} From cbe431db4201d3b83e0964c9e4c53e7855d814f8 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 11 Oct 2026 11:56:47 +0200 Subject: [PATCH 4/7] Move forNode cache reset to refreshRectors, migrate tests to RectorRegistry --- src/Application/RectorRegistry.php | 7 +- .../NodeTraverser/RectorNodeTraverser.php | 3 - .../PHPUnit/AbstractRectorTestCase.php | 5 -- tests/Application/RectorRegistryTest.php | 75 +++++++++++++++++ .../NodeTraverser/RectorNodeTraverserTest.php | 83 ------------------- .../StopTraverseOnTypeChangeTest.php | 4 +- 6 files changed, 82 insertions(+), 95 deletions(-) create mode 100644 tests/Application/RectorRegistryTest.php delete mode 100644 tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php diff --git a/src/Application/RectorRegistry.php b/src/Application/RectorRegistry.php index 0dd1918c9d1..1c68de1c5ad 100644 --- a/src/Application/RectorRegistry.php +++ b/src/Application/RectorRegistry.php @@ -19,9 +19,9 @@ final class RectorRegistry * @param RectorInterface[] $rectors */ public function __construct( - private PhpVersionedFilter $phpVersionedFilter, - private ComposerPackageConstraintFilter $composerPackageConstraintFilter, - private ConfigurationRuleFilter $configurationRuleFilter, + private readonly PhpVersionedFilter $phpVersionedFilter, + private readonly ComposerPackageConstraintFilter $composerPackageConstraintFilter, + private readonly ConfigurationRuleFilter $configurationRuleFilter, private array $rectors, ) { $this->rectors = $this->filterRectors($rectors); @@ -36,6 +36,7 @@ public function __construct( public function refreshRectors(array $rectors): void { $this->rectors = $this->filterRectors($rectors); + $this->cache = []; } /** diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index 5863560b5b7..a4f85d9bb7d 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -12,9 +12,6 @@ use Rector\Application\RectorRegistry; use Rector\Rector\RectorRunner; -/** - * @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest - */ final class RectorNodeTraverser { private bool $stopTraversal; diff --git a/src/Testing/PHPUnit/AbstractRectorTestCase.php b/src/Testing/PHPUnit/AbstractRectorTestCase.php index 828d0528714..7036ee4a373 100644 --- a/src/Testing/PHPUnit/AbstractRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php @@ -21,7 +21,6 @@ use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory; use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; -use Rector\PhpParser\NodeTraverser\RectorNodeTraverser; use Rector\Testing\Contract\RectorTestInterface; use Rector\Testing\Fixture\FixtureFileFinder; use Rector\Testing\Fixture\FixtureFileUpdater; @@ -109,10 +108,6 @@ protected function setUp(): void // no rules at all, e.g. in case of only post rector run, yields an empty array $rectors = $rectorConfig->findByContract(RectorInterface::class); - /** @var RectorNodeTraverser $rectorNodeTraverser */ - $rectorNodeTraverser = $rectorConfig->make(RectorNodeTraverser::class); - $rectorNodeTraverser->refreshPhpRectors($rectors); - // keep the shared registry in sync with the current test's rules /** @var RectorRegistry $rectorRegistry */ $rectorRegistry = $rectorConfig->make(RectorRegistry::class); diff --git a/tests/Application/RectorRegistryTest.php b/tests/Application/RectorRegistryTest.php new file mode 100644 index 00000000000..294afab94c2 --- /dev/null +++ b/tests/Application/RectorRegistryTest.php @@ -0,0 +1,75 @@ +rectorRegistry = $this->make(RectorRegistry::class); + $this->rectorRegistry->refreshRectors([]); + + $this->ruleUsingFunctionRector = new RuleUsingFunctionRector(); + $this->ruleUsingClassRector = new RuleUsingClassRector(); + $this->ruleUsingClassLikeRector = new RuleUsingClassLikeRector(); + } + + public function testForNodeWhenNoRectorsAvailable(): void + { + $rectors = $this->rectorRegistry->forNode(new Class_('test')); + + $this->assertSame([], $rectors); + } + + public function testForNodeWhenNoRectorsMatch(): void + { + $this->rectorRegistry->refreshRectors([$this->ruleUsingFunctionRector]); + + $rectors = $this->rectorRegistry->forNode(new Class_('test')); + + $this->assertSame([], $rectors); + } + + public function testForNodeWhenSomeRectorsMatch(): void + { + $this->rectorRegistry->refreshRectors([ + $this->ruleUsingFunctionRector, + $this->ruleUsingClassRector, + ]); + + $rectors = $this->rectorRegistry->forNode(new Class_('test')); + + $this->assertEquals([$this->ruleUsingClassRector], $rectors); + } + + public function testForNodeWhenAllRectorsMatch(): void + { + $this->rectorRegistry->refreshRectors([ + $this->ruleUsingClassRector, + $this->ruleUsingClassLikeRector, + ]); + + $rectors = $this->rectorRegistry->forNode(new Class_('test')); + + $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $rectors); + } +} diff --git a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php deleted file mode 100644 index d7e9208ae7d..00000000000 --- a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php +++ /dev/null @@ -1,83 +0,0 @@ -rectorNodeTraverser = $this->make(RectorNodeTraverser::class); - $this->rectorNodeTraverser->refreshPhpRectors([]); - - $this->ruleUsingFunctionRector = new RuleUsingFunctionRector(); - $this->ruleUsingClassRector = new RuleUsingClassRector(); - $this->ruleUsingClassLikeRector = new RuleUsingClassLikeRector(); - } - - public function testGetVisitorsForNodeWhenNoVisitorsAvailable(): void - { - $class = new Class_('test'); - - $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); - - $this->assertSame([], $visitors); - } - - public function testGetVisitorsForNodeWhenNoVisitorsMatch(): void - { - $class = new Class_('test'); - $this->rectorNodeTraverser->refreshPhpRectors([$this->ruleUsingFunctionRector]); - - $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); - - $this->assertSame([], $visitors); - } - - public function testGetVisitorsForNodeWhenSomeVisitorsMatch(): void - { - $class = new Class_('test'); - $this->rectorNodeTraverser->refreshPhpRectors([ - $this->ruleUsingFunctionRector, - $this->ruleUsingClassRector, - ]); - - $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); - - $this->assertEquals([$this->ruleUsingClassRector], $visitors); - } - - public function testGetVisitorsForNodeWhenAllVisitorsMatch(): void - { - $class = new Class_('test'); - $this->rectorNodeTraverser->refreshPhpRectors([ - $this->ruleUsingClassRector, - $this->ruleUsingClassLikeRector, - ]); - - $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); - - $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $visitors); - } -} diff --git a/tests/PhpParser/NodeTraverser/StopTraverseOnTypeChange/StopTraverseOnTypeChangeTest.php b/tests/PhpParser/NodeTraverser/StopTraverseOnTypeChange/StopTraverseOnTypeChangeTest.php index 53a63257e28..264eef77dd4 100644 --- a/tests/PhpParser/NodeTraverser/StopTraverseOnTypeChange/StopTraverseOnTypeChangeTest.php +++ b/tests/PhpParser/NodeTraverser/StopTraverseOnTypeChange/StopTraverseOnTypeChangeTest.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Trait_; use PhpParser\NodeFinder; +use Rector\Application\RectorRegistry; use Rector\PhpParser\NodeTraverser\RectorNodeTraverser; use Rector\Testing\PHPUnit\AbstractLazyTestCase; use Rector\Testing\TestingParser\TestingParser; @@ -25,7 +26,8 @@ protected function setUp(): void $this->rectorNodeTraverser = $this->make(RectorNodeTraverser::class); - $this->rectorNodeTraverser->refreshPhpRectors([ + $rectorRegistry = $this->make(RectorRegistry::class); + $rectorRegistry->refreshRectors([ $this->make(RuleChangingClassToTraitRector::class), $this->make(RuleCheckingClassRector::class), ]); From b015c948b7841678cb1012f4c07e3e5e452c065c Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 11 Oct 2026 12:08:07 +0200 Subject: [PATCH 5/7] Defer rule filtering to first forNode, fix dangling test @see --- src/Application/RectorRegistry.php | 37 +++++++++++-------- .../ClassLike/RuleUsingClassLikeRector.php | 2 +- .../Class_/RuleUsingClassRector.php | 2 +- .../Function_/RuleUsingFunctionRector.php | 2 +- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Application/RectorRegistry.php b/src/Application/RectorRegistry.php index 1c68de1c5ad..a9af6b8898b 100644 --- a/src/Application/RectorRegistry.php +++ b/src/Application/RectorRegistry.php @@ -15,6 +15,13 @@ */ final class RectorRegistry { + private bool $areRectorsPrepared = false; + + /** + * @var array, RectorInterface[]> + */ + private array $cache = []; + /** * @param RectorInterface[] $rectors */ @@ -24,7 +31,6 @@ public function __construct( private readonly ConfigurationRuleFilter $configurationRuleFilter, private array $rectors, ) { - $this->rectors = $this->filterRectors($rectors); } /** @@ -35,15 +41,11 @@ public function __construct( */ public function refreshRectors(array $rectors): void { - $this->rectors = $this->filterRectors($rectors); + $this->rectors = $rectors; + $this->areRectorsPrepared = false; $this->cache = []; } - /** - * @var array, RectorInterface[]> - */ - private array $cache = []; - /** * @return RectorInterface[] * @@ -51,6 +53,8 @@ public function refreshRectors(array $rectors): void */ public function forNode(Node $node): array { + $this->prepareRectors(); + $nodeClass = $node::class; if (! isset($this->cache[$nodeClass])) { @@ -70,16 +74,19 @@ public function forNode(Node $node): array } /** - * @param RectorInterface[] $rectors - * @return RectorInterface[] - * - * @api used in tests to update the active rules + * Filtering must happen lazily, after the configuration is set in ProcessCommand::execute(), + * otherwise options like --only are not yet available and get ignored. */ - private function filterRectors(array $rectors): array + private function prepareRectors(): void { - $rectors = $this->phpVersionedFilter->filter($rectors); - $rectors = $this->composerPackageConstraintFilter->filter($rectors); + if ($this->areRectorsPrepared) { + return; + } + + $this->rectors = $this->phpVersionedFilter->filter($this->rectors); + $this->rectors = $this->composerPackageConstraintFilter->filter($this->rectors); + $this->rectors = $this->configurationRuleFilter->filter($this->rectors); - return $this->configurationRuleFilter->filter($rectors); + $this->areRectorsPrepared = true; } } diff --git a/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php b/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php index 8764b077f45..6c65c69dd46 100644 --- a/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php +++ b/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php @@ -11,7 +11,7 @@ use Rector\RuleDoc\RuleDefinition; /** - * @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest + * @see \Rector\Tests\Application\RectorRegistryTest */ final class RuleUsingClassLikeRector extends AbstractRector { diff --git a/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php b/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php index 1e4c272b6e9..c6436d4c113 100644 --- a/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php +++ b/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php @@ -11,7 +11,7 @@ use Rector\RuleDoc\RuleDefinition; /** - * @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest + * @see \Rector\Tests\Application\RectorRegistryTest */ final class RuleUsingClassRector extends AbstractRector { diff --git a/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php b/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php index 602118a2206..c3e13a4d156 100644 --- a/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php +++ b/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php @@ -11,7 +11,7 @@ use Rector\RuleDoc\RuleDefinition; /** - * @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest + * @see \Rector\Tests\Application\RectorRegistryTest */ final class RuleUsingFunctionRector extends AbstractRector { From ae2563a9c41d62e2be29a45f1214444e3e6b820b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 11 Oct 2026 13:20:58 +0200 Subject: [PATCH 6/7] Split activeRectors from raw rectors, tidy RectorRegistry docblocks --- src/Application/RectorRegistry.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Application/RectorRegistry.php b/src/Application/RectorRegistry.php index a9af6b8898b..47291466180 100644 --- a/src/Application/RectorRegistry.php +++ b/src/Application/RectorRegistry.php @@ -17,6 +17,11 @@ final class RectorRegistry { private bool $areRectorsPrepared = false; + /** + * @var RectorInterface[] + */ + private array $activeRectors = []; + /** * @var array, RectorInterface[]> */ @@ -35,7 +40,6 @@ public function __construct( /** * @param RectorInterface[] $rectors - * @api used in tests to update the active rules * * @internal Used only in Rector core, not supported outside. Might change any time. */ @@ -48,8 +52,6 @@ public function refreshRectors(array $rectors): void /** * @return RectorInterface[] - * - * @api used in tests */ public function forNode(Node $node): array { @@ -60,7 +62,7 @@ public function forNode(Node $node): array if (! isset($this->cache[$nodeClass])) { $this->cache[$nodeClass] = []; - foreach ($this->rectors as $rector) { + foreach ($this->activeRectors as $rector) { foreach ($rector->getNodeTypes() as $nodeType) { if (is_a($nodeClass, $nodeType, true)) { $this->cache[$nodeClass][] = $rector; @@ -83,9 +85,9 @@ private function prepareRectors(): void return; } - $this->rectors = $this->phpVersionedFilter->filter($this->rectors); - $this->rectors = $this->composerPackageConstraintFilter->filter($this->rectors); - $this->rectors = $this->configurationRuleFilter->filter($this->rectors); + $activeRectors = $this->phpVersionedFilter->filter($this->rectors); + $activeRectors = $this->composerPackageConstraintFilter->filter($activeRectors); + $this->activeRectors = $this->configurationRuleFilter->filter($activeRectors); $this->areRectorsPrepared = true; } From 45f32235765f959415910089f53ecac1ff9b99f1 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 11 Oct 2026 11:21:52 +0000 Subject: [PATCH 7/7] [ci-review] Rector Rectify --- src/Application/RectorRegistry.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Application/RectorRegistry.php b/src/Application/RectorRegistry.php index 47291466180..d5dd8d8f0fb 100644 --- a/src/Application/RectorRegistry.php +++ b/src/Application/RectorRegistry.php @@ -87,6 +87,7 @@ private function prepareRectors(): void $activeRectors = $this->phpVersionedFilter->filter($this->rectors); $activeRectors = $this->composerPackageConstraintFilter->filter($activeRectors); + $this->activeRectors = $this->configurationRuleFilter->filter($activeRectors); $this->areRectorsPrepared = true;