comparison vendor/nikic/php-parser/test/PhpParser/NodeVisitor/NameResolverTest.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 5fb285c0d0e3
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace PhpParser\NodeVisitor;
4
5 use PhpParser;
6 use PhpParser\Node;
7 use PhpParser\Node\Expr;
8 use PhpParser\Node\Name;
9 use PhpParser\Node\Stmt;
10
11 class NameResolverTest extends \PHPUnit_Framework_TestCase
12 {
13 private function canonicalize($string) {
14 return str_replace("\r\n", "\n", $string);
15 }
16
17 /**
18 * @covers PhpParser\NodeVisitor\NameResolver
19 */
20 public function testResolveNames() {
21 $code = <<<'EOC'
22 <?php
23
24 namespace Foo {
25 use Hallo as Hi;
26
27 new Bar();
28 new Hi();
29 new Hi\Bar();
30 new \Bar();
31 new namespace\Bar();
32
33 bar();
34 hi();
35 Hi\bar();
36 foo\bar();
37 \bar();
38 namespace\bar();
39 }
40 namespace {
41 use Hallo as Hi;
42
43 new Bar();
44 new Hi();
45 new Hi\Bar();
46 new \Bar();
47 new namespace\Bar();
48
49 bar();
50 hi();
51 Hi\bar();
52 foo\bar();
53 \bar();
54 namespace\bar();
55 }
56 namespace Bar {
57 use function foo\bar as baz;
58 use const foo\BAR as BAZ;
59 use foo as bar;
60
61 bar();
62 baz();
63 bar\foo();
64 baz\foo();
65 BAR();
66 BAZ();
67 BAR\FOO();
68 BAZ\FOO();
69
70 bar;
71 baz;
72 bar\foo;
73 baz\foo;
74 BAR;
75 BAZ;
76 BAR\FOO;
77 BAZ\FOO;
78 }
79 namespace Baz {
80 use A\T\{B\C, D\E};
81 use function X\T\{b\c, d\e};
82 use const Y\T\{B\C, D\E};
83 use Z\T\{G, function f, const K};
84
85 new C;
86 new E;
87 new C\D;
88 new E\F;
89 new G;
90
91 c();
92 e();
93 f();
94 C;
95 E;
96 K;
97 }
98 EOC;
99 $expectedCode = <<<'EOC'
100 namespace Foo {
101 use Hallo as Hi;
102 new \Foo\Bar();
103 new \Hallo();
104 new \Hallo\Bar();
105 new \Bar();
106 new \Foo\Bar();
107 bar();
108 hi();
109 \Hallo\bar();
110 \Foo\foo\bar();
111 \bar();
112 \Foo\bar();
113 }
114 namespace {
115 use Hallo as Hi;
116 new \Bar();
117 new \Hallo();
118 new \Hallo\Bar();
119 new \Bar();
120 new \Bar();
121 \bar();
122 \hi();
123 \Hallo\bar();
124 \foo\bar();
125 \bar();
126 \bar();
127 }
128 namespace Bar {
129 use function foo\bar as baz;
130 use const foo\BAR as BAZ;
131 use foo as bar;
132 bar();
133 \foo\bar();
134 \foo\foo();
135 \Bar\baz\foo();
136 BAR();
137 \foo\bar();
138 \foo\FOO();
139 \Bar\BAZ\FOO();
140 bar;
141 baz;
142 \foo\foo;
143 \Bar\baz\foo;
144 BAR;
145 \foo\BAR;
146 \foo\FOO;
147 \Bar\BAZ\FOO;
148 }
149 namespace Baz {
150 use A\T\{B\C, D\E};
151 use function X\T\{b\c, d\e};
152 use const Y\T\{B\C, D\E};
153 use Z\T\{G, function f, const K};
154 new \A\T\B\C();
155 new \A\T\D\E();
156 new \A\T\B\C\D();
157 new \A\T\D\E\F();
158 new \Z\T\G();
159 \X\T\b\c();
160 \X\T\d\e();
161 \Z\T\f();
162 \Y\T\B\C;
163 \Y\T\D\E;
164 \Z\T\K;
165 }
166 EOC;
167
168 $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
169 $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
170 $traverser = new PhpParser\NodeTraverser;
171 $traverser->addVisitor(new NameResolver);
172
173 $stmts = $parser->parse($code);
174 $stmts = $traverser->traverse($stmts);
175
176 $this->assertSame(
177 $this->canonicalize($expectedCode),
178 $prettyPrinter->prettyPrint($stmts)
179 );
180 }
181
182 /**
183 * @covers PhpParser\NodeVisitor\NameResolver
184 */
185 public function testResolveLocations() {
186 $code = <<<'EOC'
187 <?php
188 namespace NS;
189
190 class A extends B implements C, D {
191 use E, F, G {
192 f as private g;
193 E::h as i;
194 E::j insteadof F, G;
195 }
196 }
197
198 interface A extends C, D {
199 public function a(A $a) : A;
200 }
201
202 function fn(A $a) : A {}
203 function fn2(array $a) : array {}
204 function(A $a) : A {};
205
206 function fn3(?A $a) : ?A {}
207 function fn4(?array $a) : ?array {}
208
209 A::b();
210 A::$b;
211 A::B;
212 new A;
213 $a instanceof A;
214
215 namespace\a();
216 namespace\A;
217
218 try {
219 $someThing;
220 } catch (A $a) {
221 $someThingElse;
222 }
223 EOC;
224 $expectedCode = <<<'EOC'
225 namespace NS;
226
227 class A extends \NS\B implements \NS\C, \NS\D
228 {
229 use \NS\E, \NS\F, \NS\G {
230 f as private g;
231 \NS\E::h as i;
232 \NS\E::j insteadof \NS\F, \NS\G;
233 }
234 }
235 interface A extends \NS\C, \NS\D
236 {
237 public function a(\NS\A $a) : \NS\A;
238 }
239 function fn(\NS\A $a) : \NS\A
240 {
241 }
242 function fn2(array $a) : array
243 {
244 }
245 function (\NS\A $a) : \NS\A {
246 };
247 function fn3(?\NS\A $a) : ?\NS\A
248 {
249 }
250 function fn4(?array $a) : ?array
251 {
252 }
253 \NS\A::b();
254 \NS\A::$b;
255 \NS\A::B;
256 new \NS\A();
257 $a instanceof \NS\A;
258 \NS\a();
259 \NS\A;
260 try {
261 $someThing;
262 } catch (\NS\A $a) {
263 $someThingElse;
264 }
265 EOC;
266
267 $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
268 $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
269 $traverser = new PhpParser\NodeTraverser;
270 $traverser->addVisitor(new NameResolver);
271
272 $stmts = $parser->parse($code);
273 $stmts = $traverser->traverse($stmts);
274
275 $this->assertSame(
276 $this->canonicalize($expectedCode),
277 $prettyPrinter->prettyPrint($stmts)
278 );
279 }
280
281 public function testNoResolveSpecialName() {
282 $stmts = array(new Node\Expr\New_(new Name('self')));
283
284 $traverser = new PhpParser\NodeTraverser;
285 $traverser->addVisitor(new NameResolver);
286
287 $this->assertEquals($stmts, $traverser->traverse($stmts));
288 }
289
290 public function testAddDeclarationNamespacedName() {
291 $nsStmts = array(
292 new Stmt\Class_('A'),
293 new Stmt\Interface_('B'),
294 new Stmt\Function_('C'),
295 new Stmt\Const_(array(
296 new Node\Const_('D', new Node\Scalar\LNumber(42))
297 )),
298 new Stmt\Trait_('E'),
299 new Expr\New_(new Stmt\Class_(null)),
300 );
301
302 $traverser = new PhpParser\NodeTraverser;
303 $traverser->addVisitor(new NameResolver);
304
305 $stmts = $traverser->traverse([new Stmt\Namespace_(new Name('NS'), $nsStmts)]);
306 $this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
307 $this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
308 $this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
309 $this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
310 $this->assertSame('NS\\E', (string) $stmts[0]->stmts[4]->namespacedName);
311 $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
312
313 $stmts = $traverser->traverse([new Stmt\Namespace_(null, $nsStmts)]);
314 $this->assertSame('A', (string) $stmts[0]->stmts[0]->namespacedName);
315 $this->assertSame('B', (string) $stmts[0]->stmts[1]->namespacedName);
316 $this->assertSame('C', (string) $stmts[0]->stmts[2]->namespacedName);
317 $this->assertSame('D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
318 $this->assertSame('E', (string) $stmts[0]->stmts[4]->namespacedName);
319 $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
320 }
321
322 public function testAddRuntimeResolvedNamespacedName() {
323 $stmts = array(
324 new Stmt\Namespace_(new Name('NS'), array(
325 new Expr\FuncCall(new Name('foo')),
326 new Expr\ConstFetch(new Name('FOO')),
327 )),
328 new Stmt\Namespace_(null, array(
329 new Expr\FuncCall(new Name('foo')),
330 new Expr\ConstFetch(new Name('FOO')),
331 )),
332 );
333
334 $traverser = new PhpParser\NodeTraverser;
335 $traverser->addVisitor(new NameResolver);
336 $stmts = $traverser->traverse($stmts);
337
338 $this->assertSame('NS\\foo', (string) $stmts[0]->stmts[0]->name->getAttribute('namespacedName'));
339 $this->assertSame('NS\\FOO', (string) $stmts[0]->stmts[1]->name->getAttribute('namespacedName'));
340
341 $this->assertFalse($stmts[1]->stmts[0]->name->hasAttribute('namespacedName'));
342 $this->assertFalse($stmts[1]->stmts[1]->name->hasAttribute('namespacedName'));
343 }
344
345 /**
346 * @dataProvider provideTestError
347 */
348 public function testError(Node $stmt, $errorMsg) {
349 $this->setExpectedException('PhpParser\Error', $errorMsg);
350
351 $traverser = new PhpParser\NodeTraverser;
352 $traverser->addVisitor(new NameResolver);
353 $traverser->traverse(array($stmt));
354 }
355
356 public function provideTestError() {
357 return array(
358 array(
359 new Stmt\Use_(array(
360 new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
361 new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
362 ), Stmt\Use_::TYPE_NORMAL),
363 'Cannot use C\D as B because the name is already in use on line 2'
364 ),
365 array(
366 new Stmt\Use_(array(
367 new Stmt\UseUse(new Name('a\b'), 'b', 0, array('startLine' => 1)),
368 new Stmt\UseUse(new Name('c\d'), 'B', 0, array('startLine' => 2)),
369 ), Stmt\Use_::TYPE_FUNCTION),
370 'Cannot use function c\d as B because the name is already in use on line 2'
371 ),
372 array(
373 new Stmt\Use_(array(
374 new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
375 new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
376 ), Stmt\Use_::TYPE_CONSTANT),
377 'Cannot use const C\D as B because the name is already in use on line 2'
378 ),
379 array(
380 new Expr\New_(new Name\FullyQualified('self', array('startLine' => 3))),
381 "'\\self' is an invalid class name on line 3"
382 ),
383 array(
384 new Expr\New_(new Name\Relative('self', array('startLine' => 3))),
385 "'\\self' is an invalid class name on line 3"
386 ),
387 array(
388 new Expr\New_(new Name\FullyQualified('PARENT', array('startLine' => 3))),
389 "'\\PARENT' is an invalid class name on line 3"
390 ),
391 array(
392 new Expr\New_(new Name\Relative('STATIC', array('startLine' => 3))),
393 "'\\STATIC' is an invalid class name on line 3"
394 ),
395 );
396 }
397
398 public function testClassNameIsCaseInsensitive()
399 {
400 $source = <<<'EOC'
401 <?php
402 namespace Foo;
403 use Bar\Baz;
404 $test = new baz();
405 EOC;
406
407 $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
408 $stmts = $parser->parse($source);
409
410 $traverser = new PhpParser\NodeTraverser;
411 $traverser->addVisitor(new NameResolver);
412
413 $stmts = $traverser->traverse($stmts);
414 $stmt = $stmts[0];
415
416 $this->assertSame(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts);
417 }
418
419 public function testSpecialClassNamesAreCaseInsensitive() {
420 $source = <<<'EOC'
421 <?php
422 namespace Foo;
423
424 class Bar
425 {
426 public static function method()
427 {
428 SELF::method();
429 PARENT::method();
430 STATIC::method();
431 }
432 }
433 EOC;
434
435 $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
436 $stmts = $parser->parse($source);
437
438 $traverser = new PhpParser\NodeTraverser;
439 $traverser->addVisitor(new NameResolver);
440
441 $stmts = $traverser->traverse($stmts);
442 $classStmt = $stmts[0];
443 $methodStmt = $classStmt->stmts[0]->stmts[0];
444
445 $this->assertSame('SELF', (string)$methodStmt->stmts[0]->class);
446 $this->assertSame('PARENT', (string)$methodStmt->stmts[1]->class);
447 $this->assertSame('STATIC', (string)$methodStmt->stmts[2]->class);
448 }
449
450 public function testAddOriginalNames() {
451 $traverser = new PhpParser\NodeTraverser;
452 $traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true]));
453
454 $n1 = new Name('Bar');
455 $n2 = new Name('bar');
456 $origStmts = [
457 new Stmt\Namespace_(new Name('Foo'), [
458 new Expr\ClassConstFetch($n1, 'FOO'),
459 new Expr\FuncCall($n2),
460 ])
461 ];
462
463 $stmts = $traverser->traverse($origStmts);
464
465 $this->assertSame($n1, $stmts[0]->stmts[0]->class->getAttribute('originalName'));
466 $this->assertSame($n2, $stmts[0]->stmts[1]->name->getAttribute('originalName'));
467 }
468 }