NameResolverTest.php 14 KB

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