Removed modules/contrib/media module to allow update to the core media module
[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     /**
121      * @expectedException \LogicException
122      * @expectedExceptionMessage Invalid node structure: Contains nested arrays
123      */
124     public function testInvalidDeepArray() {
125         $strNode = new String_('Foo');
126         $stmts = [[[$strNode]]];
127
128         $traverser = new NodeTraverser;
129         $this->assertEquals($stmts, $traverser->traverse($stmts));
130     }
131
132     public function testDontTraverseChildren() {
133         $strNode = new String_('str');
134         $printNode = new Expr\Print_($strNode);
135         $varNode = new Expr\Variable('foo');
136         $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode);
137         $negNode = new Expr\UnaryMinus($mulNode);
138         $stmts = [$printNode, $negNode];
139
140         $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
141         $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
142
143         $visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
144             ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
145         $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
146
147         $visitor1->expects($this->at(2))->method('leaveNode')->with($printNode);
148         $visitor2->expects($this->at(2))->method('leaveNode')->with($printNode);
149
150         $visitor1->expects($this->at(3))->method('enterNode')->with($negNode);
151         $visitor2->expects($this->at(3))->method('enterNode')->with($negNode);
152
153         $visitor1->expects($this->at(4))->method('enterNode')->with($mulNode);
154         $visitor2->expects($this->at(4))->method('enterNode')->with($mulNode)
155             ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
156
157         $visitor1->expects($this->at(5))->method('leaveNode')->with($mulNode);
158         $visitor2->expects($this->at(5))->method('leaveNode')->with($mulNode);
159
160         $visitor1->expects($this->at(6))->method('leaveNode')->with($negNode);
161         $visitor2->expects($this->at(6))->method('leaveNode')->with($negNode);
162
163         $traverser = new NodeTraverser;
164         $traverser->addVisitor($visitor1);
165         $traverser->addVisitor($visitor2);
166
167         $this->assertEquals($stmts, $traverser->traverse($stmts));
168     }
169
170     public function testStopTraversal() {
171         $varNode1 = new Expr\Variable('a');
172         $varNode2 = new Expr\Variable('b');
173         $varNode3 = new Expr\Variable('c');
174         $mulNode = new Expr\BinaryOp\Mul($varNode1, $varNode2);
175         $printNode = new Expr\Print_($varNode3);
176         $stmts = [$mulNode, $printNode];
177
178         // From enterNode() with array parent
179         $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
180         $visitor->expects($this->at(1))->method('enterNode')->with($mulNode)
181             ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
182         $visitor->expects($this->at(2))->method('afterTraverse');
183         $traverser = new NodeTraverser;
184         $traverser->addVisitor($visitor);
185         $this->assertEquals($stmts, $traverser->traverse($stmts));
186
187         // From enterNode with Node parent
188         $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
189         $visitor->expects($this->at(2))->method('enterNode')->with($varNode1)
190             ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
191         $visitor->expects($this->at(3))->method('afterTraverse');
192         $traverser = new NodeTraverser;
193         $traverser->addVisitor($visitor);
194         $this->assertEquals($stmts, $traverser->traverse($stmts));
195
196         // From leaveNode with Node parent
197         $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
198         $visitor->expects($this->at(3))->method('leaveNode')->with($varNode1)
199             ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
200         $visitor->expects($this->at(4))->method('afterTraverse');
201         $traverser = new NodeTraverser;
202         $traverser->addVisitor($visitor);
203         $this->assertEquals($stmts, $traverser->traverse($stmts));
204
205         // From leaveNode with array parent
206         $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
207         $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
208             ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
209         $visitor->expects($this->at(7))->method('afterTraverse');
210         $traverser = new NodeTraverser;
211         $traverser->addVisitor($visitor);
212         $this->assertEquals($stmts, $traverser->traverse($stmts));
213
214         // Check that pending array modifications are still carried out
215         $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
216         $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
217             ->will($this->returnValue(NodeTraverser::REMOVE_NODE));
218         $visitor->expects($this->at(7))->method('enterNode')->with($printNode)
219             ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
220         $visitor->expects($this->at(8))->method('afterTraverse');
221         $traverser = new NodeTraverser;
222         $traverser->addVisitor($visitor);
223         $this->assertEquals([$printNode], $traverser->traverse($stmts));
224
225     }
226
227     public function testRemovingVisitor() {
228         $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
229         $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
230         $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
231
232         $traverser = new NodeTraverser;
233         $traverser->addVisitor($visitor1);
234         $traverser->addVisitor($visitor2);
235         $traverser->addVisitor($visitor3);
236
237         $preExpected = [$visitor1, $visitor2, $visitor3];
238         $this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added');
239
240         $traverser->removeVisitor($visitor2);
241
242         $postExpected = [0 => $visitor1, 2 => $visitor3];
243         $this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal');
244     }
245
246     public function testNoCloneNodes() {
247         $stmts = [new Node\Stmt\Echo_([new String_('Foo'), new String_('Bar')])];
248
249         $traverser = new NodeTraverser;
250
251         $this->assertSame($stmts, $traverser->traverse($stmts));
252     }
253
254     /**
255      * @dataProvider provideTestInvalidReturn
256      */
257     public function testInvalidReturn($visitor, $message) {
258         $this->expectException(\LogicException::class);
259         $this->expectExceptionMessage($message);
260
261         $stmts = [new Node\Stmt\Expression(new Node\Scalar\LNumber(42))];
262
263         $traverser = new NodeTraverser();
264         $traverser->addVisitor($visitor);
265         $traverser->traverse($stmts);
266     }
267
268     public function provideTestInvalidReturn() {
269         $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
270         $visitor1->expects($this->at(1))->method('enterNode')
271             ->willReturn('foobar');
272
273         $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
274         $visitor2->expects($this->at(2))->method('enterNode')
275             ->willReturn('foobar');
276
277         $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
278         $visitor3->expects($this->at(3))->method('leaveNode')
279             ->willReturn('foobar');
280
281         $visitor4 = $this->getMockBuilder(NodeVisitor::class)->getMock();
282         $visitor4->expects($this->at(4))->method('leaveNode')
283             ->willReturn('foobar');
284
285         $visitor5 = $this->getMockBuilder(NodeVisitor::class)->getMock();
286         $visitor5->expects($this->at(3))->method('leaveNode')
287             ->willReturn([new Node\Scalar\DNumber(42.0)]);
288
289         $visitor6 = $this->getMockBuilder(NodeVisitor::class)->getMock();
290         $visitor6->expects($this->at(4))->method('leaveNode')
291             ->willReturn(false);
292
293         $visitor7 = $this->getMockBuilder(NodeVisitor::class)->getMock();
294         $visitor7->expects($this->at(1))->method('enterNode')
295             ->willReturn(new Node\Scalar\LNumber(42));
296
297         $visitor8 = $this->getMockBuilder(NodeVisitor::class)->getMock();
298         $visitor8->expects($this->at(2))->method('enterNode')
299             ->willReturn(new Node\Stmt\Return_());
300
301         return [
302             [$visitor1, 'enterNode() returned invalid value of type string'],
303             [$visitor2, 'enterNode() returned invalid value of type string'],
304             [$visitor3, 'leaveNode() returned invalid value of type string'],
305             [$visitor4, 'leaveNode() returned invalid value of type string'],
306             [$visitor5, 'leaveNode() may only return an array if the parent structure is an array'],
307             [$visitor6, 'bool(false) return from leaveNode() no longer supported. Return NodeTraverser::REMOVE_NODE instead'],
308             [$visitor7, 'Trying to replace statement (Stmt_Expression) with expression (Scalar_LNumber). Are you missing a Stmt_Expression wrapper?'],
309             [$visitor8, 'Trying to replace expression (Scalar_LNumber) with statement (Stmt_Return)'],
310         ];
311     }
312 }