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