Skip to content

Commit 1e75743

Browse files
authored
[internal] Move RectorNodeTraverser logic up to AbstractImmutableNodeTraverser (#7827)
1 parent edc3ee2 commit 1e75743

File tree

3 files changed

+90
-124
lines changed

3 files changed

+90
-124
lines changed

phpstan.neon

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ includes:
55
parameters:
66
level: 8
77

8-
# reportUnmatchedIgnoredErrors: false
8+
reportUnmatchedIgnoredErrors: false
99

1010
errorFormat: symplify
1111

@@ -373,9 +373,6 @@ parameters:
373373
- '#Method Rector\\Utils\\Rector\\RemoveRefactorDuplicatedNodeInstanceCheckRector\:\:getInstanceofNodeClass\(\) should return class\-string<PhpParser\\Node>\|null but returns class\-string#'
374374

375375
# copied from /vendor, to keep as original as possible
376-
-
377-
identifier: missingType.iterableValue
378-
path: src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php
379376
-
380377
identifier: symplify.noDynamicName
381378
path: src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php

src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
use PhpParser\Node\Stmt;
1111
use PhpParser\NodeTraverserInterface;
1212
use PhpParser\NodeVisitor;
13+
use Rector\Configuration\ConfigurationRuleFilter;
14+
use Rector\Contract\Rector\RectorInterface;
1315
use 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

1521
abstract 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
}

src/PhpParser/NodeTraverser/RectorNodeTraverser.php

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -4,116 +4,9 @@
44

55
namespace Rector\PhpParser\NodeTraverser;
66

7-
use PhpParser\Node;
8-
use PhpParser\Node\Stmt;
9-
use PhpParser\NodeVisitor;
10-
use Rector\Configuration\ConfigurationRuleFilter;
11-
use Rector\Contract\Rector\RectorInterface;
12-
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
13-
use Rector\PhpParser\Node\FileNode;
14-
use Rector\VersionBonding\PhpVersionedFilter;
15-
use Webmozart\Assert\Assert;
16-
177
/**
188
* @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest
199
*/
2010
final class RectorNodeTraverser extends AbstractImmutableNodeTraverser
2111
{
22-
private bool $areNodeVisitorsPrepared = false;
23-
24-
/**
25-
* @var array<class-string<Node>, NodeVisitor[]>
26-
*/
27-
private array $visitorsPerNodeClass = [];
28-
29-
/**
30-
* @param RectorInterface[] $rectors
31-
*/
32-
public function __construct(
33-
private array $rectors,
34-
private readonly PhpVersionedFilter $phpVersionedFilter,
35-
private readonly ConfigurationRuleFilter $configurationRuleFilter,
36-
) {
37-
parent::__construct();
38-
}
39-
40-
/**
41-
* @param Stmt[] $nodes
42-
* @return Stmt[]
43-
*/
44-
public function traverse(array $nodes): array
45-
{
46-
$this->prepareNodeVisitors();
47-
48-
return parent::traverse($nodes);
49-
}
50-
51-
/**
52-
* @param RectorInterface[] $rectors
53-
* @api used in tests to update the active rules
54-
*
55-
* @internal Used only in Rector core, not supported outside. Might change any time.
56-
*/
57-
public function refreshPhpRectors(array $rectors): void
58-
{
59-
Assert::allIsInstanceOf($rectors, RectorInterface::class);
60-
61-
$this->rectors = $rectors;
62-
$this->visitors = [];
63-
$this->visitorsPerNodeClass = [];
64-
65-
$this->areNodeVisitorsPrepared = false;
66-
67-
$this->prepareNodeVisitors();
68-
}
69-
70-
/**
71-
* @return NodeVisitor[]
72-
*/
73-
public function getVisitorsForNode(Node $node): array
74-
{
75-
$nodeClass = $node::class;
76-
77-
if (! isset($this->visitorsPerNodeClass[$nodeClass])) {
78-
$this->visitorsPerNodeClass[$nodeClass] = [];
79-
80-
/** @var RectorInterface $visitor */
81-
foreach ($this->visitors as $visitor) {
82-
foreach ($visitor->getNodeTypes() as $nodeType) {
83-
// BC layer matching
84-
if ($nodeType === FileWithoutNamespace::class && $nodeClass === FileNode::class) {
85-
$this->visitorsPerNodeClass[$nodeClass][] = $visitor;
86-
continue;
87-
}
88-
89-
if (is_a($nodeClass, $nodeType, true)) {
90-
$this->visitorsPerNodeClass[$nodeClass][] = $visitor;
91-
continue 2;
92-
}
93-
}
94-
}
95-
}
96-
97-
return $this->visitorsPerNodeClass[$nodeClass];
98-
}
99-
100-
/**
101-
* This must happen after $this->configuration is set after ProcessCommand::execute() is run, otherwise we get default false positives.
102-
*
103-
* This should be removed after https://github.com/rectorphp/rector/issues/5584 is resolved
104-
*/
105-
private function prepareNodeVisitors(): void
106-
{
107-
if ($this->areNodeVisitorsPrepared) {
108-
return;
109-
}
110-
111-
// filer out by version
112-
$this->visitors = $this->phpVersionedFilter->filter($this->rectors);
113-
114-
// filter by configuration
115-
$this->visitors = $this->configurationRuleFilter->filter($this->visitors);
116-
117-
$this->areNodeVisitorsPrepared = true;
118-
}
11912
}

0 commit comments

Comments
 (0)