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