PageRenderTime 86ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/test/ng/parseSpec.js

https://github.com/ruslanas/angular.js
JavaScript | 3510 lines | 3484 code | 22 blank | 4 comment | 7 complexity | a8c0895bf1a8fd4a62fad8e255d17ada MD5 | raw file
Possible License(s): JSON

Large files files are truncated, but you can click here to view the full file

  1. 'use strict';
  2. describe('parser', function() {
  3. beforeEach(function() {
  4. /* global getterFnCacheDefault: true */
  5. /* global getterFnCacheExpensive: true */
  6. // clear caches
  7. getterFnCacheDefault = createMap();
  8. getterFnCacheExpensive = createMap();
  9. });
  10. describe('lexer', function() {
  11. var lex;
  12. beforeEach(function() {
  13. /* global Lexer: false */
  14. lex = function() {
  15. var lexer = new Lexer({csp: false});
  16. return lexer.lex.apply(lexer, arguments);
  17. };
  18. });
  19. it('should only match number chars with isNumber', function() {
  20. expect(Lexer.prototype.isNumber('0')).toBe(true);
  21. expect(Lexer.prototype.isNumber('')).toBeFalsy();
  22. expect(Lexer.prototype.isNumber(' ')).toBeFalsy();
  23. expect(Lexer.prototype.isNumber(0)).toBeFalsy();
  24. expect(Lexer.prototype.isNumber(false)).toBeFalsy();
  25. expect(Lexer.prototype.isNumber(true)).toBeFalsy();
  26. expect(Lexer.prototype.isNumber(undefined)).toBeFalsy();
  27. expect(Lexer.prototype.isNumber(null)).toBeFalsy();
  28. });
  29. it('should tokenize a string', function() {
  30. var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
  31. var i = 0;
  32. expect(tokens[i].index).toEqual(0);
  33. expect(tokens[i].text).toEqual('a');
  34. i++;
  35. expect(tokens[i].index).toEqual(1);
  36. expect(tokens[i].text).toEqual('.');
  37. i++;
  38. expect(tokens[i].index).toEqual(2);
  39. expect(tokens[i].text).toEqual('bc');
  40. i++;
  41. expect(tokens[i].index).toEqual(4);
  42. expect(tokens[i].text).toEqual('[');
  43. i++;
  44. expect(tokens[i].index).toEqual(5);
  45. expect(tokens[i].text).toEqual('22');
  46. expect(tokens[i].value).toEqual(22);
  47. expect(tokens[i].constant).toEqual(true);
  48. i++;
  49. expect(tokens[i].index).toEqual(7);
  50. expect(tokens[i].text).toEqual(']');
  51. i++;
  52. expect(tokens[i].index).toEqual(8);
  53. expect(tokens[i].text).toEqual('+');
  54. i++;
  55. expect(tokens[i].index).toEqual(9);
  56. expect(tokens[i].text).toEqual('1.3');
  57. expect(tokens[i].value).toEqual(1.3);
  58. expect(tokens[i].constant).toEqual(true);
  59. i++;
  60. expect(tokens[i].index).toEqual(12);
  61. expect(tokens[i].text).toEqual('|');
  62. i++;
  63. expect(tokens[i].index).toEqual(13);
  64. expect(tokens[i].text).toEqual('f');
  65. i++;
  66. expect(tokens[i].index).toEqual(14);
  67. expect(tokens[i].text).toEqual(':');
  68. i++;
  69. expect(tokens[i].index).toEqual(15);
  70. expect(tokens[i].value).toEqual("a'c");
  71. i++;
  72. expect(tokens[i].index).toEqual(21);
  73. expect(tokens[i].text).toEqual(':');
  74. i++;
  75. expect(tokens[i].index).toEqual(22);
  76. expect(tokens[i].value).toEqual('d"e');
  77. });
  78. it('should tokenize identifiers with spaces around dots the same as without spaces', function() {
  79. function getText(t) { return t.text; }
  80. var spaces = lex('foo. bar . baz').map(getText);
  81. var noSpaces = lex('foo.bar.baz').map(getText);
  82. expect(spaces).toEqual(noSpaces);
  83. });
  84. it('should tokenize undefined', function() {
  85. var tokens = lex("undefined");
  86. var i = 0;
  87. expect(tokens[i].index).toEqual(0);
  88. expect(tokens[i].text).toEqual('undefined');
  89. });
  90. it('should tokenize quoted string', function() {
  91. var str = "['\\'', \"\\\"\"]";
  92. var tokens = lex(str);
  93. expect(tokens[1].index).toEqual(1);
  94. expect(tokens[1].value).toEqual("'");
  95. expect(tokens[3].index).toEqual(7);
  96. expect(tokens[3].value).toEqual('"');
  97. });
  98. it('should tokenize escaped quoted string', function() {
  99. var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
  100. var tokens = lex(str);
  101. expect(tokens[0].value).toEqual('"\n\f\r\t\v\u00A0');
  102. });
  103. it('should tokenize unicode', function() {
  104. var tokens = lex('"\\u00A0"');
  105. expect(tokens.length).toEqual(1);
  106. expect(tokens[0].value).toEqual('\u00a0');
  107. });
  108. it('should ignore whitespace', function() {
  109. var tokens = lex("a \t \n \r b");
  110. expect(tokens[0].text).toEqual('a');
  111. expect(tokens[1].text).toEqual('b');
  112. });
  113. it('should tokenize relation and equality', function() {
  114. var tokens = lex("! == != < > <= >= === !==");
  115. expect(tokens[0].text).toEqual('!');
  116. expect(tokens[1].text).toEqual('==');
  117. expect(tokens[2].text).toEqual('!=');
  118. expect(tokens[3].text).toEqual('<');
  119. expect(tokens[4].text).toEqual('>');
  120. expect(tokens[5].text).toEqual('<=');
  121. expect(tokens[6].text).toEqual('>=');
  122. expect(tokens[7].text).toEqual('===');
  123. expect(tokens[8].text).toEqual('!==');
  124. });
  125. it('should tokenize logical and ternary', function() {
  126. var tokens = lex("&& || ? :");
  127. expect(tokens[0].text).toEqual('&&');
  128. expect(tokens[1].text).toEqual('||');
  129. expect(tokens[2].text).toEqual('?');
  130. expect(tokens[3].text).toEqual(':');
  131. });
  132. it('should tokenize statements', function() {
  133. var tokens = lex("a;b;");
  134. expect(tokens[0].text).toEqual('a');
  135. expect(tokens[1].text).toEqual(';');
  136. expect(tokens[2].text).toEqual('b');
  137. expect(tokens[3].text).toEqual(';');
  138. });
  139. it('should tokenize function invocation', function() {
  140. var tokens = lex("a()");
  141. expect(tokens.map(function(t) { return t.text;})).toEqual(['a', '(', ')']);
  142. });
  143. it('should tokenize method invocation', function() {
  144. var tokens = lex("a.b.c (d) - e.f()");
  145. expect(tokens.map(function(t) { return t.text;})).
  146. toEqual(['a', '.', 'b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
  147. });
  148. it('should tokenize number', function() {
  149. var tokens = lex("0.5");
  150. expect(tokens[0].value).toEqual(0.5);
  151. });
  152. it('should tokenize negative number', inject(function($rootScope) {
  153. var value = $rootScope.$eval("-0.5");
  154. expect(value).toEqual(-0.5);
  155. value = $rootScope.$eval("{a:-0.5}");
  156. expect(value).toEqual({a:-0.5});
  157. }));
  158. it('should tokenize number with exponent', inject(function($rootScope) {
  159. var tokens = lex("0.5E-10");
  160. expect(tokens[0].value).toEqual(0.5E-10);
  161. expect($rootScope.$eval("0.5E-10")).toEqual(0.5E-10);
  162. tokens = lex("0.5E+10");
  163. expect(tokens[0].value).toEqual(0.5E+10);
  164. }));
  165. it('should throws exception for invalid exponent', function() {
  166. expect(function() {
  167. lex("0.5E-");
  168. }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-].');
  169. expect(function() {
  170. lex("0.5E-A");
  171. }).toThrowMinErr('$parse', 'lexerr', 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-A].');
  172. });
  173. it('should tokenize number starting with a dot', function() {
  174. var tokens = lex(".5");
  175. expect(tokens[0].value).toEqual(0.5);
  176. });
  177. it('should throw error on invalid unicode', function() {
  178. expect(function() {
  179. lex("'\\u1''bla'");
  180. }).toThrowMinErr("$parse", "lexerr", "Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla'].");
  181. });
  182. });
  183. describe('ast', function() {
  184. var createAst;
  185. beforeEach(function() {
  186. /* global AST: false */
  187. createAst = function() {
  188. var lexer = new Lexer({csp: false});
  189. var ast = new AST(lexer, {csp: false});
  190. return ast.ast.apply(ast, arguments);
  191. };
  192. });
  193. it('should handle an empty list of tokens', function() {
  194. expect(createAst('')).toEqual({type: 'Program', body: []});
  195. });
  196. it('should understand identifiers', function() {
  197. expect(createAst('foo')).toEqual(
  198. {
  199. type: 'Program',
  200. body: [
  201. {
  202. type: 'ExpressionStatement',
  203. expression: { type: 'Identifier', name: 'foo' }
  204. }
  205. ]
  206. }
  207. );
  208. });
  209. it('should understand non-computed member expressions', function() {
  210. expect(createAst('foo.bar')).toEqual(
  211. {
  212. type: 'Program',
  213. body: [
  214. {
  215. type: 'ExpressionStatement',
  216. expression: {
  217. type: 'MemberExpression',
  218. object: { type: 'Identifier', name: 'foo'},
  219. property: {type: 'Identifier', name: 'bar'},
  220. computed: false
  221. }
  222. }
  223. ]
  224. }
  225. );
  226. });
  227. it('should associate non-computed member expressions left-to-right', function() {
  228. expect(createAst('foo.bar.baz')).toEqual(
  229. {
  230. type: 'Program',
  231. body: [
  232. {
  233. type: 'ExpressionStatement',
  234. expression: {
  235. type: 'MemberExpression',
  236. object: {
  237. type: 'MemberExpression',
  238. object: { type: 'Identifier', name: 'foo'},
  239. property: { type: 'Identifier', name: 'bar' },
  240. computed: false
  241. },
  242. property: {type: 'Identifier', name: 'baz'},
  243. computed: false
  244. }
  245. }
  246. ]
  247. }
  248. );
  249. });
  250. it('should understand computed member expressions', function() {
  251. expect(createAst('foo[bar]')).toEqual(
  252. {
  253. type: 'Program',
  254. body: [
  255. {
  256. type: 'ExpressionStatement',
  257. expression: {
  258. type: 'MemberExpression',
  259. object: { type: 'Identifier', name: 'foo'},
  260. property: {type: 'Identifier', name: 'bar'},
  261. computed: true
  262. }
  263. }
  264. ]
  265. }
  266. );
  267. });
  268. it('should associate computed member expressions left-to-right', function() {
  269. expect(createAst('foo[bar][baz]')).toEqual(
  270. {
  271. type: 'Program',
  272. body: [
  273. {
  274. type: 'ExpressionStatement',
  275. expression: {
  276. type: 'MemberExpression',
  277. object: {
  278. type: 'MemberExpression',
  279. object: { type: 'Identifier', name: 'foo' },
  280. property: { type: 'Identifier', name: 'bar' },
  281. computed: true
  282. },
  283. property: { type: 'Identifier', name: 'baz' },
  284. computed: true
  285. }
  286. }
  287. ]
  288. }
  289. );
  290. });
  291. it('should understand call expressions', function() {
  292. expect(createAst('foo()')).toEqual(
  293. {
  294. type: 'Program',
  295. body: [
  296. {
  297. type: 'ExpressionStatement',
  298. expression: {
  299. type: 'CallExpression',
  300. callee: { type: 'Identifier', name: 'foo'},
  301. arguments: []
  302. }
  303. }
  304. ]
  305. }
  306. );
  307. });
  308. it('should parse call expression arguments', function() {
  309. expect(createAst('foo(bar, baz)')).toEqual(
  310. {
  311. type: 'Program',
  312. body: [
  313. {
  314. type: 'ExpressionStatement',
  315. expression: {
  316. type: 'CallExpression',
  317. callee: { type: 'Identifier', name: 'foo'},
  318. arguments: [
  319. { type: 'Identifier', name: 'bar' },
  320. { type: 'Identifier', name: 'baz' }
  321. ]
  322. }
  323. }
  324. ]
  325. }
  326. );
  327. });
  328. it('should parse call expression left-to-right', function() {
  329. expect(createAst('foo(bar, baz)(man, shell)')).toEqual(
  330. {
  331. type: 'Program',
  332. body: [
  333. {
  334. type: 'ExpressionStatement',
  335. expression: {
  336. type: 'CallExpression',
  337. callee: {
  338. type: 'CallExpression',
  339. callee: { type: 'Identifier', name: 'foo' },
  340. arguments: [
  341. { type: 'Identifier', name: 'bar' },
  342. { type: 'Identifier', name: 'baz' }
  343. ]
  344. },
  345. arguments: [
  346. { type: 'Identifier', name: 'man' },
  347. { type: 'Identifier', name: 'shell' }
  348. ]
  349. }
  350. }
  351. ]
  352. }
  353. );
  354. });
  355. it('should keep the context when having superfluous parenthesis', function() {
  356. expect(createAst('(foo)(bar, baz)')).toEqual(
  357. {
  358. type: 'Program',
  359. body: [
  360. {
  361. type: 'ExpressionStatement',
  362. expression: {
  363. type: 'CallExpression',
  364. callee: { type: 'Identifier', name: 'foo'},
  365. arguments: [
  366. { type: 'Identifier', name: 'bar' },
  367. { type: 'Identifier', name: 'baz' }
  368. ]
  369. }
  370. }
  371. ]
  372. }
  373. );
  374. });
  375. it('should treat member expressions and call expression with the same precedence', function() {
  376. expect(createAst('foo.bar[baz]()')).toEqual(
  377. {
  378. type: 'Program',
  379. body: [
  380. {
  381. type: 'ExpressionStatement',
  382. expression: {
  383. type: 'CallExpression',
  384. callee: {
  385. type: 'MemberExpression',
  386. object: {
  387. type: 'MemberExpression',
  388. object: { type: 'Identifier', name: 'foo' },
  389. property: { type: 'Identifier', name: 'bar' },
  390. computed: false
  391. },
  392. property: { type: 'Identifier', name: 'baz' },
  393. computed: true
  394. },
  395. arguments: []
  396. }
  397. }
  398. ]
  399. }
  400. );
  401. expect(createAst('foo[bar]().baz')).toEqual(
  402. {
  403. type: 'Program',
  404. body: [
  405. {
  406. type: 'ExpressionStatement',
  407. expression: {
  408. type: 'MemberExpression',
  409. object: {
  410. type: 'CallExpression',
  411. callee: {
  412. type: 'MemberExpression',
  413. object: { type: 'Identifier', name: 'foo' },
  414. property: { type: 'Identifier', name: 'bar' },
  415. computed: true
  416. },
  417. arguments: []
  418. },
  419. property: { type: 'Identifier', name: 'baz' },
  420. computed: false
  421. }
  422. }
  423. ]
  424. }
  425. );
  426. expect(createAst('foo().bar[baz]')).toEqual(
  427. {
  428. type: 'Program',
  429. body: [
  430. {
  431. type: 'ExpressionStatement',
  432. expression: {
  433. type: 'MemberExpression',
  434. object: {
  435. type: 'MemberExpression',
  436. object: {
  437. type: 'CallExpression',
  438. callee: { type: 'Identifier', name: 'foo' },
  439. arguments: [] },
  440. property: { type: 'Identifier', name: 'bar' },
  441. computed: false
  442. },
  443. property: { type: 'Identifier', name: 'baz' },
  444. computed: true
  445. }
  446. }
  447. ]
  448. }
  449. );
  450. });
  451. it('should understand literals', function() {
  452. // In a strict sense, `undefined` is not a literal but an identifier
  453. forEach({'123': 123, '"123"': '123', 'true': true, 'false': false, 'null': null, 'undefined': undefined}, function(value, expression) {
  454. expect(createAst(expression)).toEqual(
  455. {
  456. type: 'Program',
  457. body: [
  458. {
  459. type: 'ExpressionStatement',
  460. expression: { type: 'Literal', value: value }
  461. }
  462. ]
  463. }
  464. );
  465. });
  466. });
  467. it('should understand the `this` expression', function() {
  468. expect(createAst('this')).toEqual(
  469. {
  470. type: 'Program',
  471. body: [
  472. {
  473. type: 'ExpressionStatement',
  474. expression: { type: 'ThisExpression' }
  475. }
  476. ]
  477. }
  478. );
  479. });
  480. it('should not confuse `this`, `undefined`, `true`, `false`, `null` when used as identfiers', function() {
  481. forEach(['this', 'undefined', 'true', 'false', 'null'], function(identifier) {
  482. expect(createAst('foo.' + identifier)).toEqual(
  483. {
  484. type: 'Program',
  485. body: [
  486. {
  487. type: 'ExpressionStatement',
  488. expression: {
  489. type: 'MemberExpression',
  490. object: { type: 'Identifier', name: 'foo' },
  491. property: { type: 'Identifier', name: identifier },
  492. computed: false
  493. }
  494. }
  495. ]
  496. }
  497. );
  498. });
  499. });
  500. it('should throw when trying to use non-identifiers as identifiers', function() {
  501. expect(function() { createAst('foo.)'); }).toThrowMinErr('$parse', 'syntax',
  502. "Syntax Error: Token ')' is not a valid identifier at column 5 of the expression [foo.)");
  503. });
  504. it('should throw when all tokens are not consumed', function() {
  505. expect(function() { createAst('foo bar'); }).toThrowMinErr('$parse', 'syntax',
  506. "Syntax Error: Token 'bar' is an unexpected token at column 5 of the expression [foo bar] starting at [bar]");
  507. });
  508. it('should understand the unary operators `-`, `+` and `!`', function() {
  509. forEach(['-', '+', '!'], function(operator) {
  510. expect(createAst(operator + 'foo')).toEqual(
  511. {
  512. type: 'Program',
  513. body: [
  514. {
  515. type: 'ExpressionStatement',
  516. expression: {
  517. type: 'UnaryExpression',
  518. operator: operator,
  519. prefix: true,
  520. argument: { type: 'Identifier', name: 'foo' }
  521. }
  522. }
  523. ]
  524. }
  525. );
  526. });
  527. });
  528. it('should handle all unary operators with the same precedence', function() {
  529. forEach([['+', '-', '!'], ['-', '!', '+'], ['!', '+', '-']], function(operators) {
  530. expect(createAst(operators.join('') + 'foo')).toEqual(
  531. {
  532. type: 'Program',
  533. body: [
  534. {
  535. type: 'ExpressionStatement',
  536. expression: {
  537. type: 'UnaryExpression',
  538. operator: operators[0],
  539. prefix: true,
  540. argument: {
  541. type: 'UnaryExpression',
  542. operator: operators[1],
  543. prefix: true,
  544. argument: {
  545. type: 'UnaryExpression',
  546. operator: operators[2],
  547. prefix: true,
  548. argument: { type: 'Identifier', name: 'foo' }
  549. }
  550. }
  551. }
  552. }
  553. ]
  554. }
  555. );
  556. });
  557. });
  558. it('should be able to understand binary operators', function() {
  559. forEach(['*', '/', '%', '+', '-', '<', '>', '<=', '>=', '==','!=','===','!=='], function(operator) {
  560. expect(createAst('foo' + operator + 'bar')).toEqual(
  561. {
  562. type: 'Program',
  563. body: [
  564. {
  565. type: 'ExpressionStatement',
  566. expression: {
  567. type: 'BinaryExpression',
  568. operator: operator,
  569. left: { type: 'Identifier', name: 'foo' },
  570. right: { type: 'Identifier', name: 'bar' }
  571. }
  572. }
  573. ]
  574. }
  575. );
  576. });
  577. });
  578. it('should associate binary operators with the same precendence left-to-right', function() {
  579. var operatorsByPrecedence = [['*', '/', '%'], ['+', '-'], ['<', '>', '<=', '>='], ['==','!=','===','!==']];
  580. forEach(operatorsByPrecedence, function(operators) {
  581. forEach(operators, function(op1) {
  582. forEach(operators, function(op2) {
  583. expect(createAst('foo' + op1 + 'bar' + op2 + 'baz')).toEqual(
  584. {
  585. type: 'Program',
  586. body: [
  587. {
  588. type: 'ExpressionStatement',
  589. expression: {
  590. type: 'BinaryExpression',
  591. operator: op2,
  592. left: {
  593. type: 'BinaryExpression',
  594. operator: op1,
  595. left: { type: 'Identifier', name: 'foo' },
  596. right: { type: 'Identifier', name: 'bar' }
  597. },
  598. right: { type: 'Identifier', name: 'baz' }
  599. }
  600. }
  601. ]
  602. }
  603. );
  604. });
  605. });
  606. });
  607. });
  608. it('should give higher prcedence to member calls than to unary expressions', function() {
  609. forEach(['!', '+', '-'], function(operator) {
  610. expect(createAst(operator + 'foo()')).toEqual(
  611. {
  612. type: 'Program',
  613. body: [
  614. {
  615. type: 'ExpressionStatement',
  616. expression: {
  617. type: 'UnaryExpression',
  618. operator: operator,
  619. prefix: true,
  620. argument: {
  621. type: 'CallExpression',
  622. callee: { type: 'Identifier', name: 'foo' },
  623. arguments: []
  624. }
  625. }
  626. }
  627. ]
  628. }
  629. );
  630. expect(createAst(operator + 'foo.bar')).toEqual(
  631. {
  632. type: 'Program',
  633. body: [
  634. {
  635. type: 'ExpressionStatement',
  636. expression: {
  637. type: 'UnaryExpression',
  638. operator: operator,
  639. prefix: true,
  640. argument: {
  641. type: 'MemberExpression',
  642. object: { type: 'Identifier', name: 'foo' },
  643. property: { type: 'Identifier', name: 'bar' },
  644. computed: false
  645. }
  646. }
  647. }
  648. ]
  649. }
  650. );
  651. expect(createAst(operator + 'foo[bar]')).toEqual(
  652. {
  653. type: 'Program',
  654. body: [
  655. {
  656. type: 'ExpressionStatement',
  657. expression: {
  658. type: 'UnaryExpression',
  659. operator: operator,
  660. prefix: true,
  661. argument: {
  662. type: 'MemberExpression',
  663. object: { type: 'Identifier', name: 'foo' },
  664. property: { type: 'Identifier', name: 'bar' },
  665. computed: true
  666. }
  667. }
  668. }
  669. ]
  670. }
  671. );
  672. });
  673. });
  674. it('should give higher precedence to unary operators over multiplicative operators', function() {
  675. forEach(['!', '+', '-'], function(op1) {
  676. forEach(['*', '/', '%'], function(op2) {
  677. expect(createAst(op1 + 'foo' + op2 + op1 + 'bar')).toEqual(
  678. {
  679. type: 'Program',
  680. body: [
  681. {
  682. type: 'ExpressionStatement',
  683. expression: {
  684. type: 'BinaryExpression',
  685. operator: op2,
  686. left: {
  687. type: 'UnaryExpression',
  688. operator: op1,
  689. prefix: true,
  690. argument: { type: 'Identifier', name: 'foo' }
  691. },
  692. right: {
  693. type: 'UnaryExpression',
  694. operator: op1,
  695. prefix: true,
  696. argument: { type: 'Identifier', name: 'bar' }
  697. }
  698. }
  699. }
  700. ]
  701. }
  702. );
  703. });
  704. });
  705. });
  706. it('should give binary operators their right precedence', function() {
  707. var operatorsByPrecedence = [['*', '/', '%'], ['+', '-'], ['<', '>', '<=', '>='], ['==','!=','===','!==']];
  708. for (var i = 0; i < operatorsByPrecedence.length - 1; ++i) {
  709. forEach(operatorsByPrecedence[i], function(op1) {
  710. forEach(operatorsByPrecedence[i + 1], function(op2) {
  711. expect(createAst('foo' + op1 + 'bar' + op2 + 'baz' + op1 + 'man')).toEqual(
  712. {
  713. type: 'Program',
  714. body: [
  715. {
  716. type: 'ExpressionStatement',
  717. expression: {
  718. type: 'BinaryExpression',
  719. operator: op2,
  720. left: {
  721. type: 'BinaryExpression',
  722. operator: op1,
  723. left: { type: 'Identifier', name: 'foo' },
  724. right: { type: 'Identifier', name: 'bar' }
  725. },
  726. right: {
  727. type: 'BinaryExpression',
  728. operator: op1,
  729. left: { type: 'Identifier', name: 'baz' },
  730. right: { type: 'Identifier', name: 'man' }
  731. }
  732. }
  733. }
  734. ]
  735. }
  736. );
  737. });
  738. });
  739. }
  740. });
  741. it('should understand logical operators', function() {
  742. forEach(['||', '&&'], function(operator) {
  743. expect(createAst('foo' + operator + 'bar')).toEqual(
  744. {
  745. type: 'Program',
  746. body: [
  747. {
  748. type: 'ExpressionStatement',
  749. expression: {
  750. type: 'LogicalExpression',
  751. operator: operator,
  752. left: { type: 'Identifier', name: 'foo' },
  753. right: { type: 'Identifier', name: 'bar' }
  754. }
  755. }
  756. ]
  757. }
  758. );
  759. });
  760. });
  761. it('should associate logical operators left-to-right', function() {
  762. forEach(['||', '&&'], function(op) {
  763. expect(createAst('foo' + op + 'bar' + op + 'baz')).toEqual(
  764. {
  765. type: 'Program',
  766. body: [
  767. {
  768. type: 'ExpressionStatement',
  769. expression: {
  770. type: 'LogicalExpression',
  771. operator: op,
  772. left: {
  773. type: 'LogicalExpression',
  774. operator: op,
  775. left: { type: 'Identifier', name: 'foo' },
  776. right: { type: 'Identifier', name: 'bar' }
  777. },
  778. right: { type: 'Identifier', name: 'baz' }
  779. }
  780. }
  781. ]
  782. }
  783. );
  784. });
  785. });
  786. it('should understand ternary operators', function() {
  787. expect(createAst('foo?bar:baz')).toEqual(
  788. {
  789. type: 'Program',
  790. body: [
  791. {
  792. type: 'ExpressionStatement',
  793. expression: {
  794. type: 'ConditionalExpression',
  795. test: { type: 'Identifier', name: 'foo' },
  796. alternate: { type: 'Identifier', name: 'bar' },
  797. consequent: { type: 'Identifier', name: 'baz' }
  798. }
  799. }
  800. ]
  801. }
  802. );
  803. });
  804. it('should associate the conditional operator right-to-left', function() {
  805. expect(createAst('foo0?foo1:foo2?bar0?bar1:bar2:man0?man1:man2')).toEqual(
  806. {
  807. type: 'Program',
  808. body: [
  809. {
  810. type: 'ExpressionStatement',
  811. expression: {
  812. type: 'ConditionalExpression',
  813. test: { type: 'Identifier', name: 'foo0' },
  814. alternate: { type: 'Identifier', name: 'foo1' },
  815. consequent: {
  816. type: 'ConditionalExpression',
  817. test: { type: 'Identifier', name: 'foo2' },
  818. alternate: {
  819. type: 'ConditionalExpression',
  820. test: { type: 'Identifier', name: 'bar0' },
  821. alternate: { type: 'Identifier', name: 'bar1' },
  822. consequent: { type: 'Identifier', name: 'bar2' }
  823. },
  824. consequent: {
  825. type: 'ConditionalExpression',
  826. test: { type: 'Identifier', name: 'man0' },
  827. alternate: { type: 'Identifier', name: 'man1' },
  828. consequent: { type: 'Identifier', name: 'man2' }
  829. }
  830. }
  831. }
  832. }
  833. ]
  834. }
  835. );
  836. });
  837. it('should understand assignment operator', function() {
  838. // Currently, only `=` is supported
  839. expect(createAst('foo=bar')).toEqual(
  840. {
  841. type: 'Program',
  842. body: [
  843. {
  844. type: 'ExpressionStatement',
  845. expression: {
  846. type: 'AssignmentExpression',
  847. left: { type: 'Identifier', name: 'foo' },
  848. right: { type: 'Identifier', name: 'bar' },
  849. operator: '='
  850. }
  851. }
  852. ]
  853. }
  854. );
  855. });
  856. it('should associate assignments right-to-left', function() {
  857. // Currently, only `=` is supported
  858. expect(createAst('foo=bar=man')).toEqual(
  859. {
  860. type: 'Program',
  861. body: [
  862. {
  863. type: 'ExpressionStatement',
  864. expression: {
  865. type: 'AssignmentExpression',
  866. left: { type: 'Identifier', name: 'foo' },
  867. right: {
  868. type: 'AssignmentExpression',
  869. left: { type: 'Identifier', name: 'bar' },
  870. right: { type: 'Identifier', name: 'man' },
  871. operator: '='
  872. },
  873. operator: '='
  874. }
  875. }
  876. ]
  877. }
  878. );
  879. });
  880. it('should give higher precedence to equality than to the logical `and` operator', function() {
  881. forEach(['==','!=','===','!=='], function(operator) {
  882. expect(createAst('foo' + operator + 'bar && man' + operator + 'shell')).toEqual(
  883. {
  884. type: 'Program',
  885. body: [
  886. {
  887. type: 'ExpressionStatement',
  888. expression: {
  889. type: 'LogicalExpression',
  890. operator: '&&',
  891. left: {
  892. type: 'BinaryExpression',
  893. operator: operator,
  894. left: { type: 'Identifier', name: 'foo' },
  895. right: { type: 'Identifier', name: 'bar' }
  896. },
  897. right: {
  898. type: 'BinaryExpression',
  899. operator: operator,
  900. left: { type: 'Identifier', name: 'man' },
  901. right: { type: 'Identifier', name: 'shell' }
  902. }
  903. }
  904. }
  905. ]
  906. }
  907. );
  908. });
  909. });
  910. it('should give higher precedence to logical `and` than to logical `or`', function() {
  911. expect(createAst('foo&&bar||man&&shell')).toEqual(
  912. {
  913. type: 'Program',
  914. body: [
  915. {
  916. type: 'ExpressionStatement',
  917. expression: {
  918. type: 'LogicalExpression',
  919. operator: '||',
  920. left: {
  921. type: 'LogicalExpression',
  922. operator: '&&',
  923. left: { type: 'Identifier', name: 'foo' },
  924. right: { type: 'Identifier', name: 'bar' }
  925. },
  926. right: {
  927. type: 'LogicalExpression',
  928. operator: '&&',
  929. left: { type: 'Identifier', name: 'man' },
  930. right: { type: 'Identifier', name: 'shell' }
  931. }
  932. }
  933. }
  934. ]
  935. }
  936. );
  937. });
  938. it('should give higher precedence to the logical `or` than to the conditional operator', function() {
  939. expect(createAst('foo||bar?man:shell')).toEqual(
  940. {
  941. type: 'Program',
  942. body: [
  943. {
  944. type: 'ExpressionStatement',
  945. expression: {
  946. type: 'ConditionalExpression',
  947. test: {
  948. type: 'LogicalExpression',
  949. operator: '||',
  950. left: { type: 'Identifier', name: 'foo' },
  951. right: { type: 'Identifier', name: 'bar' }
  952. },
  953. alternate: { type: 'Identifier', name: 'man' },
  954. consequent: { type: 'Identifier', name: 'shell' }
  955. }
  956. }
  957. ]
  958. }
  959. );
  960. });
  961. it('should give higher precedence to the conditional operator than to assignment operators', function() {
  962. expect(createAst('foo=bar?man:shell')).toEqual(
  963. {
  964. type: 'Program',
  965. body: [
  966. {
  967. type: 'ExpressionStatement',
  968. expression: {
  969. type: 'AssignmentExpression',
  970. left: { type: 'Identifier', name: 'foo' },
  971. right: {
  972. type: 'ConditionalExpression',
  973. test: { type: 'Identifier', name: 'bar' },
  974. alternate: { type: 'Identifier', name: 'man' },
  975. consequent: { type: 'Identifier', name: 'shell' }
  976. },
  977. operator: '='
  978. }
  979. }
  980. ]
  981. }
  982. );
  983. });
  984. it('should understand array literals', function() {
  985. expect(createAst('[]')).toEqual(
  986. {
  987. type: 'Program',
  988. body: [
  989. {
  990. type: 'ExpressionStatement',
  991. expression: {
  992. type: 'ArrayExpression',
  993. elements: []
  994. }
  995. }
  996. ]
  997. }
  998. );
  999. expect(createAst('[foo]')).toEqual(
  1000. {
  1001. type: 'Program',
  1002. body: [
  1003. {
  1004. type: 'ExpressionStatement',
  1005. expression: {
  1006. type: 'ArrayExpression',
  1007. elements: [
  1008. { type: 'Identifier', name: 'foo' }
  1009. ]
  1010. }
  1011. }
  1012. ]
  1013. }
  1014. );
  1015. expect(createAst('[foo,]')).toEqual(
  1016. {
  1017. type: 'Program',
  1018. body: [
  1019. {
  1020. type: 'ExpressionStatement',
  1021. expression: {
  1022. type: 'ArrayExpression',
  1023. elements: [
  1024. { type: 'Identifier', name: 'foo' }
  1025. ]
  1026. }
  1027. }
  1028. ]
  1029. }
  1030. );
  1031. expect(createAst('[foo,bar,man,shell]')).toEqual(
  1032. {
  1033. type: 'Program',
  1034. body: [
  1035. {
  1036. type: 'ExpressionStatement',
  1037. expression: {
  1038. type: 'ArrayExpression',
  1039. elements: [
  1040. { type: 'Identifier', name: 'foo' },
  1041. { type: 'Identifier', name: 'bar' },
  1042. { type: 'Identifier', name: 'man' },
  1043. { type: 'Identifier', name: 'shell' }
  1044. ]
  1045. }
  1046. }
  1047. ]
  1048. }
  1049. );
  1050. expect(createAst('[foo,bar,man,shell,]')).toEqual(
  1051. {
  1052. type: 'Program',
  1053. body: [
  1054. {
  1055. type: 'ExpressionStatement',
  1056. expression: {
  1057. type: 'ArrayExpression',
  1058. elements: [
  1059. { type: 'Identifier', name: 'foo' },
  1060. { type: 'Identifier', name: 'bar' },
  1061. { type: 'Identifier', name: 'man' },
  1062. { type: 'Identifier', name: 'shell' }
  1063. ]
  1064. }
  1065. }
  1066. ]
  1067. }
  1068. );
  1069. });
  1070. it('should understand objects', function() {
  1071. expect(createAst('{}')).toEqual(
  1072. {
  1073. type: 'Program',
  1074. body: [
  1075. {
  1076. type: 'ExpressionStatement',
  1077. expression: {
  1078. type: 'ObjectExpression',
  1079. properties: []
  1080. }
  1081. }
  1082. ]
  1083. }
  1084. );
  1085. expect(createAst('{foo: bar}')).toEqual(
  1086. {
  1087. type: 'Program',
  1088. body: [
  1089. {
  1090. type: 'ExpressionStatement',
  1091. expression: {
  1092. type: 'ObjectExpression',
  1093. properties: [
  1094. {
  1095. type: 'Property',
  1096. kind: 'init',
  1097. key: { type: 'Identifier', name: 'foo' },
  1098. value: { type: 'Identifier', name: 'bar' }
  1099. }
  1100. ]
  1101. }
  1102. }
  1103. ]
  1104. }
  1105. );
  1106. expect(createAst('{foo: bar,}')).toEqual(
  1107. {
  1108. type: 'Program',
  1109. body: [
  1110. {
  1111. type: 'ExpressionStatement',
  1112. expression: {
  1113. type: 'ObjectExpression',
  1114. properties: [
  1115. {
  1116. type: 'Property',
  1117. kind: 'init',
  1118. key: { type: 'Identifier', name: 'foo' },
  1119. value: { type: 'Identifier', name: 'bar' }
  1120. }
  1121. ]
  1122. }
  1123. }
  1124. ]
  1125. }
  1126. );
  1127. expect(createAst('{foo: bar, "man": "shell", 42: 23}')).toEqual(
  1128. {
  1129. type: 'Program',
  1130. body: [
  1131. {
  1132. type: 'ExpressionStatement',
  1133. expression: {
  1134. type: 'ObjectExpression',
  1135. properties: [
  1136. {
  1137. type: 'Property',
  1138. kind: 'init',
  1139. key: { type: 'Identifier', name: 'foo' },
  1140. value: { type: 'Identifier', name: 'bar' }
  1141. },
  1142. {
  1143. type: 'Property',
  1144. kind: 'init',
  1145. key: { type: 'Literal', value: 'man' },
  1146. value: { type: 'Literal', value: 'shell' }
  1147. },
  1148. {
  1149. type: 'Property',
  1150. kind: 'init',
  1151. key: { type: 'Literal', value: 42 },
  1152. value: { type: 'Literal', value: 23 }
  1153. }
  1154. ]
  1155. }
  1156. }
  1157. ]
  1158. }
  1159. );
  1160. expect(createAst('{foo: bar, "man": "shell", 42: 23,}')).toEqual(
  1161. {
  1162. type: 'Program',
  1163. body: [
  1164. {
  1165. type: 'ExpressionStatement',
  1166. expression: {
  1167. type: 'ObjectExpression',
  1168. properties: [
  1169. {
  1170. type: 'Property',
  1171. kind: 'init',
  1172. key: { type: 'Identifier', name: 'foo' },
  1173. value: { type: 'Identifier', name: 'bar' }
  1174. },
  1175. {
  1176. type: 'Property',
  1177. kind: 'init',
  1178. key: { type: 'Literal', value: 'man' },
  1179. value: { type: 'Literal', value: 'shell' }
  1180. },
  1181. {
  1182. type: 'Property',
  1183. kind: 'init',
  1184. key: { type: 'Literal', value: 42 },
  1185. value: { type: 'Literal', value: 23 }
  1186. }
  1187. ]
  1188. }
  1189. }
  1190. ]
  1191. }
  1192. );
  1193. });
  1194. it('should understand multiple expressions', function() {
  1195. expect(createAst('foo = bar; man = shell')).toEqual(
  1196. {
  1197. type: 'Program',
  1198. body: [
  1199. {
  1200. type: 'ExpressionStatement',
  1201. expression: {
  1202. type: 'AssignmentExpression',
  1203. left: { type: 'Identifier', name: 'foo' },
  1204. right: { type: 'Identifier', name: 'bar' },
  1205. operator: '='
  1206. }
  1207. },
  1208. {
  1209. type: 'ExpressionStatement',
  1210. expression: {
  1211. type: 'AssignmentExpression',
  1212. left: { type: 'Identifier', name: 'man' },
  1213. right: { type: 'Identifier', name: 'shell' },
  1214. operator: '='
  1215. }
  1216. }
  1217. ]
  1218. }
  1219. );
  1220. });
  1221. // This is non-standard syntax
  1222. it('should understand filters', function() {
  1223. expect(createAst('foo | bar')).toEqual(
  1224. {
  1225. type: 'Program',
  1226. body: [
  1227. {
  1228. type: 'ExpressionStatement',
  1229. expression: {
  1230. type: 'CallExpression',
  1231. callee: { type: 'Identifier', name: 'bar'},
  1232. arguments: [
  1233. { type: 'Identifier', name: 'foo' }
  1234. ],
  1235. filter: true
  1236. }
  1237. }
  1238. ]
  1239. }
  1240. );
  1241. });
  1242. it('should understand filters with extra parameters', function() {
  1243. expect(createAst('foo | bar:baz')).toEqual(
  1244. {
  1245. type: 'Program',
  1246. body: [
  1247. {
  1248. type: 'ExpressionStatement',
  1249. expression: {
  1250. type: 'CallExpression',
  1251. callee: { type: 'Identifier', name: 'bar'},
  1252. arguments: [
  1253. { type: 'Identifier', name: 'foo' },
  1254. { type: 'Identifier', name: 'baz' }
  1255. ],
  1256. filter: true
  1257. }
  1258. }
  1259. ]
  1260. }
  1261. );
  1262. });
  1263. it('should associate filters right-to-left', function() {
  1264. expect(createAst('foo | bar:man | shell')).toEqual(
  1265. {
  1266. type: 'Program',
  1267. body: [
  1268. {
  1269. type: 'ExpressionStatement',
  1270. expression: {
  1271. type: 'CallExpression',
  1272. callee: { type: 'Identifier', name: 'shell' },
  1273. arguments: [
  1274. {
  1275. type: 'CallExpression',
  1276. callee: { type: 'Identifier', name: 'bar' },
  1277. arguments: [
  1278. { type: 'Identifier', name: 'foo' },
  1279. { type: 'Identifier', name: 'man' }
  1280. ],
  1281. filter: true
  1282. }
  1283. ],
  1284. filter: true
  1285. }
  1286. }
  1287. ]
  1288. }
  1289. );
  1290. });
  1291. it('should give higher precedence to assignments over filters', function() {
  1292. expect(createAst('foo=bar | man')).toEqual(
  1293. {
  1294. type: 'Program',
  1295. body: [
  1296. {
  1297. type: 'ExpressionStatement',
  1298. expression: {
  1299. type: 'CallExpression',
  1300. callee: { type: 'Identifier', name: 'man' },
  1301. arguments: [
  1302. {
  1303. type: 'AssignmentExpression',
  1304. left: { type: 'Identifier', name: 'foo' },
  1305. right: { type: 'Identifier', name: 'bar' },
  1306. operator: '='
  1307. }
  1308. ],
  1309. filter: true
  1310. }
  1311. }
  1312. ]
  1313. }
  1314. );
  1315. });
  1316. it('should accept expression as filters parameters', function() {
  1317. expect(createAst('foo | bar:baz=man')).toEqual(
  1318. {
  1319. type: 'Program',
  1320. body: [
  1321. {
  1322. type: 'ExpressionStatement',
  1323. expression: {
  1324. type: 'CallExpression',
  1325. callee: { type: 'Identifier', name: 'bar' },
  1326. arguments: [
  1327. { type: 'Identifier', name: 'foo' },
  1328. {
  1329. type: 'AssignmentExpression',
  1330. left: { type: 'Identifier', name: 'baz' },
  1331. right: { type: 'Identifier', name: 'man' },
  1332. operator: '='
  1333. }
  1334. ],
  1335. filter: true
  1336. }
  1337. }
  1338. ]
  1339. }
  1340. );
  1341. });
  1342. it('should accept expression as computer members', function() {
  1343. expect(createAst('foo[a = 1]')).toEqual(
  1344. {
  1345. type: 'Program',
  1346. body: [
  1347. {
  1348. type: 'ExpressionStatement',
  1349. expression: {
  1350. type: 'MemberExpression',
  1351. object: { type: 'Identifier', name: 'foo' },
  1352. property: {
  1353. type: 'AssignmentExpression',
  1354. left: { type: 'Identifier', name: 'a' },
  1355. right: { type: 'Literal', value: 1 },
  1356. operator: '='
  1357. },
  1358. computed: true
  1359. }
  1360. }
  1361. ]
  1362. }
  1363. );
  1364. });
  1365. it('should accept expression in function arguments', function() {
  1366. expect(createAst('foo(a = 1)')).toEqual(
  1367. {
  1368. type: 'Program',
  1369. body: [
  1370. {
  1371. type: 'ExpressionStatement',
  1372. expression: {
  1373. type: 'CallExpression',
  1374. callee: { type: 'Identifier', name: 'foo' },
  1375. arguments: [
  1376. {
  1377. type: 'AssignmentExpression',
  1378. left: { type: 'Identifier', name: 'a' },
  1379. right: { type: 'Literal', value: 1 },
  1380. operator: '='
  1381. }
  1382. ]
  1383. }
  1384. }
  1385. ]
  1386. }
  1387. );
  1388. });
  1389. it('should accept expression as part of ternary operators', function() {
  1390. expect(createAst('foo || bar ? man = 1 : shell = 1')).toEqual(
  1391. {
  1392. type: 'Program',
  1393. body: [
  1394. {
  1395. type: 'ExpressionStatement',
  1396. expression: {
  1397. type: 'ConditionalExpression',
  1398. test: {
  1399. type: 'LogicalExpression',
  1400. operator: '||',
  1401. left: { type: 'Identifier', name: 'foo' },
  1402. right: { type: 'Identifier', name: 'bar' }
  1403. },
  1404. alternate: {
  1405. type: 'AssignmentExpression',
  1406. left: { type: 'Identifier', name: 'man' },
  1407. right: { type: 'Literal', value: 1 },
  1408. operator: '='
  1409. },
  1410. consequent: {
  1411. type: 'AssignmentExpression',
  1412. left: { type: 'Identifier', name: 'shell' },
  1413. right: { type: 'Literal', value: 1 },
  1414. operator: '='
  1415. }
  1416. }
  1417. }
  1418. ]
  1419. }
  1420. );
  1421. });
  1422. it('should accept expression as part of array literals', function() {
  1423. expect(createAst('[foo = 1]')).toEqual(
  1424. {
  1425. type: 'Program',
  1426. body: [
  1427. {
  1428. type: 'ExpressionStatement',
  1429. expression: {
  1430. type: 'ArrayExpression',
  1431. elements: [
  1432. {
  1433. type: 'AssignmentExpression',
  1434. left: { type: 'Identifier', name: 'foo' },
  1435. right: { type: 'Literal', value: 1 },
  1436. operator: '='
  1437. }
  1438. ]
  1439. }
  1440. }
  1441. ]
  1442. }
  1443. );
  1444. });
  1445. it('should accept expression as part of object literals', function() {
  1446. expect(createAst('{foo: bar = 1}')).toEqual(
  1447. {
  1448. type: 'Program',
  1449. body: [
  1450. {
  1451. type: 'ExpressionStatement',
  1452. expression: {
  1453. type: 'ObjectExpression',
  1454. properties: [
  1455. {
  1456. type: 'Property',
  1457. kind: 'init',
  1458. key: { type: 'Identifier', name: 'foo' },
  1459. value: {
  1460. type: 'AssignmentExpression',
  1461. left: { type: 'Identifier', name: 'bar' },
  1462. right: { type: 'Literal', value: 1 },
  1463. operator: '='
  1464. }
  1465. }
  1466. ]
  1467. }
  1468. }
  1469. ]
  1470. }
  1471. );
  1472. });
  1473. it('should be possible to use parenthesis to indicate precedence', function() {
  1474. expect(createAst('(foo + bar).man')).toEqual(
  1475. {
  1476. type: 'Program',
  1477. body: [
  1478. {
  1479. type: 'ExpressionStatement',
  1480. expression: {
  1481. type: 'MemberExpression',
  1482. object: {
  1483. type: 'BinaryExpression',
  1484. operator: '+',
  1485. left: { type: 'Identifier', name: 'foo' },
  1486. right: { type: 'Identifier', name: 'bar' }
  1487. },
  1488. property: { type: 'Identifier', name: 'man' },
  1489. computed: false
  1490. }
  1491. }
  1492. ]
  1493. }
  1494. );
  1495. });
  1496. it('should skip empty expressions', function() {
  1497. expect(createAst('foo;;;;bar')).toEqual(
  1498. {
  1499. type: 'Program',
  1500. body: [
  1501. {
  1502. type: 'ExpressionStatement',
  1503. expression: { type: 'Identifier', name: 'foo' }
  1504. },
  1505. {
  1506. type: 'ExpressionStatement',
  1507. expression: { type: 'Identifier', name: 'bar' }
  1508. }
  1509. ]
  1510. }
  1511. );
  1512. expect(createAst(';foo')).toEqual(
  1513. {
  1514. type: 'Program',
  1515. body: [
  1516. {
  1517. type: 'ExpressionStatement',
  1518. expression: { type: 'Identifier', name: 'foo' }
  1519. }
  1520. ]
  1521. }
  1522. );
  1523. expect(createAst('foo;')).toEqual({
  1524. type: 'Program',
  1525. body: [
  1526. {
  1527. type: 'ExpressionStatement',
  1528. expression: { type: 'Identifier', name: 'foo' }
  1529. }
  1530. ]
  1531. });
  1532. expect(createAst(';;;;')).toEqual({type: 'Program', body: []});
  1533. expect(createAst('')).toEqual({type: 'Program', body: []});

Large files files are truncated, but you can click here to view the full file