NameResolverTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <?php
  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. $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
  155. $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
  156. $traverser = new PhpParser\NodeTraverser;
  157. $traverser->addVisitor(new NameResolver);
  158. $stmts = $parser->parse($code);
  159. $stmts = $traverser->traverse($stmts);
  160. $this->assertSame(
  161. $this->canonicalize($expectedCode),
  162. $prettyPrinter->prettyPrint($stmts)
  163. );
  164. }
  165. /**
  166. * @covers PhpParser\NodeVisitor\NameResolver
  167. */
  168. public function testResolveLocations() {
  169. $code = <<<'EOC'
  170. <?php
  171. namespace NS;
  172. class A extends B implements C, D {
  173. use E, F, G {
  174. f as private g;
  175. E::h as i;
  176. E::j insteadof F, G;
  177. }
  178. }
  179. interface A extends C, D {
  180. public function a(A $a) : A;
  181. }
  182. function fn(A $a) : A {}
  183. function fn2(array $a) : array {}
  184. function(A $a) : A {};
  185. function fn3(?A $a) : ?A {}
  186. function fn4(?array $a) : ?array {}
  187. A::b();
  188. A::$b;
  189. A::B;
  190. new A;
  191. $a instanceof A;
  192. namespace\a();
  193. namespace\A;
  194. try {
  195. $someThing;
  196. } catch (A $a) {
  197. $someThingElse;
  198. }
  199. EOC;
  200. $expectedCode = <<<'EOC'
  201. namespace NS;
  202. class A extends \NS\B implements \NS\C, \NS\D
  203. {
  204. use \NS\E, \NS\F, \NS\G {
  205. f as private g;
  206. \NS\E::h as i;
  207. \NS\E::j insteadof \NS\F, \NS\G;
  208. }
  209. }
  210. interface A extends \NS\C, \NS\D
  211. {
  212. public function a(\NS\A $a) : \NS\A;
  213. }
  214. function fn(\NS\A $a) : \NS\A
  215. {
  216. }
  217. function fn2(array $a) : array
  218. {
  219. }
  220. function (\NS\A $a) : \NS\A {
  221. };
  222. function fn3(?\NS\A $a) : ?\NS\A
  223. {
  224. }
  225. function fn4(?array $a) : ?array
  226. {
  227. }
  228. \NS\A::b();
  229. \NS\A::$b;
  230. \NS\A::B;
  231. new \NS\A();
  232. $a instanceof \NS\A;
  233. \NS\a();
  234. \NS\A;
  235. try {
  236. $someThing;
  237. } catch (\NS\A $a) {
  238. $someThingElse;
  239. }
  240. EOC;
  241. $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
  242. $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
  243. $traverser = new PhpParser\NodeTraverser;
  244. $traverser->addVisitor(new NameResolver);
  245. $stmts = $parser->parse($code);
  246. $stmts = $traverser->traverse($stmts);
  247. $this->assertSame(
  248. $this->canonicalize($expectedCode),
  249. $prettyPrinter->prettyPrint($stmts)
  250. );
  251. }
  252. public function testNoResolveSpecialName() {
  253. $stmts = array(new Node\Expr\New_(new Name('self')));
  254. $traverser = new PhpParser\NodeTraverser;
  255. $traverser->addVisitor(new NameResolver);
  256. $this->assertEquals($stmts, $traverser->traverse($stmts));
  257. }
  258. public function testAddDeclarationNamespacedName() {
  259. $nsStmts = array(
  260. new Stmt\Class_('A'),
  261. new Stmt\Interface_('B'),
  262. new Stmt\Function_('C'),
  263. new Stmt\Const_(array(
  264. new Node\Const_('D', new Node\Scalar\LNumber(42))
  265. )),
  266. new Stmt\Trait_('E'),
  267. new Expr\New_(new Stmt\Class_(null)),
  268. );
  269. $traverser = new PhpParser\NodeTraverser;
  270. $traverser->addVisitor(new NameResolver);
  271. $stmts = $traverser->traverse([new Stmt\Namespace_(new Name('NS'), $nsStmts)]);
  272. $this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
  273. $this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
  274. $this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
  275. $this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
  276. $this->assertSame('NS\\E', (string) $stmts[0]->stmts[4]->namespacedName);
  277. $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
  278. $stmts = $traverser->traverse([new Stmt\Namespace_(null, $nsStmts)]);
  279. $this->assertSame('A', (string) $stmts[0]->stmts[0]->namespacedName);
  280. $this->assertSame('B', (string) $stmts[0]->stmts[1]->namespacedName);
  281. $this->assertSame('C', (string) $stmts[0]->stmts[2]->namespacedName);
  282. $this->assertSame('D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
  283. $this->assertSame('E', (string) $stmts[0]->stmts[4]->namespacedName);
  284. $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
  285. }
  286. public function testAddRuntimeResolvedNamespacedName() {
  287. $stmts = array(
  288. new Stmt\Namespace_(new Name('NS'), array(
  289. new Expr\FuncCall(new Name('foo')),
  290. new Expr\ConstFetch(new Name('FOO')),
  291. )),
  292. new Stmt\Namespace_(null, array(
  293. new Expr\FuncCall(new Name('foo')),
  294. new Expr\ConstFetch(new Name('FOO')),
  295. )),
  296. );
  297. $traverser = new PhpParser\NodeTraverser;
  298. $traverser->addVisitor(new NameResolver);
  299. $stmts = $traverser->traverse($stmts);
  300. $this->assertSame('NS\\foo', (string) $stmts[0]->stmts[0]->name->getAttribute('namespacedName'));
  301. $this->assertSame('NS\\FOO', (string) $stmts[0]->stmts[1]->name->getAttribute('namespacedName'));
  302. $this->assertFalse($stmts[1]->stmts[0]->name->hasAttribute('namespacedName'));
  303. $this->assertFalse($stmts[1]->stmts[1]->name->hasAttribute('namespacedName'));
  304. }
  305. /**
  306. * @dataProvider provideTestError
  307. */
  308. public function testError(Node $stmt, $errorMsg) {
  309. $this->setExpectedException('PhpParser\Error', $errorMsg);
  310. $traverser = new PhpParser\NodeTraverser;
  311. $traverser->addVisitor(new NameResolver);
  312. $traverser->traverse(array($stmt));
  313. }
  314. public function provideTestError() {
  315. return array(
  316. array(
  317. new Stmt\Use_(array(
  318. new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
  319. new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
  320. ), Stmt\Use_::TYPE_NORMAL),
  321. 'Cannot use C\D as B because the name is already in use on line 2'
  322. ),
  323. array(
  324. new Stmt\Use_(array(
  325. new Stmt\UseUse(new Name('a\b'), 'b', 0, array('startLine' => 1)),
  326. new Stmt\UseUse(new Name('c\d'), 'B', 0, array('startLine' => 2)),
  327. ), Stmt\Use_::TYPE_FUNCTION),
  328. 'Cannot use function c\d as B because the name is already in use on line 2'
  329. ),
  330. array(
  331. new Stmt\Use_(array(
  332. new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
  333. new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
  334. ), Stmt\Use_::TYPE_CONSTANT),
  335. 'Cannot use const C\D as B because the name is already in use on line 2'
  336. ),
  337. array(
  338. new Expr\New_(new Name\FullyQualified('self', array('startLine' => 3))),
  339. "'\\self' is an invalid class name on line 3"
  340. ),
  341. array(
  342. new Expr\New_(new Name\Relative('self', array('startLine' => 3))),
  343. "'\\self' is an invalid class name on line 3"
  344. ),
  345. array(
  346. new Expr\New_(new Name\FullyQualified('PARENT', array('startLine' => 3))),
  347. "'\\PARENT' is an invalid class name on line 3"
  348. ),
  349. array(
  350. new Expr\New_(new Name\Relative('STATIC', array('startLine' => 3))),
  351. "'\\STATIC' is an invalid class name on line 3"
  352. ),
  353. );
  354. }
  355. public function testClassNameIsCaseInsensitive()
  356. {
  357. $source = <<<'EOC'
  358. <?php
  359. namespace Foo;
  360. use Bar\Baz;
  361. $test = new baz();
  362. EOC;
  363. $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
  364. $stmts = $parser->parse($source);
  365. $traverser = new PhpParser\NodeTraverser;
  366. $traverser->addVisitor(new NameResolver);
  367. $stmts = $traverser->traverse($stmts);
  368. $stmt = $stmts[0];
  369. $this->assertSame(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts);
  370. }
  371. public function testSpecialClassNamesAreCaseInsensitive() {
  372. $source = <<<'EOC'
  373. <?php
  374. namespace Foo;
  375. class Bar
  376. {
  377. public static function method()
  378. {
  379. SELF::method();
  380. PARENT::method();
  381. STATIC::method();
  382. }
  383. }
  384. EOC;
  385. $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
  386. $stmts = $parser->parse($source);
  387. $traverser = new PhpParser\NodeTraverser;
  388. $traverser->addVisitor(new NameResolver);
  389. $stmts = $traverser->traverse($stmts);
  390. $classStmt = $stmts[0];
  391. $methodStmt = $classStmt->stmts[0]->stmts[0];
  392. $this->assertSame('SELF', (string)$methodStmt->stmts[0]->class);
  393. $this->assertSame('PARENT', (string)$methodStmt->stmts[1]->class);
  394. $this->assertSame('STATIC', (string)$methodStmt->stmts[2]->class);
  395. }
  396. public function testAddOriginalNames() {
  397. $traverser = new PhpParser\NodeTraverser;
  398. $traverser->addVisitor(new NameResolver(null, ['preserveOriginalNames' => true]));
  399. $n1 = new Name('Bar');
  400. $n2 = new Name('bar');
  401. $origStmts = [
  402. new Stmt\Namespace_(new Name('Foo'), [
  403. new Expr\ClassConstFetch($n1, 'FOO'),
  404. new Expr\FuncCall($n2),
  405. ])
  406. ];
  407. $stmts = $traverser->traverse($origStmts);
  408. $this->assertSame($n1, $stmts[0]->stmts[0]->class->getAttribute('originalName'));
  409. $this->assertSame($n2, $stmts[0]->stmts[1]->name->getAttribute('originalName'));
  410. }
  411. }