mirror of
https://github.com/danog/PHP-Parser.git
synced 2024-11-26 20:04:48 +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?
|
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
|
The AST does not store parent nodes by default. However, the `ParentConnectingVisitor` can be used to achieve this:
|
||||||
attribute using a custom node visitor:
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use PhpParser\Node;
|
use PhpParser\NodeTraverser;
|
||||||
use PhpParser\NodeVisitorAbstract;
|
use PhpParser\NodeVisitor\ParentConnectingVisitor;
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
|
||||||
class ParentConnector extends NodeVisitorAbstract {
|
$code = '...';
|
||||||
private $stack;
|
|
||||||
public function beforeTraverse(array $nodes) {
|
$traverser = new NodeTraverser;
|
||||||
$this->stack = [];
|
$traverser->addVisitor(new ParentConnectingVisitor);
|
||||||
}
|
|
||||||
public function enterNode(Node $node) {
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
if (!empty($this->stack)) {
|
$ast = $parser->parse($code);
|
||||||
$node->setAttribute('parent', $this->stack[count($this->stack)-1]);
|
$ast = $traverser->traverse($ast);
|
||||||
}
|
|
||||||
$this->stack[] = $node;
|
|
||||||
}
|
|
||||||
public function leaveNode(Node $node) {
|
|
||||||
array_pop($this->stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
After running this visitor, the parent node can be obtained through `$node->getAttribute('parent')`.
|
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?
|
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
|
Again, siblings are not stored by default, but the `NodeConnectingVisitor` can be used to store
|
||||||
extended to store the previous / next node with a common parent as well:
|
the previous / next node with a common parent as well:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use PhpParser\Node;
|
use PhpParser\NodeTraverser;
|
||||||
use PhpParser\NodeVisitorAbstract;
|
use PhpParser\NodeVisitor\NodeConnectingVisitor;
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
|
||||||
class NodeConnector extends NodeVisitorAbstract {
|
$code = '...';
|
||||||
private $stack;
|
|
||||||
private $prev;
|
$traverser = new NodeTraverser;
|
||||||
public function beforeTraverse(array $nodes) {
|
$traverser->addVisitor(new NodeConnectingVisitor);
|
||||||
$this->stack = [];
|
|
||||||
$this->prev = null;
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
}
|
$ast = $parser->parse($code);
|
||||||
public function enterNode(Node $node) {
|
$ast = $traverser->traverse($ast);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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