diff --git a/lib/PHPParser/Node.php b/lib/PHPParser/Node.php index a0a42e0..9c9d817 100644 --- a/lib/PHPParser/Node.php +++ b/lib/PHPParser/Node.php @@ -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. * diff --git a/lib/PHPParser/NodeAbstract.php b/lib/PHPParser/NodeAbstract.php index 13011af..b7c911a 100644 --- a/lib/PHPParser/NodeAbstract.php +++ b/lib/PHPParser/NodeAbstract.php @@ -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. * diff --git a/lib/PHPParser/NodeTraverser.php b/lib/PHPParser/NodeTraverser.php index 8bfd5bd..e4fbbf0 100644 --- a/lib/PHPParser/NodeTraverser.php +++ b/lib/PHPParser/NodeTraverser.php @@ -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; } } } diff --git a/test/PHPParser/Tests/NodeAbstractTest.php b/test/PHPParser/Tests/NodeAbstractTest.php index efb1136..057b787 100644 --- a/test/PHPParser/Tests/NodeAbstractTest.php +++ b/test/PHPParser/Tests/NodeAbstractTest.php @@ -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)); } diff --git a/test/PHPParser/Tests/NodeTraverserTest.php b/test/PHPParser/Tests/NodeTraverserTest.php index 0a0358f..e072cb2 100644 --- a/test/PHPParser/Tests/NodeTraverserTest.php +++ b/test/PHPParser/Tests/NodeTraverserTest.php @@ -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)); } } \ No newline at end of file