diff --git a/doc/component/FAQ.markdown b/doc/component/FAQ.markdown index b8bf834..1a70a48 100644 --- a/doc/component/FAQ.markdown +++ b/doc/component/FAQ.markdown @@ -7,28 +7,21 @@ Frequently Asked Questions How can the parent of a node be obtained? ----- -The AST does not store parent nodes by default. However, it is easy to add a custom parent node -attribute using a custom node visitor: +The AST does not store parent nodes by default. However, the `ParentConnectingVisitor` can be used to achieve this: ```php -use PhpParser\Node; -use PhpParser\NodeVisitorAbstract; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\ParentConnectingVisitor; +use PhpParser\ParserFactory; -class ParentConnector extends NodeVisitorAbstract { - private $stack; - public function beforeTraverse(array $nodes) { - $this->stack = []; - } - public function enterNode(Node $node) { - if (!empty($this->stack)) { - $node->setAttribute('parent', $this->stack[count($this->stack)-1]); - } - $this->stack[] = $node; - } - public function leaveNode(Node $node) { - array_pop($this->stack); - } -} +$code = '...'; + +$traverser = new NodeTraverser; +$traverser->addVisitor(new ParentConnectingVisitor); + +$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); +$ast = $parser->parse($code); +$ast = $traverser->traverse($ast); ``` After running this visitor, the parent node can be obtained through `$node->getAttribute('parent')`. @@ -36,33 +29,27 @@ After running this visitor, the parent node can be obtained through `$node->getA How can the next/previous sibling of a node be obtained? ----- -Again, siblings are not stored by default, but the visitor from the previous entry can be easily -extended to store the previous / next node with a common parent as well: +Again, siblings are not stored by default, but the `NodeConnectingVisitor` can be used to store +the previous / next node with a common parent as well: ```php -use PhpParser\Node; -use PhpParser\NodeVisitorAbstract; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NodeConnectingVisitor; +use PhpParser\ParserFactory; -class NodeConnector extends NodeVisitorAbstract { - private $stack; - private $prev; - public function beforeTraverse(array $nodes) { - $this->stack = []; - $this->prev = null; - } - public function enterNode(Node $node) { - if (!empty($this->stack)) { - $node->setAttribute('parent', $this->stack[count($this->stack)-1]); - } - if ($this->prev && $this->prev->getAttribute('parent') == $node->getAttribute('parent')) { - $node->setAttribute('prev', $this->prev); - $this->prev->setAttribute('next', $node); - } - $this->stack[] = $node; - } - public function leaveNode(Node $node) { - $this->prev = $node; - array_pop($this->stack); - } -} +$code = '...'; + +$traverser = new NodeTraverser; +$traverser->addVisitor(new NodeConnectingVisitor); + +$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); +$ast = $parser->parse($code); +$ast = $traverser->traverse($ast); ``` + +After running this visitor, the parent node can be obtained through `$node->getAttribute('parent')`, +the previous node can be obtained through `$node->getAttribute('previous')`, and the next node can be +obtained through `$node->getAttribute('next')`. + +`ParentConnectingVisitor` and `NodeConnectingVisitor` should not be used at the same time. The latter +includes the functionality of the former. diff --git a/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php b/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php new file mode 100644 index 0000000..ea372e5 --- /dev/null +++ b/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php @@ -0,0 +1,52 @@ +$node->getAttribute('parent'), the previous + * node can be accessed through $node->getAttribute('previous'), + * and the next node can be accessed through $node->getAttribute('next'). + */ +final class NodeConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $stack = []; + + /** + * @var ?Node + */ + private $previous; + + public function beforeTraverse(array $nodes) { + $this->stack = []; + $this->previous = null; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) { + $node->setAttribute('previous', $this->previous); + $this->previous->setAttribute('next', $node); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + $this->previous = $node; + + array_pop($this->stack); + } +} diff --git a/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php b/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php new file mode 100644 index 0000000..b98d2bf --- /dev/null +++ b/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php @@ -0,0 +1,41 @@ +$node->getAttribute('parent'). + */ +final class ParentConnectingVisitor extends NodeVisitorAbstract +{ + /** + * @var Node[] + */ + private $stack = []; + + public function beforeTraverse(array $nodes) + { + $this->stack = []; + } + + public function enterNode(Node $node) + { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) + { + array_pop($this->stack); + } +} diff --git a/test/PhpParser/NodeVisitor/NodeConnectingVisitorTest.php b/test/PhpParser/NodeVisitor/NodeConnectingVisitorTest.php new file mode 100644 index 0000000..b9b1bff --- /dev/null +++ b/test/PhpParser/NodeVisitor/NodeConnectingVisitorTest.php @@ -0,0 +1,35 @@ +create(ParserFactory::PREFER_PHP7)->parse( + 'addVisitor(new NodeConnectingVisitor); + + $ast = $traverser->traverse($ast); + + $node = (new NodeFinder)->findFirstInstanceof($ast, Else_::class); + + $this->assertSame(If_::class, get_class($node->getAttribute('parent'))); + $this->assertSame(ConstFetch::class, get_class($node->getAttribute('previous'))); + + $node = (new NodeFinder)->findFirstInstanceof($ast, ConstFetch::class); + + $this->assertSame(Else_::class, get_class($node->getAttribute('next'))); + } +} diff --git a/test/PhpParser/NodeVisitor/ParentConnectingVisitorTest.php b/test/PhpParser/NodeVisitor/ParentConnectingVisitorTest.php new file mode 100644 index 0000000..d1cc482 --- /dev/null +++ b/test/PhpParser/NodeVisitor/ParentConnectingVisitorTest.php @@ -0,0 +1,28 @@ +create(ParserFactory::PREFER_PHP7)->parse( + 'addVisitor(new ParentConnectingVisitor); + + $ast = $traverser->traverse($ast); + + $node = (new NodeFinder)->findFirstInstanceof($ast, ClassMethod::class); + + $this->assertSame('C', $node->getAttribute('parent')->name->toString()); + } +}