Chris@13: getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: Chris@0: $visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts); Chris@0: $visitor->expects($this->at(1))->method('enterNode')->with($echoNode); Chris@0: $visitor->expects($this->at(2))->method('enterNode')->with($str1Node); Chris@0: $visitor->expects($this->at(3))->method('leaveNode')->with($str1Node); Chris@0: $visitor->expects($this->at(4))->method('enterNode')->with($str2Node); Chris@0: $visitor->expects($this->at(5))->method('leaveNode')->with($str2Node); Chris@0: $visitor->expects($this->at(6))->method('leaveNode')->with($echoNode); Chris@0: $visitor->expects($this->at(7))->method('afterTraverse')->with($stmts); Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: Chris@0: $this->assertEquals($stmts, $traverser->traverse($stmts)); Chris@0: } Chris@0: Chris@0: public function testModifying() { Chris@0: $str1Node = new String_('Foo'); Chris@0: $str2Node = new String_('Bar'); Chris@0: $printNode = new Expr\Print_($str1Node); Chris@0: Chris@0: // first visitor changes the node, second verifies the change Chris@13: $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: Chris@0: // replace empty statements with string1 node Chris@13: $visitor1->expects($this->at(0))->method('beforeTraverse')->with([]) Chris@17: ->willReturn([$str1Node]); Chris@13: $visitor2->expects($this->at(0))->method('beforeTraverse')->with([$str1Node]); Chris@0: Chris@0: // replace string1 node with print node Chris@0: $visitor1->expects($this->at(1))->method('enterNode')->with($str1Node) Chris@17: ->willReturn($printNode); Chris@0: $visitor2->expects($this->at(1))->method('enterNode')->with($printNode); Chris@0: Chris@0: // replace string1 node with string2 node Chris@0: $visitor1->expects($this->at(2))->method('enterNode')->with($str1Node) Chris@17: ->willReturn($str2Node); Chris@0: $visitor2->expects($this->at(2))->method('enterNode')->with($str2Node); Chris@0: Chris@0: // replace string2 node with string1 node again Chris@0: $visitor1->expects($this->at(3))->method('leaveNode')->with($str2Node) Chris@17: ->willReturn($str1Node); Chris@0: $visitor2->expects($this->at(3))->method('leaveNode')->with($str1Node); Chris@0: Chris@0: // replace print node with string1 node again Chris@0: $visitor1->expects($this->at(4))->method('leaveNode')->with($printNode) Chris@17: ->willReturn($str1Node); Chris@0: $visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node); Chris@0: Chris@0: // replace string1 node with empty statements again Chris@13: $visitor1->expects($this->at(5))->method('afterTraverse')->with([$str1Node]) Chris@17: ->willReturn([]); Chris@13: $visitor2->expects($this->at(5))->method('afterTraverse')->with([]); Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor1); Chris@0: $traverser->addVisitor($visitor2); Chris@0: Chris@0: // as all operations are reversed we end where we start Chris@13: $this->assertEquals([], $traverser->traverse([])); Chris@0: } Chris@0: Chris@0: public function testRemove() { Chris@0: $str1Node = new String_('Foo'); Chris@0: $str2Node = new String_('Bar'); Chris@0: Chris@13: $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: Chris@0: // remove the string1 node, leave the string2 node Chris@0: $visitor->expects($this->at(2))->method('leaveNode')->with($str1Node) Chris@17: ->willReturn(NodeTraverser::REMOVE_NODE); Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: Chris@13: $this->assertEquals([$str2Node], $traverser->traverse([$str1Node, $str2Node])); Chris@0: } Chris@0: Chris@0: public function testMerge() { Chris@0: $strStart = new String_('Start'); Chris@0: $strMiddle = new String_('End'); Chris@0: $strEnd = new String_('Middle'); Chris@0: $strR1 = new String_('Replacement 1'); Chris@0: $strR2 = new String_('Replacement 2'); Chris@0: Chris@13: $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: Chris@0: // replace strMiddle with strR1 and strR2 by merge Chris@0: $visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle) Chris@17: ->willReturn([$strR1, $strR2]); Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: Chris@0: $this->assertEquals( Chris@13: [$strStart, $strR1, $strR2, $strEnd], Chris@13: $traverser->traverse([$strStart, $strMiddle, $strEnd]) Chris@0: ); Chris@0: } Chris@0: Chris@13: public function testInvalidDeepArray() { Chris@17: $this->expectException(\LogicException::class); Chris@17: $this->expectExceptionMessage('Invalid node structure: Contains nested arrays'); Chris@0: $strNode = new String_('Foo'); Chris@13: $stmts = [[[$strNode]]]; Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: $this->assertEquals($stmts, $traverser->traverse($stmts)); Chris@0: } Chris@0: Chris@0: public function testDontTraverseChildren() { Chris@0: $strNode = new String_('str'); Chris@0: $printNode = new Expr\Print_($strNode); Chris@0: $varNode = new Expr\Variable('foo'); Chris@0: $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode); Chris@0: $negNode = new Expr\UnaryMinus($mulNode); Chris@13: $stmts = [$printNode, $negNode]; Chris@0: Chris@13: $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: Chris@0: $visitor1->expects($this->at(1))->method('enterNode')->with($printNode) Chris@17: ->willReturn(NodeTraverser::DONT_TRAVERSE_CHILDREN); Chris@0: $visitor2->expects($this->at(1))->method('enterNode')->with($printNode); Chris@0: Chris@0: $visitor1->expects($this->at(2))->method('leaveNode')->with($printNode); Chris@0: $visitor2->expects($this->at(2))->method('leaveNode')->with($printNode); Chris@0: Chris@0: $visitor1->expects($this->at(3))->method('enterNode')->with($negNode); Chris@0: $visitor2->expects($this->at(3))->method('enterNode')->with($negNode); Chris@0: Chris@0: $visitor1->expects($this->at(4))->method('enterNode')->with($mulNode); Chris@0: $visitor2->expects($this->at(4))->method('enterNode')->with($mulNode) Chris@17: ->willReturn(NodeTraverser::DONT_TRAVERSE_CHILDREN); Chris@0: Chris@0: $visitor1->expects($this->at(5))->method('leaveNode')->with($mulNode); Chris@0: $visitor2->expects($this->at(5))->method('leaveNode')->with($mulNode); Chris@0: Chris@0: $visitor1->expects($this->at(6))->method('leaveNode')->with($negNode); Chris@0: $visitor2->expects($this->at(6))->method('leaveNode')->with($negNode); Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor1); Chris@0: $traverser->addVisitor($visitor2); Chris@0: Chris@0: $this->assertEquals($stmts, $traverser->traverse($stmts)); Chris@0: } Chris@0: Chris@17: public function testDontTraverseCurrentAndChildren() { Chris@17: // print 'str'; -($foo * $foo); Chris@17: $strNode = new String_('str'); Chris@17: $printNode = new Expr\Print_($strNode); Chris@17: $varNode = new Expr\Variable('foo'); Chris@17: $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode); Chris@17: $divNode = new Expr\BinaryOp\Div($varNode, $varNode); Chris@17: $negNode = new Expr\UnaryMinus($mulNode); Chris@17: $stmts = [$printNode, $negNode]; Chris@17: Chris@17: $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@17: $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@17: Chris@17: $visitor1->expects($this->at(1))->method('enterNode')->with($printNode) Chris@17: ->willReturn(NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN); Chris@17: $visitor1->expects($this->at(2))->method('leaveNode')->with($printNode); Chris@17: Chris@17: $visitor1->expects($this->at(3))->method('enterNode')->with($negNode); Chris@17: $visitor2->expects($this->at(1))->method('enterNode')->with($negNode); Chris@17: Chris@17: $visitor1->expects($this->at(4))->method('enterNode')->with($mulNode) Chris@17: ->willReturn(NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN); Chris@17: $visitor1->expects($this->at(5))->method('leaveNode')->with($mulNode)->willReturn($divNode); Chris@17: Chris@17: $visitor1->expects($this->at(6))->method('leaveNode')->with($negNode); Chris@17: $visitor2->expects($this->at(2))->method('leaveNode')->with($negNode); Chris@17: Chris@17: $traverser = new NodeTraverser; Chris@17: $traverser->addVisitor($visitor1); Chris@17: $traverser->addVisitor($visitor2); Chris@17: Chris@17: $resultStmts = $traverser->traverse($stmts); Chris@17: Chris@17: $this->assertInstanceOf(Expr\BinaryOp\Div::class, $resultStmts[1]->expr); Chris@17: } Chris@17: Chris@0: public function testStopTraversal() { Chris@0: $varNode1 = new Expr\Variable('a'); Chris@0: $varNode2 = new Expr\Variable('b'); Chris@0: $varNode3 = new Expr\Variable('c'); Chris@0: $mulNode = new Expr\BinaryOp\Mul($varNode1, $varNode2); Chris@0: $printNode = new Expr\Print_($varNode3); Chris@0: $stmts = [$mulNode, $printNode]; Chris@0: Chris@0: // From enterNode() with array parent Chris@13: $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: $visitor->expects($this->at(1))->method('enterNode')->with($mulNode) Chris@17: ->willReturn(NodeTraverser::STOP_TRAVERSAL); Chris@0: $visitor->expects($this->at(2))->method('afterTraverse'); Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: $this->assertEquals($stmts, $traverser->traverse($stmts)); Chris@0: Chris@0: // From enterNode with Node parent Chris@13: $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: $visitor->expects($this->at(2))->method('enterNode')->with($varNode1) Chris@17: ->willReturn(NodeTraverser::STOP_TRAVERSAL); Chris@0: $visitor->expects($this->at(3))->method('afterTraverse'); Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: $this->assertEquals($stmts, $traverser->traverse($stmts)); Chris@0: Chris@0: // From leaveNode with Node parent Chris@13: $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: $visitor->expects($this->at(3))->method('leaveNode')->with($varNode1) Chris@17: ->willReturn(NodeTraverser::STOP_TRAVERSAL); Chris@0: $visitor->expects($this->at(4))->method('afterTraverse'); Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: $this->assertEquals($stmts, $traverser->traverse($stmts)); Chris@0: Chris@0: // From leaveNode with array parent Chris@13: $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode) Chris@17: ->willReturn(NodeTraverser::STOP_TRAVERSAL); Chris@0: $visitor->expects($this->at(7))->method('afterTraverse'); Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: $this->assertEquals($stmts, $traverser->traverse($stmts)); Chris@0: Chris@0: // Check that pending array modifications are still carried out Chris@13: $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode) Chris@17: ->willReturn(NodeTraverser::REMOVE_NODE); Chris@0: $visitor->expects($this->at(7))->method('enterNode')->with($printNode) Chris@17: ->willReturn(NodeTraverser::STOP_TRAVERSAL); Chris@0: $visitor->expects($this->at(8))->method('afterTraverse'); Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor); Chris@0: $this->assertEquals([$printNode], $traverser->traverse($stmts)); Chris@0: Chris@0: } Chris@0: Chris@0: public function testRemovingVisitor() { Chris@13: $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: $traverser->addVisitor($visitor1); Chris@0: $traverser->addVisitor($visitor2); Chris@0: $traverser->addVisitor($visitor3); Chris@0: Chris@13: $preExpected = [$visitor1, $visitor2, $visitor3]; Chris@0: $this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added'); Chris@0: Chris@0: $traverser->removeVisitor($visitor2); Chris@0: Chris@13: $postExpected = [0 => $visitor1, 2 => $visitor3]; Chris@0: $this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal'); Chris@0: } Chris@0: Chris@0: public function testNoCloneNodes() { Chris@13: $stmts = [new Node\Stmt\Echo_([new String_('Foo'), new String_('Bar')])]; Chris@0: Chris@0: $traverser = new NodeTraverser; Chris@0: Chris@0: $this->assertSame($stmts, $traverser->traverse($stmts)); Chris@0: } Chris@0: Chris@0: /** Chris@13: * @dataProvider provideTestInvalidReturn Chris@0: */ Chris@13: public function testInvalidReturn($visitor, $message) { Chris@13: $this->expectException(\LogicException::class); Chris@13: $this->expectExceptionMessage($message); Chris@0: Chris@13: $stmts = [new Node\Stmt\Expression(new Node\Scalar\LNumber(42))]; Chris@0: Chris@0: $traverser = new NodeTraverser(); Chris@0: $traverser->addVisitor($visitor); Chris@0: $traverser->traverse($stmts); Chris@0: } Chris@13: Chris@13: public function provideTestInvalidReturn() { Chris@13: $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor1->expects($this->at(1))->method('enterNode') Chris@13: ->willReturn('foobar'); Chris@13: Chris@13: $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor2->expects($this->at(2))->method('enterNode') Chris@13: ->willReturn('foobar'); Chris@13: Chris@13: $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor3->expects($this->at(3))->method('leaveNode') Chris@13: ->willReturn('foobar'); Chris@13: Chris@13: $visitor4 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor4->expects($this->at(4))->method('leaveNode') Chris@13: ->willReturn('foobar'); Chris@13: Chris@13: $visitor5 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor5->expects($this->at(3))->method('leaveNode') Chris@13: ->willReturn([new Node\Scalar\DNumber(42.0)]); Chris@13: Chris@13: $visitor6 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor6->expects($this->at(4))->method('leaveNode') Chris@13: ->willReturn(false); Chris@13: Chris@13: $visitor7 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor7->expects($this->at(1))->method('enterNode') Chris@13: ->willReturn(new Node\Scalar\LNumber(42)); Chris@13: Chris@13: $visitor8 = $this->getMockBuilder(NodeVisitor::class)->getMock(); Chris@13: $visitor8->expects($this->at(2))->method('enterNode') Chris@13: ->willReturn(new Node\Stmt\Return_()); Chris@13: Chris@13: return [ Chris@13: [$visitor1, 'enterNode() returned invalid value of type string'], Chris@13: [$visitor2, 'enterNode() returned invalid value of type string'], Chris@13: [$visitor3, 'leaveNode() returned invalid value of type string'], Chris@13: [$visitor4, 'leaveNode() returned invalid value of type string'], Chris@13: [$visitor5, 'leaveNode() may only return an array if the parent structure is an array'], Chris@13: [$visitor6, 'bool(false) return from leaveNode() no longer supported. Return NodeTraverser::REMOVE_NODE instead'], Chris@13: [$visitor7, 'Trying to replace statement (Stmt_Expression) with expression (Scalar_LNumber). Are you missing a Stmt_Expression wrapper?'], Chris@13: [$visitor8, 'Trying to replace expression (Scalar_LNumber) with statement (Stmt_Return)'], Chris@13: ]; Chris@13: } Chris@0: }