NameResolverTest.php 13 KB

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