diff --git a/phpstan.neon b/phpstan.neon index b683b853111..873d2f7a549 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -376,6 +376,7 @@ parameters: - identifier: symplify.noDynamicName path: src/PhpParser/NodeTraverser/RectorNodeTraverser.php + - identifier: offsetAccess.nonArray path: src/PhpParser/NodeTraverser/RectorNodeTraverser.php diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index f8bb6c4dba2..dc9990a970c 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -19,6 +19,12 @@ use Webmozart\Assert\Assert; /** + * Based on native NodeTraverser class, but heavily customized for Rector needs. + * + * The main differences are: + * - no leaveNode(), as we do all in enterNode() that calls refactor() method + * - cached visitors per node class for performance, e.g. when we find rules for Class_ node, they're cached for next time + * - immutability features, register Rector rules once, then use; no changes on the fly * @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest */ final class RectorNodeTraverser implements NodeTraverserInterface @@ -33,7 +39,7 @@ final class RectorNodeTraverser implements NodeTraverserInterface private bool $areNodeVisitorsPrepared = false; /** - * @var array, NodeVisitor[]> + * @var array, RectorInterface[]> */ private array $visitorsPerNodeClass = []; @@ -103,26 +109,30 @@ public function refreshPhpRectors(array $rectors): void } /** - * @return NodeVisitor[] + * @api used in tests + * @return RectorInterface[] */ public function getVisitorsForNode(Node $node): array { $nodeClass = $node::class; + if (! $this->areNodeVisitorsPrepared) { + $this->prepareNodeVisitors(); + } + if (! isset($this->visitorsPerNodeClass[$nodeClass])) { $this->visitorsPerNodeClass[$nodeClass] = []; - /** @var RectorInterface $visitor */ - foreach ($this->visitors as $visitor) { - foreach ($visitor->getNodeTypes() as $nodeType) { + foreach ($this->rectors as $rector) { + foreach ($rector->getNodeTypes() as $nodeType) { // BC layer matching if ($nodeType === FileWithoutNamespace::class && $nodeClass === FileNode::class) { - $this->visitorsPerNodeClass[$nodeClass][] = $visitor; + $this->visitorsPerNodeClass[$nodeClass][] = $rector; continue; } if (is_a($nodeClass, $nodeType, true)) { - $this->visitorsPerNodeClass[$nodeClass][] = $visitor; + $this->visitorsPerNodeClass[$nodeClass][] = $rector; continue 2; } } diff --git a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php index d7e9208ae7d..0aae5d42ff9 100644 --- a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php +++ b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php @@ -58,6 +58,7 @@ public function testGetVisitorsForNodeWhenNoVisitorsMatch(): void public function testGetVisitorsForNodeWhenSomeVisitorsMatch(): void { $class = new Class_('test'); + $this->rectorNodeTraverser->refreshPhpRectors([ $this->ruleUsingFunctionRector, $this->ruleUsingClassRector,