NameResolverTest.php 13 KB

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