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