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:
parent
f56db26d8b
commit
4071c4645d
@ -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.
|
||||
|
@ -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)) {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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');
|
||||
|
Loading…
x
Reference in New Issue
Block a user