NodeAbstractTest.php 15 KB

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