From e271168cf604611a176f2c9452dcabde4bceab36 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 4 Jan 2015 22:13:18 +0100 Subject: [PATCH 01/36] Make onToken only fire for actual final tokens Issue #189 --- acorn.js | 11 ++++------- test/tests.js | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/acorn.js b/acorn.js index aa9b60664e..1fec39199f 100644 --- a/acorn.js +++ b/acorn.js @@ -634,9 +634,6 @@ if (shouldSkipSpace !== false) skipSpace(); tokVal = val; tokRegexpAllowed = type.beforeExpr; - if (options.onToken) { - options.onToken(new Token()); - } } function skipBlockComment() { @@ -1232,6 +1229,9 @@ // Continue to the next token. function next() { + if (options.onToken) + options.onToken(new Token()); + lastStart = tokStart; lastEnd = tokEnd; lastEndLoc = tokEndLoc; @@ -1542,9 +1542,7 @@ first = false; } - lastStart = tokStart; - lastEnd = tokEnd; - lastEndLoc = tokEndLoc; + next(); return finishNode(node, "Program"); } @@ -2664,5 +2662,4 @@ node.generator = isGenerator; return finishNode(node, "ComprehensionExpression"); } - }); diff --git a/test/tests.js b/test/tests.js index f6df2edc5e..f1d7ce340a 100644 --- a/test/tests.js +++ b/test/tests.js @@ -28757,6 +28757,7 @@ var tokTypes = acorn.tokTypes; test('var x = (1 + 2)', {}, { locations: true, + loose: false, onToken: [ { type: tokTypes._var, From 045d8c02f5f5be5d1796edd4b00de80baa0ef945 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 4 Jan 2015 22:28:16 +0100 Subject: [PATCH 02/36] Revert "Require superclass expressions to be lvals" This reverts commit 0b59fc198baf2cd970091ee96fe5dd0f90034692. Issue #187 --- acorn.js | 1 - test/tests-harmony.js | 66 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/acorn.js b/acorn.js index 1fec39199f..4212ac3d7e 100644 --- a/acorn.js +++ b/acorn.js @@ -2413,7 +2413,6 @@ next(); node.id = tokType === _name ? parseIdent() : isStatement ? unexpected() : null; node.superClass = eat(_extends) ? parseExpression() : null; - if (node.superClass) checkLVal(node.superClass); var classBody = startNode(); classBody.body = []; expect(_braceL); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index f59838c0d3..74a927f0d0 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -6465,6 +6465,72 @@ test("var A = class extends B {}", { locations: true }); +test("class A extends class B extends C {} {}", { + type: "Program", + body: [{ + type: "ClassDeclaration", + id: { + type: "Identifier", + name: "A", + loc: { + start: {line: 1, column: 6}, + end: {line: 1, column: 7} + } + }, + superClass: { + type: "ClassExpression", + id: { + type: "Identifier", + name: "B", + loc: { + start: {line: 1, column: 22}, + end: {line: 1, column: 23} + } + }, + superClass: { + type: "Identifier", + name: "C", + loc: { + start: {line: 1, column: 32}, + end: {line: 1, column: 33} + } + }, + body: { + type: "ClassBody", + body: [], + loc: { + start: {line: 1, column: 34}, + end: {line: 1, column: 36} + } + }, + loc: { + start: {line: 1, column: 16}, + end: {line: 1, column: 36} + } + }, + body: { + type: "ClassBody", + body: [], + loc: { + start: {line: 1, column: 37}, + end: {line: 1, column: 39} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 39} + } + }], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 39} + } +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); + test("class A {get() {}}", { type: "Program", body: [{ From 7c6a8b7c7618d8d466641a50ce39ecc8f295e5ec Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 4 Jan 2015 22:30:50 +0100 Subject: [PATCH 03/36] Use parseExprSubscripts instead of parseExpression when parsing superclass Issue #187 --- acorn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acorn.js b/acorn.js index 4212ac3d7e..30b87d6e5a 100644 --- a/acorn.js +++ b/acorn.js @@ -2412,7 +2412,7 @@ function parseClass(node, isStatement) { next(); node.id = tokType === _name ? parseIdent() : isStatement ? unexpected() : null; - node.superClass = eat(_extends) ? parseExpression() : null; + node.superClass = eat(_extends) ? parseExprSubscripts() : null; var classBody = startNode(); classBody.body = []; expect(_braceL); From 459a16926227fa506d145910cc2e456b86fe6b6d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 4 Jan 2015 22:34:42 +0100 Subject: [PATCH 04/36] Make tests pass again for loose parser Issue #181 --- acorn_loose.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/acorn_loose.js b/acorn_loose.js index a0d822a8a5..74a5816a28 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -304,6 +304,7 @@ case "ObjectPattern": case "ArrayPattern": case "SpreadElement": + case "AssignmentPattern": return expr; default: @@ -983,6 +984,13 @@ case "SpreadElement": node.argument = toAssignable(node.argument); break; + + case "AssignmentExpression": + if (node.operator === "=") + node.type = "AssignmentPattern"; + else + unexpected(node.left.end); + break; } } return checkLVal(node); From af0debc8499c0e2521610e7ad67ec7d47058d0c7 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 2 Jan 2015 16:07:03 -0800 Subject: [PATCH 05/36] Add support for running just the tokenizer to the bin/acorn script. --- bin/acorn | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/bin/acorn b/bin/acorn index 2a226da063..ca6e32a0c6 100755 --- a/bin/acorn +++ b/bin/acorn @@ -4,12 +4,12 @@ var path = require("path"); var fs = require("fs"); var acorn = require("../acorn.js"); -var infile, parsed, options = {}, silent = false, compact = false; +var infile, parsed, tokens, options = {}, silent = false, compact = false, tokenize = false; function help(status) { var print = (status == 0) ? console.log : console.error; print("usage: " + path.basename(process.argv[1]) + " [--ecma3|--ecma5|--ecma6] [--strictSemicolons]"); - print(" [--locations] [--compact] [--silent] [--help] [--] infile"); + print(" [--tokenize] [--locations] [--compact] [--silent] [--help] [--] infile"); process.exit(status); } @@ -25,16 +25,29 @@ for (var i = 2; i < process.argv.length; ++i) { else if (arg == "--silent") silent = true; else if (arg == "--compact") compact = true; else if (arg == "--help") help(0); + else if (arg == "--tokenize") tokenize = true; else help(1); } try { var code = fs.readFileSync(infile, "utf8"); - parsed = acorn.parse(code, options); + + if (!tokenize) + parsed = acorn.parse(code, options); + else { + var get = acorn.tokenize(code, options); + tokens = []; + while (true) { + var token = get(); + tokens.push(token); + if (token.type.type == "eof") + break; + } + } } catch(e) { console.log(e.message); process.exit(1); } if (!silent) - console.log(JSON.stringify(parsed, null, compact ? null : 2)); + console.log(JSON.stringify(tokenize ? tokens : parsed, null, compact ? null : 2)); From b6b085ac8e3623ec9204a918e3fc4ea40dd8db02 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 5 Jan 2015 23:23:24 +0100 Subject: [PATCH 06/36] Make tokenizer independent of parser (with regards to '/' disambiguation) Issue #189 --- acorn.js | 70 +++++++++++++++++++++++++++++--------------------- acorn_loose.js | 1 - test/tests.js | 2 ++ 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/acorn.js b/acorn.js index 30b87d6e5a..64b59ed259 100644 --- a/acorn.js +++ b/acorn.js @@ -223,12 +223,12 @@ initTokenState(); skipSpace(); - function getToken(forceRegexp) { + function getToken() { lastEnd = tokEnd; - readToken(forceRegexp); + readToken(); return new Token(); } - getToken.jumpTo = function(pos, reAllowed) { + getToken.jumpTo = function(pos, exprAllowed) { tokPos = pos; if (options.locations) { tokCurLine = 1; @@ -239,12 +239,9 @@ tokLineStart = match.index + match[0].length; } } - tokRegexpAllowed = reAllowed; + tokExprAllowed = !!exprAllowed; skipSpace(); }; - getToken.noRegexp = function() { - tokRegexpAllowed = false; - }; getToken.options = options; return getToken; }; @@ -277,12 +274,13 @@ // Internal state for the tokenizer. To distinguish between division // operators and regular expressions, it remembers whether the last - // token was one that is allowed to be followed by an expression. - // (If it is, a slash is probably a regexp, if it isn't it's a - // division operator. See the `parseStatement` function for a - // caveat.) + // token was one that is allowed to be followed by an expression. In + // some cases, notably after ')' or '}' tokens, the situation + // depends on the context before the matching opening bracket, so + // tokContext keeps a stack of information about current bracketed + // forms. - var tokRegexpAllowed; + var tokContext, tokExprAllowed; // When `options.locations` is true, these are used to keep // track of the current line, and know when a new line has been @@ -615,7 +613,8 @@ tokCurLine = 1; tokPos = tokLineStart = 0; } - tokRegexpAllowed = true; + tokContext = []; + tokExprAllowed = true; metParenL = 0; templates = []; if (tokPos === 0 && options.allowHashBang && input.slice(0, 2) === '#!') { @@ -624,16 +623,39 @@ } // Called at the end of every token. Sets `tokEnd`, `tokVal`, and - // `tokRegexpAllowed`, and skips the space after the token, so that - // the next one's `tokStart` will point at the right position. + // maintains `tokContext` and `tokExprAllowed`, and skips the space + // after the token, so that the next one's `tokStart` will point at + // the right position. function finishToken(type, val, shouldSkipSpace) { tokEnd = tokPos; if (options.locations) tokEndLoc = curPosition(); + var prevType = tokType; tokType = type; if (shouldSkipSpace !== false) skipSpace(); tokVal = val; - tokRegexpAllowed = type.beforeExpr; + + // Update context info + if (type == _parenR || type == _braceR) { + tokExprAllowed = tokContext.pop(); + } else if (type == _braceL) { + var ctx = tokExprAllowed === false; + if (prevType == _return && newline.test(input.slice(lastEnd, tokStart))) { + console.log("fired"); + ctx = false; + } + tokContext.push(ctx); + } else if (type == _parenL) { + tokContext.push(prevType == _if || prevType == _for || prevType == _with || prevType == _while); + } else if (type == _incDec) { + // tokExprAllowed stays unchanged + } else if (type.keyword && prevType == _dot) { + tokExprAllowed = false; + } else if (tokExprAllowed && type == _function) { + tokExprAllowed = null; + } else { + tokExprAllowed = type.beforeExpr; + } } function skipBlockComment() { @@ -719,9 +741,6 @@ // // All in the name of speed. // - // The `forceRegexp` parameter is used in the one case where the - // `tokRegexpAllowed` trick does not work. See `parseStatement`. - function readToken_dot() { var next = input.charCodeAt(tokPos + 1); if (next >= 48 && next <= 57) return readNumber(true); @@ -737,7 +756,7 @@ function readToken_slash() { // '/' var next = input.charCodeAt(tokPos + 1); - if (tokRegexpAllowed) {++tokPos; return readRegexp();} + if (tokExprAllowed) {++tokPos; return readRegexp();} if (next === 61) return finishOp(_assign, 2); return finishOp(_slash, 1); } @@ -889,11 +908,9 @@ return false; } - function readToken(forceRegexp) { - if (!forceRegexp) tokStart = tokPos; - else tokPos = tokStart + 1; + function readToken() { + tokStart = tokPos; if (options.locations) tokStartLoc = curPosition(); - if (forceRegexp) return readRegexp(); if (tokPos >= inputLen) return finishToken(_eof); var code = input.charCodeAt(tokPos); @@ -1556,9 +1573,6 @@ // does not help. function parseStatement(topLevel) { - if (tokType === _slash || tokType === _assign && tokVal == "/=") - readToken(true); - var starttype = tokType, node = startNode(); // Most types of statements are recognized by the keyword they @@ -1993,7 +2007,6 @@ node.operator = tokVal; node.prefix = true; } - tokRegexpAllowed = true; next(); node.argument = parseMaybeUnary(); if (update) checkLVal(node.argument); @@ -2482,7 +2495,6 @@ } else { unexpected(); } - tokRegexpAllowed = false; next(); return finishNode(node, "Identifier"); } diff --git a/acorn_loose.js b/acorn_loose.js index 74a5816a28..3cd8e89f6b 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -943,7 +943,6 @@ function parseIdent() { var node = startNode(); node.name = token.type === tt.name ? token.value : token.type.keyword; - fetchToken.noRegexp(); next(); return finishNode(node, "Identifier"); } diff --git a/test/tests.js b/test/tests.js index f1d7ce340a..105a7255b2 100644 --- a/test/tests.js +++ b/test/tests.js @@ -26687,6 +26687,8 @@ test("a.in / b", { ] }); +test("return {}\n/foo/", {}, {allowReturnOutsideFunction: true}); + test("{}/=/", { type: "Program", body: [ From 0897901f1fe628fd9bd547f637b540266729d04f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 6 Jan 2015 11:03:50 +0100 Subject: [PATCH 07/36] Slight cleanup of '/' disambiguation Issue #189 --- acorn.js | 44 ++++++++++++++++++++++++++++++++------------ acorn_loose.js | 2 -- test/tests.js | 16 +++++++++++++++- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/acorn.js b/acorn.js index 64b59ed259..b116315ada 100644 --- a/acorn.js +++ b/acorn.js @@ -596,7 +596,7 @@ Position.prototype.offset = function(n) { return new Position(this.line, this.column + n); - } + }; function curPosition() { return new Position(tokCurLine, tokPos - tokLineStart); @@ -613,6 +613,7 @@ tokCurLine = 1; tokPos = tokLineStart = 0; } + tokType = _eof; tokContext = []; tokExprAllowed = true; metParenL = 0; @@ -622,6 +623,26 @@ } } + // The algorithm used to determine whether a regexp can appear at a + // given point in the program is loosely based on sweet.js' approach. + // See https://github.com/mozilla/sweet.js/wiki/design + + var b_stat = {token: "{", isExpr: false}, b_expr = {token: "{", isExpr: true}; + var p_stat = {token: "(", isExpr: false}, p_expr = {token: "(", isExpr: true}; + + function braceIsBlock(prevType) { + var parent; + if (prevType === _colon && (parent = tokContext[tokContext.length - 1]).token == "{") + return !parent.isExpr; + if (prevType === _return) + return newline.test(input.slice(lastEnd, tokStart)); + if (prevType === _else || prevType === _semi || prevType === _eof) + return true; + if (prevType == _braceL) + return tokContext[tokContext.length - 1] === b_stat; + return !tokExprAllowed; + } + // Called at the end of every token. Sets `tokEnd`, `tokVal`, and // maintains `tokContext` and `tokExprAllowed`, and skips the space // after the token, so that the next one's `tokStart` will point at @@ -636,17 +657,16 @@ tokVal = val; // Update context info - if (type == _parenR || type == _braceR) { - tokExprAllowed = tokContext.pop(); - } else if (type == _braceL) { - var ctx = tokExprAllowed === false; - if (prevType == _return && newline.test(input.slice(lastEnd, tokStart))) { - console.log("fired"); - ctx = false; - } - tokContext.push(ctx); + if (type === _parenR || type === _braceR) { + var out = tokContext.pop(); + tokExprAllowed = !(out && out.isExpr); + } else if (type === _braceL) { + tokContext.push(braceIsBlock(prevType) ? b_stat : b_expr); + tokExprAllowed = true; } else if (type == _parenL) { - tokContext.push(prevType == _if || prevType == _for || prevType == _with || prevType == _while); + var statementParens = prevType === _if || prevType === _for || prevType === _with || prevType === _while; + tokContext.push(statementParens ? p_stat : p_expr); + tokExprAllowed = true; } else if (type == _incDec) { // tokExprAllowed stays unchanged } else if (type.keyword && prevType == _dot) { @@ -1089,7 +1109,7 @@ out += readEscapedChar(); } else { ++tokPos; - if (newline.test(String.fromCharCode(ch))) { + if (isNewLine(ch)) { raise(tokStart, "Unterminated string constant"); } out += String.fromCharCode(ch); // '\' diff --git a/acorn_loose.js b/acorn_loose.js index 3cd8e89f6b..8bc6dfda26 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -987,8 +987,6 @@ case "AssignmentExpression": if (node.operator === "=") node.type = "AssignmentPattern"; - else - unexpected(node.left.end); break; } } diff --git a/test/tests.js b/test/tests.js index 105a7255b2..172eb1fb87 100644 --- a/test/tests.js +++ b/test/tests.js @@ -26687,7 +26687,21 @@ test("a.in / b", { ] }); -test("return {}\n/foo/", {}, {allowReturnOutsideFunction: true}); +// A number of slash-disambiguation corner cases +test("return {} / 2", {}, {allowReturnOutsideFunction: true}); +test("return\n{}\n/foo/", {}, {allowReturnOutsideFunction: true}); +test("+{} / 2", {}); +test("{}\n/foo/", {}); +test("x++\n{}\n/foo/", {}); +test("{{}\n/foo/}", {}); +test("while (1) /foo/", {}); +test("(1) / 2", {}); +test("({a: [1]}+[]) / 2", {}); +test("{[1]}\n/foo/", {}); +test("switch(a) { case 1: {}\n/foo/ }", {}); +test("({1: {} / 2})", {}); +test("+x++ / 2", {}); +test("foo.in\n{}\n/foo/", {}); test("{}/=/", { type: "Program", From db59bd0296c16796a07e82d2dc284861f464603e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 6 Jan 2015 11:15:34 +0100 Subject: [PATCH 08/36] Remove outdated note about loose parser not supporting ES6 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cac0f0e46..5f790a8592 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ but never raises an error, and will do its best to parse syntactically invalid code in as meaningful a way as it can. It'll insert identifier nodes with name `"✖"` as placeholders in places where it can't make sense of the input. Depends on `acorn.js`, because it uses the same -tokenizer. The loose parser does not support ECMAScript 6 syntax yet. +tokenizer. ### util/walk.js ### From 4d4a76588c58c37fea3942a8c7c4fde6a0c6d914 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 8 Jan 2015 17:27:13 +0200 Subject: [PATCH 09/36] Parse assignment patterns in-place in certain contexts. * Parsing assignables without extra transform step when possible (speed-up). * Added support for shorthand defaults in such certain contexts (issue #181). --- acorn.js | 75 +++++++++++++++++++++++---- acorn_loose.js | 3 +- test/tests-harmony.js | 117 +++++++++++++++++++++++++++++++++++------- 3 files changed, 163 insertions(+), 32 deletions(-) diff --git a/acorn.js b/acorn.js index b116315ada..20d4b35ccd 100644 --- a/acorn.js +++ b/acorn.js @@ -1414,6 +1414,7 @@ function has(obj, propName) { return Object.prototype.hasOwnProperty.call(obj, propName); } + // Convert existing expression atom to assignable pattern // if possible. @@ -1464,6 +1465,53 @@ return node; } + // Parses lvalue (assignable) atom. + + function parseAssignableAtom() { + if (options.ecmaVersion < 6) return parseIdent(); + switch (tokType) { + case _name: + return parseIdent(); + + case _bracketL: + var node = startNode(); + next(); + var elts = node.elements = [], first = true; + while (!eat(_bracketR)) { + first ? first = false : expect(_comma); + if (tokType === _ellipsis) { + var spread = startNode(); + next(); + spread.argument = parseAssignableAtom(); + checkSpreadAssign(spread.argument); + elts.push(finishNode(spread, "SpreadElement")); + expect(_bracketR); + break; + } + elts.push(tokType === _comma ? null : parseMaybeDefault()); + } + return finishNode(node, "ArrayPattern"); + + case _braceL: + return parseObj(true); + + default: + unexpected(); + } + } + + // Parses assignment pattern around given atom if possible. + + function parseMaybeDefault(startPos, left) { + left = left || parseAssignableAtom(); + if (!eat(_eq)) return left; + var node = startPos ? startNodeAt(startPos) : startNode(); + node.operator = "="; + node.left = left; + node.right = parseMaybeAssign(); + return finishNode(node, "AssignmentPattern"); + } + // Checks if node can be assignable spread argument. function checkSpreadAssign(node) { @@ -1917,7 +1965,7 @@ node.kind = kind; for (;;) { var decl = startNode(); - decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent(); + decl.id = parseAssignableAtom(); checkLVal(decl.id, true); decl.init = eat(_eq) ? parseExpression(true, noIn) : (kind === _const.keyword ? unexpected() : null); node.declarations.push(finishNode(decl, "VariableDeclarator")); @@ -2238,7 +2286,7 @@ // Parse an object literal. - function parseObj() { + function parseObj(isPattern) { var node = startNode(), first = true, propHash = {}; node.properties = []; next(); @@ -2248,37 +2296,42 @@ if (options.allowTrailingCommas && eat(_braceR)) break; } else first = false; - var prop = startNode(), isGenerator; + var prop = startNode(), isGenerator, start; if (options.ecmaVersion >= 6) { prop.method = false; prop.shorthand = false; - isGenerator = eat(_star); + if (isPattern) { + start = storeCurrentPos(); + } else { + isGenerator = eat(_star); + } } parsePropertyName(prop); if (eat(_colon)) { - prop.value = parseExpression(true); + prop.value = isPattern ? parseMaybeDefault(start) : parseMaybeAssign(); prop.kind = "init"; } else if (options.ecmaVersion >= 6 && tokType === _parenL) { + if (isPattern) unexpected(); prop.kind = "init"; prop.method = true; prop.value = parseMethod(isGenerator); } else if (options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set") && (tokType != _comma && tokType != _braceR)) { - if (isGenerator) unexpected(); + if (isGenerator || isPattern) unexpected(); prop.kind = prop.key.name; parsePropertyName(prop); prop.value = parseMethod(false); } else if (options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { prop.kind = "init"; - prop.value = prop.key; + prop.value = isPattern ? parseMaybeDefault(start, prop.key) : prop.key; prop.shorthand = true; } else unexpected(); checkPropClash(prop, propHash); node.properties.push(finishNode(prop, "Property")); } - return finishNode(node, "ObjectExpression"); + return finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression"); } function parsePropertyName(prop) { @@ -2382,13 +2435,13 @@ if (eat(_parenR)) { break; } else if (options.ecmaVersion >= 6 && eat(_ellipsis)) { - node.rest = toAssignable(parseExprAtom(), false, true); + node.rest = parseAssignableAtom(); checkSpreadAssign(node.rest); expect(_parenR); defaults.push(null); break; } else { - node.params.push(options.ecmaVersion >= 6 ? toAssignable(parseExprAtom(), false, true) : parseIdent()); + node.params.push(parseAssignableAtom()); if (options.ecmaVersion >= 6) { if (eat(_eq)) { hasDefaults = true; @@ -2676,7 +2729,7 @@ var block = startNode(); next(); expect(_parenL); - block.left = toAssignable(parseExprAtom()); + block.left = parseAssignableAtom(); checkLVal(block.left, true); if (tokType !== _name || tokVal !== "of") unexpected(); next(); diff --git a/acorn_loose.js b/acorn_loose.js index 8bc6dfda26..33a3cfe9d4 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -985,8 +985,7 @@ break; case "AssignmentExpression": - if (node.operator === "=") - node.type = "AssignmentPattern"; + node.type = "AssignmentPattern"; break; } } diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 74a927f0d0..4c571b3ac5 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14444,30 +14444,39 @@ test('var {get} = obj;', { test("var {propName: localVar = defaultValue} = obj", { type: "Program", + range: [0, 45], body: [{ type: "VariableDeclaration", + range: [0, 45], declarations: [{ type: "VariableDeclarator", + range: [4, 45], id: { type: "ObjectPattern", + range: [4, 39], properties: [{ type: "Property", + range: [5, 38], method: false, shorthand: false, computed: false, key: { type: "Identifier", + range: [5, 13], name: "propName" }, value: { type: "AssignmentPattern", + range: [5, 38], operator: "=", left: { type: "Identifier", + range: [15, 23], name: "localVar" }, right: { type: "Identifier", + range: [26, 38], name: "defaultValue" } }, @@ -14476,44 +14485,114 @@ test("var {propName: localVar = defaultValue} = obj", { }, init: { type: "Identifier", + range: [42, 45], name: "obj" } }], kind: "var" }] -}, {ecmaVersion: 6}); +}, { + ecmaVersion: 6, + ranges: true, + locations: true, + loose: false +}); -test("var [a = 1, b = 2] = arr", { +test("var {propName = defaultValue} = obj", { type: "Program", + range: [0, 35], body: [{ type: "VariableDeclaration", + range: [0, 35], declarations: [{ type: "VariableDeclarator", + range: [4, 35], id: { - type: "ArrayPattern", - elements: [ - { - type: "AssignmentPattern", - operator: "=", - left: {type: "Identifier", name: "a"}, - right: { - type: "Literal", - value: 1 - } + type: "ObjectPattern", + range: [4, 29], + properties: [{ + type: "Property", + range: [5, 28], + method: false, + shorthand: true, + computed: false, + key: { + type: "Identifier", + range: [5, 13], + name: "propName" }, - { + kind: "init", + value: { type: "AssignmentPattern", + range: [5, 28], operator: "=", - left: {type: "Identifier", name: "b"}, + left: { + type: "Identifier", + range: [5, 13], + name: "propName" + }, right: { - type: "Literal", - value: 2 + type: "Identifier", + range: [16, 28], + name: "defaultValue" } } - ] + }] }, - init: {type: "Identifier", name: "arr"} + init: { + type: "Identifier", + range: [32, 35], + name: "obj" + } }], kind: "var" }] -}, {ecmaVersion: 6}); +}, { + ecmaVersion: 6, + ranges: true, + locations: true, + loose: false +}); + +test("var [localVar = defaultValue] = obj", { + type: "Program", + range: [0, 35], + body: [{ + type: "VariableDeclaration", + range: [0, 35], + declarations: [{ + type: "VariableDeclarator", + range: [4, 35], + id: { + type: "ArrayPattern", + range: [4, 29], + elements: [{ + type: "AssignmentPattern", + range: [16, 28], + operator: "=", + left: { + type: "Identifier", + range: [5, 13], + name: "localVar" + }, + right: { + type: "Identifier", + range: [16, 28], + name: "defaultValue" + } + }] + }, + init: { + type: "Identifier", + range: [32, 35], + name: "obj" + } + }], + kind: "var" + }] +}, { + ecmaVersion: 6, + ranges: true, + locations: true, + loose: false +}); From 85087f2a0951f60ca035ca6d785e4bab6d96d8f7 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 8 Jan 2015 23:00:22 +0200 Subject: [PATCH 10/36] Disallow parentheses in lvalue except as in computed keys or default values. --- acorn.js | 7 +++++++ test/tests-harmony.js | 20 +++++++++++--------- test/tests.js | 4 ++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/acorn.js b/acorn.js index 20d4b35ccd..ff3881bb88 100644 --- a/acorn.js +++ b/acorn.js @@ -2003,14 +2003,18 @@ function parseMaybeAssign(noIn) { var start = storeCurrentPos(); + var oldParenL = metParenL; var left = parseMaybeConditional(noIn); if (tokType.isAssign) { + if (metParenL !== oldParenL) raise(tokStart, "Assigning to rvalue"); var node = startNodeAt(start); node.operator = tokVal; node.left = tokType === _eq ? toAssignable(left) : left; checkLVal(left); next(); node.right = parseMaybeAssign(noIn); + // restore count of '(' so they are allowed in lvalue's defaults + metParenL = oldParenL; return finishNode(node, "AssignmentExpression"); } return left; @@ -2338,7 +2342,10 @@ if (options.ecmaVersion >= 6) { if (eat(_bracketL)) { prop.computed = true; + // save & restore count of '(' so they are allowed in lvalue's computed props + var oldParenL = metParenL; prop.key = parseExpression(); + metParenL = oldParenL; expect(_bracketR); return; } else { diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 4c571b3ac5..bb8406582b 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -3940,7 +3940,7 @@ test("[a, b] = [b, a]", { locations: true }); -test("({ responseText: text }) = res", { +test("({ responseText: text } = res)", { type: "Program", body: [{ type: "ExpressionStatement", @@ -3985,13 +3985,13 @@ test("({ responseText: text }) = res", { type: "Identifier", name: "res", loc: { - start: {line: 1, column: 27}, - end: {line: 1, column: 30} + start: {line: 1, column: 26}, + end: {line: 1, column: 29} } }, loc: { - start: {line: 1, column: 0}, - end: {line: 1, column: 30} + start: {line: 1, column: 1}, + end: {line: 1, column: 29} } }, loc: { @@ -13826,9 +13826,11 @@ testFail("[v] += ary", "Assigning to rvalue (1:0)", {ecmaVersion: 6}); testFail("[2] = 42", "Assigning to rvalue (1:1)", {ecmaVersion: 6}); -testFail("({ obj:20 }) = 42", "Assigning to rvalue (1:7)", {ecmaVersion: 6}); +testFail("({ obj:20 }) = 42", "Assigning to rvalue (1:13)", {ecmaVersion: 6}); -testFail("( { get x() {} } ) = 0", "Unexpected token (1:8)", {ecmaVersion: 6}); +testFail("({ obj:20 } = 42)", "Assigning to rvalue (1:7)", {ecmaVersion: 6}); + +testFail("( { get x() {} } = 0 )", "Unexpected token (1:8)", {ecmaVersion: 6}); testFail("x \n is y", "Unexpected token (2:4)", {ecmaVersion: 6}); @@ -13848,9 +13850,9 @@ testFail("let default", "Unexpected token (1:4)", {ecmaVersion: 6}); testFail("const default", "Unexpected token (1:6)", {ecmaVersion: 6}); -testFail("\"use strict\"; ({ v: eval }) = obj", "Assigning to eval in strict mode (1:20)", {ecmaVersion: 6}); +testFail("\"use strict\"; ({ v: eval } = obj)", "Assigning to eval in strict mode (1:20)", {ecmaVersion: 6}); -testFail("\"use strict\"; ({ v: arguments }) = obj", "Assigning to arguments in strict mode (1:20)", {ecmaVersion: 6}); +testFail("\"use strict\"; ({ v: arguments } = obj)", "Assigning to arguments in strict mode (1:20)", {ecmaVersion: 6}); testFail("for (let x = 42 in list) process(x);", "Unexpected token (1:16)", {ecmaVersion: 6}); diff --git a/test/tests.js b/test/tests.js index 172eb1fb87..1037330068 100644 --- a/test/tests.js +++ b/test/tests.js @@ -403,7 +403,7 @@ test("(1 + 2 ) * 3", { preserveParens: true }); -testFail("(x) = 23", "Assigning to rvalue (1:0)", { preserveParens: true }); +testFail("(x) = 23", "Assigning to rvalue (1:4)"); test("x = []", { type: "Program", @@ -26883,7 +26883,7 @@ testFail("func() = 4", "Assigning to rvalue (1:0)"); testFail("(1 + 1) = 10", - "Assigning to rvalue (1:1)"); + "Assigning to rvalue (1:8)"); testFail("1++", "Assigning to rvalue (1:0)"); From 3d9048a6921739bb4a9a5b90b5bad643d402806c Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 9 Jan 2015 02:50:32 +0200 Subject: [PATCH 11/36] Allow pattern in catch param (fixes #191). --- acorn.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/acorn.js b/acorn.js index ff3881bb88..2a7c89ef0f 100644 --- a/acorn.js +++ b/acorn.js @@ -1836,9 +1836,8 @@ var clause = startNode(); next(); expect(_parenL); - clause.param = parseIdent(); - if (strict && isStrictBadIdWord(clause.param.name)) - raise(clause.param.start, "Binding " + clause.param.name + " in strict mode"); + clause.param = parseAssignableAtom(); + checkLVal(clause.param, true); expect(_parenR); clause.guard = null; clause.body = parseBlock(); From bc2e01aa03ef33d25bf866605cdeb5e88ffe0b5f Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 9 Jan 2015 15:31:01 +0200 Subject: [PATCH 12/36] Allow `static` as method name in class (fixes #192). Add uncommitted tests for #191. --- acorn.js | 11 +-- acorn_loose.js | 14 +++- test/tests-harmony.js | 157 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 6 deletions(-) diff --git a/acorn.js b/acorn.js index 2a7c89ef0f..90eee0664b 100644 --- a/acorn.js +++ b/acorn.js @@ -2510,14 +2510,17 @@ expect(_braceL); while (!eat(_braceR)) { var method = startNode(); - if (tokType === _name && tokVal === "static") { - next(); + var isGenerator = eat(_star); + parsePropertyName(method); + if (tokType !== _parenL && !method.computed && method.key.type === "Identifier" && + method.key.name === "static") { + if (isGenerator) unexpected(); method['static'] = true; + isGenerator = eat(_star); + parsePropertyName(method); } else { method['static'] = false; } - var isGenerator = eat(_star); - parsePropertyName(method); if (tokType !== _parenL && !method.computed && method.key.type === "Identifier" && (method.key.name === "get" || method.key.name === "set")) { if (isGenerator) unexpected(); diff --git a/acorn_loose.js b/acorn_loose.js index 33a3cfe9d4..010a2d7fdf 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -862,7 +862,7 @@ var prop = startNode(), isGenerator; if (options.ecmaVersion >= 6) { if (isClass) { - if (prop['static'] = (token.type === tt.name && token.value === "static")) next(); + prop['static'] = false; } else { prop.method = false; prop.shorthand = false; @@ -871,6 +871,16 @@ } parsePropertyName(prop); if (isDummy(prop.key)) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } + if (isClass) { + if (prop.key.type === "Identifier" && !prop.computed && prop.key.name === "static" && + (token.type != tt.parenL && token.type != tt.braceL)) { + prop['static'] = true; + isGenerator = eat(tt.star); + parsePropertyName(prop); + } else { + prop['static'] = false; + } + } if (!isClass && eat(tt.colon)) { prop.kind = "init"; prop.value = parseExpression(true); @@ -883,7 +893,7 @@ } prop.value = parseMethod(isGenerator); } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" && - (prop.key.name === "get" || prop.key.name === "set") && + !prop.computed && (prop.key.name === "get" || prop.key.name === "set") && (token.type != tt.comma && token.type != tt.braceR)) { prop.kind = prop.key.name; parsePropertyName(prop); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index bb8406582b..e38275483e 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14598,3 +14598,160 @@ test("var [localVar = defaultValue] = obj", { locations: true, loose: false }); + +// https://github.com/marijnh/acorn/issues/191 + +test("try {} catch ({message}) {}", { + type: "Program", + range: [0, 27], + body: [{ + type: "TryStatement", + range: [0, 27], + block: { + type: "BlockStatement", + range: [4, 6], + body: [] + }, + handler: { + type: "CatchClause", + range: [7, 27], + param: { + type: "ObjectPattern", + range: [14, 23], + properties: [{ + type: "Property", + range: [15, 22], + method: false, + shorthand: true, + computed: false, + key: { + type: "Identifier", + range: [15, 22], + name: "message" + }, + kind: "init", + value: { + type: "Identifier", + range: [15, 22], + name: "message" + } + }] + }, + guard: null, + body: { + type: "BlockStatement", + range: [25, 27], + body: [] + } + }, + guardedHandlers: [], + finalizer: null + }] +}, { + ecmaVersion: 6, + ranges: true, + locations: true, + loose: false +}); + +// https://github.com/marijnh/acorn/issues/192 + +test("class A { static() {} }", { + type: "Program", + range: [0, 23], + body: [{ + type: "ClassDeclaration", + range: [0, 23], + id: { + type: "Identifier", + range: [6, 7], + name: "A" + }, + superClass: null, + body: { + type: "ClassBody", + range: [8, 23], + body: [{ + type: "MethodDefinition", + range: [10, 21], + computed: false, + key: { + type: "Identifier", + range: [10, 16], + name: "static" + }, + static: false, + kind: "", + value: { + type: "FunctionExpression", + range: [16, 21], + id: null, + params: [], + defaults: [], + rest: null, + generator: false, + body: { + type: "BlockStatement", + range: [19, 21], + body: [] + }, + expression: false + } + }] + } + }] +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); + +test("class A { *static() {} }", { + type: "Program", + range: [0, 24], + body: [{ + type: "ClassDeclaration", + range: [0, 24], + id: { + type: "Identifier", + range: [6, 7], + name: "A" + }, + superClass: null, + body: { + type: "ClassBody", + range: [8, 24], + body: [{ + type: "MethodDefinition", + range: [10, 22], + computed: false, + key: { + type: "Identifier", + range: [11, 17], + name: "static" + }, + static: false, + kind: "", + value: { + type: "FunctionExpression", + range: [17, 22], + id: null, + params: [], + defaults: [], + rest: null, + generator: true, + body: { + type: "BlockStatement", + range: [20, 22], + body: [] + }, + expression: false + } + }] + } + }] +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); From d1f95ece42e0a69aa2c4aaf2360ce3c904e1f696 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 12 Jan 2015 20:31:38 +0200 Subject: [PATCH 13/36] Revert "Disallow parentheses in lvalue except as in computed keys or default values." This reverts commit 85087f2a0951f60ca035ca6d785e4bab6d96d8f7. Fixes #193. --- acorn.js | 7 ------- test/tests-harmony.js | 20 +++++++++----------- test/tests.js | 4 ++-- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/acorn.js b/acorn.js index 90eee0664b..3836dc46a8 100644 --- a/acorn.js +++ b/acorn.js @@ -2002,18 +2002,14 @@ function parseMaybeAssign(noIn) { var start = storeCurrentPos(); - var oldParenL = metParenL; var left = parseMaybeConditional(noIn); if (tokType.isAssign) { - if (metParenL !== oldParenL) raise(tokStart, "Assigning to rvalue"); var node = startNodeAt(start); node.operator = tokVal; node.left = tokType === _eq ? toAssignable(left) : left; checkLVal(left); next(); node.right = parseMaybeAssign(noIn); - // restore count of '(' so they are allowed in lvalue's defaults - metParenL = oldParenL; return finishNode(node, "AssignmentExpression"); } return left; @@ -2341,10 +2337,7 @@ if (options.ecmaVersion >= 6) { if (eat(_bracketL)) { prop.computed = true; - // save & restore count of '(' so they are allowed in lvalue's computed props - var oldParenL = metParenL; prop.key = parseExpression(); - metParenL = oldParenL; expect(_bracketR); return; } else { diff --git a/test/tests-harmony.js b/test/tests-harmony.js index e38275483e..d5cd958f15 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -3940,7 +3940,7 @@ test("[a, b] = [b, a]", { locations: true }); -test("({ responseText: text } = res)", { +test("({ responseText: text }) = res", { type: "Program", body: [{ type: "ExpressionStatement", @@ -3985,13 +3985,13 @@ test("({ responseText: text } = res)", { type: "Identifier", name: "res", loc: { - start: {line: 1, column: 26}, - end: {line: 1, column: 29} + start: {line: 1, column: 27}, + end: {line: 1, column: 30} } }, loc: { - start: {line: 1, column: 1}, - end: {line: 1, column: 29} + start: {line: 1, column: 0}, + end: {line: 1, column: 30} } }, loc: { @@ -13826,11 +13826,9 @@ testFail("[v] += ary", "Assigning to rvalue (1:0)", {ecmaVersion: 6}); testFail("[2] = 42", "Assigning to rvalue (1:1)", {ecmaVersion: 6}); -testFail("({ obj:20 }) = 42", "Assigning to rvalue (1:13)", {ecmaVersion: 6}); +testFail("({ obj:20 }) = 42", "Assigning to rvalue (1:7)", {ecmaVersion: 6}); -testFail("({ obj:20 } = 42)", "Assigning to rvalue (1:7)", {ecmaVersion: 6}); - -testFail("( { get x() {} } = 0 )", "Unexpected token (1:8)", {ecmaVersion: 6}); +testFail("( { get x() {} } ) = 0", "Unexpected token (1:8)", {ecmaVersion: 6}); testFail("x \n is y", "Unexpected token (2:4)", {ecmaVersion: 6}); @@ -13850,9 +13848,9 @@ testFail("let default", "Unexpected token (1:4)", {ecmaVersion: 6}); testFail("const default", "Unexpected token (1:6)", {ecmaVersion: 6}); -testFail("\"use strict\"; ({ v: eval } = obj)", "Assigning to eval in strict mode (1:20)", {ecmaVersion: 6}); +testFail("\"use strict\"; ({ v: eval }) = obj", "Assigning to eval in strict mode (1:20)", {ecmaVersion: 6}); -testFail("\"use strict\"; ({ v: arguments } = obj)", "Assigning to arguments in strict mode (1:20)", {ecmaVersion: 6}); +testFail("\"use strict\"; ({ v: arguments }) = obj", "Assigning to arguments in strict mode (1:20)", {ecmaVersion: 6}); testFail("for (let x = 42 in list) process(x);", "Unexpected token (1:16)", {ecmaVersion: 6}); diff --git a/test/tests.js b/test/tests.js index 1037330068..172eb1fb87 100644 --- a/test/tests.js +++ b/test/tests.js @@ -403,7 +403,7 @@ test("(1 + 2 ) * 3", { preserveParens: true }); -testFail("(x) = 23", "Assigning to rvalue (1:4)"); +testFail("(x) = 23", "Assigning to rvalue (1:0)", { preserveParens: true }); test("x = []", { type: "Program", @@ -26883,7 +26883,7 @@ testFail("func() = 4", "Assigning to rvalue (1:0)"); testFail("(1 + 1) = 10", - "Assigning to rvalue (1:8)"); + "Assigning to rvalue (1:1)"); testFail("1++", "Assigning to rvalue (1:0)"); From 1b8069e48c440232f05a91eb96b204455df7e003 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 13 Jan 2015 09:52:57 +0100 Subject: [PATCH 14/36] Restore onToken functionality for loose parser --- acorn_loose.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/acorn_loose.js b/acorn_loose.js index 010a2d7fdf..f575dd3dd8 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -66,6 +66,8 @@ ahead.length = 0; token = ahead.shift() || readToken(forceRegexp); + if (options.onToken) + options.onToken(token); if (token.start >= nextLineStart) { while (token.start >= nextLineStart) { From cee56dab67b9feee11cd1b8099c59df77748b146 Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Tue, 13 Jan 2015 18:08:33 +0000 Subject: [PATCH 15/36] Add --ecma7 option to CLI --- bin/acorn | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/acorn b/bin/acorn index ca6e32a0c6..b80ad29d51 100755 --- a/bin/acorn +++ b/bin/acorn @@ -20,6 +20,7 @@ for (var i = 2; i < process.argv.length; ++i) { else if (arg == "--ecma3") options.ecmaVersion = 3; else if (arg == "--ecma5") options.ecmaVersion = 5; else if (arg == "--ecma6") options.ecmaVersion = 6; + else if (arg == "--ecma7") options.ecmaVersion = 7; else if (arg == "--strictSemicolons") options.strictSemicolons = true; else if (arg == "--locations") options.locations = true; else if (arg == "--silent") silent = true; From 0f55a53a7df559847cac7bb748b70d3b454d0cd8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 13 Jan 2015 22:18:55 +0100 Subject: [PATCH 16/36] [loose parser] Fetch token before comment when tokenizer raises unterminated comment error Closes #197 --- acorn.js | 1 + acorn_loose.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/acorn.js b/acorn.js index 3836dc46a8..06134a0992 100644 --- a/acorn.js +++ b/acorn.js @@ -242,6 +242,7 @@ tokExprAllowed = !!exprAllowed; skipSpace(); }; + getToken.current = function() { return new Token(); }; getToken.options = options; return getToken; }; diff --git a/acorn_loose.js b/acorn_loose.js index f575dd3dd8..62626fe97f 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -105,6 +105,8 @@ replace = {start: e.pos, end: pos, type: input.charAt(e.pos) == "`" ? tt.template : tt.templateContinued, value: input.slice(e.pos + 1, pos)}; + } else if (/comment/.test(msg)) { + replace = fetchToken.current(); } else { replace = false; } From 802c4cd8cb41692582b76ae2dda4890449c6853f Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 14 Jan 2015 12:18:08 +0200 Subject: [PATCH 17/36] Initial rewrite of JSX parser onto new tokenizer (all tests passing). --- acorn.js | 654 +++++++++++++++++++++++++++++++++++++++++++++++- test/index.html | 1 + test/run.js | 1 + 3 files changed, 643 insertions(+), 13 deletions(-) diff --git a/acorn.js b/acorn.js index 90eee0664b..3d952ef184 100644 --- a/acorn.js +++ b/acorn.js @@ -242,6 +242,19 @@ tokExprAllowed = !!exprAllowed; skipSpace(); }; + if (typeof Symbol !== 'undefined') { + getToken[Symbol.iterator] = function () { + return { + next: function () { + var token = getToken(); + return { + done: token.type === _eof, + value: token + }; + } + }; + }; + } getToken.options = options; return getToken; }; @@ -354,6 +367,7 @@ var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; var _name = {type: "name"}, _eof = {type: "eof"}; + var _xjsName = {type: "xjsName"}; // Keyword tokens. The `keyword` property (also used in keyword-like // operators) indicates that the token originated from an @@ -414,7 +428,9 @@ var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; - var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}, _templateContinued = {type: "templateContinued"}; + var _arrow = {type: "=>", beforeExpr: true}; + var _template = {type: "template"}, _templateContinued = {type: "templateContinued"}; + var _xjsText = {type: "xjsText"}; var _ellipsis = {type: "...", prefix: true, beforeExpr: true}; // Operators. These carry several kinds of properties to help the @@ -450,6 +466,9 @@ // '*' may be multiply or have special meaning in ES6 var _star = {binop: 10, beforeExpr: true}; + // JSX tag boundaries + var _xjsTagStart = {type: "xjsTagStart"}, _xjsTagEnd = {type: "xjsTagEnd"}; + // Provide access to the token types for external users of the // tokenizer. @@ -549,6 +568,9 @@ var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); + var decimalNumber = /^\d+$/; + var hexNumber = /^[\da-fA-F]+$/; + // Whether a single character denotes a newline. var newline = /[\n\r\u2028\u2029]/; @@ -629,17 +651,26 @@ var b_stat = {token: "{", isExpr: false}, b_expr = {token: "{", isExpr: true}; var p_stat = {token: "(", isExpr: false}, p_expr = {token: "(", isExpr: true}; + var j_oTag = {token: "...", isExpr: true}; + + function curTokContext() { + return tokContext[tokContext.length - 1]; + } function braceIsBlock(prevType) { var parent; - if (prevType === _colon && (parent = tokContext[tokContext.length - 1]).token == "{") + if (prevType === _colon && (parent = curTokContext()).token == "{") return !parent.isExpr; if (prevType === _return) return newline.test(input.slice(lastEnd, tokStart)); if (prevType === _else || prevType === _semi || prevType === _eof) return true; if (prevType == _braceL) - return tokContext[tokContext.length - 1] === b_stat; + return curTokContext() === b_stat; + if (prevType === _xjsTagEnd || prevType === _xjsText) + return true; + if (prevType === _xjsName) + return false; return !tokExprAllowed; } @@ -648,12 +679,11 @@ // after the token, so that the next one's `tokStart` will point at // the right position. - function finishToken(type, val, shouldSkipSpace) { + function finishToken(type, val) { tokEnd = tokPos; if (options.locations) tokEndLoc = curPosition(); var prevType = tokType; tokType = type; - if (shouldSkipSpace !== false) skipSpace(); tokVal = val; // Update context info @@ -672,10 +702,30 @@ } else if (type.keyword && prevType == _dot) { tokExprAllowed = false; } else if (tokExprAllowed && type == _function) { - tokExprAllowed = null; + tokExprAllowed = false; + } else if (type === _xjsTagStart) { + tokContext.push(j_expr); // treat as beginning of JSX expression + tokContext.push(j_oTag); // start opening tag context + tokExprAllowed = false; + } else if (type === _xjsTagEnd) { + var out = tokContext.pop(); + if (out === j_oTag && prevType === _slash || out === j_cTag) { + tokContext.pop(); + tokExprAllowed = curTokContext() === j_expr; + } else { + tokExprAllowed = true; + } + } else if (type === _xjsText) { + tokExprAllowed = true; + } else if (type === _slash && prevType === _xjsTagStart) { + tokContext.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore + tokContext.push(j_cTag); // reconsider as closing tag context + tokExprAllowed = false; } else { tokExprAllowed = type.beforeExpr; } + + if (curTokContext() !== j_expr) skipSpace(); } function skipBlockComment() { @@ -833,6 +883,17 @@ } if (next === 61) size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2; + if (tokExprAllowed && code === 60) { + ++tokPos; + return finishToken(_xjsTagStart); + } + if (code === 62) { + var context = curTokContext(); + if (context === j_oTag || context === j_cTag) { + ++tokPos; + return finishToken(_xjsTagEnd); + } + } return finishOp(_relational, size); } @@ -860,11 +921,11 @@ case 44: ++tokPos; return finishToken(_comma); case 91: ++tokPos; return finishToken(_bracketL); case 93: ++tokPos; return finishToken(_bracketR); - case 123: + case 123: // '{' ++tokPos; if (templates.length) ++templates[templates.length - 1]; return finishToken(_braceL); - case 125: + case 125: // '}' ++tokPos; if (templates.length && --templates[templates.length - 1] === 0) return readTemplateString(_templateContinued); @@ -934,10 +995,18 @@ if (tokPos >= inputLen) return finishToken(_eof); var code = input.charCodeAt(tokPos); + var context = curTokContext(); - // Identifier or keyword. '\uXXXX' sequences are allowed in - // identifiers, so '\' also dispatches to that. - if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord(); + if (context === j_oTag || context === j_cTag) { + // JSX identifier + if (isIdentifierStart(code)) return readJSXWord(); + } else if (context === j_expr) { + return readXJSToken(); + } else { + // Identifier or keyword. '\uXXXX' sequences are allowed in + // identifiers, so '\' also dispatches to that. + if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord(); + } var tok = getTokenFromCode(code); @@ -1096,6 +1165,7 @@ } function readString(quote) { + var isJSX = curTokContext() === j_oTag; ++tokPos; var out = ""; for (;;) { @@ -1105,8 +1175,10 @@ ++tokPos; return finishToken(_string, out); } - if (ch === 92) { // '\' + if (ch === 92 && !isJSX) { // '\' out += readEscapedChar(); + } else if (ch === 38 && isJSX) { // '&' + out += readXJSEntity(); } else { ++tokPos; if (isNewLine(ch)) { @@ -1149,6 +1221,332 @@ } } + var XHTMLEntities = { + quot: '\u0022', + amp: '&', + apos: '\u0027', + lt: '<', + gt: '>', + nbsp: '\u00A0', + iexcl: '\u00A1', + cent: '\u00A2', + pound: '\u00A3', + curren: '\u00A4', + yen: '\u00A5', + brvbar: '\u00A6', + sect: '\u00A7', + uml: '\u00A8', + copy: '\u00A9', + ordf: '\u00AA', + laquo: '\u00AB', + not: '\u00AC', + shy: '\u00AD', + reg: '\u00AE', + macr: '\u00AF', + deg: '\u00B0', + plusmn: '\u00B1', + sup2: '\u00B2', + sup3: '\u00B3', + acute: '\u00B4', + micro: '\u00B5', + para: '\u00B6', + middot: '\u00B7', + cedil: '\u00B8', + sup1: '\u00B9', + ordm: '\u00BA', + raquo: '\u00BB', + frac14: '\u00BC', + frac12: '\u00BD', + frac34: '\u00BE', + iquest: '\u00BF', + Agrave: '\u00C0', + Aacute: '\u00C1', + Acirc: '\u00C2', + Atilde: '\u00C3', + Auml: '\u00C4', + Aring: '\u00C5', + AElig: '\u00C6', + Ccedil: '\u00C7', + Egrave: '\u00C8', + Eacute: '\u00C9', + Ecirc: '\u00CA', + Euml: '\u00CB', + Igrave: '\u00CC', + Iacute: '\u00CD', + Icirc: '\u00CE', + Iuml: '\u00CF', + ETH: '\u00D0', + Ntilde: '\u00D1', + Ograve: '\u00D2', + Oacute: '\u00D3', + Ocirc: '\u00D4', + Otilde: '\u00D5', + Ouml: '\u00D6', + times: '\u00D7', + Oslash: '\u00D8', + Ugrave: '\u00D9', + Uacute: '\u00DA', + Ucirc: '\u00DB', + Uuml: '\u00DC', + Yacute: '\u00DD', + THORN: '\u00DE', + szlig: '\u00DF', + agrave: '\u00E0', + aacute: '\u00E1', + acirc: '\u00E2', + atilde: '\u00E3', + auml: '\u00E4', + aring: '\u00E5', + aelig: '\u00E6', + ccedil: '\u00E7', + egrave: '\u00E8', + eacute: '\u00E9', + ecirc: '\u00EA', + euml: '\u00EB', + igrave: '\u00EC', + iacute: '\u00ED', + icirc: '\u00EE', + iuml: '\u00EF', + eth: '\u00F0', + ntilde: '\u00F1', + ograve: '\u00F2', + oacute: '\u00F3', + ocirc: '\u00F4', + otilde: '\u00F5', + ouml: '\u00F6', + divide: '\u00F7', + oslash: '\u00F8', + ugrave: '\u00F9', + uacute: '\u00FA', + ucirc: '\u00FB', + uuml: '\u00FC', + yacute: '\u00FD', + thorn: '\u00FE', + yuml: '\u00FF', + OElig: '\u0152', + oelig: '\u0153', + Scaron: '\u0160', + scaron: '\u0161', + Yuml: '\u0178', + fnof: '\u0192', + circ: '\u02C6', + tilde: '\u02DC', + Alpha: '\u0391', + Beta: '\u0392', + Gamma: '\u0393', + Delta: '\u0394', + Epsilon: '\u0395', + Zeta: '\u0396', + Eta: '\u0397', + Theta: '\u0398', + Iota: '\u0399', + Kappa: '\u039A', + Lambda: '\u039B', + Mu: '\u039C', + Nu: '\u039D', + Xi: '\u039E', + Omicron: '\u039F', + Pi: '\u03A0', + Rho: '\u03A1', + Sigma: '\u03A3', + Tau: '\u03A4', + Upsilon: '\u03A5', + Phi: '\u03A6', + Chi: '\u03A7', + Psi: '\u03A8', + Omega: '\u03A9', + alpha: '\u03B1', + beta: '\u03B2', + gamma: '\u03B3', + delta: '\u03B4', + epsilon: '\u03B5', + zeta: '\u03B6', + eta: '\u03B7', + theta: '\u03B8', + iota: '\u03B9', + kappa: '\u03BA', + lambda: '\u03BB', + mu: '\u03BC', + nu: '\u03BD', + xi: '\u03BE', + omicron: '\u03BF', + pi: '\u03C0', + rho: '\u03C1', + sigmaf: '\u03C2', + sigma: '\u03C3', + tau: '\u03C4', + upsilon: '\u03C5', + phi: '\u03C6', + chi: '\u03C7', + psi: '\u03C8', + omega: '\u03C9', + thetasym: '\u03D1', + upsih: '\u03D2', + piv: '\u03D6', + ensp: '\u2002', + emsp: '\u2003', + thinsp: '\u2009', + zwnj: '\u200C', + zwj: '\u200D', + lrm: '\u200E', + rlm: '\u200F', + ndash: '\u2013', + mdash: '\u2014', + lsquo: '\u2018', + rsquo: '\u2019', + sbquo: '\u201A', + ldquo: '\u201C', + rdquo: '\u201D', + bdquo: '\u201E', + dagger: '\u2020', + Dagger: '\u2021', + bull: '\u2022', + hellip: '\u2026', + permil: '\u2030', + prime: '\u2032', + Prime: '\u2033', + lsaquo: '\u2039', + rsaquo: '\u203A', + oline: '\u203E', + frasl: '\u2044', + euro: '\u20AC', + image: '\u2111', + weierp: '\u2118', + real: '\u211C', + trade: '\u2122', + alefsym: '\u2135', + larr: '\u2190', + uarr: '\u2191', + rarr: '\u2192', + darr: '\u2193', + harr: '\u2194', + crarr: '\u21B5', + lArr: '\u21D0', + uArr: '\u21D1', + rArr: '\u21D2', + dArr: '\u21D3', + hArr: '\u21D4', + forall: '\u2200', + part: '\u2202', + exist: '\u2203', + empty: '\u2205', + nabla: '\u2207', + isin: '\u2208', + notin: '\u2209', + ni: '\u220B', + prod: '\u220F', + sum: '\u2211', + minus: '\u2212', + lowast: '\u2217', + radic: '\u221A', + prop: '\u221D', + infin: '\u221E', + ang: '\u2220', + and: '\u2227', + or: '\u2228', + cap: '\u2229', + cup: '\u222A', + 'int': '\u222B', + there4: '\u2234', + sim: '\u223C', + cong: '\u2245', + asymp: '\u2248', + ne: '\u2260', + equiv: '\u2261', + le: '\u2264', + ge: '\u2265', + sub: '\u2282', + sup: '\u2283', + nsub: '\u2284', + sube: '\u2286', + supe: '\u2287', + oplus: '\u2295', + otimes: '\u2297', + perp: '\u22A5', + sdot: '\u22C5', + lceil: '\u2308', + rceil: '\u2309', + lfloor: '\u230A', + rfloor: '\u230B', + lang: '\u2329', + rang: '\u232A', + loz: '\u25CA', + spades: '\u2660', + clubs: '\u2663', + hearts: '\u2665', + diams: '\u2666' + }; + + function readXJSEntity() { + var str = '', count = 0, entity; + var ch = input[tokPos]; + if (ch !== '&') raise(tokPos, "Entity must start with an ampersand"); + var startPos = ++tokPos; + while (tokPos < inputLen && count++ < 10) { + ch = input[tokPos++]; + if (ch === ';') { + if (str[0] === '#') { + if (str[1] === 'x') { + str = str.substr(2); + if (hexNumber.test(str)) { + entity = String.fromCharCode(parseInt(str, 16)); + } + } else { + str = str.substr(1); + if (decimalNumber.test(str)) { + entity = String.fromCharCode(parseInt(str, 10)); + } + } + } else { + entity = XHTMLEntities[str]; + } + break; + } + str += ch; + } + if (!entity) { + tokPos = startPos; + return '&'; + } + return entity; + } + + // Reads inline JSX contents token. + + function readXJSToken() { + var out = "", start = tokPos; + for (;;) { + if (tokPos >= inputLen) raise(tokStart, "Unterminated JSX contents"); + var ch = input.charAt(tokPos); + switch (ch) { + case "{": + case "<": + if (tokPos === start) { + return getTokenFromCode(ch.charCodeAt(0)); + } + return finishToken(_xjsText, out); + + case "&": + out += readXJSEntity(); + break; + + default: + ++tokPos; + if (newline.test(ch)) { + if (ch === "\r" && input.charCodeAt(tokPos) === 10) { + ++tokPos; + ch = "\n"; + } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } + out += ch; + } + } + } + // Used to read escaped characters function readEscapedChar() { @@ -1241,6 +1639,21 @@ return finishToken(type, word); } + // Read a JSX identifier (valid tag or attribute name). + // + // Optimized version since JSX identifiers can't contain + // escape characters and so can be read as single slice. + // Also assumes that first character was already checked + // by isIdentifierStart in readToken. + + function readJSXWord() { + var ch, start = tokPos; + do { + ch = input.charCodeAt(++tokPos); + } while (isIdentifierChar(ch) || ch === 45); // '-' + return finishToken(_xjsName, input.slice(start, tokPos)); + } + // ## Parser // A recursive descent parser operates by defining functions for all @@ -2165,7 +2578,7 @@ next(); return finishNode(node, "Literal"); - case _num: case _string: + case _num: case _string: case _xjsText: var node = startNode(); node.value = tokVal; node.raw = input.slice(tokStart, tokEnd); @@ -2244,6 +2657,9 @@ case _template: return parseTemplate(); + case _xjsTagStart: + return parseXJSElement(); + default: unexpected(); } @@ -2755,4 +3171,216 @@ node.generator = isGenerator; return finishNode(node, "ComprehensionExpression"); } + + // Transforms JSX element name to string. + + function getQualifiedXJSName(object) { + if (object.type === "XJSIdentifier") { + return object.name; + } + if (object.type === "XJSNamespacedName") { + return object.namespace.name + ':' + object.name.name; + } + if (object.type === "XJSMemberExpression") { + return ( + getQualifiedXJSName(object.object) + '.' + + getQualifiedXJSName(object.property) + ); + } + } + + // Parse next token as JSX identifier + + function parseXJSIdentifier() { + var node = startNode(); + if (tokType === _xjsName) { + node.name = tokVal; + } else if (tokType.keyword) { + node.name = tokType.keyword; + } else { + unexpected(); + } + next(); + return finishNode(node, "XJSIdentifier"); + } + + // Parse namespaced identifier. + + function parseXJSNamespacedName() { + var start = storeCurrentPos(); + var name = parseXJSIdentifier(); + if (!eat(_colon)) return name; + var node = startNodeAt(start); + node.namespace = name; + node.name = parseXJSIdentifier(); + return finishNode(node, "XJSNamespacedName"); + } + + // Parses element name in any form - namespaced, member + // or single identifier. + + function parseXJSElementName() { + var start = storeCurrentPos(); + var node = parseXJSNamespacedName(); + while (eat(_dot)) { + var newNode = startNodeAt(start); + newNode.object = node; + newNode.property = parseXJSIdentifier(); + node = finishNode(newNode, "XJSMemberExpression"); + } + return node; + } + + // Parses any type of JSX attribute value. + + function parseXJSAttributeValue() { + switch (tokType) { + case _braceL: + var node = parseXJSExpressionContainer(); + if (node.expression.type === "XJSEmptyExpression") { + raise( + node.start, + 'XJS attributes must only be assigned a non-empty ' + + 'expression' + ); + } + return node; + + case _xjsTagStart: + return parseXJSElement(); + + case _xjsText: + case _string: + return parseExprAtom(); + + default: + raise(tokStart, "XJS value should be either an expression or a quoted XJS text"); + } + } + + // XJSEmptyExpression is unique type since it doesn't actually parse anything, + // and so it should start at the end of last read token (left brace) and finish + // at the beginning of the next one (right brace). + + function parseXJSEmptyExpression() { + if (tokType !== _braceR) { + unexpected(); + } + + var tmp; + + tmp = tokStart; + tokStart = lastEnd; + lastEnd = tmp; + + tmp = tokStartLoc; + tokStartLoc = lastEndLoc; + lastEndLoc = tmp; + + return finishNode(startNode(), "XJSEmptyExpression"); + } + + // Parses JSX expression enclosed into curly brackets. + + function parseXJSExpressionContainer() { + var node = startNode(); + next(); + node.expression = tokType === _braceR ? parseXJSEmptyExpression() : parseExpression(); + expect(_braceR); + return finishNode(node, "XJSExpressionContainer"); + } + + // Parses following JSX attribute name-value pair. + + function parseXJSAttribute() { + var node = startNode(); + if (eat(_braceL)) { + if (tokType !== _ellipsis) unexpected(); + node.argument = parseMaybeUnary().argument; + expect(_braceR); + return finishNode(node, "XJSSpreadAttribute"); + } + node.name = parseXJSNamespacedName(); + node.value = eat(_eq) ? parseXJSAttributeValue() : null; + return finishNode(node, "XJSAttribute"); + } + + // Parses JSX opening tag starting after '<'. + + function parseXJSOpeningElementAt(start) { + var node = startNodeAt(start); + node.attributes = []; + node.name = parseXJSElementName(); + while (tokType !== _slash && tokType !== _xjsTagEnd) { + node.attributes.push(parseXJSAttribute()); + } + node.selfClosing = eat(_slash); + expect(_xjsTagEnd); + return finishNode(node, "XJSOpeningElement"); + } + + // Parses JSX closing tag starting after '" + ); + } + } + + node.openingElement = openingElement; + node.closingElement = closingElement; + node.children = children; + return finishNode(node, "XJSElement"); + } + + // Parses entire JSX element from current position. + + function parseXJSElement() { + var start = storeCurrentPos(); + next(); + return parseXJSElementAt(start); + } }); diff --git a/test/index.html b/test/index.html index ef80eb7b15..29489e7e01 100644 --- a/test/index.html +++ b/test/index.html @@ -7,6 +7,7 @@ +
    diff --git a/test/run.js b/test/run.js index 04f752dc3b..a11cdddf19 100644 --- a/test/run.js +++ b/test/run.js @@ -5,6 +5,7 @@ driver = require("./driver.js"); require("./tests.js"); require("./tests-harmony.js"); + require("./tests-jsx.js"); } else { driver = window; } From ad9411d2aec4ac37bf441bc452a85e3217b6a05d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 14 Jan 2015 12:27:58 +0200 Subject: [PATCH 18/36] Made tokenize() compliant with ES6 iterables for easier processing. --- README.md | 11 +++++++++++ acorn.js | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/README.md b/README.md index 5f790a8592..7714e873a7 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,17 @@ token, and returns a `{start, end, type, value}` object (with added `loc` property when the `locations` option is enabled and `range` property when the `ranges` option is enabled). +In ES6 environment, returned result can be used as any other protocol-compliant iterable: + +```javascript +for (let token of acorn.tokenize(str)) { + // iterate over the tokens +} + +// transform code to array of tokens: +var tokens = [...acorn.tokenize(str)]; +``` + **tokTypes** holds an object mapping names to the token type objects that end up in the `type` properties of tokens. diff --git a/acorn.js b/acorn.js index 06134a0992..f3a9d614ba 100644 --- a/acorn.js +++ b/acorn.js @@ -243,6 +243,19 @@ skipSpace(); }; getToken.current = function() { return new Token(); }; + if (typeof Symbol !== 'undefined') { + getToken[Symbol.iterator] = function () { + return { + next: function () { + var token = getToken(); + return { + done: token.type === _eof, + value: token + }; + } + }; + }; + } getToken.options = options; return getToken; }; From 7e85da74cbb835963960721ff67a4520d106a387 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 14 Jan 2015 12:31:59 +0200 Subject: [PATCH 19/36] `shouldSkipSpace` is no more needed in `finishToken`. --- acorn.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acorn.js b/acorn.js index f3a9d614ba..beb4959cf5 100644 --- a/acorn.js +++ b/acorn.js @@ -662,12 +662,12 @@ // after the token, so that the next one's `tokStart` will point at // the right position. - function finishToken(type, val, shouldSkipSpace) { + function finishToken(type, val) { tokEnd = tokPos; if (options.locations) tokEndLoc = curPosition(); var prevType = tokType; tokType = type; - if (shouldSkipSpace !== false) skipSpace(); + skipSpace(); tokVal = val; // Update context info @@ -686,7 +686,7 @@ } else if (type.keyword && prevType == _dot) { tokExprAllowed = false; } else if (tokExprAllowed && type == _function) { - tokExprAllowed = null; + tokExprAllowed = false; } else { tokExprAllowed = type.beforeExpr; } From e7beee177d60040890b5e284df93fc70a884701f Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 14 Jan 2015 12:35:00 +0200 Subject: [PATCH 20/36] Remove deprecated `ComprehensionBlock.of` property. Comprehensions were moved to ES7 anyway, so there is no sense in keeping intermediate no-more-supported syntax. --- acorn.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/acorn.js b/acorn.js index beb4959cf5..7d3bcb2082 100644 --- a/acorn.js +++ b/acorn.js @@ -2749,9 +2749,6 @@ checkLVal(block.left, true); if (tokType !== _name || tokVal !== "of") unexpected(); next(); - // `of` property is here for compatibility with Esprima's AST - // which also supports deprecated [for (... in ...) expr] - block.of = true; block.right = parseExpression(); expect(_parenR); node.blocks.push(finishNode(block, "ComprehensionBlock")); From d34aea63ab8fb726de111d37c1504be94feb3bb6 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 14 Jan 2015 12:36:25 +0200 Subject: [PATCH 21/36] Update tests. --- test/tests-harmony.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/test/tests-harmony.js b/test/tests-harmony.js index d5cd958f15..df983c7ccd 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -3328,8 +3328,7 @@ test("[for (x of array) x]", { loc: { start: {line: 1, column: 1}, end: {line: 1, column: 17} - }, - of: true + } }], body: { type: "Identifier", @@ -3412,8 +3411,7 @@ test("[for (x of array) for (y of array2) if (x === test) x]", { loc: { start: {line: 1, column: 1}, end: {line: 1, column: 17} - }, - of: true + } }, { type: "ComprehensionBlock", @@ -3436,8 +3434,7 @@ test("[for (x of array) for (y of array2) if (x === test) x]", { loc: { start: {line: 1, column: 18}, end: {line: 1, column: 35} - }, - of: true + } } ], body: { @@ -3521,8 +3518,7 @@ test("(for (x of array) for (y of array2) if (x === test) x)", { loc: { start: {line: 1, column: 1}, end: {line: 1, column: 17} - }, - of: true + } }, { type: "ComprehensionBlock", @@ -3545,8 +3541,7 @@ test("(for (x of array) for (y of array2) if (x === test) x)", { loc: { start: {line: 1, column: 18}, end: {line: 1, column: 35} - }, - of: true + } } ], body: { @@ -3617,8 +3612,7 @@ test("[for ([,x] of array) for ({[start.x]: x, [start.y]: y} of array2) x]", { loc: { start: {line: 1, column: 1}, end: {line: 1, column: 20} - }, - of: true + } }, { type: "ComprehensionBlock", @@ -3728,8 +3722,7 @@ test("[for ([,x] of array) for ({[start.x]: x, [start.y]: y} of array2) x]", { loc: { start: {line: 1, column: 21}, end: {line: 1, column: 65} - }, - of: true + } } ], body: { From f6c45ac59f613585eae2a4fbb556b26c325a4a6c Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 14 Jan 2015 23:10:10 +0200 Subject: [PATCH 22/36] Re-read only number or string after "use strict". Fixes double-entering same tokContext for various parentheses. --- acorn.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/acorn.js b/acorn.js index 7d3bcb2082..bdb026566e 100644 --- a/acorn.js +++ b/acorn.js @@ -1289,11 +1289,12 @@ readToken(); } - // Enter strict mode. Re-reads the next token to please pedantic - // tests ("use strict"; 010; -- should fail). + // Enter strict mode. Re-reads the next number or string to + // please pedantic tests ("use strict"; 010; -- should fail). function setStrict(strct) { strict = strct; + if (tokType !== _num && tokType !== _string) return; tokPos = tokStart; if (options.locations) { while (tokPos < tokLineStart) { From 6dee98d1b9709419bcd57fc24a24c4e61b2e202b Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 15 Jan 2015 12:21:28 +0200 Subject: [PATCH 23/36] Adapt ES6 template handling to new tokenizer. Avoid need for: * extra `templates` array in favor of new `tokContext`; * special location handling for first & last template elements; * separate `_templateContinued` token in favor of same `_template`. Adds: * token types for backQuote and dollarBraceL instead of skipping them so they can be handled (i.e. highlighted differently). --- acorn.js | 121 ++++++++++++++++++++++-------------------- acorn_loose.js | 39 +++++++------- test/tests-harmony.js | 2 +- 3 files changed, 84 insertions(+), 78 deletions(-) diff --git a/acorn.js b/acorn.js index bdb026566e..4945981d59 100644 --- a/acorn.js +++ b/acorn.js @@ -320,13 +320,6 @@ var metParenL; - // This is used by the tokenizer to track the template strings it is - // inside, and count the amount of open braces seen inside them, to - // be able to switch back to a template token when the } to match ${ - // is encountered. It will hold an array of integers. - - var templates; - function initParserState() { lastStart = lastEnd = tokPos; if (options.locations) lastEndLoc = curPosition(); @@ -428,8 +421,9 @@ var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; - var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}, _templateContinued = {type: "templateContinued"}; + var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}; var _ellipsis = {type: "...", prefix: true, beforeExpr: true}; + var _backQuote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is @@ -471,8 +465,8 @@ parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string, - arrow: _arrow, template: _template, templateContinued: _templateContinued, star: _star, - assign: _assign}; + arrow: _arrow, template: _template, star: _star, assign: _assign, + backQuote: _backQuote, dollarBraceL: _dollarBraceL}; for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on @@ -631,7 +625,6 @@ tokContext = []; tokExprAllowed = true; metParenL = 0; - templates = []; if (tokPos === 0 && options.allowHashBang && input.slice(0, 2) === '#!') { skipLineComment(2); } @@ -641,8 +634,9 @@ // given point in the program is loosely based on sweet.js' approach. // See https://github.com/mozilla/sweet.js/wiki/design - var b_stat = {token: "{", isExpr: false}, b_expr = {token: "{", isExpr: true}; + var b_stat = {token: "{", isExpr: false}, b_expr = {token: "{", isExpr: true}, b_tmpl = {token: "${", isExpr: true}; var p_stat = {token: "(", isExpr: false}, p_expr = {token: "(", isExpr: true}; + var q_tmpl = {token: "`", isExpr: true}; function braceIsBlock(prevType) { var parent; @@ -665,18 +659,21 @@ function finishToken(type, val) { tokEnd = tokPos; if (options.locations) tokEndLoc = curPosition(); - var prevType = tokType; + var prevType = tokType, preserveSpace = false; tokType = type; - skipSpace(); tokVal = val; // Update context info if (type === _parenR || type === _braceR) { var out = tokContext.pop(); tokExprAllowed = !(out && out.isExpr); + preserveSpace = out === b_tmpl; } else if (type === _braceL) { tokContext.push(braceIsBlock(prevType) ? b_stat : b_expr); tokExprAllowed = true; + } else if (type === _dollarBraceL) { + tokContext.push(b_tmpl); + tokExprAllowed = true; } else if (type == _parenL) { var statementParens = prevType === _if || prevType === _for || prevType === _with || prevType === _while; tokContext.push(statementParens ? p_stat : p_expr); @@ -687,9 +684,19 @@ tokExprAllowed = false; } else if (tokExprAllowed && type == _function) { tokExprAllowed = false; + } else if (type === _backQuote) { + if (tokContext[tokContext.length - 1] === q_tmpl) { + tokContext.pop(); + } else { + tokContext.push(q_tmpl); + preserveSpace = true; + } + tokExprAllowed = false; } else { tokExprAllowed = type.beforeExpr; } + + if (!preserveSpace) skipSpace(); } function skipBlockComment() { @@ -874,23 +881,17 @@ case 44: ++tokPos; return finishToken(_comma); case 91: ++tokPos; return finishToken(_bracketL); case 93: ++tokPos; return finishToken(_bracketR); - case 123: - ++tokPos; - if (templates.length) ++templates[templates.length - 1]; - return finishToken(_braceL); - case 125: - ++tokPos; - if (templates.length && --templates[templates.length - 1] === 0) - return readTemplateString(_templateContinued); - else - return finishToken(_braceR); + case 123: ++tokPos; return finishToken(_braceL); + case 125: ++tokPos; return finishToken(_braceR); case 58: ++tokPos; return finishToken(_colon); case 63: ++tokPos; return finishToken(_question); case 96: // '`' if (options.ecmaVersion >= 6) { ++tokPos; - return readTemplateString(_template); + return finishToken(_backQuote); + } else { + return false; } case 48: // '0' @@ -947,6 +948,10 @@ if (options.locations) tokStartLoc = curPosition(); if (tokPos >= inputLen) return finishToken(_eof); + if (tokContext[tokContext.length - 1] === q_tmpl) { + return readTmplToken(); + } + var code = input.charCodeAt(tokPos); // Identifier or keyword. '\uXXXX' sequences are allowed in @@ -1131,34 +1136,40 @@ } } - function readTemplateString(type) { - if (type == _templateContinued) templates.pop(); - var out = "", start = tokPos;; + // Reads template string tokens. + + function readTmplToken() { + var out = "", start = tokPos; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated template"); - var ch = input.charAt(tokPos); - if (ch === "`" || ch === "$" && input.charCodeAt(tokPos + 1) === 123) { // '`', '${' - var raw = input.slice(start, tokPos); - ++tokPos; - if (ch == "$") { ++tokPos; templates.push(1); } - return finishToken(type, {cooked: out, raw: raw}); + var ch = input.charCodeAt(tokPos); + if (ch === 96 || ch === 36 && input.charCodeAt(tokPos + 1) === 123) { // '`', '${' + if (tokPos === start && tokType === _template) { + if (ch === 36) { + tokPos += 2; + return finishToken(_dollarBraceL); + } else { + ++tokPos; + return finishToken(_backQuote); + } + } + return finishToken(_template, out); } - - if (ch === "\\") { // '\' + if (ch === 92) { // '\' out += readEscapedChar(); } else { ++tokPos; - if (newline.test(ch)) { - if (ch === "\r" && input.charCodeAt(tokPos) === 10) { + if (isNewLine(ch)) { + if (ch === 13 && input.charCodeAt(tokPos) === 10) { ++tokPos; - ch = "\n"; + ch = 10; } if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } } - out += ch; + out += String.fromCharCode(ch); } } } @@ -1369,15 +1380,6 @@ return node; } - function finishNodeAt(node, type, pos) { - if (options.locations) { node.loc.end = pos[1]; pos = pos[0]; } - node.type = type; - node.end = pos; - if (options.ranges) - node.range[1] = pos; - return node; - } - // Test whether a statement node is the string literal `"use strict"`. function isUseStrict(stmt) { @@ -2137,7 +2139,7 @@ node.callee = base; node.arguments = parseExprList(_parenR, false); return parseSubscripts(finishNode(node, "CallExpression"), start, noCalls); - } else if (tokType === _template) { + } else if (tokType === _backQuote) { var node = startNodeAt(start); node.tag = base; node.quasi = parseTemplate(); @@ -2252,7 +2254,7 @@ case _new: return parseNew(); - case _template: + case _backQuote: return parseTemplate(); default: @@ -2277,24 +2279,29 @@ // Parse template expression. function parseTemplateElement() { - var elem = startNodeAt(options.locations ? [tokStart + 1, tokStartLoc.offset(1)] : tokStart + 1); - elem.value = tokVal; - elem.tail = input.charCodeAt(tokEnd - 1) !== 123; // '{' + var elem = startNode(); + elem.value = { + raw: input.slice(tokStart, tokEnd), + cooked: tokVal + }; next(); - var endOff = elem.tail ? 1 : 2; - return finishNodeAt(elem, "TemplateElement", options.locations ? [lastEnd - endOff, lastEndLoc.offset(-endOff)] : lastEnd - endOff); + elem.tail = tokType === _backQuote; + return finishNode(elem, "TemplateElement"); } function parseTemplate() { var node = startNode(); + next(); node.expressions = []; var curElt = parseTemplateElement(); node.quasis = [curElt]; while (!curElt.tail) { + expect(_dollarBraceL); node.expressions.push(parseExpression()); - if (tokType !== _templateContinued) unexpected(); + expect(_braceR); node.quasis.push(curElt = parseTemplateElement()); } + next(); return finishNode(node, "TemplateLiteral"); } diff --git a/acorn_loose.js b/acorn_loose.js index 62626fe97f..b1b461360d 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -103,8 +103,8 @@ replace = {start: e.pos, end: pos, type: tt.regexp, value: re}; } else if (/template/.test(msg)) { replace = {start: e.pos, end: pos, - type: input.charAt(e.pos) == "`" ? tt.template : tt.templateContinued, - value: input.slice(e.pos + 1, pos)}; + type: tt.template, + value: input.slice(e.pos, pos)}; } else if (/comment/.test(msg)) { replace = fetchToken.current(); } else { @@ -697,7 +697,7 @@ node.callee = base; node.arguments = parseExprList(tt.parenR); base = finishNode(node, "CallExpression"); - } else if (token.type == tt.template) { + } else if (token.type == tt.backQuote) { var node = startNodeAt(start); node.tag = base; node.quasi = parseTemplate(); @@ -790,7 +790,7 @@ } return finishNode(node, "YieldExpression"); - case tt.template: + case tt.backQuote: return parseTemplate(); default: @@ -813,36 +813,35 @@ } function parseTemplateElement() { - var elem = startNodeAt(options.locations ? [token.start + 1, token.startLoc.offset(1)] : token.start + 1); - elem.value = token.value; - elem.tail = input.charCodeAt(token.end - 1) !== 123; // '{' - var endOff = elem.tail ? 1 : 2; - var endPos = options.locations ? [token.end - endOff, token.endLoc.offset(-endOff)] : token.end - endOff; + var elem = startNode(); + elem.value = { + raw: input.slice(token.start, token.end), + cooked: token.value + }; next(); - return finishNodeAt(elem, "TemplateElement", endPos); + elem.tail = token.type === tt.backQuote; + return finishNode(elem, "TemplateElement"); } function parseTemplate() { var node = startNode(); + next(); node.expressions = []; var curElt = parseTemplateElement(); node.quasis = [curElt]; while (!curElt.tail) { - var next = parseExpression(); - if (isDummy(next)) { - node.quasis[node.quasis.length - 1].tail = true; - break; - } - node.expressions.push(next); - if (token.type === tt.templateContinued) { - node.quasis.push(curElt = parseTemplateElement()); + next(); + node.expressions.push(parseExpression()); + if (expect(tt.braceR)) { + curElt = parseTemplateElement(); } else { curElt = startNode(); - curElt.value = {cooked: "", raw: ""}; + curElt.value = {cooked: '', raw: ''}; curElt.tail = true; - node.quasis.push(curElt); } + node.quasis.push(curElt); } + expect(tt.backQuote); return finishNode(node, "TemplateLiteral"); } diff --git a/test/tests-harmony.js b/test/tests-harmony.js index df983c7ccd..7f45357f78 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14077,7 +14077,7 @@ testFail("class A extends yield B { }", "Unexpected token (1:22)", {ecmaVersion: testFail("class default", "Unexpected token (1:6)", {ecmaVersion: 6}); -testFail("`test", "Unterminated template (1:0)", {ecmaVersion: 6}); +testFail("`test", "Unterminated template (1:1)", {ecmaVersion: 6}); testFail("switch `test`", "Unexpected token (1:7)", {ecmaVersion: 6}); From 3e513fc6a8613bfec779faf32d19324c31422253 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 17 Jan 2015 22:22:26 +0100 Subject: [PATCH 24/36] Kill finishNodeAt in acorn_loose as well Issue #200 --- acorn_loose.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index b1b461360d..03d92b7352 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -257,14 +257,6 @@ return node; } - function finishNodeAt(node, type, pos) { - if (options.locations) { node.loc.end = pos[1]; pos = pos[0]; } - node.type = type; - node.end = pos; - if (options.ranges) node.range[1] = pos; - return node; - } - function dummyIdent() { var dummy = startNode(); dummy.name = "✖"; From dac747dfa9390e84ae9e1b1a10327bd800116d1a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 17 Jan 2015 22:26:34 +0100 Subject: [PATCH 25/36] Add a test for issue #201 --- test/tests-harmony.js | 94 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 7f45357f78..eb8b8c5812 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14746,3 +14746,97 @@ test("class A { *static() {} }", { ranges: true, locations: true }); + +test("`${/\d/.exec('1')[0]}`", { + "type": "Program", + "start": 0, + "end": 21, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 21, + "expression": { + "type": "TemplateLiteral", + "start": 0, + "end": 21, + "expressions": [ + { + "type": "MemberExpression", + "start": 3, + "end": 19, + "object": { + "type": "CallExpression", + "start": 3, + "end": 16, + "callee": { + "type": "MemberExpression", + "start": 3, + "end": 11, + "object": { + "type": "Literal", + "start": 3, + "end": 6, + "regex": { + "pattern": "d", + "flags": "" + }, + "value": {}, + "raw": "/d/" + }, + "property": { + "type": "Identifier", + "start": 7, + "end": 11, + "name": "exec" + }, + "computed": false + }, + "arguments": [ + { + "type": "Literal", + "start": 12, + "end": 15, + "value": "1", + "raw": "'1'" + } + ] + }, + "property": { + "type": "Literal", + "start": 17, + "end": 18, + "value": 0, + "raw": "0" + }, + "computed": true + } + ], + "quasis": [ + { + "type": "TemplateElement", + "start": 1, + "end": 1, + "value": { + "raw": "", + "cooked": "" + }, + "tail": false + }, + { + "type": "TemplateElement", + "start": 20, + "end": 20, + "value": { + "raw": "", + "cooked": "" + }, + "tail": true + } + ] + } + } + ] +}, { + ecmaVersion: 6 +}); From 9f7cb552648829796ab4d6836d08246e95f16b9d Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Sun, 4 Jan 2015 21:08:39 +1100 Subject: [PATCH 26/36] Add stray semicolons as class elements --- acorn.js | 3 ++- test/tests-harmony.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/acorn.js b/acorn.js index 4945981d59..6ba8f97d81 100644 --- a/acorn.js +++ b/acorn.js @@ -2524,6 +2524,8 @@ classBody.body = []; expect(_braceL); while (!eat(_braceR)) { + while (eat(_semi)); + if (tokType === _braceR) continue; var method = startNode(); var isGenerator = eat(_star); parsePropertyName(method); @@ -2546,7 +2548,6 @@ } method.value = parseMethod(isGenerator); classBody.body.push(finishNode(method, "MethodDefinition")); - eat(_semi); } node.body = finishNode(classBody, "ClassBody"); return finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index eb8b8c5812..086d85216e 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -8859,6 +8859,42 @@ test("class A { foo() {} get foo() {} }",{ locations: true }); +test("class Semicolon { ; }", { + type: "Program", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 21} + }, + body: [{ + type: "ClassDeclaration", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 21} + }, + id: { + type: "Identifier", + loc: { + start: {line: 1, column: 6}, + end: {line: 1, column: 15} + }, + name: "Semicolon" + }, + superClass: null, + body: { + type: "ClassBody", + loc: { + start: {line: 1, column: 16}, + end: {line: 1, column: 21} + }, + body: [] + } + }] +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); + // ES6: Computed Properties test("({[x]: 10})", { From a1d2561cfaa0f5070cf3b431bd154c0ebdf946cb Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 20 Jan 2015 12:02:30 +0100 Subject: [PATCH 27/36] Restore patch 9f7cb552648829796ab4d6836d08246e95f16b9d to original shape And make loose parser handle stray class semicolons Issue #190 --- acorn.js | 3 +-- acorn_loose.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/acorn.js b/acorn.js index 6ba8f97d81..95dd23bb7b 100644 --- a/acorn.js +++ b/acorn.js @@ -2524,8 +2524,7 @@ classBody.body = []; expect(_braceL); while (!eat(_braceR)) { - while (eat(_semi)); - if (tokType === _braceR) continue; + if (eat(_semi)) continue; var method = startNode(); var isGenerator = eat(_star); parsePropertyName(method); diff --git a/acorn_loose.js b/acorn_loose.js index 03d92b7352..a6542ee7d0 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -854,6 +854,7 @@ eat(tt.braceL); if (curIndent + 1 < indent) { indent = curIndent; line = curLineStart; } while (!closes(tt.braceR, indent, line)) { + if (isClass && semicolon()) continue; var prop = startNode(), isGenerator; if (options.ecmaVersion >= 6) { if (isClass) { @@ -904,7 +905,6 @@ if (isClass) { node.body.body.push(finishNode(prop, "MethodDefinition")); - semicolon(); } else { node.properties.push(finishNode(prop, "Property")); eat(tt.comma); From 33a7c9fc24187e10299dcc0d2378dcdb93887ac7 Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Tue, 13 Jan 2015 11:10:28 +0000 Subject: [PATCH 28/36] Support import and export declarations in acorn/util/walk --- util/walk.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/util/walk.js b/util/walk.js index 117f56f93c..da7ca9f7e1 100644 --- a/util/walk.js +++ b/util/walk.js @@ -285,7 +285,15 @@ c(node.object, st, "Expression"); if (node.computed) c(node.property, st, "Expression"); }; - base.Identifier = base.Literal = base.ExportDeclaration = base.ImportDeclaration = ignore; + base.ExportDeclaration = function (node, st, c) { + c(node.declaration, st); + }; + base.ImportDeclaration = function (node, st, c) { + node.specifiers.forEach(function (specifier) { + c(specifier, st); + }); + }; + base.ImportSpecifier = base.ImportBatchSpecifier = base.Identifier = base.Literal = ignore; base.TaggedTemplateExpression = function(node, st, c) { c(node.tag, st, "Expression"); From 94b5efcd3ed845cb50d3ad676cda51766d1fc465 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 20 Jan 2015 12:55:21 +0100 Subject: [PATCH 29/36] Disallow declaration statements in block-less context Closes #202 --- acorn.js | 37 +++++++++++++++++++++---------------- test/tests-harmony.js | 5 +++++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/acorn.js b/acorn.js index 95dd23bb7b..58d0d416ab 100644 --- a/acorn.js +++ b/acorn.js @@ -1638,7 +1638,7 @@ var first = true; if (!node.body) node.body = []; while (tokType !== _eof) { - var stmt = parseStatement(true); + var stmt = parseStatement(true, true); node.body.push(stmt); if (first && isUseStrict(stmt)) setStrict(true); first = false; @@ -1657,7 +1657,7 @@ // `if (foo) /blah/.exec(foo);`, where looking at the previous token // does not help. - function parseStatement(topLevel) { + function parseStatement(declaration, topLevel) { var starttype = tokType, node = startNode(); // Most types of statements are recognized by the keyword they @@ -1669,14 +1669,19 @@ case _debugger: return parseDebuggerStatement(node); case _do: return parseDoStatement(node); case _for: return parseForStatement(node); - case _function: return parseFunctionStatement(node); - case _class: return parseClass(node, true); + case _function: + if (!declaration && options.ecmaVersion >= 6) unexpected(); + return parseFunctionStatement(node); + case _class: + if (!declaration) unexpected(); + return parseClass(node, true); case _if: return parseIfStatement(node); case _return: return parseReturnStatement(node); case _switch: return parseSwitchStatement(node); case _throw: return parseThrowStatement(node); case _try: return parseTryStatement(node); - case _var: case _let: case _const: return parseVarStatement(node, starttype.keyword); + case _let: case _const: if (!declaration) unexpected(); // NOTE: falls through to _var + case _var: return parseVarStatement(node, starttype.keyword); case _while: return parseWhileStatement(node); case _with: return parseWithStatement(node); case _braceL: return parseBlock(); // no point creating a function for this @@ -1732,7 +1737,7 @@ function parseDoStatement(node) { next(); labels.push(loopLabel); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); expect(_while); node.test = parseParenExpression(); @@ -1782,8 +1787,8 @@ function parseIfStatement(node) { next(); node.test = parseParenExpression(); - node.consequent = parseStatement(); - node.alternate = eat(_else) ? parseStatement() : null; + node.consequent = parseStatement(false); + node.alternate = eat(_else) ? parseStatement(false) : null; return finishNode(node, "IfStatement"); } @@ -1827,7 +1832,7 @@ expect(_colon); } else { if (!cur) unexpected(); - cur.consequent.push(parseStatement()); + cur.consequent.push(parseStatement(true)); } } if (cur) finishNode(cur, "SwitchCase"); @@ -1878,7 +1883,7 @@ next(); node.test = parseParenExpression(); labels.push(loopLabel); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); return finishNode(node, "WhileStatement"); } @@ -1887,7 +1892,7 @@ if (strict) raise(tokStart, "'with' in strict mode"); next(); node.object = parseParenExpression(); - node.body = parseStatement(); + node.body = parseStatement(false); return finishNode(node, "WithStatement"); } @@ -1901,7 +1906,7 @@ if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared"); var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null; labels.push({name: maybeName, kind: kind}); - node.body = parseStatement(); + node.body = parseStatement(true); labels.pop(); node.label = expr; return finishNode(node, "LabeledStatement"); @@ -1932,7 +1937,7 @@ node.body = []; expect(_braceL); while (!eat(_braceR)) { - var stmt = parseStatement(); + var stmt = parseStatement(true); node.body.push(stmt); if (first && allowStrict && isUseStrict(stmt)) { oldStrict = strict; @@ -1955,7 +1960,7 @@ expect(_semi); node.update = tokType === _parenR ? null : parseExpression(); expect(_parenR); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); return finishNode(node, "ForStatement"); } @@ -1969,7 +1974,7 @@ node.left = init; node.right = parseExpression(); expect(_parenR); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); return finishNode(node, type); } @@ -2602,7 +2607,7 @@ next(); // export var|const|let|function|class ...; if (tokType === _var || tokType === _const || tokType === _let || tokType === _function || tokType === _class) { - node.declaration = parseStatement(); + node.declaration = parseStatement(true); node['default'] = false; node.specifiers = null; node.source = null; diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 086d85216e..0ea8758cf7 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14876,3 +14876,8 @@ test("`${/\d/.exec('1')[0]}`", { }, { ecmaVersion: 6 }); + +testFail("if (1) let x = 10;", "Unexpected token (1:7)", {ecmaVersion: 6}); +testFail("for (;;) const x = 10;", "Unexpected token (1:9)", {ecmaVersion: 6}); +testFail("while (1) function foo(){}", "Unexpected token (1:10)", {ecmaVersion: 6}); +testFail("if (1) ; else class Cls {}", "Unexpected token (1:14)", {ecmaVersion: 6}); From cae13fd75a5915cdf57f265f7a2d978402829105 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 21 Jan 2015 02:58:02 +0000 Subject: [PATCH 30/36] Small simplifications after merge. --- acorn.js | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/acorn.js b/acorn.js index a5820d6539..5082a2720b 100644 --- a/acorn.js +++ b/acorn.js @@ -684,7 +684,7 @@ if (type === _parenR || type === _braceR) { var out = tokContext.pop(); tokExprAllowed = !(out && out.isExpr); - preserveSpace = out === b_tmpl; + preserveSpace = out === b_tmpl || curTokContext() === j_expr; } else if (type === _braceL) { tokContext.push(braceIsBlock(prevType) ? b_stat : b_expr); tokExprAllowed = true; @@ -702,14 +702,13 @@ } else if (tokExprAllowed && type == _function) { tokExprAllowed = false; } else if (type === _backQuote) { - if (tokContext[tokContext.length - 1] === q_tmpl) { + if (curTokContext() === q_tmpl) { tokContext.pop(); } else { tokContext.push(q_tmpl); preserveSpace = true; } tokExprAllowed = false; - tokExprAllowed = false; } else if (type === _xjsTagStart) { tokContext.push(j_expr); // treat as beginning of JSX expression tokContext.push(j_oTag); // start opening tag context @@ -718,12 +717,12 @@ var out = tokContext.pop(); if (out === j_oTag && prevType === _slash || out === j_cTag) { tokContext.pop(); - tokExprAllowed = curTokContext() === j_expr; + preserveSpace = tokExprAllowed = curTokContext() === j_expr; } else { - tokExprAllowed = true; + preserveSpace = tokExprAllowed = true; } } else if (type === _xjsText) { - tokExprAllowed = true; + preserveSpace = tokExprAllowed = true; } else if (type === _slash && prevType === _xjsTagStart) { tokContext.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore tokContext.push(j_cTag); // reconsider as closing tag context @@ -732,7 +731,7 @@ tokExprAllowed = type.beforeExpr; } - if (!preserveSpace && curTokContext() !== j_expr) skipSpace(); + if (!preserveSpace) skipSpace(); } function skipBlockComment() { @@ -888,8 +887,6 @@ skipSpace(); return readToken(); } - if (next === 61) - size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2; if (tokExprAllowed && code === 60) { ++tokPos; return finishToken(_xjsTagStart); @@ -901,6 +898,8 @@ return finishToken(_xjsTagEnd); } } + if (next === 61) + size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2; return finishOp(_relational, size); } @@ -995,13 +994,17 @@ if (options.locations) tokStartLoc = curPosition(); if (tokPos >= inputLen) return finishToken(_eof); - if (tokContext[tokContext.length - 1] === q_tmpl) { + var context = curTokContext(); + + if (context === q_tmpl) { return readTmplToken(); } - var code = input.charCodeAt(tokPos); - var context = curTokContext(); + if (context === j_expr) { + return readXJSToken(); + } + var code = input.charCodeAt(tokPos); if (context === j_oTag || context === j_cTag) { // JSX identifier if (isIdentifierStart(code)) return readJSXWord(); @@ -1528,32 +1531,32 @@ var out = "", start = tokPos; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated JSX contents"); - var ch = input.charAt(tokPos); + var ch = input.charCodeAt(tokPos); switch (ch) { - case "{": - case "<": + case 123: // '{' + case 60: // '<' if (tokPos === start) { - return getTokenFromCode(ch.charCodeAt(0)); + return getTokenFromCode(ch); } return finishToken(_xjsText, out); - case "&": + case 38: // '&' out += readXJSEntity(); break; default: ++tokPos; - if (newline.test(ch)) { - if (ch === "\r" && input.charCodeAt(tokPos) === 10) { + if (isNewLine(ch)) { + if (ch === 13 && input.charCodeAt(tokPos) === 10) { ++tokPos; - ch = "\n"; + ch = 10; } if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } } - out += ch; + out += String.fromCharCode(ch); } } } From 29910d2b2d7f268a977bc1dbc10d9ea5fc5dcaa7 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 21 Jan 2015 03:00:12 +0000 Subject: [PATCH 31/36] XJS -> JSX as per facebook/esprima#83. --- acorn.js | 160 +++++++++--------- test/tests-jsx.js | 416 +++++++++++++++++++++++----------------------- 2 files changed, 288 insertions(+), 288 deletions(-) diff --git a/acorn.js b/acorn.js index 5082a2720b..c23eb47aef 100644 --- a/acorn.js +++ b/acorn.js @@ -361,7 +361,7 @@ var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; var _name = {type: "name"}, _eof = {type: "eof"}; - var _xjsName = {type: "xjsName"}; + var _jsxName = {type: "jsxName"}; // Keyword tokens. The `keyword` property (also used in keyword-like // operators) indicates that the token originated from an @@ -425,7 +425,7 @@ var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}; var _ellipsis = {type: "...", prefix: true, beforeExpr: true}; var _backQuote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; - var _xjsText = {type: "xjsText"}; + var _jsxText = {type: "jsxText"}; // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is @@ -461,7 +461,7 @@ var _star = {binop: 10, beforeExpr: true}; // JSX tag boundaries - var _xjsTagStart = {type: "xjsTagStart"}, _xjsTagEnd = {type: "xjsTagEnd"}; + var _jsxTagStart = {type: "jsxTagStart"}, _jsxTagEnd = {type: "jsxTagEnd"}; // Provide access to the token types for external users of the // tokenizer. @@ -661,9 +661,9 @@ return true; if (prevType == _braceL) return curTokContext() === b_stat; - if (prevType === _xjsTagEnd || prevType === _xjsText) + if (prevType === _jsxTagEnd || prevType === _jsxText) return true; - if (prevType === _xjsName) + if (prevType === _jsxName) return false; return !tokExprAllowed; } @@ -709,11 +709,11 @@ preserveSpace = true; } tokExprAllowed = false; - } else if (type === _xjsTagStart) { + } else if (type === _jsxTagStart) { tokContext.push(j_expr); // treat as beginning of JSX expression tokContext.push(j_oTag); // start opening tag context tokExprAllowed = false; - } else if (type === _xjsTagEnd) { + } else if (type === _jsxTagEnd) { var out = tokContext.pop(); if (out === j_oTag && prevType === _slash || out === j_cTag) { tokContext.pop(); @@ -721,9 +721,9 @@ } else { preserveSpace = tokExprAllowed = true; } - } else if (type === _xjsText) { + } else if (type === _jsxText) { preserveSpace = tokExprAllowed = true; - } else if (type === _slash && prevType === _xjsTagStart) { + } else if (type === _slash && prevType === _jsxTagStart) { tokContext.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore tokContext.push(j_cTag); // reconsider as closing tag context tokExprAllowed = false; @@ -889,13 +889,13 @@ } if (tokExprAllowed && code === 60) { ++tokPos; - return finishToken(_xjsTagStart); + return finishToken(_jsxTagStart); } if (code === 62) { var context = curTokContext(); if (context === j_oTag || context === j_cTag) { ++tokPos; - return finishToken(_xjsTagEnd); + return finishToken(_jsxTagEnd); } } if (next === 61) @@ -1001,7 +1001,7 @@ } if (context === j_expr) { - return readXJSToken(); + return readJSXToken(); } var code = input.charCodeAt(tokPos); @@ -1009,7 +1009,7 @@ // JSX identifier if (isIdentifierStart(code)) return readJSXWord(); } else if (context === j_expr) { - return readXJSToken(); + return readJSXToken(); } else { // Identifier or keyword. '\uXXXX' sequences are allowed in // identifiers, so '\' also dispatches to that. @@ -1186,7 +1186,7 @@ if (ch === 92 && !isJSX) { // '\' out += readEscapedChar(); } else if (ch === 38 && isJSX) { // '&' - out += readXJSEntity(); + out += readJSXEntity(); } else { ++tokPos; if (isNewLine(ch)) { @@ -1491,7 +1491,7 @@ diams: '\u2666' }; - function readXJSEntity() { + function readJSXEntity() { var str = '', count = 0, entity; var ch = input[tokPos]; if (ch !== '&') raise(tokPos, "Entity must start with an ampersand"); @@ -1527,7 +1527,7 @@ // Reads inline JSX contents token. - function readXJSToken() { + function readJSXToken() { var out = "", start = tokPos; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated JSX contents"); @@ -1538,10 +1538,10 @@ if (tokPos === start) { return getTokenFromCode(ch); } - return finishToken(_xjsText, out); + return finishToken(_jsxText, out); case 38: // '&' - out += readXJSEntity(); + out += readJSXEntity(); break; default: @@ -1665,7 +1665,7 @@ do { ch = input.charCodeAt(++tokPos); } while (isIdentifierChar(ch) || ch === 45); // '-' - return finishToken(_xjsName, input.slice(start, tokPos)); + return finishToken(_jsxName, input.slice(start, tokPos)); } // ## Parser @@ -2580,7 +2580,7 @@ next(); return finishNode(node, "Literal"); - case _num: case _string: case _xjsText: + case _num: case _string: case _jsxText: var node = startNode(); node.value = tokVal; node.raw = input.slice(tokStart, tokEnd); @@ -2659,8 +2659,8 @@ case _backQuote: return parseTemplate(); - case _xjsTagStart: - return parseXJSElement(); + case _jsxTagStart: + return parseJSXElement(); default: unexpected(); @@ -3175,26 +3175,26 @@ // Transforms JSX element name to string. - function getQualifiedXJSName(object) { - if (object.type === "XJSIdentifier") { + function getQualifiedJSXName(object) { + if (object.type === "JSXIdentifier") { return object.name; } - if (object.type === "XJSNamespacedName") { + if (object.type === "JSXNamespacedName") { return object.namespace.name + ':' + object.name.name; } - if (object.type === "XJSMemberExpression") { + if (object.type === "JSXMemberExpression") { return ( - getQualifiedXJSName(object.object) + '.' + - getQualifiedXJSName(object.property) + getQualifiedJSXName(object.object) + '.' + + getQualifiedJSXName(object.property) ); } } // Parse next token as JSX identifier - function parseXJSIdentifier() { + function parseJSXIdentifier() { var node = startNode(); - if (tokType === _xjsName) { + if (tokType === _jsxName) { node.name = tokVal; } else if (tokType.keyword) { node.name = tokType.keyword; @@ -3202,68 +3202,68 @@ unexpected(); } next(); - return finishNode(node, "XJSIdentifier"); + return finishNode(node, "JSXIdentifier"); } // Parse namespaced identifier. - function parseXJSNamespacedName() { + function parseJSXNamespacedName() { var start = storeCurrentPos(); - var name = parseXJSIdentifier(); + var name = parseJSXIdentifier(); if (!eat(_colon)) return name; var node = startNodeAt(start); node.namespace = name; - node.name = parseXJSIdentifier(); - return finishNode(node, "XJSNamespacedName"); + node.name = parseJSXIdentifier(); + return finishNode(node, "JSXNamespacedName"); } // Parses element name in any form - namespaced, member // or single identifier. - function parseXJSElementName() { + function parseJSXElementName() { var start = storeCurrentPos(); - var node = parseXJSNamespacedName(); + var node = parseJSXNamespacedName(); while (eat(_dot)) { var newNode = startNodeAt(start); newNode.object = node; - newNode.property = parseXJSIdentifier(); - node = finishNode(newNode, "XJSMemberExpression"); + newNode.property = parseJSXIdentifier(); + node = finishNode(newNode, "JSXMemberExpression"); } return node; } // Parses any type of JSX attribute value. - function parseXJSAttributeValue() { + function parseJSXAttributeValue() { switch (tokType) { case _braceL: - var node = parseXJSExpressionContainer(); - if (node.expression.type === "XJSEmptyExpression") { + var node = parseJSXExpressionContainer(); + if (node.expression.type === "JSXEmptyExpression") { raise( node.start, - 'XJS attributes must only be assigned a non-empty ' + + 'JSX attributes must only be assigned a non-empty ' + 'expression' ); } return node; - case _xjsTagStart: - return parseXJSElement(); + case _jsxTagStart: + return parseJSXElement(); - case _xjsText: + case _jsxText: case _string: return parseExprAtom(); default: - raise(tokStart, "XJS value should be either an expression or a quoted XJS text"); + raise(tokStart, "JSX value should be either an expression or a quoted JSX text"); } } - // XJSEmptyExpression is unique type since it doesn't actually parse anything, + // JSXEmptyExpression is unique type since it doesn't actually parse anything, // and so it should start at the end of last read token (left brace) and finish // at the beginning of the next one (right brace). - function parseXJSEmptyExpression() { + function parseJSXEmptyExpression() { if (tokType !== _braceR) { unexpected(); } @@ -3278,95 +3278,95 @@ tokStartLoc = lastEndLoc; lastEndLoc = tmp; - return finishNode(startNode(), "XJSEmptyExpression"); + return finishNode(startNode(), "JSXEmptyExpression"); } // Parses JSX expression enclosed into curly brackets. - function parseXJSExpressionContainer() { + function parseJSXExpressionContainer() { var node = startNode(); next(); - node.expression = tokType === _braceR ? parseXJSEmptyExpression() : parseExpression(); + node.expression = tokType === _braceR ? parseJSXEmptyExpression() : parseExpression(); expect(_braceR); - return finishNode(node, "XJSExpressionContainer"); + return finishNode(node, "JSXExpressionContainer"); } // Parses following JSX attribute name-value pair. - function parseXJSAttribute() { + function parseJSXAttribute() { var node = startNode(); if (eat(_braceL)) { if (tokType !== _ellipsis) unexpected(); node.argument = parseMaybeUnary().argument; expect(_braceR); - return finishNode(node, "XJSSpreadAttribute"); + return finishNode(node, "JSXSpreadAttribute"); } - node.name = parseXJSNamespacedName(); - node.value = eat(_eq) ? parseXJSAttributeValue() : null; - return finishNode(node, "XJSAttribute"); + node.name = parseJSXNamespacedName(); + node.value = eat(_eq) ? parseJSXAttributeValue() : null; + return finishNode(node, "JSXAttribute"); } // Parses JSX opening tag starting after '<'. - function parseXJSOpeningElementAt(start) { + function parseJSXOpeningElementAt(start) { var node = startNodeAt(start); node.attributes = []; - node.name = parseXJSElementName(); - while (tokType !== _slash && tokType !== _xjsTagEnd) { - node.attributes.push(parseXJSAttribute()); + node.name = parseJSXElementName(); + while (tokType !== _slash && tokType !== _jsxTagEnd) { + node.attributes.push(parseJSXAttribute()); } node.selfClosing = eat(_slash); - expect(_xjsTagEnd); - return finishNode(node, "XJSOpeningElement"); + expect(_jsxTagEnd); + return finishNode(node, "JSXOpeningElement"); } // Parses JSX closing tag starting after '" + "Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">" ); } } @@ -3374,14 +3374,14 @@ node.openingElement = openingElement; node.closingElement = closingElement; node.children = children; - return finishNode(node, "XJSElement"); + return finishNode(node, "JSXElement"); } // Parses entire JSX element from current position. - function parseXJSElement() { + function parseJSXElement() { var start = storeCurrentPos(); next(); - return parseXJSElementAt(start); + return parseJSXElementAt(start); } }); diff --git a/test/tests-jsx.js b/test/tests-jsx.js index 186a818a9a..b952a4f680 100644 --- a/test/tests-jsx.js +++ b/test/tests-jsx.js @@ -2,15 +2,15 @@ var fbTestFixture = { // Taken and adapted from esprima-fb/fbtest.js. - 'XJS': { + 'JSX': { '': { type: "ExpressionStatement", expression: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "a", range: [1, 2], loc: { @@ -43,13 +43,13 @@ var fbTestFixture = { '': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSNamespacedName', + type: 'JSXNamespacedName', namespace: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'n', range: [1, 2], loc: { @@ -58,7 +58,7 @@ var fbTestFixture = { } }, name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [3, 4], loc: { @@ -74,11 +74,11 @@ var fbTestFixture = { }, selfClosing: true, attributes: [{ - type: 'XJSAttribute', + type: 'JSXAttribute', name: { - type: 'XJSNamespacedName', + type: 'JSXNamespacedName', namespace: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'n', range: [5, 6], loc: { @@ -87,7 +87,7 @@ var fbTestFixture = { } }, name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'v', range: [7, 8], loc: { @@ -131,11 +131,11 @@ var fbTestFixture = { ' {value} ': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [1, 2], loc: { @@ -145,11 +145,11 @@ var fbTestFixture = { }, selfClosing: false, attributes: [{ - type: 'XJSAttribute', + type: 'JSXAttribute', name: { - type: 'XJSNamespacedName', + type: 'JSXNamespacedName', namespace: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'n', range: [3, 4], loc: { @@ -158,7 +158,7 @@ var fbTestFixture = { } }, name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'foo', range: [5, 8], loc: { @@ -195,9 +195,9 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [38, 39], loc: { @@ -221,7 +221,7 @@ var fbTestFixture = { end: { line: 1, column: 16 } } }, { - type: 'XJSExpressionContainer', + type: 'JSXExpressionContainer', expression: { type: 'Identifier', name: 'value', @@ -246,11 +246,11 @@ var fbTestFixture = { end: { line: 1, column: 24 } } }, { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'b', range: [25, 26], loc: { @@ -267,9 +267,9 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'b', range: [34, 35], loc: { @@ -284,11 +284,11 @@ var fbTestFixture = { } }, children: [{ - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'c', range: [28, 29], loc: { @@ -333,25 +333,25 @@ var fbTestFixture = { '': { type: "ExpressionStatement", expression: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "a", range: [1, 2] }, selfClosing: true, attributes: [ { - type: "XJSAttribute", + type: "JSXAttribute", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "b", range: [3, 4] }, value: { - type: "XJSExpressionContainer", + type: "JSXExpressionContainer", expression: { type: "Literal", value: " ", @@ -363,9 +363,9 @@ var fbTestFixture = { range: [3, 10] }, { - type: "XJSAttribute", + type: "JSXAttribute", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "c", range: [11, 12] }, @@ -378,9 +378,9 @@ var fbTestFixture = { range: [11, 16] }, { - type: "XJSAttribute", + type: "JSXAttribute", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "d", range: [17, 18] }, @@ -393,9 +393,9 @@ var fbTestFixture = { range: [17, 26] }, { - type: "XJSAttribute", + type: "JSXAttribute", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "e", range: [27, 28] }, @@ -419,11 +419,11 @@ var fbTestFixture = { '': { type: "ExpressionStatement", expression: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "a", range: [ 1, @@ -492,11 +492,11 @@ var fbTestFixture = { '<日本語>': { type: "ExpressionStatement", expression: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "日本語", range: [ 1, @@ -531,9 +531,9 @@ var fbTestFixture = { } }, closingElement: { - type: "XJSClosingElement", + type: "JSXClosingElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "日本語", range: [ 7, @@ -600,11 +600,11 @@ var fbTestFixture = { '\nbar\nbaz\n': { type: "ExpressionStatement", expression: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "AbC-def", range: [ 1, @@ -624,9 +624,9 @@ var fbTestFixture = { selfClosing: false, attributes: [ { - type: "XJSAttribute", + type: "JSXAttribute", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "test", range: [ 11, @@ -694,9 +694,9 @@ var fbTestFixture = { } }, closingElement: { - type: "XJSClosingElement", + type: "JSXClosingElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "AbC-def", range: [ 43, @@ -783,11 +783,11 @@ var fbTestFixture = { ' : } />': { type: "ExpressionStatement", expression: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "a", range: [ 1, @@ -807,9 +807,9 @@ var fbTestFixture = { selfClosing: true, attributes: [ { - type: "XJSAttribute", + type: "JSXAttribute", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "b", range: [ 3, @@ -827,7 +827,7 @@ var fbTestFixture = { } }, value: { - type: "XJSExpressionContainer", + type: "JSXExpressionContainer", expression: { type: "ConditionalExpression", test: { @@ -849,11 +849,11 @@ var fbTestFixture = { } }, consequent: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "c", range: [ 11, @@ -905,11 +905,11 @@ var fbTestFixture = { } }, alternate: { - type: "XJSElement", + type: "JSXElement", openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", name: { - type: "XJSIdentifier", + type: "JSXIdentifier", name: "d", range: [ 19, @@ -1057,11 +1057,11 @@ var fbTestFixture = { '{}': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [1, 2], loc: { @@ -1078,9 +1078,9 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [7, 8], loc: { @@ -1095,9 +1095,9 @@ var fbTestFixture = { } }, children: [{ - type: 'XJSExpressionContainer', + type: 'JSXExpressionContainer', expression: { - type: 'XJSEmptyExpression', + type: 'JSXEmptyExpression', range: [4, 4], loc: { start: { line: 1, column: 4 }, @@ -1126,11 +1126,11 @@ var fbTestFixture = { '{/* this is a comment */}': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [1, 2], loc: { @@ -1147,9 +1147,9 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [30, 31], loc: { @@ -1164,9 +1164,9 @@ var fbTestFixture = { } }, children: [{ - type: 'XJSExpressionContainer', + type: 'JSXExpressionContainer', expression: { - type: 'XJSEmptyExpression', + type: 'JSXEmptyExpression', range: [4, 27], loc: { start: { line: 1, column: 4 }, @@ -1195,11 +1195,11 @@ var fbTestFixture = { '
    @test content
    ': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'div', range: [1, 4], loc: { @@ -1216,9 +1216,9 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'div', range: [20, 23], loc: { @@ -1258,11 +1258,11 @@ var fbTestFixture = { '

    7x invalid-js-identifier
    ': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'div', range: [ 1, @@ -1297,9 +1297,9 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'div', range: [ 37, @@ -1332,11 +1332,11 @@ var fbTestFixture = { } }, children: [{ - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'br', range: [ 6, @@ -1439,11 +1439,11 @@ var fbTestFixture = { ' right=monkeys /> gorillas />': { "type": "ExpressionStatement", "expression": { - "type": "XJSElement", + "type": "JSXElement", "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "LeftRight", "range": [ 1, @@ -1463,9 +1463,9 @@ var fbTestFixture = { "selfClosing": true, "attributes": [ { - "type": "XJSAttribute", + "type": "JSXAttribute", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "left", "range": [ 11, @@ -1483,11 +1483,11 @@ var fbTestFixture = { } }, "value": { - "type": "XJSElement", + "type": "JSXElement", "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "a", "range": [ 17, @@ -1554,9 +1554,9 @@ var fbTestFixture = { } }, { - "type": "XJSAttribute", + "type": "JSXAttribute", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "right", "range": [ 22, @@ -1574,11 +1574,11 @@ var fbTestFixture = { } }, "value": { - "type": "XJSElement", + "type": "JSXElement", "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "b", "range": [ 29, @@ -1613,9 +1613,9 @@ var fbTestFixture = { } }, "closingElement": { - "type": "XJSClosingElement", + "type": "JSXClosingElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "b", "range": [ 52, @@ -1750,13 +1750,13 @@ var fbTestFixture = { '': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSMemberExpression', + type: 'JSXMemberExpression', object: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [1, 2], loc: { @@ -1765,7 +1765,7 @@ var fbTestFixture = { } }, property: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'b', range: [3, 4], loc: { @@ -1788,11 +1788,11 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSMemberExpression', + type: 'JSXMemberExpression', object: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [7, 8], loc: { @@ -1801,7 +1801,7 @@ var fbTestFixture = { } }, property: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'b', range: [9, 10], loc: { @@ -1838,15 +1838,15 @@ var fbTestFixture = { '': { type: 'ExpressionStatement', expression: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSMemberExpression', + type: 'JSXMemberExpression', object: { - type: 'XJSMemberExpression', + type: 'JSXMemberExpression', object: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [1, 2], loc: { @@ -1855,7 +1855,7 @@ var fbTestFixture = { } }, property: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'b', range: [3, 4], loc: { @@ -1870,7 +1870,7 @@ var fbTestFixture = { } }, property: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'c', range: [5, 6], loc: { @@ -1893,13 +1893,13 @@ var fbTestFixture = { } }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', name: { - type: 'XJSMemberExpression', + type: 'JSXMemberExpression', object: { - type: 'XJSMemberExpression', + type: 'JSXMemberExpression', object: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'a', range: [9, 10], loc: { @@ -1908,7 +1908,7 @@ var fbTestFixture = { } }, property: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'b', range: [11, 12], loc: { @@ -1923,7 +1923,7 @@ var fbTestFixture = { } }, property: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'c', range: [13, 14], loc: { @@ -1958,7 +1958,7 @@ var fbTestFixture = { }, // In order to more useful parse errors, we disallow following an - // XJSElement by a less-than symbol. In the rare case that the binary + // JSXElement by a less-than symbol. In the rare case that the binary // operator was intended, the tag can be wrapped in parentheses: '(
    ) < x;': { type: 'ExpressionStatement', @@ -1966,11 +1966,11 @@ var fbTestFixture = { type: 'BinaryExpression', operator: '<', left: { - type: 'XJSElement', + type: 'JSXElement', openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', name: 'div', range: [2, 5], loc: { @@ -2019,11 +2019,11 @@ var fbTestFixture = { '
    ': { "type": "ExpressionStatement", "expression": { - "type": "XJSElement", + "type": "JSXElement", "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "div", "range": [ 1, @@ -2043,7 +2043,7 @@ var fbTestFixture = { "selfClosing": true, "attributes": [ { - "type": "XJSSpreadAttribute", + "type": "JSXSpreadAttribute", "argument": { "type": "Identifier", "name": "props", @@ -2129,11 +2129,11 @@ var fbTestFixture = { '
    ': { "type": "ExpressionStatement", "expression": { - "type": "XJSElement", + "type": "JSXElement", "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "div", "range": [ 1, @@ -2153,7 +2153,7 @@ var fbTestFixture = { "selfClosing": true, "attributes": [ { - "type": "XJSSpreadAttribute", + "type": "JSXSpreadAttribute", "argument": { "type": "Identifier", "name": "props", @@ -2188,9 +2188,9 @@ var fbTestFixture = { } }, { - "type": "XJSAttribute", + "type": "JSXAttribute", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "post", "range": [ 16, @@ -2293,11 +2293,11 @@ var fbTestFixture = { '
    ': { "type": "ExpressionStatement", "expression": { - "type": "XJSElement", + "type": "JSXElement", "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "div", "range": [ 1, @@ -2317,9 +2317,9 @@ var fbTestFixture = { "selfClosing": false, "attributes": [ { - "type": "XJSAttribute", + "type": "JSXAttribute", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "pre", "range": [ 5, @@ -2371,9 +2371,9 @@ var fbTestFixture = { } }, { - "type": "XJSAttribute", + "type": "JSXAttribute", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "pre2", "range": [ 19, @@ -2425,7 +2425,7 @@ var fbTestFixture = { } }, { - "type": "XJSSpreadAttribute", + "type": "JSXSpreadAttribute", "argument": { "type": "Identifier", "name": "props", @@ -2476,9 +2476,9 @@ var fbTestFixture = { } }, "closingElement": { - "type": "XJSClosingElement", + "type": "JSXClosingElement", "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "name": "div", "range": [ 49, @@ -2560,7 +2560,7 @@ var fbTestFixture = { 52 ], "expression": { - "type": "XJSElement", + "type": "JSXElement", "start": 0, "end": 52, "loc": { @@ -2578,7 +2578,7 @@ var fbTestFixture = { 52 ], "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "start": 0, "end": 31, "loc": { @@ -2597,7 +2597,7 @@ var fbTestFixture = { ], "attributes": [ { - "type": "XJSAttribute", + "type": "JSXAttribute", "start": 3, "end": 16, "loc": { @@ -2615,7 +2615,7 @@ var fbTestFixture = { 16 ], "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "start": 3, "end": 5, "loc": { @@ -2635,7 +2635,7 @@ var fbTestFixture = { "name": "aa" }, "value": { - "type": "XJSExpressionContainer", + "type": "JSXExpressionContainer", "start": 6, "end": 16, "loc": { @@ -2755,7 +2755,7 @@ var fbTestFixture = { } }, { - "type": "XJSAttribute", + "type": "JSXAttribute", "start": 17, "end": 30, "loc": { @@ -2773,7 +2773,7 @@ var fbTestFixture = { 30 ], "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "start": 17, "end": 19, "loc": { @@ -2793,7 +2793,7 @@ var fbTestFixture = { "name": "bb" }, "value": { - "type": "XJSExpressionContainer", + "type": "JSXExpressionContainer", "start": 20, "end": 30, "loc": { @@ -2914,7 +2914,7 @@ var fbTestFixture = { } ], "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "start": 1, "end": 2, "loc": { @@ -2936,7 +2936,7 @@ var fbTestFixture = { "selfClosing": false }, "closingElement": { - "type": "XJSClosingElement", + "type": "JSXClosingElement", "start": 48, "end": 52, "loc": { @@ -2954,7 +2954,7 @@ var fbTestFixture = { 52 ], "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "start": 50, "end": 51, "loc": { @@ -2976,7 +2976,7 @@ var fbTestFixture = { }, "children": [ { - "type": "XJSElement", + "type": "JSXElement", "start": 31, "end": 48, "loc": { @@ -2994,7 +2994,7 @@ var fbTestFixture = { 48 ], "openingElement": { - "type": "XJSOpeningElement", + "type": "JSXOpeningElement", "start": 31, "end": 36, "loc": { @@ -3013,7 +3013,7 @@ var fbTestFixture = { ], "attributes": [], "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "start": 32, "end": 35, "loc": { @@ -3035,7 +3035,7 @@ var fbTestFixture = { "selfClosing": false }, "closingElement": { - "type": "XJSClosingElement", + "type": "JSXClosingElement", "start": 42, "end": 48, "loc": { @@ -3053,7 +3053,7 @@ var fbTestFixture = { 48 ], "name": { - "type": "XJSIdentifier", + "type": "JSXIdentifier", "start": 44, "end": 47, "loc": { @@ -3075,7 +3075,7 @@ var fbTestFixture = { }, "children": [ { - "type": "XJSExpressionContainer", + "type": "JSXExpressionContainer", "start": 36, "end": 42, "loc": { @@ -3165,16 +3165,16 @@ var fbTestFixture = { start: 0, end: 40, expression: { - type: "XJSElement", + type: "JSXElement", start: 0, end: 38, openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", start: 0, end: 3, attributes: [], name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 1, end: 2, name: "p" @@ -3182,11 +3182,11 @@ var fbTestFixture = { selfClosing: false }, closingElement: { - type: "XJSClosingElement", + type: "JSXClosingElement", start: 34, end: 38, name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 36, end: 37, name: "p" @@ -3201,19 +3201,19 @@ var fbTestFixture = { raw: "foo " }, { - type: "XJSElement", + type: "JSXElement", start: 7, end: 30, openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", start: 7, end: 22, attributes: [{ - type: "XJSAttribute", + type: "JSXAttribute", start: 10, end: 21, name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 10, end: 14, name: "href" @@ -3227,7 +3227,7 @@ var fbTestFixture = { } }], name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 8, end: 9, name: "a" @@ -3235,11 +3235,11 @@ var fbTestFixture = { selfClosing: false }, closingElement: { - type: "XJSClosingElement", + type: "JSXClosingElement", start: 26, end: 30, name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 28, end: 29, name: "a" @@ -3269,16 +3269,16 @@ var fbTestFixture = { start: 0, end: 30, expression: { - type: 'XJSElement', + type: 'JSXElement', start: 0, end: 30, openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', start: 0, end: 5, attributes: [], name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', start: 1, end: 4, name: 'div' @@ -3286,31 +3286,31 @@ var fbTestFixture = { selfClosing: false }, closingElement: { - type: 'XJSClosingElement', + type: 'JSXClosingElement', start: 24, end: 30, name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', start: 26, end: 29, name: 'div' } }, children: [{ - type: 'XJSExpressionContainer', + type: 'JSXExpressionContainer', start: 5, end: 24, expression: { - type: 'XJSElement', + type: 'JSXElement', start: 6, end: 23, openingElement: { - type: 'XJSOpeningElement', + type: 'JSXOpeningElement', start: 6, end: 23, attributes: [ { - type: 'XJSSpreadAttribute', + type: 'JSXSpreadAttribute', start: 11, end: 20, argument: { @@ -3322,7 +3322,7 @@ var fbTestFixture = { } ], name: { - type: 'XJSIdentifier', + type: 'JSXIdentifier', start: 7, end: 10, name: 'div' @@ -3341,16 +3341,16 @@ var fbTestFixture = { start: 0, end: 18, expression: { - type: "XJSElement", + type: "JSXElement", start: 0, end: 18, openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", start: 0, end: 5, attributes: [], name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 1, end: 4, name: "div" @@ -3358,18 +3358,18 @@ var fbTestFixture = { selfClosing: false }, closingElement: { - type: "XJSClosingElement", + type: "JSXClosingElement", start: 12, end: 18, name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 14, end: 17, name: "div" } }, children: [{ - type: "XJSExpressionContainer", + type: "JSXExpressionContainer", start: 5, end: 12, expression: { @@ -3407,16 +3407,16 @@ var fbTestFixture = { start: 0, end: 16, expression: { - type: "XJSElement", + type: "JSXElement", start: 0, end: 16, openingElement: { - type: "XJSOpeningElement", + type: "JSXOpeningElement", start: 0, end: 5, attributes: [], name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 1, end: 4, name: "div" @@ -3424,11 +3424,11 @@ var fbTestFixture = { selfClosing: false }, closingElement: { - type: "XJSClosingElement", + type: "JSXClosingElement", start: 10, end: 16, name: { - type: "XJSIdentifier", + type: "JSXIdentifier", start: 12, end: 15, name: "div" From caa5da6ce19794b5b93ec6881e2c3ced5675f829 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 21 Jan 2015 23:51:40 +0200 Subject: [PATCH 32/36] Improve spread element parsing (fix allowed contexts and error locations). --- acorn.js | 158 ++++++++++++++++++++++++++---------------- acorn_loose.js | 16 ++--- test/tests-harmony.js | 10 +-- 3 files changed, 109 insertions(+), 75 deletions(-) diff --git a/acorn.js b/acorn.js index 58d0d416ab..10cac96e47 100644 --- a/acorn.js +++ b/acorn.js @@ -315,11 +315,6 @@ var inFunction, inGenerator, labels, strict; - // This counter is used for checking that arrow expressions did - // not contain nested parentheses in argument list. - - var metParenL; - function initParserState() { lastStart = lastEnd = tokPos; if (options.locations) lastEndLoc = curPosition(); @@ -422,7 +417,7 @@ var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}; - var _ellipsis = {type: "...", prefix: true, beforeExpr: true}; + var _ellipsis = {type: "...", beforeExpr: true}; var _backQuote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; // Operators. These carry several kinds of properties to help the @@ -624,7 +619,6 @@ tokType = _eof; tokContext = []; tokExprAllowed = true; - metParenL = 0; if (tokPos === 0 && options.allowHashBang && input.slice(0, 2) === '#!') { skipLineComment(2); } @@ -1380,6 +1374,17 @@ return node; } + // Finish node at given position + + function finishNodeAt(node, type, pos) { + if (options.locations) { node.loc.end = pos[1]; pos = pos[0]; } + node.type = type; + node.end = pos; + if (options.ranges) + node.range[1] = pos; + return node; + } + // Test whether a statement node is the string literal `"use strict"`. function isUseStrict(stmt) { @@ -1440,6 +1445,9 @@ switch (node.type) { case "Identifier": case "MemberExpression": + case "ObjectPattern": + case "ArrayPattern": + case "AssignmentPattern": break; case "ObjectExpression": @@ -1482,6 +1490,21 @@ return node; } + // Parses spread element. + + function parseSpread(isBinding) { + var spread = startNode(); + next(); + if (isBinding) { + var arg = parseAssignableAtom(); + checkSpreadAssign(arg); + spread.argument = arg; + } else { + spread.argument = parseMaybeAssign(); + } + return finishNode(spread, "SpreadElement"); + } + // Parses lvalue (assignable) atom. function parseAssignableAtom() { @@ -1497,11 +1520,7 @@ while (!eat(_bracketR)) { first ? first = false : expect(_comma); if (tokType === _ellipsis) { - var spread = startNode(); - next(); - spread.argument = parseAssignableAtom(); - checkSpreadAssign(spread.argument); - elts.push(finishNode(spread, "SpreadElement")); + elts.push(parseSpread(true)); expect(_bracketR); break; } @@ -2088,21 +2107,16 @@ function parseMaybeUnary() { if (tokType.prefix) { - var node = startNode(), update = tokType.isUpdate, nodeType; - if (tokType === _ellipsis) { - nodeType = "SpreadElement"; - } else { - nodeType = update ? "UpdateExpression" : "UnaryExpression"; - node.operator = tokVal; - node.prefix = true; - } + var node = startNode(), update = tokType.isUpdate; + node.operator = tokVal; + node.prefix = true; next(); node.argument = parseMaybeUnary(); if (update) checkLVal(node.argument); else if (strict && node.operator === "delete" && node.argument.type === "Identifier") raise(node.start, "Deleting local variable in strict mode"); - return finishNode(node, nodeType); + return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); } var start = storeCurrentPos(); var expr = parseExprSubscripts(); @@ -2198,42 +2212,7 @@ return finishNode(node, "Literal"); case _parenL: - var start = storeCurrentPos(); - var val, exprList; - next(); - // check whether this is generator comprehension or regular expression - if (options.ecmaVersion >= 7 && tokType === _for) { - val = parseComprehension(startNodeAt(start), true); - } else { - var oldParenL = ++metParenL; - if (tokType !== _parenR) { - val = parseExpression(); - exprList = val.type === "SequenceExpression" ? val.expressions : [val]; - } else { - exprList = []; - } - expect(_parenR); - // if '=>' follows '(...)', convert contents to arguments - if (metParenL === oldParenL && eat(_arrow)) { - val = parseArrowExpression(startNodeAt(start), exprList); - } else { - // forbid '()' before everything but '=>' - if (!val) unexpected(lastStart); - // forbid '...' in sequence expressions - if (options.ecmaVersion >= 6) { - for (var i = 0; i < exprList.length; i++) { - if (exprList[i].type === "SpreadElement") unexpected(); - } - } - - if (options.preserveParens) { - var par = startNodeAt(start); - par.expression = val; - val = finishNode(par, "ParenthesizedExpression"); - } - } - } - return val; + return parseParenAndDistinguishExpression(); case _bracketL: var node = startNode(); @@ -2267,6 +2246,60 @@ } } + function parseParenAndDistinguishExpression() { + var start = storeCurrentPos(), val; + if (options.ecmaVersion >= 6) { + next(); + + if (options.ecmaVersion >= 7 && tokType === _for) { + return parseComprehension(startNodeAt(start), true); + } + + var innerStart = storeCurrentPos(), exprList = [], first = true, spreadStart, innerParenStart; + while (tokType !== _parenR) { + first ? first = false : expect(_comma); + if (tokType === _ellipsis) { + spreadStart = tokStart; + exprList.push(parseSpread(true)); + break; + } else { + if (tokType === _parenL && !innerParenStart) { + innerParenStart = tokStart; + } + exprList.push(parseMaybeAssign()); + } + } + var innerEnd = storeCurrentPos(); + expect(_parenR); + + if (eat(_arrow)) { + if (innerParenStart) unexpected(innerParenStart); + return parseArrowExpression(startNodeAt(start), exprList); + } + + if (!exprList.length) unexpected(lastStart); + if (spreadStart) unexpected(spreadStart); + + if (exprList.length > 1) { + val = startNodeAt(innerStart); + val.expressions = exprList; + finishNodeAt(val, "SequenceExpression", innerEnd); + } else { + val = exprList[0]; + } + } else { + val = parseParenExpression(); + } + + if (options.preserveParens) { + var par = startNodeAt(start); + par.expression = val; + return finishNode(par, "ParenthesizedExpression"); + } else { + return val; + } + } + // New's precedence is slightly tricky. It must allow its argument // to be a `[]` or dot subscript expression, but not a call — at // least, not without wrapping it in parentheses. Thus, it uses the @@ -2310,7 +2343,7 @@ return finishNode(node, "TemplateLiteral"); } - // Parse an object literal. + // Parse an object literal or binding pattern. function parseObj(isPattern) { var node = startNode(), first = true, propHash = {}; @@ -2460,7 +2493,7 @@ for (;;) { if (eat(_parenR)) { break; - } else if (options.ecmaVersion >= 6 && eat(_ellipsis)) { + } else if (eat(_ellipsis)) { node.rest = parseAssignableAtom(); checkSpreadAssign(node.rest); expect(_parenR); @@ -2571,8 +2604,11 @@ if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break; } else first = false; - if (allowEmpty && tokType === _comma) elts.push(null); - else elts.push(parseExpression(true)); + if (allowEmpty && tokType === _comma) { + elts.push(null); + } else { + elts.push(tokType === _ellipsis ? parseSpread() : parseMaybeAssign()); + } } return elts; } diff --git a/acorn_loose.js b/acorn_loose.js index a6542ee7d0..27e8b65e63 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -622,20 +622,18 @@ function parseMaybeUnary(noIn) { if (token.type.prefix) { - var node = startNode(), update = token.type.isUpdate, nodeType; - if (token.type === tt.ellipsis) { - nodeType = "SpreadElement"; - } else { - nodeType = update ? "UpdateExpression" : "UnaryExpression"; - node.operator = token.value; - node.prefix = true; - } + var node = startNode(), update = token.type.isUpdate; node.operator = token.value; node.prefix = true; next(); node.argument = parseMaybeUnary(noIn); if (update) node.argument = checkLVal(node.argument); - return finishNode(node, nodeType); + return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + } else if (token.type === tt.ellipsis) { + var node = startNode(); + next(); + node.argument = parseMaybeUnary(noIn); + return finishNode(node, "SpreadElement"); } var start = storeCurrentPos(); var expr = parseExprSubscripts(); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 0ea8758cf7..07bf912d01 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -13891,9 +13891,9 @@ testFail("import { foo, bar }", "Unexpected token (1:19)", {ecmaVersion: 6}); testFail("import foo from bar", "Unexpected token (1:16)", {ecmaVersion: 6}); -testFail("((a)) => 42", "Unexpected token (1:6)", {ecmaVersion: 6}); +testFail("((a)) => 42", "Unexpected token (1:1)", {ecmaVersion: 6}); -testFail("(a, (b)) => 42", "Unexpected token (1:9)", {ecmaVersion: 6}); +testFail("(a, (b)) => 42", "Unexpected token (1:4)", {ecmaVersion: 6}); testFail("\"use strict\"; (eval = 10) => 42", "Assigning to eval in strict mode (1:15)", {ecmaVersion: 6}); @@ -14151,7 +14151,7 @@ testFail("\"use strict\"; function x({ b: { a } }, [{ b: { a } }]){}", "Argument testFail("\"use strict\"; function x(a, ...[a]){}", "Argument name clash in strict mode (1:32)", {ecmaVersion: 6}); -testFail("(...a, b) => {}", "Unexpected token (1:1)", {ecmaVersion: 6}); +testFail("(...a, b) => {}", "Unexpected token (1:5)", {ecmaVersion: 6}); testFail("([ 5 ]) => {}", "Unexpected token (1:3)", {ecmaVersion: 6}); @@ -14226,9 +14226,9 @@ test("[...a, ] = b", { locations: true }); -testFail("if (b,...a, );", "Unexpected token (1:12)", {ecmaVersion: 6}); +testFail("if (b,...a, );", "Unexpected token (1:6)", {ecmaVersion: 6}); -testFail("(b, ...a)", "Unexpected token (1:9)", {ecmaVersion: 6}); +testFail("(b, ...a)", "Unexpected token (1:4)", {ecmaVersion: 6}); testFail("switch (cond) { case 10: let a = 20; ", "Unexpected token (1:37)", {ecmaVersion: 6}); From 0d4f1f6ce9b7affbe1d35aca17ecabb3fb40a5e3 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 22 Jan 2015 16:39:30 +0200 Subject: [PATCH 33/36] Update version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8d244ea07..2a58547de6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "ECMAScript parser", "homepage": "http://marijnhaverbeke.nl/acorn/", "main": "acorn.js", - "version": "0.11.1", + "version": "0.11.1-1", "engines": {"node": ">=0.4.0"}, "maintainers": [{"name": "Marijn Haverbeke", "email": "marijnh@gmail.com", From a7d5734a594e3eff6c3abf9baba343237e21fa32 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 22 Jan 2015 17:13:56 +0200 Subject: [PATCH 34/36] Fix spread attribute parsing after merge. --- acorn.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acorn.js b/acorn.js index 63f9afa277..4ee74883ff 100644 --- a/acorn.js +++ b/acorn.js @@ -3337,8 +3337,8 @@ function parseJSXAttribute() { var node = startNode(); if (eat(_braceL)) { - if (tokType !== _ellipsis) unexpected(); - node.argument = parseMaybeUnary().argument; + expect(_ellipsis); + node.argument = parseMaybeAssign(); expect(_braceR); return finishNode(node, "JSXSpreadAttribute"); } From 5e1f60dbae90ce1608720c49ae21aceb81883864 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 22 Jan 2015 17:25:50 +0200 Subject: [PATCH 35/36] Update repository metadata and readme. --- README.md | 10 +++++++++- package.json | 18 +++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7714e873a7..bee4df9250 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,15 @@ [![Build Status](https://travis-ci.org/marijnh/acorn.svg?branch=master)](https://travis-ci.org/marijnh/acorn) -A tiny, fast JavaScript parser, written completely in JavaScript. +This is modification of [Acorn](http://marijnhaverbeke.nl/acorn/) - a tiny, fast JavaScript parser, written completely in JavaScript. + +It was forked to create experimental alternative, faster [React.js JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) parser by integrating pieces of code from official parser, modified to match Acorn's parsing logic. + +According to [benchmarks](https://github.com/RReverser/acorn-jsx/blob/master/test/bench.html), Acorn-JSX is 2x faster than official [Esprima-based parser](https://github.com/facebook/esprima) when location tracking is turned on in both (call it "source maps enabled mode"). At the same time, it consumes all the ES6+JSX syntax that can be consumed by Esprima-FB (this is proved by [official tests](https://github.com/RReverser/acorn-jsx/blob/master/test/tests-jsx.js)). + +## Transpiler + +Please note that this tool only parses source code to JSX AST, which is useful for various language tools and services. If you want to transpile your code to regular ES5-compliant JavaScript with source map, check out [6to5 transpiler](http://6to5.org/) which used `acorn-jsx` under the hood. ## Installation diff --git a/package.json b/package.json index 2a58547de6..8ffbbede7d 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { - "name": "acorn", - "description": "ECMAScript parser", - "homepage": "http://marijnhaverbeke.nl/acorn/", + "name": "acorn-jsx", + "description": "Alternative, faster React.js JSX parser", + "homepage": "https://github.com/RReverser/acorn-jsx", "main": "acorn.js", "version": "0.11.1-1", "engines": {"node": ">=0.4.0"}, - "maintainers": [{"name": "Marijn Haverbeke", - "email": "marijnh@gmail.com", - "web": "http://marijnhaverbeke.nl"}], + "maintainers": [{"name": "Ingvar Stepanyan", + "email": "me@rreverser.com", + "web": "http://rreverser.com/"}], "repository": {"type": "git", - "url": "http://marijnhaverbeke.nl/git/acorn"}, + "url": "https://github.com/RReverser/acorn-jsx"}, "licenses": [{"type": "MIT", - "url": "http://marijnhaverbeke.nl/acorn/LICENSE"}], + "url": "https://raw.githubusercontent.com/RReverser/acorn-jsx/master/LICENSE"}], "scripts": { "test": "node test/run.js", - "prepublish": "bin/without_eval > acorn_csp.js" + "prepublish": "node bin/without_eval > acorn_csp.js" }, "bin": {"acorn": "./bin/acorn"}, "devDependencies": {"regenerate": "~0.6.2", From de23a869ae266b566b0f052b0a76b8acdac35c95 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 22 Jan 2015 17:26:18 +0200 Subject: [PATCH 36/36] Add acorn_csp.js to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7a9d2aa37c..088a673331 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules /.tern-port +/acorn_csp.js