From 2b72bae3d9ad3eeef4f013de584cfd28963d7c50 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 3 Feb 2017 22:30:26 +0100 Subject: [PATCH] Throw if NodeVisitor returns invalid value --- lib/PhpParser/NodeTraverser.php | 90 +++++++++++++++++----------- test/PhpParser/NodeTraverserTest.php | 39 ++++++++++-- 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/lib/PhpParser/NodeTraverser.php b/lib/PhpParser/NodeTraverser.php index a37f3f6..3810be3 100644 --- a/lib/PhpParser/NodeTraverser.php +++ b/lib/PhpParser/NodeTraverser.php @@ -104,7 +104,7 @@ class NodeTraverser implements NodeTraverserInterface foreach ($node->getSubNodeNames() as $name) { $subNode =& $node->$name; - if (is_array($subNode)) { + if (\is_array($subNode)) { $subNode = $this->traverseArray($subNode); if ($this->stopTraversal) { break; @@ -113,13 +113,19 @@ class NodeTraverser implements NodeTraverserInterface $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; + if (null !== $return) { + if ($return instanceof Node) { + $subNode = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } } } @@ -132,17 +138,22 @@ class NodeTraverser implements NodeTraverserInterface foreach ($this->visitors as $visitor) { $return = $visitor->leaveNode($subNode); - if (self::STOP_TRAVERSAL === $return) { - $this->stopTraversal = true; - break 2; - } else if (null !== $return) { - if (is_array($return)) { + if (null !== $return) { + if ($return instanceof Node) { + $subNode = $return; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (\is_array($return)) { throw new \LogicException( 'leaveNode() may only return an array ' . 'if the parent structure is an array' ); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); } - $subNode = $return; } } } @@ -166,13 +177,19 @@ class NodeTraverser implements NodeTraverserInterface $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; + if (null !== $return) { + if ($return instanceof Node) { + $node = $return; + } elseif (self::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } } } @@ -185,21 +202,26 @@ class NodeTraverser implements NodeTraverserInterface foreach ($this->visitors as $visitor) { $return = $visitor->leaveNode($node); - - 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; - } elseif (null !== $return) { - $node = $return; + if (null !== $return) { + if ($return instanceof Node) { + $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = array($i, $return); + break; + } elseif (self::REMOVE_NODE === $return) { + $doNodes[] = array($i, array()); + break; + } else if (self::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } } } - } else if (is_array($node)) { + } else if (\is_array($node)) { throw new \LogicException('Invalid node structure: Contains nested arrays'); } } diff --git a/test/PhpParser/NodeTraverserTest.php b/test/PhpParser/NodeTraverserTest.php index de59134..0ca485d 100644 --- a/test/PhpParser/NodeTraverserTest.php +++ b/test/PhpParser/NodeTraverserTest.php @@ -250,17 +250,44 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException \LogicException - * @expectedExceptionMessage leaveNode() may only return an array if the parent structure is an array + * @dataProvider provideTestInvalidReturn */ - public function testReplaceByArrayOnlyAllowedIfParentIsArray() { - $stmts = array(new Node\Expr\UnaryMinus(new Node\Scalar\LNumber(42))); + public function testInvalidReturn($visitor, $message) { + $this->setExpectedException(\LogicException::class, $message); - $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); - $visitor->method('leaveNode')->willReturn(array(new Node\Scalar\DNumber(42.0))); + $stmts = array(new Node\Expr\UnaryMinus(new Node\Scalar\LNumber(42))); $traverser = new NodeTraverser(); $traverser->addVisitor($visitor); $traverser->traverse($stmts); } + + public function provideTestInvalidReturn() { + $visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); + $visitor1->expects($this->at(1))->method('enterNode') + ->will($this->returnValue('foobar')); + + $visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); + $visitor2->expects($this->at(2))->method('enterNode') + ->will($this->returnValue('foobar')); + + $visitor3 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); + $visitor3->expects($this->at(3))->method('leaveNode') + ->will($this->returnValue('foobar')); + + $visitor4 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); + $visitor4->expects($this->at(4))->method('leaveNode') + ->will($this->returnValue('foobar')); + + $visitor5 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); + $visitor5->method('leaveNode')->willReturn(array(new Node\Scalar\DNumber(42.0))); + + return [ + [$visitor1, 'enterNode() returned invalid value of type string'], + [$visitor2, 'enterNode() returned invalid value of type string'], + [$visitor3, 'leaveNode() returned invalid value of type string'], + [$visitor4, 'leaveNode() returned invalid value of type string'], + [$visitor5, 'leaveNode() may only return an array if the parent structure is an array'], + ]; + } }