PageRenderTime 604ms CodeModel.GetById 62ms app.highlight 503ms RepoModel.GetById 19ms app.codeStats 2ms

/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

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

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

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