annotate vendor/nikic/php-parser/test/PhpParser/NodeVisitor/NameResolverTest.php @ 13:5fb285c0d0e3

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