NameResolverTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. <?php
  2. namespace PhpParser\NodeVisitor;
  3. use PhpParser;
  4. use PhpParser\Node;
  5. use PhpParser\Node\Name;
  6. use PhpParser\Node\Stmt;
  7. use PhpParser\Node\Expr;
  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 {}
  183. function fn2() : array {}
  184. function() : A {};
  185. A::b();
  186. A::$b;
  187. A::B;
  188. new A;
  189. $a instanceof A;
  190. namespace\a();
  191. namespace\A;
  192. try {
  193. $someThing;
  194. } catch (A $a) {
  195. $someThingElse;
  196. }
  197. EOC;
  198. $expectedCode = <<<'EOC'
  199. namespace NS;
  200. class A extends \NS\B implements \NS\C, \NS\D
  201. {
  202. use \NS\E, \NS\F, \NS\G {
  203. f as private g;
  204. \NS\E::h as i;
  205. \NS\E::j insteadof \NS\F, \NS\G;
  206. }
  207. }
  208. interface A extends \NS\C, \NS\D
  209. {
  210. public function a(\NS\A $a) : \NS\A;
  211. }
  212. function fn() : \NS\A
  213. {
  214. }
  215. function fn2() : array
  216. {
  217. }
  218. function () : \NS\A {
  219. };
  220. \NS\A::b();
  221. \NS\A::$b;
  222. \NS\A::B;
  223. new \NS\A();
  224. $a instanceof \NS\A;
  225. \NS\a();
  226. \NS\A;
  227. try {
  228. $someThing;
  229. } catch (\NS\A $a) {
  230. $someThingElse;
  231. }
  232. EOC;
  233. $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
  234. $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
  235. $traverser = new PhpParser\NodeTraverser;
  236. $traverser->addVisitor(new NameResolver);
  237. $stmts = $parser->parse($code);
  238. $stmts = $traverser->traverse($stmts);
  239. $this->assertSame(
  240. $this->canonicalize($expectedCode),
  241. $prettyPrinter->prettyPrint($stmts)
  242. );
  243. }
  244. public function testNoResolveSpecialName() {
  245. $stmts = array(new Node\Expr\New_(new Name('self')));
  246. $traverser = new PhpParser\NodeTraverser;
  247. $traverser->addVisitor(new NameResolver);
  248. $this->assertEquals($stmts, $traverser->traverse($stmts));
  249. }
  250. public function testAddNamespacedName() {
  251. $nsStmts = array(
  252. new Stmt\Class_('A'),
  253. new Stmt\Interface_('B'),
  254. new Stmt\Function_('C'),
  255. new Stmt\Const_(array(
  256. new Node\Const_('D', new Node\Scalar\LNumber(42))
  257. )),
  258. new Stmt\Trait_('E'),
  259. new Expr\New_(new Stmt\Class_(null)),
  260. );
  261. $traverser = new PhpParser\NodeTraverser;
  262. $traverser->addVisitor(new NameResolver);
  263. $stmts = $traverser->traverse([new Stmt\Namespace_(new Name('NS'), $nsStmts)]);
  264. $this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
  265. $this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
  266. $this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
  267. $this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
  268. $this->assertSame('NS\\E', (string) $stmts[0]->stmts[4]->namespacedName);
  269. $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
  270. $stmts = $traverser->traverse([new Stmt\Namespace_(null, $nsStmts)]);
  271. $this->assertSame('A', (string) $stmts[0]->stmts[0]->namespacedName);
  272. $this->assertSame('B', (string) $stmts[0]->stmts[1]->namespacedName);
  273. $this->assertSame('C', (string) $stmts[0]->stmts[2]->namespacedName);
  274. $this->assertSame('D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
  275. $this->assertSame('E', (string) $stmts[0]->stmts[4]->namespacedName);
  276. $this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
  277. }
  278. /**
  279. * @dataProvider provideTestError
  280. */
  281. public function testError(Node $stmt, $errorMsg) {
  282. $this->setExpectedException('PhpParser\Error', $errorMsg);
  283. $traverser = new PhpParser\NodeTraverser;
  284. $traverser->addVisitor(new NameResolver);
  285. $traverser->traverse(array($stmt));
  286. }
  287. public function provideTestError() {
  288. return array(
  289. array(
  290. new Stmt\Use_(array(
  291. new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
  292. new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
  293. ), Stmt\Use_::TYPE_NORMAL),
  294. 'Cannot use C\D as B because the name is already in use on line 2'
  295. ),
  296. array(
  297. new Stmt\Use_(array(
  298. new Stmt\UseUse(new Name('a\b'), 'b', 0, array('startLine' => 1)),
  299. new Stmt\UseUse(new Name('c\d'), 'B', 0, array('startLine' => 2)),
  300. ), Stmt\Use_::TYPE_FUNCTION),
  301. 'Cannot use function c\d as B because the name is already in use on line 2'
  302. ),
  303. array(
  304. new Stmt\Use_(array(
  305. new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
  306. new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
  307. ), Stmt\Use_::TYPE_CONSTANT),
  308. 'Cannot use const C\D as B because the name is already in use on line 2'
  309. ),
  310. array(
  311. new Expr\New_(new Name\FullyQualified('self', array('startLine' => 3))),
  312. "'\\self' is an invalid class name on line 3"
  313. ),
  314. array(
  315. new Expr\New_(new Name\Relative('self', array('startLine' => 3))),
  316. "'\\self' is an invalid class name on line 3"
  317. ),
  318. array(
  319. new Expr\New_(new Name\FullyQualified('PARENT', array('startLine' => 3))),
  320. "'\\PARENT' is an invalid class name on line 3"
  321. ),
  322. array(
  323. new Expr\New_(new Name\Relative('STATIC', array('startLine' => 3))),
  324. "'\\STATIC' is an invalid class name on line 3"
  325. ),
  326. );
  327. }
  328. public function testClassNameIsCaseInsensitive()
  329. {
  330. $source = <<<'EOC'
  331. <?php
  332. namespace Foo;
  333. use Bar\Baz;
  334. $test = new baz();
  335. EOC;
  336. $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
  337. $stmts = $parser->parse($source);
  338. $traverser = new PhpParser\NodeTraverser;
  339. $traverser->addVisitor(new NameResolver);
  340. $stmts = $traverser->traverse($stmts);
  341. $stmt = $stmts[0];
  342. $this->assertSame(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts);
  343. }
  344. public function testSpecialClassNamesAreCaseInsensitive() {
  345. $source = <<<'EOC'
  346. <?php
  347. namespace Foo;
  348. class Bar
  349. {
  350. public static function method()
  351. {
  352. SELF::method();
  353. PARENT::method();
  354. STATIC::method();
  355. }
  356. }
  357. EOC;
  358. $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
  359. $stmts = $parser->parse($source);
  360. $traverser = new PhpParser\NodeTraverser;
  361. $traverser->addVisitor(new NameResolver);
  362. $stmts = $traverser->traverse($stmts);
  363. $classStmt = $stmts[0];
  364. $methodStmt = $classStmt->stmts[0]->stmts[0];
  365. $this->assertSame('SELF', (string)$methodStmt->stmts[0]->class);
  366. $this->assertSame('PARENT', (string)$methodStmt->stmts[1]->class);
  367. $this->assertSame('STATIC', (string)$methodStmt->stmts[2]->class);
  368. }
  369. }