1010use PhpParser \Node \Stmt ;
1111use PhpParser \NodeTraverserInterface ;
1212use PhpParser \NodeVisitor ;
13+ use Rector \Configuration \ConfigurationRuleFilter ;
14+ use Rector \Contract \Rector \RectorInterface ;
1315use Rector \Exception \ShouldNotHappenException ;
16+ use Rector \PhpParser \Node \CustomNode \FileWithoutNamespace ;
17+ use Rector \PhpParser \Node \FileNode ;
18+ use Rector \VersionBonding \PhpVersionedFilter ;
19+ use Webmozart \Assert \Assert ;
1420
1521abstract class AbstractImmutableNodeTraverser implements NodeTraverserInterface
1622{
@@ -24,14 +30,21 @@ abstract class AbstractImmutableNodeTraverser implements NodeTraverserInterface
2430 */
2531 protected bool $ stopTraversal ;
2632
33+ private bool $ areNodeVisitorsPrepared = false ;
34+
2735 /**
28- * Create a traverser with the given visitors.
29- *
30- * @param NodeVisitor ...$visitors Node visitors
36+ * @var array<class-string<Node>, NodeVisitor[]>
3137 */
32- public function __construct (NodeVisitor ...$ visitors )
33- {
34- $ this ->visitors = $ visitors ;
38+ private array $ visitorsPerNodeClass = [];
39+
40+ /**
41+ * @param RectorInterface[] $rectors
42+ */
43+ public function __construct (
44+ private readonly PhpVersionedFilter $ phpVersionedFilter ,
45+ private readonly ConfigurationRuleFilter $ configurationRuleFilter ,
46+ private array $ rectors
47+ ) {
3548 }
3649
3750 public function addVisitor (NodeVisitor $ visitor ): void
@@ -45,14 +58,13 @@ public function removeVisitor(NodeVisitor $visitor): void
4558 }
4659
4760 /**
48- * Traverses an array of nodes using the registered visitors.
49- *
50- * @param Node[] $nodes Array of nodes
51- *
52- * @return Node[] Traversed array of nodes
61+ * @param Node[] $nodes
62+ * @return Node[]
5363 */
5464 public function traverse (array $ nodes ): array
5565 {
66+ $ this ->prepareNodeVisitors ();
67+
5668 $ this ->stopTraversal = false ;
5769 foreach ($ this ->visitors as $ visitor ) {
5870 if (null !== $ return = $ visitor ->beforeTraverse ($ nodes )) {
@@ -71,10 +83,54 @@ public function traverse(array $nodes): array
7183 return $ nodes ;
7284 }
7385
86+ /**
87+ * @param RectorInterface[] $rectors
88+ * @api used in tests to update the active rules
89+ *
90+ * @internal Used only in Rector core, not supported outside. Might change any time.
91+ */
92+ public function refreshPhpRectors (array $ rectors ): void
93+ {
94+ Assert::allIsInstanceOf ($ rectors , RectorInterface::class);
95+
96+ $ this ->rectors = $ rectors ;
97+ $ this ->visitors = [];
98+ $ this ->visitorsPerNodeClass = [];
99+
100+ $ this ->areNodeVisitorsPrepared = false ;
101+
102+ $ this ->prepareNodeVisitors ();
103+ }
104+
74105 /**
75106 * @return NodeVisitor[]
76107 */
77- abstract public function getVisitorsForNode (Node $ node ): array ;
108+ public function getVisitorsForNode (Node $ node ): array
109+ {
110+ $ nodeClass = $ node ::class;
111+
112+ if (! isset ($ this ->visitorsPerNodeClass [$ nodeClass ])) {
113+ $ this ->visitorsPerNodeClass [$ nodeClass ] = [];
114+
115+ /** @var RectorInterface $visitor */
116+ foreach ($ this ->visitors as $ visitor ) {
117+ foreach ($ visitor ->getNodeTypes () as $ nodeType ) {
118+ // BC layer matching
119+ if ($ nodeType === FileWithoutNamespace::class && $ nodeClass === FileNode::class) {
120+ $ this ->visitorsPerNodeClass [$ nodeClass ][] = $ visitor ;
121+ continue ;
122+ }
123+
124+ if (is_a ($ nodeClass , $ nodeType , true )) {
125+ $ this ->visitorsPerNodeClass [$ nodeClass ][] = $ visitor ;
126+ continue 2 ;
127+ }
128+ }
129+ }
130+ }
131+
132+ return $ this ->visitorsPerNodeClass [$ nodeClass ];
133+ }
78134
79135 protected function traverseNode (Node $ node ): void
80136 {
@@ -139,7 +195,7 @@ protected function traverseNode(Node $node): void
139195
140196 /**
141197 * @param Node[] $nodes
142- * @return array Result of traversal (may be original array or changed one)
198+ * @return Node[]
143199 */
144200 protected function traverseArray (array $ nodes ): array
145201 {
@@ -233,4 +289,24 @@ private function ensureReplacementReasonable(Node $old, Node $new): void
233289 );
234290 }
235291 }
292+
293+ /**
294+ * This must happen after $this->configuration is set after ProcessCommand::execute() is run, otherwise we get default false positives.
295+ *
296+ * This should be removed after https://github.com/rectorphp/rector/issues/5584 is resolved
297+ */
298+ private function prepareNodeVisitors (): void
299+ {
300+ if ($ this ->areNodeVisitorsPrepared ) {
301+ return ;
302+ }
303+
304+ // filer out by version
305+ $ this ->visitors = $ this ->phpVersionedFilter ->filter ($ this ->rectors );
306+
307+ // filter by configuration
308+ $ this ->visitors = $ this ->configurationRuleFilter ->filter ($ this ->visitors );
309+
310+ $ this ->areNodeVisitorsPrepared = true ;
311+ }
236312}
0 commit comments