From 45057d06fd489859fec117c6cdfe628a02be83da Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 13 Aug 2024 10:34:00 +0200 Subject: [PATCH 1/6] Optimise checking the node types allowed for each rule --- composer.json | 3 +- ...rser-lib-phpparser-nodetraverser-php.patch | 55 +++++++++++++++++++ .../NodeTraverser/RectorNodeTraverser.php | 33 +++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch diff --git a/composer.json b/composer.json index 5142158fad4..f48ddf4d2de 100644 --- a/composer.json +++ b/composer.json @@ -139,7 +139,8 @@ "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-if-php.patch", "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-case-php.patch", "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-elseif-php.patch", - "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-namespace-php.patch" + "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-namespace-php.patch", + "patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch" ] }, "composer-exit-on-patch-failure": true, diff --git a/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch b/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch new file mode 100644 index 00000000000..e5ad7f6a646 --- /dev/null +++ b/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch @@ -0,0 +1,55 @@ +--- /dev/null ++++ ../lib/PhpParser/NodeTraverser.php +@@ -119,7 +119,8 @@ + $traverseChildren = true; + $breakVisitorIndex = null; + +- foreach ($this->visitors as $visitorIndex => $visitor) { ++ $visitors = $this->getVisitorsForNode($subNode); ++ foreach ($visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { +@@ -149,7 +150,7 @@ + } + } + +- foreach ($this->visitors as $visitorIndex => $visitor) { ++ foreach ($visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($subNode); + + if (null !== $return) { +@@ -196,7 +197,8 @@ + $traverseChildren = true; + $breakVisitorIndex = null; + +- foreach ($this->visitors as $visitorIndex => $visitor) { ++ $visitors = $this->getVisitorsForNode($node); ++ foreach ($visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($node); + if (null !== $return) { + if ($return instanceof Node) { +@@ -226,7 +228,7 @@ + } + } + +- foreach ($this->visitors as $visitorIndex => $visitor) { ++ foreach ($visitors as $visitorIndex => $visitor) { + $return = $visitor->leaveNode($node); + + if (null !== $return) { +@@ -270,6 +272,14 @@ + } + + return $nodes; ++ } ++ ++ /** ++ * @return NodeVisitor[] ++ */ ++ protected function getVisitorsForNode(Node $node) ++ { ++ return $this->visitors; + } + + private function ensureReplacementReasonable($old, $new) { diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index b4acb6dea99..8c02146a300 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -4,8 +4,10 @@ namespace Rector\PhpParser\NodeTraverser; +use PhpParser\Node; use PhpParser\Node\Stmt; use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; use Rector\Contract\Rector\RectorInterface; use Rector\VersionBonding\PhpVersionedFilter; @@ -13,6 +15,11 @@ final class RectorNodeTraverser extends NodeTraverser { private bool $areNodeVisitorsPrepared = false; + /** + * @var array,RectorInterface[]> + */ + private array $visitorsPerNodeClass; + /** * @param RectorInterface[] $rectors */ @@ -46,6 +53,31 @@ public function refreshPhpRectors(array $rectors): void $this->areNodeVisitorsPrepared = false; } + /** + * We return the list of visitors (rector rules) that can be applied to each node class + * This list is cached so that we don't need to continually check if a rule can be applied to a node + * + * @return NodeVisitor[] + */ + protected function getVisitorsForNode(Node $node): array + { + $nodeClass = $node::class; + if (! isset($this->visitorsPerNodeClass[$nodeClass])) { + $this->visitorsPerNodeClass[$nodeClass] = []; + foreach ($this->visitors as $visitor) { + assert($visitor instanceof RectorInterface); + foreach ($visitor->getNodeTypes() as $nodeType) { + if (is_a($nodeClass, $nodeType, true)) { + $this->visitorsPerNodeClass[$nodeClass][] = $visitor; + break; + } + } + } + } + + return $this->visitorsPerNodeClass[$nodeClass]; + } + /** * This must happen after $this->configuration is set after ProcessCommand::execute() is run, * otherwise we get default false positives. @@ -60,6 +92,7 @@ private function prepareNodeVisitors(): void // filer out by version $this->visitors = $this->phpVersionedFilter->filter($this->rectors); + $this->visitorsPerNodeClass = []; $this->areNodeVisitorsPrepared = true; } } From 5d61bf587d5b6739b22d3cf14d278948d13f4411 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 13 Aug 2024 14:51:14 +0200 Subject: [PATCH 2/6] Add tests for `getVisitorsForNode()` function --- .../NodeTraverser/RectorNodeTraverser.php | 1 + .../ClassLike/RuleUsingClassLikeRector.php | 35 ++++++ .../Class_/RuleUsingClassRector.php | 35 ++++++ .../Function_/RuleUsingFunctionRector.php | 35 ++++++ .../NodeTraverser/RectorNodeTraverserTest.php | 117 ++++++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php create mode 100644 tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php create mode 100644 tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php create mode 100644 tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index 8c02146a300..3e6930e2261 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -49,6 +49,7 @@ public function refreshPhpRectors(array $rectors): void { $this->rectors = $rectors; $this->visitors = []; + $this->visitorsPerNodeClass = []; $this->areNodeVisitorsPrepared = false; } diff --git a/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php b/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php new file mode 100644 index 00000000000..44361f9432d --- /dev/null +++ b/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php @@ -0,0 +1,35 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassLike::class]; + } + + public function refactor(Node $node): Node + { + return $node; + } +} diff --git a/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php b/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php new file mode 100644 index 00000000000..02e8070f83f --- /dev/null +++ b/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php @@ -0,0 +1,35 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + public function refactor(Node $node): Node + { + return $node; + } +} diff --git a/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php b/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php new file mode 100644 index 00000000000..6c23a37406d --- /dev/null +++ b/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php @@ -0,0 +1,35 @@ +> + */ + public function getNodeTypes(): array + { + return [Function_::class]; + } + + public function refactor(Node $node): Node + { + return $node; + } +} diff --git a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php new file mode 100644 index 00000000000..52033a14a11 --- /dev/null +++ b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php @@ -0,0 +1,117 @@ +rectorNodeTraverser = $this->make(RectorNodeTraverser::class); + $this->rectorNodeTraverser->refreshPhpRectors([]); + + $this->privatesAccessor = new PrivatesAccessor(); + + $this->ruleUsingFunctionRector = new RuleUsingFunctionRector(); + $this->ruleUsingClassRector = new RuleUsingClassRector(); + $this->ruleUsingClassLikeRector = new RuleUsingClassLikeRector(); + } + + public function testGetVisitorsForNodeWhenNoVisitorsAvailable(): void + { + $class = new Class_('test'); + $visitors = $this->privatesAccessor->callPrivateMethod( + $this->rectorNodeTraverser, + 'getVisitorsForNode', + [$class] + ); + + $this->assertSame([], $visitors); + } + + public function testGetVisitorsForNodeWhenNoVisitorsMatch(): void + { + $class = new Class_('test'); + $this->rectorNodeTraverser->addVisitor($this->ruleUsingFunctionRector); + $visitors = $this->privatesAccessor->callPrivateMethod( + $this->rectorNodeTraverser, + 'getVisitorsForNode', + [$class] + ); + + $this->assertSame([], $visitors); + } + + public function testGetVisitorsForNodeWhenSomeVisitorsMatch(): void + { + $class = new Class_('test'); + $this->rectorNodeTraverser->addVisitor($this->ruleUsingFunctionRector); + $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); + + $visitors = $this->privatesAccessor->callPrivateMethod( + $this->rectorNodeTraverser, + 'getVisitorsForNode', + [$class] + ); + + $this->assertEquals([$this->ruleUsingClassRector], $visitors); + } + + public function testGetVisitorsForNodeWhenAllVisitorsMatch(): void + { + $class = new Class_('test'); + $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); + $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassLikeRector); + + $visitors = $this->privatesAccessor->callPrivateMethod( + $this->rectorNodeTraverser, + 'getVisitorsForNode', + [$class] + ); + + $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $visitors); + } + + public function testGetVisitorsForNodeUsesCachedValue(): void + { + $class = new Class_('test'); + $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); + $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassLikeRector); + + $visitors = $this->privatesAccessor->callPrivateMethod( + $this->rectorNodeTraverser, + 'getVisitorsForNode', + [$class] + ); + + $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $visitors); + + $this->rectorNodeTraverser->removeVisitor($this->ruleUsingClassRector); + $visitors = $this->privatesAccessor->callPrivateMethod( + $this->rectorNodeTraverser, + 'getVisitorsForNode', + [$class] + ); + + $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $visitors); + } +} From 4503a70332d92c60aeb5eb2bb108166d5cc896d4 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 13 Aug 2024 15:36:49 +0200 Subject: [PATCH 3/6] Make getVisitorsForNode function public --- ...rser-lib-phpparser-nodetraverser-php.patch | 2 +- .../NodeTraverser/RectorNodeTraverser.php | 2 +- .../NodeTraverser/RectorNodeTraverserTest.php | 43 ++++--------------- 3 files changed, 10 insertions(+), 37 deletions(-) diff --git a/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch b/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch index e5ad7f6a646..776f9df22bc 100644 --- a/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch +++ b/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch @@ -47,7 +47,7 @@ + /** + * @return NodeVisitor[] + */ -+ protected function getVisitorsForNode(Node $node) ++ public function getVisitorsForNode(Node $node) + { + return $this->visitors; } diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index 3e6930e2261..26fe2005f7f 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -60,7 +60,7 @@ public function refreshPhpRectors(array $rectors): void * * @return NodeVisitor[] */ - protected function getVisitorsForNode(Node $node): array + public function getVisitorsForNode(Node $node): array { $nodeClass = $node::class; if (! isset($this->visitorsPerNodeClass[$nodeClass])) { diff --git a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php index 52033a14a11..6bdb57b7644 100644 --- a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php +++ b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php @@ -10,14 +10,11 @@ use Rector\Tests\PhpParser\NodeTraverser\Class_\RuleUsingClassRector; use Rector\Tests\PhpParser\NodeTraverser\ClassLike\RuleUsingClassLikeRector; use Rector\Tests\PhpParser\NodeTraverser\Function_\RuleUsingFunctionRector; -use Rector\Util\Reflection\PrivatesAccessor; final class RectorNodeTraverserTest extends AbstractLazyTestCase { private RectorNodeTraverser $rectorNodeTraverser; - private PrivatesAccessor $privatesAccessor; - private RuleUsingFunctionRector $ruleUsingFunctionRector; private RuleUsingClassRector $ruleUsingClassRector; @@ -29,8 +26,6 @@ protected function setUp(): void $this->rectorNodeTraverser = $this->make(RectorNodeTraverser::class); $this->rectorNodeTraverser->refreshPhpRectors([]); - $this->privatesAccessor = new PrivatesAccessor(); - $this->ruleUsingFunctionRector = new RuleUsingFunctionRector(); $this->ruleUsingClassRector = new RuleUsingClassRector(); $this->ruleUsingClassLikeRector = new RuleUsingClassLikeRector(); @@ -39,11 +34,8 @@ protected function setUp(): void public function testGetVisitorsForNodeWhenNoVisitorsAvailable(): void { $class = new Class_('test'); - $visitors = $this->privatesAccessor->callPrivateMethod( - $this->rectorNodeTraverser, - 'getVisitorsForNode', - [$class] - ); + + $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); $this->assertSame([], $visitors); } @@ -52,11 +44,8 @@ public function testGetVisitorsForNodeWhenNoVisitorsMatch(): void { $class = new Class_('test'); $this->rectorNodeTraverser->addVisitor($this->ruleUsingFunctionRector); - $visitors = $this->privatesAccessor->callPrivateMethod( - $this->rectorNodeTraverser, - 'getVisitorsForNode', - [$class] - ); + + $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); $this->assertSame([], $visitors); } @@ -67,11 +56,7 @@ public function testGetVisitorsForNodeWhenSomeVisitorsMatch(): void $this->rectorNodeTraverser->addVisitor($this->ruleUsingFunctionRector); $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); - $visitors = $this->privatesAccessor->callPrivateMethod( - $this->rectorNodeTraverser, - 'getVisitorsForNode', - [$class] - ); + $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); $this->assertEquals([$this->ruleUsingClassRector], $visitors); } @@ -82,11 +67,7 @@ public function testGetVisitorsForNodeWhenAllVisitorsMatch(): void $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassLikeRector); - $visitors = $this->privatesAccessor->callPrivateMethod( - $this->rectorNodeTraverser, - 'getVisitorsForNode', - [$class] - ); + $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $visitors); } @@ -97,20 +78,12 @@ public function testGetVisitorsForNodeUsesCachedValue(): void $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassLikeRector); - $visitors = $this->privatesAccessor->callPrivateMethod( - $this->rectorNodeTraverser, - 'getVisitorsForNode', - [$class] - ); + $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $visitors); $this->rectorNodeTraverser->removeVisitor($this->ruleUsingClassRector); - $visitors = $this->privatesAccessor->callPrivateMethod( - $this->rectorNodeTraverser, - 'getVisitorsForNode', - [$class] - ); + $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); $this->assertEquals([$this->ruleUsingClassRector, $this->ruleUsingClassLikeRector], $visitors); } From 53a1f9bc5e1da066c67bd96141b61bd32ef828fd Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 13 Aug 2024 15:52:21 +0200 Subject: [PATCH 4/6] Move initialisation of `visitorsPerNodeClass` property --- src/PhpParser/NodeTraverser/RectorNodeTraverser.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index 26fe2005f7f..e3e105f1609 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -18,7 +18,7 @@ final class RectorNodeTraverser extends NodeTraverser /** * @var array,RectorInterface[]> */ - private array $visitorsPerNodeClass; + private array $visitorsPerNodeClass = []; /** * @param RectorInterface[] $rectors @@ -93,7 +93,6 @@ private function prepareNodeVisitors(): void // filer out by version $this->visitors = $this->phpVersionedFilter->filter($this->rectors); - $this->visitorsPerNodeClass = []; $this->areNodeVisitorsPrepared = true; } } From 920786f7a036857c6c17e7edbe7bf05db36ed106 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Fri, 16 Aug 2024 08:38:22 +0200 Subject: [PATCH 5/6] Use continue 2 instead of break --- src/PhpParser/NodeTraverser/RectorNodeTraverser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index e3e105f1609..e5fb6d1b25d 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -70,7 +70,7 @@ public function getVisitorsForNode(Node $node): array foreach ($visitor->getNodeTypes() as $nodeType) { if (is_a($nodeClass, $nodeType, true)) { $this->visitorsPerNodeClass[$nodeClass][] = $visitor; - break; + continue 2; } } } From eaf34a72f90f76b7eeef654b6dd4e9dace4739fc Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 20 Aug 2024 09:09:06 +0200 Subject: [PATCH 6/6] Use patch from vendor-patches instead of local patch --- composer.json | 2 +- ...rser-lib-phpparser-nodetraverser-php.patch | 55 ------------------- 2 files changed, 1 insertion(+), 56 deletions(-) delete mode 100644 patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch diff --git a/composer.json b/composer.json index f48ddf4d2de..5694f744f2d 100644 --- a/composer.json +++ b/composer.json @@ -140,7 +140,7 @@ "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-case-php.patch", "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-elseif-php.patch", "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-node-stmt-namespace-php.patch", - "patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch" + "https://raw.githubusercontent.com/rectorphp/vendor-patches/main/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch" ] }, "composer-exit-on-patch-failure": true, diff --git a/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch b/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch deleted file mode 100644 index 776f9df22bc..00000000000 --- a/patches/nikic-php-parser-lib-phpparser-nodetraverser-php.patch +++ /dev/null @@ -1,55 +0,0 @@ ---- /dev/null -+++ ../lib/PhpParser/NodeTraverser.php -@@ -119,7 +119,8 @@ - $traverseChildren = true; - $breakVisitorIndex = null; - -- foreach ($this->visitors as $visitorIndex => $visitor) { -+ $visitors = $this->getVisitorsForNode($subNode); -+ foreach ($visitors as $visitorIndex => $visitor) { - $return = $visitor->enterNode($subNode); - if (null !== $return) { - if ($return instanceof Node) { -@@ -149,7 +150,7 @@ - } - } - -- foreach ($this->visitors as $visitorIndex => $visitor) { -+ foreach ($visitors as $visitorIndex => $visitor) { - $return = $visitor->leaveNode($subNode); - - if (null !== $return) { -@@ -196,7 +197,8 @@ - $traverseChildren = true; - $breakVisitorIndex = null; - -- foreach ($this->visitors as $visitorIndex => $visitor) { -+ $visitors = $this->getVisitorsForNode($node); -+ foreach ($visitors as $visitorIndex => $visitor) { - $return = $visitor->enterNode($node); - if (null !== $return) { - if ($return instanceof Node) { -@@ -226,7 +228,7 @@ - } - } - -- foreach ($this->visitors as $visitorIndex => $visitor) { -+ foreach ($visitors as $visitorIndex => $visitor) { - $return = $visitor->leaveNode($node); - - if (null !== $return) { -@@ -270,6 +272,14 @@ - } - - return $nodes; -+ } -+ -+ /** -+ * @return NodeVisitor[] -+ */ -+ public function getVisitorsForNode(Node $node) -+ { -+ return $this->visitors; - } - - private function ensureReplacementReasonable($old, $new) {