1
0
mirror of https://github.com/danog/PHP-Parser.git synced 2024-11-30 04:19:30 +01:00

Merge branch '3.x'

Conflicts:
	lib/PhpParser/NodeVisitor.php
This commit is contained in:
Nikita Popov 2017-01-29 22:36:33 +01:00
commit 5cc2750ebc
3 changed files with 102 additions and 3 deletions

View File

@ -13,6 +13,14 @@ class NodeTraverser implements NodeTraverserInterface
*/
const DONT_TRAVERSE_CHILDREN = 1;
/**
* If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
* STOP_TRAVERSAL, traversal is aborted.
*
* The afterTraverse() method will still be invoked.
*/
const STOP_TRAVERSAL = 2;
/**
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
* in an array, it will be removed from the array.
@ -25,6 +33,9 @@ class NodeTraverser implements NodeTraverserInterface
/** @var NodeVisitor[] Visitors */
protected $visitors;
/** @var bool Whether traversal should be stopped */
protected $stopTraversal;
/**
* Constructs a node traverser.
*/
@ -63,6 +74,8 @@ class NodeTraverser implements NodeTraverserInterface
* @return Node[] Traversed array of nodes
*/
public function traverse(array $nodes) {
$this->stopTraversal = false;
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->beforeTraverse($nodes)) {
$nodes = $return;
@ -93,12 +106,18 @@ class NodeTraverser implements NodeTraverserInterface
if (is_array($subNode)) {
$subNode = $this->traverseArray($subNode);
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
$return = $visitor->enterNode($subNode);
if (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} else if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else if (null !== $return) {
$subNode = $return;
}
@ -106,10 +125,17 @@ class NodeTraverser implements NodeTraverserInterface
if ($traverseChildren) {
$subNode = $this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->leaveNode($subNode)) {
$return = $visitor->leaveNode($subNode);
if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else if (null !== $return) {
if (is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
@ -138,12 +164,18 @@ class NodeTraverser implements NodeTraverserInterface
foreach ($nodes as $i => &$node) {
if (is_array($node)) {
$node = $this->traverseArray($node);
if ($this->stopTraversal) {
break;
}
} elseif ($node instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
$return = $visitor->enterNode($node);
if (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} else if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else if (null !== $return) {
$node = $return;
}
@ -151,6 +183,9 @@ class NodeTraverser implements NodeTraverserInterface
if ($traverseChildren) {
$node = $this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitor) {
@ -159,6 +194,9 @@ class NodeTraverser implements NodeTraverserInterface
if (self::REMOVE_NODE === $return) {
$doNodes[] = array($i, array());
break;
} else if (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (is_array($return)) {
$doNodes[] = array($i, $return);
break;

View File

@ -25,12 +25,14 @@ interface NodeVisitor
* => $node stays as-is
* * NodeTraverser::DONT_TRAVERSE_CHILDREN
* => Children of $node are not traversed. $node stays as-is
* * NodeTraverser::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * otherwise
* => $node is set to the return value
*
* @param Node $node Node
*
* @return null|Node|int Replacement node (or special return value)
* @return null|int|Node Replacement node (or special return value)
*/
public function enterNode(Node $node);
@ -42,6 +44,8 @@ interface NodeVisitor
* => $node stays as-is
* * NodeTraverser::REMOVE_NODE
* => $node is removed from the parent array
* * NodeTraverser::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * array (of Nodes)
* => The return value is merged into the parent array (at the position of the $node)
* * otherwise
@ -49,7 +53,7 @@ interface NodeVisitor
*
* @param Node $node Node
*
* @return null|Node|false|Node[] Replacement node (or special return value)
* @return null|false|int|Node|Node[] Replacement node (or special return value)
*/
public function leaveNode(Node $node);

View File

@ -166,6 +166,63 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($stmts, $traverser->traverse($stmts));
}
public function testStopTraversal() {
$varNode1 = new Expr\Variable('a');
$varNode2 = new Expr\Variable('b');
$varNode3 = new Expr\Variable('c');
$mulNode = new Expr\BinaryOp\Mul($varNode1, $varNode2);
$printNode = new Expr\Print_($varNode3);
$stmts = [$mulNode, $printNode];
// From enterNode() with array parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(1))->method('enterNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(2))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// From enterNode with Node parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(2))->method('enterNode')->with($varNode1)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(3))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// From leaveNode with Node parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(3))->method('leaveNode')->with($varNode1)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(4))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// From leaveNode with array parent
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(7))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts));
// Check that pending array modifications are still carried out
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::REMOVE_NODE));
$visitor->expects($this->at(7))->method('enterNode')->with($printNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(8))->method('afterTraversal');
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$this->assertEquals([$printNode], $traverser->traverse($stmts));
}
public function testRemovingVisitor() {
$visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();