From d1f95ece42e0a69aa2c4aaf2360ce3c904e1f696 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 12 Jan 2015 20:31:38 +0200 Subject: [PATCH 01/12] 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 02/12] 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 03/12] 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 04/12] [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 ad9411d2aec4ac37bf441bc452a85e3217b6a05d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 14 Jan 2015 12:27:58 +0200 Subject: [PATCH 05/12] 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 06/12] `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 07/12] 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 08/12] 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 09/12] 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 10/12] 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 11/12] 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 12/12] 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 +});