diff --git a/phpstan.neon b/phpstan.neon index 8fd15450e65..73fb9c2e8a8 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -386,9 +386,7 @@ parameters: - identifier: offsetAccess.nonArray path: src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php - - - message: '#Property PhpParser\\Node\\Stmt\\ClassMethod\:\:\$stmts \(array\|null\) does not accept array#' - path: scripts/create-immutable-node-visitor.php + - message: '#Property Rector\\PhpParser\\NodeTraverser\\AbstractImmutableNodeTraverser\:\:\$visitors \(list\) does not accept array#' path: src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php diff --git a/scripts/create-immutable-node-visitor.php b/scripts/create-immutable-node-visitor.php deleted file mode 100644 index 2ce47c1d635..00000000000 --- a/scripts/create-immutable-node-visitor.php +++ /dev/null @@ -1,263 +0,0 @@ -createForHostVersion(); -$stmts = $parser->parse(FileSystem::read($vendorNodeTraverserFilePath)); - -Assert::isArray($stmts); -Assert::allIsInstanceOf($stmts, Stmt::class); - -$originalStmts = $stmts; - -final class ReplaceForeachThisVisitorNodeVisitor extends NodeVisitorAbstract -{ - public function __construct( - private readonly string $nodeName - ) { - - } - - /** - * @return Stmt[]|null - */ - public function enterNode(Node $node): ?array - { - if (! $node instanceof Foreach_) { - return null; - } - - if (! $node->expr instanceof PropertyFetch) { - return null; - } - - $foreachedPropertyFetch = $node->expr; - if (! $foreachedPropertyFetch->var instanceof Variable) { - return null; - } - - if ($foreachedPropertyFetch->var->name !== 'this') { - return null; - } - - if (! $foreachedPropertyFetch->name instanceof Identifier) { - return null; - } - - if ($foreachedPropertyFetch->name->toString() !== 'visitors') { - return null; - } - - // replace $this->visitors with $currentNodeVisitors - $currentNodeVisitorsVariable = new Variable(ImmutableNodeTraverserName::CURRENT_NODE_VISITORS); - $node->expr = $currentNodeVisitorsVariable; - - // add before foreach: $currentNodeVisitors = $this->getVisitorsForNode($node); - $assign = new Assign( - $currentNodeVisitorsVariable, - new MethodCall( - new Variable('this'), - ImmutableNodeTraverserName::GET_VISITORS_FOR_NODE_METHOD, - [new Arg(new Variable($this->nodeName))] - ) - ); - - return [new Expression($assign), $node]; - } -} - -final class ReplaceThisVisitorsWithThisGetVisitorsNodeVisitor extends NodeVisitorAbstract -{ - public function enterNode(Node $node): ?ClassMethod - { - if (! $node instanceof ClassMethod || $node->stmts === null) { - return null; - } - - if (! in_array($node->name->toString(), ['traverseArray', 'traverseNode'])) { - return null; - } - - $traverseArrayNodeName = $node->name->toString() === 'traverseNode' ? 'subNode' : 'node'; - - // handle foreach $this->visitors - $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor(new ReplaceForeachThisVisitorNodeVisitor($traverseArrayNodeName)); - - $node->stmts = $nodeTraverser->traverse($node->stmts); - - return $node; - } -} - -final class RenameNamespaceNodeVisitor extends NodeVisitorAbstract -{ - public function enterNode(Node $node): ?Namespace_ - { - if (! $node instanceof Namespace_) { - return null; - } - - // add uses for PHPParser nodes as locations are now changed - $uses = [ - new Use_([new UseItem(new Name(NodeTraverserInterface::class))]), - new Use_([new UseItem(new Name(NodeVisitor::class))]), - new Use_([new UseItem(new Name(Node::class))]), - ]; - - /** @var Stmt[] $newStmts */ - $newStmts = array_merge($uses, (array) $node->stmts); - $node->stmts = $newStmts; - - $node->name = new Name('Rector\PhpParser\NodeTraverser'); - return $node; - } -} - -final class ReplaceThisVisitorsArrayDimFetchWithCurrentNodeVisitorsNodeVisitor extends NodeVisitorAbstract -{ - public function enterNode(Node $node): ?Node - { - if (! $node instanceof ArrayDimFetch) { - return null; - } - - if (! $node->var instanceof PropertyFetch) { - return null; - } - - $propertyFetch = $node->var; - if (! $propertyFetch->var instanceof Variable) { - return null; - } - - if ($propertyFetch->var->name !== 'this') { - return null; - } - - if (! $propertyFetch->name instanceof Identifier) { - return null; - } - - if ($propertyFetch->name->toString() !== 'visitors') { - return null; - } - - if (! $node->dim instanceof Variable) { - return null; - } - - if ($node->dim->name !== 'visitorIndex') { - return null; - } - - $node->var = new Variable(ImmutableNodeTraverserName::CURRENT_NODE_VISITORS); - - return $node; - } -} - -final class DecorateClassNodeVisitor extends NodeVisitorAbstract -{ - public function enterNode(Node $node): ?Class_ - { - if (! $node instanceof Class_) { - return null; - } - - $node->flags |= Modifiers::ABSTRACT; - $node->name = new Identifier('AbstractImmutableNodeTraverser'); - - $getVisitorsForNodeClassMethod = new ClassMethod(ImmutableNodeTraverserName::GET_VISITORS_FOR_NODE_METHOD, [ - 'flags' => Modifiers::PUBLIC | Modifiers::ABSTRACT, - 'params' => [new Param(var: new Variable('node'), type: new FullyQualified(Node::class))], - 'returnType' => new Name('array'), - 'stmts' => null, - ]); - - // add @return NodeVisitor[] docblock - $getVisitorsForNodeClassMethod->setDocComment(new Doc(<<<'DOC' -/** - * @return NodeVisitor[] - */ -DOC - )); - - $node->stmts[] = $getVisitorsForNodeClassMethod; - - return $node; - } -} - -$nodeTraverser = new NodeTraverser(); - -$nodeTraverser->addVisitor(new DecorateClassNodeVisitor()); -$nodeTraverser->addVisitor(new ReplaceThisVisitorsWithThisGetVisitorsNodeVisitor()); -$nodeTraverser->addVisitor(new ReplaceThisVisitorsArrayDimFetchWithCurrentNodeVisitorsNodeVisitor()); -$nodeTraverser->addVisitor(new RenameNamespaceNodeVisitor()); - -$stmts = $nodeTraverser->traverse($stmts); - -final class ImmutableNodeTraverserName -{ - /** - * @var string - */ - public const GET_VISITORS_FOR_NODE_METHOD = 'getVisitorsForNode'; - - /** - * @var string - */ - public const CURRENT_NODE_VISITORS = 'currentNodeVisitors'; -} - -// print node traverser contents -$standard = new Standard(); -$immutableNodeTraverserFileContents = $standard->printFormatPreserving($stmts, $originalStmts, $parser->getTokens()); - -// save the file -FileSystem::write( - __DIR__ . '/../src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php', - $immutableNodeTraverserFileContents -); - -echo sprintf('New file "%s" was created', 'src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php') . PHP_EOL; diff --git a/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php b/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php index fe0a16c5fc9..c371781d627 100644 --- a/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php @@ -34,14 +34,9 @@ public function __construct(NodeVisitor ...$visitors) $this->visitors = $visitors; } - /** - * Adds a visitor. - * - * @param NodeVisitor $visitor Visitor to add - */ public function addVisitor(NodeVisitor $visitor): void { - $this->visitors[] = $visitor; + throw new ShouldNotHappenException('The immutable node traverser does not support adding visitors.'); } public function removeVisitor(NodeVisitor $visitor): void diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index fe61e018022..e5600c2ea33 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -12,6 +12,7 @@ use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace; use Rector\PhpParser\Node\FileNode; use Rector\VersionBonding\PhpVersionedFilter; +use Webmozart\Assert\Assert; /** * @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest @@ -50,14 +51,20 @@ public function traverse(array $nodes): 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. */ public function refreshPhpRectors(array $rectors): void { + Assert::allIsInstanceOf($rectors, RectorInterface::class); + $this->rectors = $rectors; $this->visitors = []; $this->visitorsPerNodeClass = []; $this->areNodeVisitorsPrepared = false; + + $this->prepareNodeVisitors(); } /** diff --git a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php index 428dccdb296..35b4a074abd 100644 --- a/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php +++ b/tests/PhpParser/NodeTraverser/RectorNodeTraverserTest.php @@ -48,7 +48,7 @@ public function testGetVisitorsForNodeWhenNoVisitorsAvailable(): void public function testGetVisitorsForNodeWhenNoVisitorsMatch(): void { $class = new Class_('test'); - $this->rectorNodeTraverser->addVisitor($this->ruleUsingFunctionRector); + $this->rectorNodeTraverser->refreshPhpRectors([$this->ruleUsingFunctionRector]); $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); @@ -58,8 +58,10 @@ public function testGetVisitorsForNodeWhenNoVisitorsMatch(): void public function testGetVisitorsForNodeWhenSomeVisitorsMatch(): void { $class = new Class_('test'); - $this->rectorNodeTraverser->addVisitor($this->ruleUsingFunctionRector); - $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); + $this->rectorNodeTraverser->refreshPhpRectors([ + $this->ruleUsingFunctionRector, + $this->ruleUsingClassRector, + ]); $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class); @@ -69,8 +71,10 @@ public function testGetVisitorsForNodeWhenSomeVisitorsMatch(): void public function testGetVisitorsForNodeWhenAllVisitorsMatch(): void { $class = new Class_('test'); - $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassRector); - $this->rectorNodeTraverser->addVisitor($this->ruleUsingClassLikeRector); + $this->rectorNodeTraverser->refreshPhpRectors([ + $this->ruleUsingClassRector, + $this->ruleUsingClassLikeRector, + ]); $visitors = $this->rectorNodeTraverser->getVisitorsForNode($class);