1
0
mirror of https://github.com/danog/PHP-Parser.git synced 2025-01-20 12:46:47 +01:00

Add NodeTraverser::DONT_TRAVERSE_CHILDREN support

This commit is contained in:
Nikita Popov 2015-01-11 22:13:58 +01:00
parent f56db26d8b
commit 4071c4645d
4 changed files with 88 additions and 16 deletions

View File

@ -241,23 +241,27 @@ methods:
public function leaveNode(PhpParser\Node $node);
public function afterTraverse(array $nodes);
The `beforeTraverse` method is called once before the traversal begins and is passed the nodes the
The `beforeTraverse()` method is called once before the traversal begins and is passed the nodes the
traverser was called with. This method can be used for resetting values before traversation or
preparing the tree for traversal.
The `afterTraverse` method is similar to the `beforeTraverse` method, with the only difference that
The `afterTraverse()` method is similar to the `beforeTraverse()` method, with the only difference that
it is called once after the traversal.
The `enterNode` and `leaveNode` methods are called on every node, the former when it is entered,
The `enterNode()` and `leaveNode()` methods are called on every node, the former when it is entered,
i.e. before its subnodes are traversed, the latter when it is left.
All four methods can either return the changed node or not return at all (i.e. `null`) in which
case the current node is not changed. The `leaveNode` method can additionally return two special
values:
case the current node is not changed.
If `false` is returned the current node will be removed from the parent array. If an array is returned
it will be merged into the parent array at the offset of the current node. I.e. if in `array(A, B, C)`
the node `B` should be replaced with `array(X, Y, Z)` the result will be `array(A, X, Y, Z, C)`.
The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`,
which instructs the traverser to skip all children of the current node.
The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
case the current node will be removed from the parent array. Furthermove it is possible to return
an array of nodes, which will be merged into the parent array at the offset of the current node.
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
be `array(A, X, Y, Z, C)`.
Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
class, which will define empty default implementations for all the above methods.

View File

@ -73,13 +73,19 @@ class NodeTraverser implements NodeTraverserInterface
if (is_array($subNode)) {
$subNode = $this->traverseArray($subNode);
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->enterNode($subNode)) {
$return = $visitor->enterNode($subNode);
if (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} else if (null !== $return) {
$subNode = $return;
}
}
if ($traverseChildren) {
$subNode = $this->traverseNode($subNode);
}
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->leaveNode($subNode)) {
@ -99,18 +105,24 @@ class NodeTraverser implements NodeTraverserInterface
if (is_array($node)) {
$node = $this->traverseArray($node);
} elseif ($node instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->enterNode($node)) {
$return = $visitor->enterNode($node);
if (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} else if (null !== $return) {
$node = $return;
}
}
if ($traverseChildren) {
$node = $this->traverseNode($node);
}
foreach ($this->visitors as $visitor) {
$return = $visitor->leaveNode($node);
if (false === $return) {
if (self::REMOVE_NODE === $return) {
$doNodes[] = array($i, array());
break;
} elseif (is_array($return)) {

View File

@ -4,6 +4,24 @@ namespace PhpParser;
interface NodeTraverserInterface
{
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will still be called on the current
* node and leaveNode() will also be invoked for the current node.
*/
const DONT_TRAVERSE_CHILDREN = 1;
/**
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
* in an array, it will be removed from the array.
*
* For subsequent visitors leaveNode() will still be invoked for the
* removed node.
*/
const REMOVE_NODE = false;
/**
* Adds a visitor.
*

View File

@ -3,6 +3,7 @@
namespace PhpParser;
use PhpParser\Node\Scalar\String;
use PhpParser\Node\Expr;
class NodeTraverserTest extends \PHPUnit_Framework_TestCase
{
@ -32,7 +33,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
public function testModifying() {
$str1Node = new String('Foo');
$str2Node = new String('Bar');
$printNode = new Node\Expr\Print_($str1Node);
$printNode = new Expr\Print_($str1Node);
// first visitor changes the node, second verifies the change
$visitor1 = $this->getMock('PhpParser\NodeVisitor');
@ -127,6 +128,43 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($stmts, $traverser->traverse($stmts));
}
public function testDontTraverseChildren() {
$strNode = new String('str');
$printNode = new Expr\Print_($strNode);
$argNode = new Node\Arg($strNode);
$callNode = new Expr\FuncCall(new Node\Name('test'), array($argNode));
$stmts = array($printNode, $callNode);
$visitor1 = $this->getMock('PhpParser\NodeVisitor');
$visitor2 = $this->getMock('PhpParser\NodeVisitor');
$visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
$visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
$visitor1->expects($this->at(2))->method('leaveNode')->with($printNode);
$visitor2->expects($this->at(2))->method('leaveNode')->with($printNode);
$visitor1->expects($this->at(3))->method('enterNode')->with($callNode);
$visitor2->expects($this->at(3))->method('enterNode')->with($callNode);
$visitor1->expects($this->at(6))->method('enterNode')->with($argNode);
$visitor2->expects($this->at(6))->method('enterNode')->with($argNode)
->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
$visitor1->expects($this->at(7))->method('leaveNode')->with($argNode);
$visitor2->expects($this->at(7))->method('leaveNode')->with($argNode);
$visitor1->expects($this->at(8))->method('leaveNode')->with($callNode);
$visitor2->expects($this->at(8))->method('leaveNode')->with($callNode);
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor1);
$traverser->addVisitor($visitor2);
$this->assertEquals($stmts, $traverser->traverse($stmts));
}
public function testRemovingVisitor() {
$visitor1 = $this->getMock('PhpParser\NodeVisitor');
$visitor2 = $this->getMock('PhpParser\NodeVisitor');