NameResolverTest.php 14 KB

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