mirror of
https://github.com/danog/PHP-Parser.git
synced 2024-11-26 11:54:52 +01:00
Add ParentConnectingVisitor and NodeConnectingVisitor (#681)
This commit is contained in:
parent
c346bbfafe
commit
f545f18a87
@ -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.
|
||||
|
52
lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
Normal file
52
lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\NodeVisitor;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
/**
|
||||
* Visitor that connects a child node to its parent node
|
||||
* as well as its sibling nodes.
|
||||
*
|
||||
* On the child node, the parent node can be accessed through
|
||||
* <code>$node->getAttribute('parent')</code>, the previous
|
||||
* node can be accessed through <code>$node->getAttribute('previous')</code>,
|
||||
* and the next node can be accessed through <code>$node->getAttribute('next')</code>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
41
lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
Normal file
41
lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\NodeVisitor;
|
||||
|
||||
use function array_pop;
|
||||
use function count;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
/**
|
||||
* Visitor that connects a child node to its parent node.
|
||||
*
|
||||
* On the child node, the parent node can be accessed through
|
||||
* <code>$node->getAttribute('parent')</code>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
35
test/PhpParser/NodeVisitor/NodeConnectingVisitorTest.php
Normal file
35
test/PhpParser/NodeVisitor/NodeConnectingVisitorTest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\NodeVisitor;
|
||||
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Stmt\Else_;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\NodeFinder;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
final class NodeConnectingVisitorTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testConnectsNodeToItsParentNodeAndItsSiblingNodes()
|
||||
{
|
||||
$ast = (new ParserFactory)->create(ParserFactory::PREFER_PHP7)->parse(
|
||||
'<?php if (true) {} else {}'
|
||||
);
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
$traverser->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')));
|
||||
}
|
||||
}
|
28
test/PhpParser/NodeVisitor/ParentConnectingVisitorTest.php
Normal file
28
test/PhpParser/NodeVisitor/ParentConnectingVisitorTest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\NodeVisitor;
|
||||
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\NodeFinder;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
final class ParentConnectingVisitorTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testConnectsChildNodeToParentNode()
|
||||
{
|
||||
$ast = (new ParserFactory)->create(ParserFactory::PREFER_PHP7)->parse(
|
||||
'<?php class C { public function m() {} }'
|
||||
);
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
$traverser->addVisitor(new ParentConnectingVisitor);
|
||||
|
||||
$ast = $traverser->traverse($ast);
|
||||
|
||||
$node = (new NodeFinder)->findFirstInstanceof($ast, ClassMethod::class);
|
||||
|
||||
$this->assertSame('C', $node->getAttribute('parent')->name->toString());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user