annotate vendor/nikic/php-parser/test/PhpParser/NodeTraverserTest.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@13 1 <?php declare(strict_types=1);
Chris@0 2
Chris@0 3 namespace PhpParser;
Chris@0 4
Chris@0 5 use PhpParser\Node\Expr;
Chris@0 6 use PhpParser\Node\Scalar\String_;
Chris@0 7
Chris@17 8 class NodeTraverserTest extends \PHPUnit\Framework\TestCase
Chris@0 9 {
Chris@0 10 public function testNonModifying() {
Chris@0 11 $str1Node = new String_('Foo');
Chris@0 12 $str2Node = new String_('Bar');
Chris@13 13 $echoNode = new Node\Stmt\Echo_([$str1Node, $str2Node]);
Chris@13 14 $stmts = [$echoNode];
Chris@0 15
Chris@13 16 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 17
Chris@0 18 $visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts);
Chris@0 19 $visitor->expects($this->at(1))->method('enterNode')->with($echoNode);
Chris@0 20 $visitor->expects($this->at(2))->method('enterNode')->with($str1Node);
Chris@0 21 $visitor->expects($this->at(3))->method('leaveNode')->with($str1Node);
Chris@0 22 $visitor->expects($this->at(4))->method('enterNode')->with($str2Node);
Chris@0 23 $visitor->expects($this->at(5))->method('leaveNode')->with($str2Node);
Chris@0 24 $visitor->expects($this->at(6))->method('leaveNode')->with($echoNode);
Chris@0 25 $visitor->expects($this->at(7))->method('afterTraverse')->with($stmts);
Chris@0 26
Chris@0 27 $traverser = new NodeTraverser;
Chris@0 28 $traverser->addVisitor($visitor);
Chris@0 29
Chris@0 30 $this->assertEquals($stmts, $traverser->traverse($stmts));
Chris@0 31 }
Chris@0 32
Chris@0 33 public function testModifying() {
Chris@0 34 $str1Node = new String_('Foo');
Chris@0 35 $str2Node = new String_('Bar');
Chris@0 36 $printNode = new Expr\Print_($str1Node);
Chris@0 37
Chris@0 38 // first visitor changes the node, second verifies the change
Chris@13 39 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 40 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 41
Chris@0 42 // replace empty statements with string1 node
Chris@13 43 $visitor1->expects($this->at(0))->method('beforeTraverse')->with([])
Chris@17 44 ->willReturn([$str1Node]);
Chris@13 45 $visitor2->expects($this->at(0))->method('beforeTraverse')->with([$str1Node]);
Chris@0 46
Chris@0 47 // replace string1 node with print node
Chris@0 48 $visitor1->expects($this->at(1))->method('enterNode')->with($str1Node)
Chris@17 49 ->willReturn($printNode);
Chris@0 50 $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
Chris@0 51
Chris@0 52 // replace string1 node with string2 node
Chris@0 53 $visitor1->expects($this->at(2))->method('enterNode')->with($str1Node)
Chris@17 54 ->willReturn($str2Node);
Chris@0 55 $visitor2->expects($this->at(2))->method('enterNode')->with($str2Node);
Chris@0 56
Chris@0 57 // replace string2 node with string1 node again
Chris@0 58 $visitor1->expects($this->at(3))->method('leaveNode')->with($str2Node)
Chris@17 59 ->willReturn($str1Node);
Chris@0 60 $visitor2->expects($this->at(3))->method('leaveNode')->with($str1Node);
Chris@0 61
Chris@0 62 // replace print node with string1 node again
Chris@0 63 $visitor1->expects($this->at(4))->method('leaveNode')->with($printNode)
Chris@17 64 ->willReturn($str1Node);
Chris@0 65 $visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node);
Chris@0 66
Chris@0 67 // replace string1 node with empty statements again
Chris@13 68 $visitor1->expects($this->at(5))->method('afterTraverse')->with([$str1Node])
Chris@17 69 ->willReturn([]);
Chris@13 70 $visitor2->expects($this->at(5))->method('afterTraverse')->with([]);
Chris@0 71
Chris@0 72 $traverser = new NodeTraverser;
Chris@0 73 $traverser->addVisitor($visitor1);
Chris@0 74 $traverser->addVisitor($visitor2);
Chris@0 75
Chris@0 76 // as all operations are reversed we end where we start
Chris@13 77 $this->assertEquals([], $traverser->traverse([]));
Chris@0 78 }
Chris@0 79
Chris@0 80 public function testRemove() {
Chris@0 81 $str1Node = new String_('Foo');
Chris@0 82 $str2Node = new String_('Bar');
Chris@0 83
Chris@13 84 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 85
Chris@0 86 // remove the string1 node, leave the string2 node
Chris@0 87 $visitor->expects($this->at(2))->method('leaveNode')->with($str1Node)
Chris@17 88 ->willReturn(NodeTraverser::REMOVE_NODE);
Chris@0 89
Chris@0 90 $traverser = new NodeTraverser;
Chris@0 91 $traverser->addVisitor($visitor);
Chris@0 92
Chris@13 93 $this->assertEquals([$str2Node], $traverser->traverse([$str1Node, $str2Node]));
Chris@0 94 }
Chris@0 95
Chris@0 96 public function testMerge() {
Chris@0 97 $strStart = new String_('Start');
Chris@0 98 $strMiddle = new String_('End');
Chris@0 99 $strEnd = new String_('Middle');
Chris@0 100 $strR1 = new String_('Replacement 1');
Chris@0 101 $strR2 = new String_('Replacement 2');
Chris@0 102
Chris@13 103 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 104
Chris@0 105 // replace strMiddle with strR1 and strR2 by merge
Chris@0 106 $visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle)
Chris@17 107 ->willReturn([$strR1, $strR2]);
Chris@0 108
Chris@0 109 $traverser = new NodeTraverser;
Chris@0 110 $traverser->addVisitor($visitor);
Chris@0 111
Chris@0 112 $this->assertEquals(
Chris@13 113 [$strStart, $strR1, $strR2, $strEnd],
Chris@13 114 $traverser->traverse([$strStart, $strMiddle, $strEnd])
Chris@0 115 );
Chris@0 116 }
Chris@0 117
Chris@13 118 public function testInvalidDeepArray() {
Chris@17 119 $this->expectException(\LogicException::class);
Chris@17 120 $this->expectExceptionMessage('Invalid node structure: Contains nested arrays');
Chris@0 121 $strNode = new String_('Foo');
Chris@13 122 $stmts = [[[$strNode]]];
Chris@0 123
Chris@0 124 $traverser = new NodeTraverser;
Chris@0 125 $this->assertEquals($stmts, $traverser->traverse($stmts));
Chris@0 126 }
Chris@0 127
Chris@0 128 public function testDontTraverseChildren() {
Chris@0 129 $strNode = new String_('str');
Chris@0 130 $printNode = new Expr\Print_($strNode);
Chris@0 131 $varNode = new Expr\Variable('foo');
Chris@0 132 $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode);
Chris@0 133 $negNode = new Expr\UnaryMinus($mulNode);
Chris@13 134 $stmts = [$printNode, $negNode];
Chris@0 135
Chris@13 136 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 137 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 138
Chris@0 139 $visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
Chris@17 140 ->willReturn(NodeTraverser::DONT_TRAVERSE_CHILDREN);
Chris@0 141 $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
Chris@0 142
Chris@0 143 $visitor1->expects($this->at(2))->method('leaveNode')->with($printNode);
Chris@0 144 $visitor2->expects($this->at(2))->method('leaveNode')->with($printNode);
Chris@0 145
Chris@0 146 $visitor1->expects($this->at(3))->method('enterNode')->with($negNode);
Chris@0 147 $visitor2->expects($this->at(3))->method('enterNode')->with($negNode);
Chris@0 148
Chris@0 149 $visitor1->expects($this->at(4))->method('enterNode')->with($mulNode);
Chris@0 150 $visitor2->expects($this->at(4))->method('enterNode')->with($mulNode)
Chris@17 151 ->willReturn(NodeTraverser::DONT_TRAVERSE_CHILDREN);
Chris@0 152
Chris@0 153 $visitor1->expects($this->at(5))->method('leaveNode')->with($mulNode);
Chris@0 154 $visitor2->expects($this->at(5))->method('leaveNode')->with($mulNode);
Chris@0 155
Chris@0 156 $visitor1->expects($this->at(6))->method('leaveNode')->with($negNode);
Chris@0 157 $visitor2->expects($this->at(6))->method('leaveNode')->with($negNode);
Chris@0 158
Chris@0 159 $traverser = new NodeTraverser;
Chris@0 160 $traverser->addVisitor($visitor1);
Chris@0 161 $traverser->addVisitor($visitor2);
Chris@0 162
Chris@0 163 $this->assertEquals($stmts, $traverser->traverse($stmts));
Chris@0 164 }
Chris@0 165
Chris@17 166 public function testDontTraverseCurrentAndChildren() {
Chris@17 167 // print 'str'; -($foo * $foo);
Chris@17 168 $strNode = new String_('str');
Chris@17 169 $printNode = new Expr\Print_($strNode);
Chris@17 170 $varNode = new Expr\Variable('foo');
Chris@17 171 $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode);
Chris@17 172 $divNode = new Expr\BinaryOp\Div($varNode, $varNode);
Chris@17 173 $negNode = new Expr\UnaryMinus($mulNode);
Chris@17 174 $stmts = [$printNode, $negNode];
Chris@17 175
Chris@17 176 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@17 177 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@17 178
Chris@17 179 $visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
Chris@17 180 ->willReturn(NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN);
Chris@17 181 $visitor1->expects($this->at(2))->method('leaveNode')->with($printNode);
Chris@17 182
Chris@17 183 $visitor1->expects($this->at(3))->method('enterNode')->with($negNode);
Chris@17 184 $visitor2->expects($this->at(1))->method('enterNode')->with($negNode);
Chris@17 185
Chris@17 186 $visitor1->expects($this->at(4))->method('enterNode')->with($mulNode)
Chris@17 187 ->willReturn(NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN);
Chris@17 188 $visitor1->expects($this->at(5))->method('leaveNode')->with($mulNode)->willReturn($divNode);
Chris@17 189
Chris@17 190 $visitor1->expects($this->at(6))->method('leaveNode')->with($negNode);
Chris@17 191 $visitor2->expects($this->at(2))->method('leaveNode')->with($negNode);
Chris@17 192
Chris@17 193 $traverser = new NodeTraverser;
Chris@17 194 $traverser->addVisitor($visitor1);
Chris@17 195 $traverser->addVisitor($visitor2);
Chris@17 196
Chris@17 197 $resultStmts = $traverser->traverse($stmts);
Chris@17 198
Chris@17 199 $this->assertInstanceOf(Expr\BinaryOp\Div::class, $resultStmts[1]->expr);
Chris@17 200 }
Chris@17 201
Chris@0 202 public function testStopTraversal() {
Chris@0 203 $varNode1 = new Expr\Variable('a');
Chris@0 204 $varNode2 = new Expr\Variable('b');
Chris@0 205 $varNode3 = new Expr\Variable('c');
Chris@0 206 $mulNode = new Expr\BinaryOp\Mul($varNode1, $varNode2);
Chris@0 207 $printNode = new Expr\Print_($varNode3);
Chris@0 208 $stmts = [$mulNode, $printNode];
Chris@0 209
Chris@0 210 // From enterNode() with array parent
Chris@13 211 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 212 $visitor->expects($this->at(1))->method('enterNode')->with($mulNode)
Chris@17 213 ->willReturn(NodeTraverser::STOP_TRAVERSAL);
Chris@0 214 $visitor->expects($this->at(2))->method('afterTraverse');
Chris@0 215 $traverser = new NodeTraverser;
Chris@0 216 $traverser->addVisitor($visitor);
Chris@0 217 $this->assertEquals($stmts, $traverser->traverse($stmts));
Chris@0 218
Chris@0 219 // From enterNode with Node parent
Chris@13 220 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 221 $visitor->expects($this->at(2))->method('enterNode')->with($varNode1)
Chris@17 222 ->willReturn(NodeTraverser::STOP_TRAVERSAL);
Chris@0 223 $visitor->expects($this->at(3))->method('afterTraverse');
Chris@0 224 $traverser = new NodeTraverser;
Chris@0 225 $traverser->addVisitor($visitor);
Chris@0 226 $this->assertEquals($stmts, $traverser->traverse($stmts));
Chris@0 227
Chris@0 228 // From leaveNode with Node parent
Chris@13 229 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 230 $visitor->expects($this->at(3))->method('leaveNode')->with($varNode1)
Chris@17 231 ->willReturn(NodeTraverser::STOP_TRAVERSAL);
Chris@0 232 $visitor->expects($this->at(4))->method('afterTraverse');
Chris@0 233 $traverser = new NodeTraverser;
Chris@0 234 $traverser->addVisitor($visitor);
Chris@0 235 $this->assertEquals($stmts, $traverser->traverse($stmts));
Chris@0 236
Chris@0 237 // From leaveNode with array parent
Chris@13 238 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 239 $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
Chris@17 240 ->willReturn(NodeTraverser::STOP_TRAVERSAL);
Chris@0 241 $visitor->expects($this->at(7))->method('afterTraverse');
Chris@0 242 $traverser = new NodeTraverser;
Chris@0 243 $traverser->addVisitor($visitor);
Chris@0 244 $this->assertEquals($stmts, $traverser->traverse($stmts));
Chris@0 245
Chris@0 246 // Check that pending array modifications are still carried out
Chris@13 247 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 248 $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
Chris@17 249 ->willReturn(NodeTraverser::REMOVE_NODE);
Chris@0 250 $visitor->expects($this->at(7))->method('enterNode')->with($printNode)
Chris@17 251 ->willReturn(NodeTraverser::STOP_TRAVERSAL);
Chris@0 252 $visitor->expects($this->at(8))->method('afterTraverse');
Chris@0 253 $traverser = new NodeTraverser;
Chris@0 254 $traverser->addVisitor($visitor);
Chris@0 255 $this->assertEquals([$printNode], $traverser->traverse($stmts));
Chris@0 256
Chris@0 257 }
Chris@0 258
Chris@0 259 public function testRemovingVisitor() {
Chris@13 260 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 261 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 262 $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@0 263
Chris@0 264 $traverser = new NodeTraverser;
Chris@0 265 $traverser->addVisitor($visitor1);
Chris@0 266 $traverser->addVisitor($visitor2);
Chris@0 267 $traverser->addVisitor($visitor3);
Chris@0 268
Chris@13 269 $preExpected = [$visitor1, $visitor2, $visitor3];
Chris@0 270 $this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added');
Chris@0 271
Chris@0 272 $traverser->removeVisitor($visitor2);
Chris@0 273
Chris@13 274 $postExpected = [0 => $visitor1, 2 => $visitor3];
Chris@0 275 $this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal');
Chris@0 276 }
Chris@0 277
Chris@0 278 public function testNoCloneNodes() {
Chris@13 279 $stmts = [new Node\Stmt\Echo_([new String_('Foo'), new String_('Bar')])];
Chris@0 280
Chris@0 281 $traverser = new NodeTraverser;
Chris@0 282
Chris@0 283 $this->assertSame($stmts, $traverser->traverse($stmts));
Chris@0 284 }
Chris@0 285
Chris@0 286 /**
Chris@13 287 * @dataProvider provideTestInvalidReturn
Chris@0 288 */
Chris@13 289 public function testInvalidReturn($visitor, $message) {
Chris@13 290 $this->expectException(\LogicException::class);
Chris@13 291 $this->expectExceptionMessage($message);
Chris@0 292
Chris@13 293 $stmts = [new Node\Stmt\Expression(new Node\Scalar\LNumber(42))];
Chris@0 294
Chris@0 295 $traverser = new NodeTraverser();
Chris@0 296 $traverser->addVisitor($visitor);
Chris@0 297 $traverser->traverse($stmts);
Chris@0 298 }
Chris@13 299
Chris@13 300 public function provideTestInvalidReturn() {
Chris@13 301 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 302 $visitor1->expects($this->at(1))->method('enterNode')
Chris@13 303 ->willReturn('foobar');
Chris@13 304
Chris@13 305 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 306 $visitor2->expects($this->at(2))->method('enterNode')
Chris@13 307 ->willReturn('foobar');
Chris@13 308
Chris@13 309 $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 310 $visitor3->expects($this->at(3))->method('leaveNode')
Chris@13 311 ->willReturn('foobar');
Chris@13 312
Chris@13 313 $visitor4 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 314 $visitor4->expects($this->at(4))->method('leaveNode')
Chris@13 315 ->willReturn('foobar');
Chris@13 316
Chris@13 317 $visitor5 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 318 $visitor5->expects($this->at(3))->method('leaveNode')
Chris@13 319 ->willReturn([new Node\Scalar\DNumber(42.0)]);
Chris@13 320
Chris@13 321 $visitor6 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 322 $visitor6->expects($this->at(4))->method('leaveNode')
Chris@13 323 ->willReturn(false);
Chris@13 324
Chris@13 325 $visitor7 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 326 $visitor7->expects($this->at(1))->method('enterNode')
Chris@13 327 ->willReturn(new Node\Scalar\LNumber(42));
Chris@13 328
Chris@13 329 $visitor8 = $this->getMockBuilder(NodeVisitor::class)->getMock();
Chris@13 330 $visitor8->expects($this->at(2))->method('enterNode')
Chris@13 331 ->willReturn(new Node\Stmt\Return_());
Chris@13 332
Chris@13 333 return [
Chris@13 334 [$visitor1, 'enterNode() returned invalid value of type string'],
Chris@13 335 [$visitor2, 'enterNode() returned invalid value of type string'],
Chris@13 336 [$visitor3, 'leaveNode() returned invalid value of type string'],
Chris@13 337 [$visitor4, 'leaveNode() returned invalid value of type string'],
Chris@13 338 [$visitor5, 'leaveNode() may only return an array if the parent structure is an array'],
Chris@13 339 [$visitor6, 'bool(false) return from leaveNode() no longer supported. Return NodeTraverser::REMOVE_NODE instead'],
Chris@13 340 [$visitor7, 'Trying to replace statement (Stmt_Expression) with expression (Scalar_LNumber). Are you missing a Stmt_Expression wrapper?'],
Chris@13 341 [$visitor8, 'Trying to replace expression (Scalar_LNumber) with statement (Stmt_Return)'],
Chris@13 342 ];
Chris@13 343 }
Chris@0 344 }