mirror of
https://github.com/phabelio/PHP-Parser.git
synced 2024-11-27 04:24:43 +01:00
Cover NodeTraverser and bugs it found
a) ->traverseNode() now operates on a clone of the node, otherwise the original node will be modified too b) before nodes were passed to the following visitor unchanged, even though they were already changed in the tree
This commit is contained in:
parent
8c2bf0373c
commit
b49c55c9e5
@ -9,6 +9,13 @@ interface PHPParser_Node
|
||||
*/
|
||||
public function getType();
|
||||
|
||||
/**
|
||||
* Gets the names of the sub nodes.
|
||||
*
|
||||
* @return array Names of sub nodes
|
||||
*/
|
||||
public function getSubNodeNames();
|
||||
|
||||
/**
|
||||
* Gets line the node started in.
|
||||
*
|
||||
|
@ -28,6 +28,15 @@ abstract class PHPParser_NodeAbstract implements PHPParser_Node, IteratorAggrega
|
||||
return substr(get_class($this), 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of the sub nodes.
|
||||
*
|
||||
* @return array Names of sub nodes
|
||||
*/
|
||||
public function getSubNodeNames() {
|
||||
return array_keys($this->subNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets line the node started in.
|
||||
*
|
||||
|
@ -49,21 +49,25 @@ class PHPParser_NodeTraverser
|
||||
}
|
||||
|
||||
protected function traverseNode(PHPParser_Node $node) {
|
||||
foreach ($node as $name => $subNode) {
|
||||
$node = clone $node;
|
||||
|
||||
foreach ($node->getSubNodeNames() as $name) {
|
||||
$subNode =& $node->$name;
|
||||
|
||||
if (is_array($subNode)) {
|
||||
$node->$name = $this->traverseArray($subNode);
|
||||
$subNode = $this->traverseArray($subNode);
|
||||
} elseif ($subNode instanceof PHPParser_Node) {
|
||||
foreach ($this->visitors as $visitor) {
|
||||
if (null !== $return = $visitor->enterNode($subNode)) {
|
||||
$node->$name = $return;
|
||||
$subNode = $return;
|
||||
}
|
||||
}
|
||||
|
||||
$node->$name = $this->traverseNode($subNode);
|
||||
$subNode = $this->traverseNode($subNode);
|
||||
|
||||
foreach ($this->visitors as $visitor) {
|
||||
if (null !== $return = $visitor->leaveNode($subNode)) {
|
||||
$node->$name = $return;
|
||||
$subNode = $return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,17 +79,17 @@ class PHPParser_NodeTraverser
|
||||
protected function traverseArray(array $nodes) {
|
||||
$doNodes = array();
|
||||
|
||||
foreach ($nodes as $i => $node) {
|
||||
foreach ($nodes as $i => &$node) {
|
||||
if (is_array($node)) {
|
||||
$nodes[$i] = $this->traverseArray($node);
|
||||
$node = $this->traverseArray($node);
|
||||
} elseif ($node instanceof PHPParser_Node) {
|
||||
foreach ($this->visitors as $visitor) {
|
||||
if (null !== $return = $visitor->enterNode($node)) {
|
||||
$nodes[$i] = $return;
|
||||
$node = $return;
|
||||
}
|
||||
}
|
||||
|
||||
$nodes[$i] = $this->traverseNode($node);
|
||||
$node = $this->traverseNode($node);
|
||||
|
||||
foreach ($this->visitors as $j => $visitor) {
|
||||
$return = $visitor->leaveNode($node);
|
||||
@ -103,7 +107,7 @@ class PHPParser_NodeTraverser
|
||||
$doNodes[] = array($i, $return);
|
||||
break;
|
||||
} elseif (null !== $return) {
|
||||
$nodes[$i] = $return;
|
||||
$node = $return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,10 @@ class PHPParser_Tests_NodeAbstractTest extends PHPUnit_Framework_TestCase
|
||||
'PHPParser_Node_Dummy'
|
||||
);
|
||||
|
||||
$this->assertEquals('Dummy', $node->getType());
|
||||
$this->assertEquals(array('subNode'), $node->getSubNodeNames());
|
||||
$this->assertEquals(10, $node->getLine());
|
||||
$this->assertEquals('/** doc comment */', $node->getDocComment());
|
||||
$this->assertEquals('Dummy', $node->getType());
|
||||
$this->assertEquals('value', $node->subNode);
|
||||
$this->assertTrue(isset($node->subNode));
|
||||
|
||||
@ -28,15 +29,24 @@ class PHPParser_Tests_NodeAbstractTest extends PHPUnit_Framework_TestCase
|
||||
* @depends testConstruct
|
||||
*/
|
||||
public function testChange(PHPParser_Node $node) {
|
||||
// change of line
|
||||
$node->setLine(15);
|
||||
$this->assertEquals(15, $node->getLine());
|
||||
|
||||
// change of doc comment
|
||||
$node->setDocComment('/** other doc comment */');
|
||||
$this->assertEquals('/** other doc comment */', $node->getDocComment());
|
||||
|
||||
// direct modification
|
||||
$node->subNode = 'newValue';
|
||||
$this->assertEquals('newValue', $node->subNode);
|
||||
|
||||
// indirect modification
|
||||
$subNode =& $node->subNode;
|
||||
$subNode = 'newNewValue';
|
||||
$this->assertEquals('newNewValue', $node->subNode);
|
||||
|
||||
// removal
|
||||
unset($node->subNode);
|
||||
$this->assertFalse(isset($node->subNode));
|
||||
}
|
||||
|
@ -2,118 +2,124 @@
|
||||
|
||||
class PHPParser_Tests_NodeTraverserTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function getTestNode() {
|
||||
return array(
|
||||
new PHPParser_Node_Stmt_Namespace(
|
||||
new PHPParser_Node_Name(array('Foo', 'Bar')),
|
||||
array(
|
||||
new PHPParser_Node_Stmt_Echo(array(
|
||||
new PHPParser_Node_Scalar_String('Hallo World')
|
||||
)),
|
||||
new PHPParser_Node_Expr_Print(
|
||||
new PHPParser_Node_Scalar_String('Hallo World, again!')
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
public function testNonModifying() {
|
||||
$str1Node = new PHPParser_Node_Scalar_String('Foo');
|
||||
$str2Node = new PHPParser_Node_Scalar_String('Bar');
|
||||
$echoNode = new PHPParser_Node_Stmt_Echo(array($str1Node, $str2Node));
|
||||
$stmts = array($echoNode);
|
||||
|
||||
public function testTraverse() {
|
||||
$node = $this->getTestNode();
|
||||
$visitor = $this->getMock('PHPParser_NodeVisitor');
|
||||
|
||||
$visitor = new PHPParser_Tests_NodeVisitor;
|
||||
$visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts);
|
||||
$visitor->expects($this->at(1))->method('enterNode')->with($echoNode);
|
||||
$visitor->expects($this->at(2))->method('enterNode')->with($str1Node);
|
||||
$visitor->expects($this->at(3))->method('leaveNode')->with($str1Node);
|
||||
$visitor->expects($this->at(4))->method('enterNode')->with($str2Node);
|
||||
$visitor->expects($this->at(5))->method('leaveNode')->with($str2Node);
|
||||
$visitor->expects($this->at(6))->method('leaveNode')->with($echoNode);
|
||||
$visitor->expects($this->at(7))->method('afterTraverse')->with($stmts);
|
||||
|
||||
$traverser = new PHPParser_NodeTraverser;
|
||||
$traverser->addVisitor($visitor);
|
||||
|
||||
$node = $traverser->traverse($node);
|
||||
|
||||
$this->assertEquals($node, $visitor->beforeTraverseNodes);
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Stmt_Namespace',
|
||||
'Name',
|
||||
'Stmt_Echo',
|
||||
'Scalar_String',
|
||||
'Expr_Print',
|
||||
'Scalar_String',
|
||||
),
|
||||
$visitor->enteredNodes
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'Name',
|
||||
'Scalar_String',
|
||||
'Stmt_Echo',
|
||||
'Scalar_String',
|
||||
'Expr_Print',
|
||||
'Stmt_Namespace',
|
||||
),
|
||||
$visitor->leftNodes
|
||||
);
|
||||
|
||||
$this->assertEquals($node, $visitor->afterTraverseNodes);
|
||||
$this->assertEquals($stmts, $traverser->traverse($stmts));
|
||||
}
|
||||
|
||||
public function testModifyingTraverse() {
|
||||
$node = $this->getTestNode();
|
||||
public function testModifying() {
|
||||
$str1Node = new PHPParser_Node_Scalar_String('Foo');
|
||||
$str2Node = new PHPParser_Node_Scalar_String('Bar');
|
||||
$printNode = new PHPParser_Node_Expr_Print($str1Node);
|
||||
|
||||
// first visitor changes the node, second verifies the change
|
||||
$visitor1 = $this->getMock('PHPParser_NodeVisitor');
|
||||
$visitor2 = $this->getMock('PHPParser_NodeVisitor');
|
||||
|
||||
// replace empty statements with string1 node
|
||||
$visitor1->expects($this->at(0))->method('beforeTraverse')->with(array())
|
||||
->will($this->returnValue(array($str1Node)));
|
||||
$visitor2->expects($this->at(0))->method('beforeTraverse')->with(array($str1Node));
|
||||
|
||||
// replace string1 node with print node
|
||||
$visitor1->expects($this->at(1))->method('enterNode')->with($str1Node)
|
||||
->will($this->returnValue($printNode));
|
||||
$visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
|
||||
|
||||
// replace string1 node with string2 node
|
||||
$visitor1->expects($this->at(2))->method('enterNode')->with($str1Node)
|
||||
->will($this->returnValue($str2Node));
|
||||
$visitor2->expects($this->at(2))->method('enterNode')->with($str2Node);
|
||||
|
||||
// replace string2 node with string1 node again
|
||||
$visitor1->expects($this->at(3))->method('leaveNode')->with($str2Node)
|
||||
->will($this->returnValue($str1Node));
|
||||
$visitor2->expects($this->at(3))->method('leaveNode')->with($str1Node);
|
||||
|
||||
// replace print node with string1 node again
|
||||
$visitor1->expects($this->at(4))->method('leaveNode')->with($printNode)
|
||||
->will($this->returnValue($str1Node));
|
||||
$visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node);
|
||||
|
||||
// replace string1 node with empty statements again
|
||||
$visitor1->expects($this->at(5))->method('afterTraverse')->with(array($str1Node))
|
||||
->will($this->returnValue(array()));
|
||||
$visitor2->expects($this->at(5))->method('afterTraverse')->with(array());
|
||||
|
||||
$traverser = new PHPParser_NodeTraverser;
|
||||
$traverser->addVisitor(new PHPParser_Tests_ModifyingNodeVisitor);
|
||||
$traverser->addVisitor($visitor1);
|
||||
$traverser->addVisitor($visitor2);
|
||||
|
||||
// as all operations are reversed we end where we start
|
||||
$this->assertEquals(array(), $traverser->traverse(array()));
|
||||
}
|
||||
|
||||
public function testRemove() {
|
||||
$str1Node = new PHPParser_Node_Scalar_String('Foo');
|
||||
$str2Node = new PHPParser_Node_Scalar_String('Bar');
|
||||
|
||||
$visitor = $this->getMock('PHPParser_NodeVisitor');
|
||||
|
||||
// remove the string1 node, leave the string2 node
|
||||
$visitor->expects($this->at(2))->method('leaveNode')->with($str1Node)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$traverser = new PHPParser_NodeTraverser;
|
||||
$traverser->addVisitor($visitor);
|
||||
|
||||
$this->assertEquals(array($str2Node), $traverser->traverse(array($str1Node, $str2Node)));
|
||||
}
|
||||
|
||||
public function testMerge() {
|
||||
$strStart = new PHPParser_Node_Scalar_String('Start');
|
||||
$strMiddle = new PHPParser_Node_Scalar_String('End');
|
||||
$strEnd = new PHPParser_Node_Scalar_String('Middle');
|
||||
$strR1 = new PHPParser_Node_Scalar_String('Replacement 1');
|
||||
$strR2 = new PHPParser_Node_Scalar_String('Replacement 2');
|
||||
|
||||
$visitor = $this->getMock('PHPParser_NodeVisitor');
|
||||
|
||||
// replace strMiddle with strR1 and strR2 by merge
|
||||
$visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle)
|
||||
->will($this->returnValue(array($strR1, $strR2)));
|
||||
|
||||
$traverser = new PHPParser_NodeTraverser;
|
||||
$traverser->addVisitor($visitor);
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
new PHPParser_Node_Stmt_Echo(array(
|
||||
new PHPParser_Node_Scalar_String('Foo Bar')
|
||||
)),
|
||||
),
|
||||
$traverser->traverse($node)
|
||||
array($strStart, $strR1, $strR2, $strEnd),
|
||||
$traverser->traverse(array($strStart, $strMiddle, $strEnd))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PHPParser_Tests_NodeVisitor extends PHPParser_NodeVisitorAbstract
|
||||
{
|
||||
public $beforeTraverseNodes;
|
||||
public $enteredNodes;
|
||||
public $leftNodes;
|
||||
public $afterTraverseNodes;
|
||||
public function testDeepArray() {
|
||||
$strNode = new PHPParser_Node_Scalar_String('Foo');
|
||||
$stmts = array(array(array($strNode)));
|
||||
|
||||
public function __construct() {
|
||||
$this->enteredNodes = $this->leftNodes = array();
|
||||
}
|
||||
$visitor = $this->getMock('PHPParser_NodeVisitor');
|
||||
$visitor->expects($this->at(1))->method('enterNode')->with($strNode);
|
||||
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->beforeTraverseNodes = $nodes;
|
||||
}
|
||||
$traverser = new PHPParser_NodeTraverser;
|
||||
$traverser->addVisitor($visitor);
|
||||
|
||||
public function enterNode(PHPParser_Node $node) {
|
||||
$this->enteredNodes[] = $node->getType();
|
||||
}
|
||||
|
||||
public function leaveNode(PHPParser_Node $node) {
|
||||
$this->leftNodes[] = $node->getType();
|
||||
}
|
||||
|
||||
public function afterTraverse(array $nodes) {
|
||||
$this->afterTraverseNodes = $nodes;
|
||||
}
|
||||
}
|
||||
|
||||
class PHPParser_Tests_ModifyingNodeVisitor extends PHPParser_NodeVisitorAbstract
|
||||
{
|
||||
public function leaveNode(PHPParser_Node $node) {
|
||||
// delete namespace nodes by merging them
|
||||
if ($node instanceof PHPParser_Node_Stmt_Namespace) {
|
||||
return $node->stmts;
|
||||
// remove print nodes completely
|
||||
} elseif ($node instanceof PHPParser_Node_Expr_Print) {
|
||||
return false;
|
||||
// change string contents to 'Foo Bar'
|
||||
} elseif ($node instanceof PHPParser_Node_Scalar_String) {
|
||||
$node->value = 'Foo Bar';
|
||||
}
|
||||
$this->assertEquals($stmts, $traverser->traverse($stmts));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user