NodeAbstractTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <?php declare(strict_types=1);
  2. namespace PhpParser;
  3. class DummyNode extends NodeAbstract {
  4. public $subNode1;
  5. public $subNode2;
  6. public $notSubNode;
  7. public function __construct($subNode1, $subNode2, $notSubNode, $attributes) {
  8. parent::__construct($attributes);
  9. $this->subNode1 = $subNode1;
  10. $this->subNode2 = $subNode2;
  11. $this->notSubNode = $notSubNode;
  12. }
  13. public function getSubNodeNames(): array {
  14. return ['subNode1', 'subNode2'];
  15. }
  16. // This method is only overwritten because the node is located in an unusual namespace
  17. public function getType(): string {
  18. return 'Dummy';
  19. }
  20. }
  21. class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
  22. public static function provideNodes() {
  23. $attributes = [
  24. 'startLine' => 10,
  25. 'endLine' => 11,
  26. 'startTokenPos' => 12,
  27. 'endTokenPos' => 13,
  28. 'startFilePos' => 14,
  29. 'endFilePos' => 15,
  30. 'comments' => [
  31. new Comment('// Comment 1' . "\n"),
  32. new Comment\Doc('/** doc comment */'),
  33. new Comment('// Comment 2' . "\n"),
  34. ],
  35. ];
  36. $node = new DummyNode('value1', 'value2', 'value3', $attributes);
  37. return [
  38. [$attributes, $node],
  39. ];
  40. }
  41. /**
  42. * @dataProvider provideNodes
  43. */
  44. public function testConstruct(array $attributes, Node $node) {
  45. $this->assertSame('Dummy', $node->getType());
  46. $this->assertSame(['subNode1', 'subNode2'], $node->getSubNodeNames());
  47. $this->assertSame(10, $node->getLine());
  48. $this->assertSame(10, $node->getStartLine());
  49. $this->assertSame(11, $node->getEndLine());
  50. $this->assertSame(12, $node->getStartTokenPos());
  51. $this->assertSame(13, $node->getEndTokenPos());
  52. $this->assertSame(14, $node->getStartFilePos());
  53. $this->assertSame(15, $node->getEndFilePos());
  54. $this->assertSame('/** doc comment */', $node->getDocComment()->getText());
  55. $this->assertSame('value1', $node->subNode1);
  56. $this->assertSame('value2', $node->subNode2);
  57. $this->assertTrue(isset($node->subNode1));
  58. $this->assertTrue(isset($node->subNode2));
  59. $this->assertTrue(!isset($node->subNode3));
  60. $this->assertSame($attributes, $node->getAttributes());
  61. $this->assertSame($attributes['comments'], $node->getComments());
  62. return $node;
  63. }
  64. /**
  65. * @dataProvider provideNodes
  66. */
  67. public function testGetDocComment(array $attributes, Node $node): void {
  68. $this->assertSame('/** doc comment */', $node->getDocComment()->getText());
  69. $comments = $node->getComments();
  70. array_splice($comments, 1, 1, []); // remove doc comment
  71. $node->setAttribute('comments', $comments);
  72. $this->assertNull($node->getDocComment());
  73. // Remove all comments.
  74. $node->setAttribute('comments', []);
  75. $this->assertNull($node->getDocComment());
  76. }
  77. public function testSetDocComment(): void {
  78. $node = new DummyNode(null, null, null, []);
  79. // Add doc comment to node without comments
  80. $docComment = new Comment\Doc('/** doc */');
  81. $node->setDocComment($docComment);
  82. $this->assertSame($docComment, $node->getDocComment());
  83. // Replace it
  84. $docComment = new Comment\Doc('/** doc 2 */');
  85. $node->setDocComment($docComment);
  86. $this->assertSame($docComment, $node->getDocComment());
  87. // Add docmment to node with other comments
  88. $c1 = new Comment('/* foo */');
  89. $c2 = new Comment('/* bar */');
  90. $docComment = new Comment\Doc('/** baz */');
  91. $node->setAttribute('comments', [$c1, $c2]);
  92. $node->setDocComment($docComment);
  93. $this->assertSame([$c1, $c2, $docComment], $node->getAttribute('comments'));
  94. // Replace doc comment that is not at the end.
  95. $newDocComment = new Comment\Doc('/** new baz */');
  96. $node->setAttribute('comments', [$c1, $docComment, $c2]);
  97. $node->setDocComment($newDocComment);
  98. $this->assertSame([$c1, $newDocComment, $c2], $node->getAttribute('comments'));
  99. }
  100. /**
  101. * @dataProvider provideNodes
  102. */
  103. public function testChange(array $attributes, DummyNode $node): void {
  104. // direct modification
  105. $node->subNode1 = 'newValue';
  106. $this->assertSame('newValue', $node->subNode1);
  107. // indirect modification
  108. $subNode = &$node->subNode1;
  109. $subNode = 'newNewValue';
  110. $this->assertSame('newNewValue', $node->subNode1);
  111. // removal
  112. unset($node->subNode1);
  113. $this->assertFalse(isset($node->subNode1));
  114. }
  115. /**
  116. * @dataProvider provideNodes
  117. */
  118. public function testIteration(array $attributes, Node $node): void {
  119. // Iteration is simple object iteration over properties,
  120. // not over subnodes
  121. $i = 0;
  122. foreach ($node as $key => $value) {
  123. if ($i === 0) {
  124. $this->assertSame('subNode1', $key);
  125. $this->assertSame('value1', $value);
  126. } elseif ($i === 1) {
  127. $this->assertSame('subNode2', $key);
  128. $this->assertSame('value2', $value);
  129. } elseif ($i === 2) {
  130. $this->assertSame('notSubNode', $key);
  131. $this->assertSame('value3', $value);
  132. } else {
  133. throw new \Exception();
  134. }
  135. $i++;
  136. }
  137. $this->assertSame(3, $i);
  138. }
  139. public function testAttributes(): void {
  140. /** @var $node Node */
  141. $node = $this->getMockForAbstractClass(NodeAbstract::class);
  142. $this->assertEmpty($node->getAttributes());
  143. $node->setAttribute('key', 'value');
  144. $this->assertTrue($node->hasAttribute('key'));
  145. $this->assertSame('value', $node->getAttribute('key'));
  146. $this->assertFalse($node->hasAttribute('doesNotExist'));
  147. $this->assertNull($node->getAttribute('doesNotExist'));
  148. $this->assertSame('default', $node->getAttribute('doesNotExist', 'default'));
  149. $node->setAttribute('null', null);
  150. $this->assertTrue($node->hasAttribute('null'));
  151. $this->assertNull($node->getAttribute('null'));
  152. $this->assertNull($node->getAttribute('null', 'default'));
  153. $this->assertSame(
  154. [
  155. 'key' => 'value',
  156. 'null' => null,
  157. ],
  158. $node->getAttributes()
  159. );
  160. $node->setAttributes(
  161. [
  162. 'a' => 'b',
  163. 'c' => null,
  164. ]
  165. );
  166. $this->assertSame(
  167. [
  168. 'a' => 'b',
  169. 'c' => null,
  170. ],
  171. $node->getAttributes()
  172. );
  173. }
  174. public function testJsonSerialization(): void {
  175. $code = <<<'PHP'
  176. <?php
  177. // comment
  178. /** doc comment */
  179. function functionName(&$a = 0, $b = 1.0) {
  180. echo 'Foo';
  181. }
  182. PHP;
  183. $expected = <<<'JSON'
  184. [
  185. {
  186. "nodeType": "Stmt_Function",
  187. "byRef": false,
  188. "name": {
  189. "nodeType": "Identifier",
  190. "name": "functionName",
  191. "attributes": {
  192. "startLine": 4,
  193. "startTokenPos": 7,
  194. "startFilePos": 45,
  195. "endLine": 4,
  196. "endTokenPos": 7,
  197. "endFilePos": 56
  198. }
  199. },
  200. "params": [
  201. {
  202. "nodeType": "Param",
  203. "type": null,
  204. "byRef": true,
  205. "variadic": false,
  206. "var": {
  207. "nodeType": "Expr_Variable",
  208. "name": "a",
  209. "attributes": {
  210. "startLine": 4,
  211. "startTokenPos": 10,
  212. "startFilePos": 59,
  213. "endLine": 4,
  214. "endTokenPos": 10,
  215. "endFilePos": 60
  216. }
  217. },
  218. "default": {
  219. "nodeType": "Scalar_Int",
  220. "value": 0,
  221. "attributes": {
  222. "startLine": 4,
  223. "startTokenPos": 14,
  224. "startFilePos": 64,
  225. "endLine": 4,
  226. "endTokenPos": 14,
  227. "endFilePos": 64,
  228. "rawValue": "0",
  229. "kind": 10
  230. }
  231. },
  232. "flags": 0,
  233. "attrGroups": [],
  234. "attributes": {
  235. "startLine": 4,
  236. "startTokenPos": 9,
  237. "startFilePos": 58,
  238. "endLine": 4,
  239. "endTokenPos": 14,
  240. "endFilePos": 64
  241. }
  242. },
  243. {
  244. "nodeType": "Param",
  245. "type": null,
  246. "byRef": false,
  247. "variadic": false,
  248. "var": {
  249. "nodeType": "Expr_Variable",
  250. "name": "b",
  251. "attributes": {
  252. "startLine": 4,
  253. "startTokenPos": 17,
  254. "startFilePos": 67,
  255. "endLine": 4,
  256. "endTokenPos": 17,
  257. "endFilePos": 68
  258. }
  259. },
  260. "default": {
  261. "nodeType": "Scalar_Float",
  262. "value": 1,
  263. "attributes": {
  264. "startLine": 4,
  265. "startTokenPos": 21,
  266. "startFilePos": 72,
  267. "endLine": 4,
  268. "endTokenPos": 21,
  269. "endFilePos": 74,
  270. "rawValue": "1.0"
  271. }
  272. },
  273. "flags": 0,
  274. "attrGroups": [],
  275. "attributes": {
  276. "startLine": 4,
  277. "startTokenPos": 17,
  278. "startFilePos": 67,
  279. "endLine": 4,
  280. "endTokenPos": 21,
  281. "endFilePos": 74
  282. }
  283. }
  284. ],
  285. "returnType": null,
  286. "stmts": [
  287. {
  288. "nodeType": "Stmt_Echo",
  289. "exprs": [
  290. {
  291. "nodeType": "Scalar_String",
  292. "value": "Foo",
  293. "attributes": {
  294. "startLine": 5,
  295. "startTokenPos": 28,
  296. "startFilePos": 88,
  297. "endLine": 5,
  298. "endTokenPos": 28,
  299. "endFilePos": 92,
  300. "kind": 1,
  301. "rawValue": "'Foo'"
  302. }
  303. }
  304. ],
  305. "attributes": {
  306. "startLine": 5,
  307. "startTokenPos": 26,
  308. "startFilePos": 83,
  309. "endLine": 5,
  310. "endTokenPos": 29,
  311. "endFilePos": 93
  312. }
  313. }
  314. ],
  315. "attrGroups": [],
  316. "attributes": {
  317. "startLine": 4,
  318. "startTokenPos": 5,
  319. "startFilePos": 36,
  320. "endLine": 6,
  321. "endTokenPos": 31,
  322. "endFilePos": 95,
  323. "comments": [
  324. {
  325. "nodeType": "Comment",
  326. "text": "\/\/ comment",
  327. "line": 2,
  328. "filePos": 6,
  329. "tokenPos": 1,
  330. "endLine": 2,
  331. "endFilePos": 15,
  332. "endTokenPos": 1
  333. },
  334. {
  335. "nodeType": "Comment_Doc",
  336. "text": "\/** doc comment *\/",
  337. "line": 3,
  338. "filePos": 17,
  339. "tokenPos": 3,
  340. "endLine": 3,
  341. "endFilePos": 34,
  342. "endTokenPos": 3
  343. }
  344. ]
  345. }
  346. }
  347. ]
  348. JSON;
  349. $expected81 = <<<'JSON'
  350. [
  351. {
  352. "nodeType": "Stmt_Function",
  353. "attributes": {
  354. "startLine": 4,
  355. "startTokenPos": 5,
  356. "startFilePos": 36,
  357. "endLine": 6,
  358. "endTokenPos": 31,
  359. "endFilePos": 95,
  360. "comments": [
  361. {
  362. "nodeType": "Comment",
  363. "text": "\/\/ comment",
  364. "line": 2,
  365. "filePos": 6,
  366. "tokenPos": 1,
  367. "endLine": 2,
  368. "endFilePos": 15,
  369. "endTokenPos": 1
  370. },
  371. {
  372. "nodeType": "Comment_Doc",
  373. "text": "\/** doc comment *\/",
  374. "line": 3,
  375. "filePos": 17,
  376. "tokenPos": 3,
  377. "endLine": 3,
  378. "endFilePos": 34,
  379. "endTokenPos": 3
  380. }
  381. ]
  382. },
  383. "byRef": false,
  384. "name": {
  385. "nodeType": "Identifier",
  386. "attributes": {
  387. "startLine": 4,
  388. "startTokenPos": 7,
  389. "startFilePos": 45,
  390. "endLine": 4,
  391. "endTokenPos": 7,
  392. "endFilePos": 56
  393. },
  394. "name": "functionName"
  395. },
  396. "params": [
  397. {
  398. "nodeType": "Param",
  399. "attributes": {
  400. "startLine": 4,
  401. "startTokenPos": 9,
  402. "startFilePos": 58,
  403. "endLine": 4,
  404. "endTokenPos": 14,
  405. "endFilePos": 64
  406. },
  407. "type": null,
  408. "byRef": true,
  409. "variadic": false,
  410. "var": {
  411. "nodeType": "Expr_Variable",
  412. "attributes": {
  413. "startLine": 4,
  414. "startTokenPos": 10,
  415. "startFilePos": 59,
  416. "endLine": 4,
  417. "endTokenPos": 10,
  418. "endFilePos": 60
  419. },
  420. "name": "a"
  421. },
  422. "default": {
  423. "nodeType": "Scalar_Int",
  424. "attributes": {
  425. "startLine": 4,
  426. "startTokenPos": 14,
  427. "startFilePos": 64,
  428. "endLine": 4,
  429. "endTokenPos": 14,
  430. "endFilePos": 64,
  431. "rawValue": "0",
  432. "kind": 10
  433. },
  434. "value": 0
  435. },
  436. "flags": 0,
  437. "attrGroups": []
  438. },
  439. {
  440. "nodeType": "Param",
  441. "attributes": {
  442. "startLine": 4,
  443. "startTokenPos": 17,
  444. "startFilePos": 67,
  445. "endLine": 4,
  446. "endTokenPos": 21,
  447. "endFilePos": 74
  448. },
  449. "type": null,
  450. "byRef": false,
  451. "variadic": false,
  452. "var": {
  453. "nodeType": "Expr_Variable",
  454. "attributes": {
  455. "startLine": 4,
  456. "startTokenPos": 17,
  457. "startFilePos": 67,
  458. "endLine": 4,
  459. "endTokenPos": 17,
  460. "endFilePos": 68
  461. },
  462. "name": "b"
  463. },
  464. "default": {
  465. "nodeType": "Scalar_Float",
  466. "attributes": {
  467. "startLine": 4,
  468. "startTokenPos": 21,
  469. "startFilePos": 72,
  470. "endLine": 4,
  471. "endTokenPos": 21,
  472. "endFilePos": 74,
  473. "rawValue": "1.0"
  474. },
  475. "value": 1
  476. },
  477. "flags": 0,
  478. "attrGroups": []
  479. }
  480. ],
  481. "returnType": null,
  482. "stmts": [
  483. {
  484. "nodeType": "Stmt_Echo",
  485. "attributes": {
  486. "startLine": 5,
  487. "startTokenPos": 26,
  488. "startFilePos": 83,
  489. "endLine": 5,
  490. "endTokenPos": 29,
  491. "endFilePos": 93
  492. },
  493. "exprs": [
  494. {
  495. "nodeType": "Scalar_String",
  496. "attributes": {
  497. "startLine": 5,
  498. "startTokenPos": 28,
  499. "startFilePos": 88,
  500. "endLine": 5,
  501. "endTokenPos": 28,
  502. "endFilePos": 92,
  503. "kind": 1,
  504. "rawValue": "'Foo'"
  505. },
  506. "value": "Foo"
  507. }
  508. ]
  509. }
  510. ],
  511. "attrGroups": []
  512. }
  513. ]
  514. JSON;
  515. if (version_compare(PHP_VERSION, '8.1', '>=')) {
  516. $expected = $expected81;
  517. }
  518. $parser = new Parser\Php7(new Lexer());
  519. $stmts = $parser->parse(canonicalize($code));
  520. $json = json_encode($stmts, JSON_PRETTY_PRINT);
  521. $this->assertEquals(canonicalize($expected), canonicalize($json));
  522. }
  523. }