From 41ad304955d6770f2f226edfdb463b74699628c5 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 23 Jan 2015 21:36:16 +0200 Subject: [PATCH 1/5] Introduce helpers for contextual keywords. --- acorn.js | 48 ++++++++++++++++++++++++++---------------------- acorn_loose.js | 42 +++++++++++++++++------------------------- 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/acorn.js b/acorn.js index 6cf6a60443..35015ad1aa 100644 --- a/acorn.js +++ b/acorn.js @@ -1414,6 +1414,24 @@ } } + // Tests whether parsed token is a contextual keyword. + + function isContextual(name) { + return tokType === _name && tokVal === name; + } + + // Consumes contextual keyword if possible. + + function eatContextual(name) { + return tokVal === name && eat(_name); + } + + // Asserts that following token is given contextual keyword. + + function expectContextual(name) { + if (!eatContextual(name)) unexpected(); + } + // Test whether a semicolon can be inserted at the current position. function canInsertSemicolon() { @@ -1814,13 +1832,13 @@ next(); parseVar(init, true, varKind); finishNode(init, "VariableDeclaration"); - if ((tokType === _in || (options.ecmaVersion >= 6 && tokType === _name && tokVal === "of")) && init.declarations.length === 1 && + if ((tokType === _in || (options.ecmaVersion >= 6 && isContextual("of"))) && init.declarations.length === 1 && !(isLet && init.declarations[0].init)) return parseForIn(node, init); return parseFor(node, init); } var init = parseExpression(false, true); - if (tokType === _in || (options.ecmaVersion >= 6 && tokType === _name && tokVal === "of")) { + if (tokType === _in || (options.ecmaVersion >= 6 && isContextual("of"))) { checkLVal(init); return parseForIn(node, init); } @@ -2631,8 +2649,7 @@ node.declaration = null; node['default'] = false; node.specifiers = parseExportSpecifiers(); - if (tokType === _name && tokVal === "from") { - next(); + if (eatContextual("from")) { node.source = tokType === _string ? parseExprAtom() : unexpected(); } else { if (isBatch) unexpected(); @@ -2663,12 +2680,7 @@ var node = startNode(); node.id = parseIdent(tokType === _default); - if (tokType === _name && tokVal === "as") { - next(); - node.name = parseIdent(true); - } else { - node.name = null; - } + node.name = eatContextual("as") ? parseIdent(true) : null; nodes.push(finishNode(node, "ExportSpecifier")); } } @@ -2686,8 +2698,7 @@ node.kind = ""; } else { node.specifiers = parseImportSpecifiers(); - if (tokType !== _name || tokVal !== "from") unexpected(); - next(); + expectContextual("from"); node.source = tokType === _string ? parseExprAtom() : unexpected(); } semicolon(); @@ -2711,8 +2722,7 @@ if (tokType === _star) { var node = startNode(); next(); - if (tokType !== _name || tokVal !== "as") unexpected(); - next(); + expectContextual("as"); node.name = parseIdent(); checkLVal(node.name, true); nodes.push(finishNode(node, "ImportBatchSpecifier")); @@ -2727,12 +2737,7 @@ var node = startNode(); node.id = parseIdent(true); - if (tokType === _name && tokVal === "as") { - next(); - node.name = parseIdent(); - } else { - node.name = null; - } + node.name = eatContextual("as") ? parseIdent() : null; checkLVal(node.name || node.id, true); node['default'] = false; nodes.push(finishNode(node, "ImportSpecifier")); @@ -2765,8 +2770,7 @@ expect(_parenL); block.left = parseAssignableAtom(); checkLVal(block.left, true); - if (tokType !== _name || tokVal !== "of") unexpected(); - next(); + expectContextual("of"); block.right = parseExpression(); expect(_parenR); node.blocks.push(finishNode(block, "ComprehensionBlock")); diff --git a/acorn_loose.js b/acorn_loose.js index e1ee14fc3c..44b0870cd9 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -271,9 +271,18 @@ } } + function isContextual(name) { + return token.type === tt.name && token.value === name; + } + + function eatContextual(name) { + return token.value === name && eat(tt.name); + } + function canInsertSemicolon() { return (token.type === tt.eof || token.type === tt.braceR || newline.test(input.slice(lastEnd, token.start))); } + function semicolon() { return eat(tt.semi); } @@ -352,13 +361,13 @@ if (token.type === tt.semi) return parseFor(node, null); if (token.type === tt._var || token.type === tt._let) { var init = parseVar(true); - if (init.declarations.length === 1 && (token.type === tt._in || token.type === tt.name && token.value === "of")) { + if (init.declarations.length === 1 && (token.type === tt._in || isContextual("of"))) { return parseForIn(node, init); } return parseFor(node, init); } var init = parseExpression(false, true); - if (token.type === tt._in || token.type === tt.name && token.value === "of") { + if (token.type === tt._in || isContextual("of")) { return parseForIn(node, checkLVal(init)); } return parseFor(node, init); @@ -1080,10 +1089,7 @@ if (token.type === tt.star) { var elt = startNode(); next(); - if (token.type === tt.name && token.value === "as") { - next(); - elt.name = parseIdent(); - } + if (eatContextual("as")) elt.name = parseIdent(); elts.push(finishNode(elt, prefix + "BatchSpecifier")); } else { var indent = curIndent, line = curLineStart, continuedLine = nextLineStart; @@ -1092,22 +1098,13 @@ if (curLineStart > continuedLine) continuedLine = curLineStart; while (!closes(tt.braceR, indent + (curLineStart <= continuedLine ? 1 : 0), line)) { var elt = startNode(); - if (token.type === tt.star) { - next(); - if (token.type === tt.name && token.value === "as") { - next(); - elt.name = parseIdent(); - } + if (eat(tt.star)) { + if (eatContextual("as")) elt.name = parseIdent(); finishNode(elt, prefix + "BatchSpecifier"); } else { - if (token.type === tt.name && token.value === "from") break; + if (isContextual("from")) break; elt.id = parseIdent(); - if (token.type === tt.name && token.value === "as") { - next(); - elt.name = parseIdent(); - } else { - elt.name = null; - } + elt.name = eatContextual("as") ? parseIdent() : null; finishNode(elt, prefix + "Specifier"); } elts.push(elt); @@ -1116,12 +1113,7 @@ eat(tt.braceR); popCx(); } - if (token.type === tt.name && token.value === "from") { - next(); - node.source = parseExprAtom(); - } else { - node.source = null; - } + node.source = eatContextual("from") ? parseExprAtom() : null; } function parseExprList(close, allowEmpty) { From f0569147e6b34b94c60f0bdf2edcd8d092012a5d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 23 Jan 2015 21:48:33 +0200 Subject: [PATCH 2/5] Avoid extra call and arg in parseExpression for single-expression case. --- acorn.js | 18 +++++++++--------- acorn_loose.js | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/acorn.js b/acorn.js index 35015ad1aa..d7fc369fed 100644 --- a/acorn.js +++ b/acorn.js @@ -1837,7 +1837,7 @@ return parseForIn(node, init); return parseFor(node, init); } - var init = parseExpression(false, true); + var init = parseExpression(true); if (tokType === _in || (options.ecmaVersion >= 6 && isContextual("of"))) { checkLVal(init); return parseForIn(node, init); @@ -2054,7 +2054,7 @@ var decl = startNode(); decl.id = parseAssignableAtom(); checkLVal(decl.id, true); - decl.init = eat(_eq) ? parseExpression(true, noIn) : (kind === _const.keyword ? unexpected() : null); + decl.init = eat(_eq) ? parseMaybeAssign(noIn) : (kind === _const.keyword ? unexpected() : null); node.declarations.push(finishNode(decl, "VariableDeclarator")); if (!eat(_comma)) break; } @@ -2073,10 +2073,10 @@ // sequences (in argument lists, array literals, or object literals) // or the `in` operator (in for loops initalization expressions). - function parseExpression(noComma, noIn) { + function parseExpression(noIn) { var start = storeCurrentPos(); var expr = parseMaybeAssign(noIn); - if (!noComma && tokType === _comma) { + if (tokType === _comma) { var node = startNodeAt(start); node.expressions = [expr]; while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn)); @@ -2111,9 +2111,9 @@ if (eat(_question)) { var node = startNodeAt(start); node.test = expr; - node.consequent = parseExpression(true); + node.consequent = parseMaybeAssign(); expect(_colon); - node.alternate = parseExpression(true, noIn); + node.alternate = parseMaybeAssign(noIn); return finishNode(node, "ConditionalExpression"); } return expr; @@ -2514,7 +2514,7 @@ var isExpression = allowExpression && tokType !== _braceL; if (isExpression) { - node.body = parseExpression(true); + node.body = parseMaybeAssign(); node.expression = true; } else { // Start a new scope with regard to labels and the `inFunction` @@ -2637,7 +2637,7 @@ } else // export default ...; if (eat(_default)) { - node.declaration = parseExpression(true); + node.declaration = parseMaybeAssign(); node['default'] = true; node.specifiers = null; node.source = null; @@ -2755,7 +2755,7 @@ node.argument = null; } else { node.delegate = eat(_star); - node.argument = parseExpression(true); + node.argument = parseMaybeAssign(); } return finishNode(node, "YieldExpression"); } diff --git a/acorn_loose.js b/acorn_loose.js index 44b0870cd9..297589a498 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -366,7 +366,7 @@ } return parseFor(node, init); } - var init = parseExpression(false, true); + var init = parseExpression(true); if (token.type === tt._in || isContextual("of")) { return parseForIn(node, checkLVal(init)); } @@ -539,7 +539,7 @@ do { var decl = startNode(); decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent(); - decl.init = eat(tt.eq) ? parseExpression(true, noIn) : null; + decl.init = eat(tt.eq) ? parseMaybeAssign(noIn) : null; node.declarations.push(finishNode(decl, "VariableDeclarator")); } while (eat(tt.comma)); if (!node.declarations.length) { @@ -551,10 +551,10 @@ return finishNode(node, "VariableDeclaration"); } - function parseExpression(noComma, noIn) { + function parseExpression(noIn) { var start = storeCurrentPos(); var expr = parseMaybeAssign(noIn); - if (!noComma && token.type === tt.comma) { + if (token.type === tt.comma) { var node = startNodeAt(start); node.expressions = [expr]; while (eat(tt.comma)) node.expressions.push(parseMaybeAssign(noIn)); @@ -592,8 +592,8 @@ if (eat(tt.question)) { var node = startNodeAt(start); node.test = expr; - node.consequent = parseExpression(true); - node.alternate = expect(tt.colon) ? parseExpression(true, noIn) : dummyIdent(); + node.consequent = parseMaybeAssign(); + node.alternate = expect(tt.colon) ? parseMaybeAssign(noIn) : dummyIdent(); return finishNode(node, "ConditionalExpression"); } return expr; @@ -783,7 +783,7 @@ node.argument = null; } else { node.delegate = eat(tt.star); - node.argument = parseExpression(true); + node.argument = parseMaybeAssign(); } return finishNode(node, "YieldExpression"); @@ -871,7 +871,7 @@ isGenerator = eat(tt.star); } parsePropertyName(prop); - if (isDummy(prop.key)) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } + if (isDummy(prop.key)) { if (isDummy(parseMaybeAssign())) 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)) { @@ -884,7 +884,7 @@ } if (!isClass && eat(tt.colon)) { prop.kind = "init"; - prop.value = parseExpression(true); + prop.value = parseMaybeAssign(); } else if (options.ecmaVersion >= 6 && (token.type === tt.parenL || token.type === tt.braceL)) { if (isClass) { prop.kind = ""; @@ -1029,7 +1029,7 @@ node.params = parseFunctionParams(); node.generator = isGenerator || false; node.expression = options.ecmaVersion >= 6 && token.type !== tt.braceL; - node.body = node.expression ? parseExpression(true) : parseBlock(); + node.body = node.expression ? parseMaybeAssign() : parseBlock(); return finishNode(node, "FunctionExpression"); } @@ -1037,7 +1037,7 @@ initFunction(node); node.params = toAssignableList(params); node.expression = token.type !== tt.braceL; - node.body = node.expression ? parseExpression(true) : parseBlock(); + node.body = node.expression ? parseMaybeAssign() : parseBlock(); return finishNode(node, "ArrowFunctionExpression"); } @@ -1124,7 +1124,7 @@ elts.push(allowEmpty ? null : dummyIdent()); continue; } - var elt = parseExpression(true); + var elt = parseMaybeAssign(); if (isDummy(elt)) { if (closes(close, indent, line)) break; next(); From 65d09eac6eeeec5dda2a03db715e881892a9675d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 23 Jan 2015 23:41:03 +0200 Subject: [PATCH 3/5] Implement shorthand property assignment in ambiguous contexts. Issue #181. --- acorn.js | 138 ++++++++++++++-------- test/tests-harmony.js | 265 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 348 insertions(+), 55 deletions(-) diff --git a/acorn.js b/acorn.js index d7fc369fed..01f7037033 100644 --- a/acorn.js +++ b/acorn.js @@ -1468,7 +1468,7 @@ // Convert existing expression atom to assignable pattern // if possible. - function toAssignable(node, allowSpread, checkType) { + function toAssignable(node) { if (options.ecmaVersion >= 6 && node) { switch (node.type) { case "Identifier": @@ -1476,43 +1476,32 @@ case "ObjectPattern": case "ArrayPattern": case "AssignmentPattern": - case "RestElement": break; case "ObjectExpression": node.type = "ObjectPattern"; for (var i = 0; i < node.properties.length; i++) { var prop = node.properties[i]; - if (prop.kind !== "init") unexpected(prop.key.start); - toAssignable(prop.value, false, checkType); + if (prop.kind !== "init") raise(prop.key.start, "Object pattern can't contain getter or setter"); + toAssignable(prop.value); } break; case "ArrayExpression": node.type = "ArrayPattern"; - toAssignableList(node.elements, checkType); - break; - - case "SpreadElement": - if (allowSpread) { - node.type = "RestElement"; - toAssignable(node.argument, false, checkType); - checkSpreadAssign(node.argument); - } else { - unexpected(node.start); - } + toAssignableList(node.elements); break; case "AssignmentExpression": if (node.operator === "=") { node.type = "AssignmentPattern"; } else { - unexpected(node.left.end); + raise(node.left.end, "Only '=' operator can be used for specifying default value."); } break; default: - if (checkType) unexpected(node.start); + raise(node.start, "Assigning to rvalue"); } } return node; @@ -1520,19 +1509,33 @@ // Convert list of expression atoms to binding list. - function toAssignableList(exprList, checkType) { - for (var i = 0; i < exprList.length; i++) { - toAssignable(exprList[i], i === exprList.length - 1, checkType); + function toAssignableList(exprList) { + if (exprList.length) { + for (var i = 0; i < exprList.length - 1; i++) { + toAssignable(exprList[i]); + } + var last = exprList[exprList.length - 1]; + switch (last.type) { + case "RestElement": + break; + case "SpreadElement": + last.type = "RestElement"; + toAssignable(last.argument); + checkSpreadAssign(last.argument); + break; + default: + toAssignable(last); + } } return exprList; } // Parses spread element. - function parseSpread() { + function parseSpread(refShorthandDefaultPos) { var node = startNode(); next(); - node.argument = parseMaybeAssign(); + node.argument = parseMaybeAssign(refShorthandDefaultPos); return finishNode(node, "SpreadElement"); } @@ -1837,10 +1840,14 @@ return parseForIn(node, init); return parseFor(node, init); } - var init = parseExpression(true); + var refShorthandDefaultPos = {start: 0}; + var init = parseExpression(true, refShorthandDefaultPos); if (tokType === _in || (options.ecmaVersion >= 6 && isContextual("of"))) { + toAssignable(init); checkLVal(init); return parseForIn(node, init); + } else if (refShorthandDefaultPos.start) { + unexpected(refShorthandDefaultPos.start); } return parseFor(node, init); } @@ -2069,17 +2076,20 @@ // and, *if* the syntactic construct they handle is present, wrap // the AST node that the inner parser gave them in another node. - // Parse a full expression. The arguments are used to forbid comma - // sequences (in argument lists, array literals, or object literals) - // or the `in` operator (in for loops initalization expressions). + // Parse a full expression. The optional arguments are used to + // forbid the `in` operator (in for loops initalization expressions) + // and provide reference for storing '=' operator inside shorthand + // property assignment in contexts where both object expression + // and object pattern might appear (so it's possible to raise + // delayed syntax error at correct position). - function parseExpression(noIn) { + function parseExpression(noIn, refShorthandDefaultPos) { var start = storeCurrentPos(); - var expr = parseMaybeAssign(noIn); + var expr = parseMaybeAssign(noIn, refShorthandDefaultPos); if (tokType === _comma) { var node = startNodeAt(start); node.expressions = [expr]; - while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn)); + while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn, refShorthandDefaultPos)); return finishNode(node, "SequenceExpression"); } return expr; @@ -2088,26 +2098,37 @@ // Parse an assignment expression. This includes applications of // operators like `+=`. - function parseMaybeAssign(noIn) { + function parseMaybeAssign(noIn, refShorthandDefaultPos) { + var failOnShorthandAssign; + if (!refShorthandDefaultPos) { + refShorthandDefaultPos = {start: 0}; + failOnShorthandAssign = true; + } else { + failOnShorthandAssign = false; + } var start = storeCurrentPos(); - var left = parseMaybeConditional(noIn); + var left = parseMaybeConditional(noIn, refShorthandDefaultPos); if (tokType.isAssign) { var node = startNodeAt(start); node.operator = tokVal; node.left = tokType === _eq ? toAssignable(left) : left; + refShorthandDefaultPos.start = 0; // reset because shorthand default was used correctly checkLVal(left); next(); node.right = parseMaybeAssign(noIn); return finishNode(node, "AssignmentExpression"); + } else if (failOnShorthandAssign && refShorthandDefaultPos.start) { + unexpected(refShorthandDefaultPos.start); } return left; } // Parse a ternary conditional (`?:`) operator. - function parseMaybeConditional(noIn) { + function parseMaybeConditional(noIn, refShorthandDefaultPos) { var start = storeCurrentPos(); - var expr = parseExprOps(noIn); + var expr = parseExprOps(noIn, refShorthandDefaultPos); + if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; if (eat(_question)) { var node = startNodeAt(start); node.test = expr; @@ -2121,9 +2142,11 @@ // Start the precedence parser. - function parseExprOps(noIn) { + function parseExprOps(noIn, refShorthandDefaultPos) { var start = storeCurrentPos(); - return parseExprOp(parseMaybeUnary(), start, -1, noIn); + var expr = parseMaybeUnary(refShorthandDefaultPos); + if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; + return parseExprOp(expr, start, -1, noIn); } // Parse binary operators with the operator precedence parsing @@ -2152,13 +2175,14 @@ // Parse unary operators, both prefix and postfix. - function parseMaybeUnary() { + function parseMaybeUnary(refShorthandDefaultPos) { if (tokType.prefix) { var node = startNode(), update = tokType.isUpdate; node.operator = tokVal; node.prefix = true; next(); node.argument = parseMaybeUnary(); + if (refShorthandDefaultPos && refShorthandDefaultPos.start) unexpected(refShorthandDefaultPos.start); if (update) checkLVal(node.argument); else if (strict && node.operator === "delete" && node.argument.type === "Identifier") @@ -2166,7 +2190,8 @@ return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); } var start = storeCurrentPos(); - var expr = parseExprSubscripts(); + var expr = parseExprSubscripts(refShorthandDefaultPos); + if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; while (tokType.postfix && !canInsertSemicolon()) { var node = startNodeAt(start); node.operator = tokVal; @@ -2181,9 +2206,11 @@ // Parse call, dot, and `[]`-subscript expressions. - function parseExprSubscripts() { + function parseExprSubscripts(refShorthandDefaultPos) { var start = storeCurrentPos(); - return parseSubscripts(parseExprAtom(), start); + var expr = parseExprAtom(refShorthandDefaultPos); + if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; + return parseSubscripts(expr, start); } function parseSubscripts(base, start, noCalls) { @@ -2218,7 +2245,7 @@ // `new`, or an expression wrapped in punctuation like `()`, `[]`, // or `{}`. - function parseExprAtom() { + function parseExprAtom(refShorthandDefaultPos) { switch (tokType) { case _this: var node = startNode(); @@ -2268,11 +2295,11 @@ if (options.ecmaVersion >= 7 && tokType === _for) { return parseComprehension(node, false); } - node.elements = parseExprList(_bracketR, true, true); + node.elements = parseExprList(_bracketR, true, true, refShorthandDefaultPos); return finishNode(node, "ArrayExpression"); case _braceL: - return parseObj(); + return parseObj(false, refShorthandDefaultPos); case _function: var node = startNode(); @@ -2302,7 +2329,8 @@ return parseComprehension(startNodeAt(start), true); } - var innerStart = storeCurrentPos(), exprList = [], first = true, spreadStart, innerParenStart; + var innerStart = storeCurrentPos(), exprList = [], first = true; + var refShorthandDefaultPos = {start: 0}, spreadStart, innerParenStart; while (tokType !== _parenR) { first ? first = false : expect(_comma); if (tokType === _ellipsis) { @@ -2313,7 +2341,7 @@ if (tokType === _parenL && !innerParenStart) { innerParenStart = tokStart; } - exprList.push(parseMaybeAssign()); + exprList.push(parseMaybeAssign(false, refShorthandDefaultPos)); } } var innerEnd = storeCurrentPos(); @@ -2326,6 +2354,7 @@ if (!exprList.length) unexpected(lastStart); if (spreadStart) unexpected(spreadStart); + if (refShorthandDefaultPos.start) unexpected(refShorthandDefaultPos.start); if (exprList.length > 1) { val = startNodeAt(innerStart); @@ -2392,7 +2421,7 @@ // Parse an object literal or binding pattern. - function parseObj(isPattern) { + function parseObj(isPattern, refShorthandDefaultPos) { var node = startNode(), first = true, propHash = {}; node.properties = []; next(); @@ -2414,7 +2443,7 @@ } parsePropertyName(prop); if (eat(_colon)) { - prop.value = isPattern ? parseMaybeDefault(start) : parseMaybeAssign(); + prop.value = isPattern ? parseMaybeDefault(start) : parseMaybeAssign(false, refShorthandDefaultPos); prop.kind = "init"; } else if (options.ecmaVersion >= 6 && tokType === _parenL) { if (isPattern) unexpected(); @@ -2430,7 +2459,15 @@ prop.value = parseMethod(false); } else if (options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { prop.kind = "init"; - prop.value = isPattern ? parseMaybeDefault(start, prop.key) : prop.key; + if (isPattern) { + prop.value = parseMaybeDefault(start, prop.key); + } else if (tokType === _eq && refShorthandDefaultPos) { + if (!refShorthandDefaultPos.start) + refShorthandDefaultPos.start = tokStart; + prop.value = parseMaybeDefault(start, prop.key); + } else { + prop.value = prop.key; + } prop.shorthand = true; } else unexpected(); @@ -2583,7 +2620,7 @@ // nothing in between them to be parsed as `null` (which is needed // for array literals). - function parseExprList(close, allowTrailingComma, allowEmpty) { + function parseExprList(close, allowTrailingComma, allowEmpty, refShorthandDefaultPos) { var elts = [], first = true; while (!eat(close)) { if (!first) { @@ -2594,7 +2631,10 @@ if (allowEmpty && tokType === _comma) { elts.push(null); } else { - elts.push(tokType === _ellipsis ? parseSpread() : parseMaybeAssign()); + if (tokType === _ellipsis) + elts.push(parseSpread(refShorthandDefaultPos)); + else + elts.push(parseMaybeAssign(false, refShorthandDefaultPos)); } } return elts; diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 9fc9dec59c..641f18657f 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -13745,7 +13745,7 @@ testFail("[2] = 42", "Assigning to rvalue (1:1)", {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", "Object pattern can't contain getter or setter (1:8)", {ecmaVersion: 6}); testFail("x \n is y", "Unexpected token (2:4)", {ecmaVersion: 6}); @@ -13801,9 +13801,9 @@ testFail("\"use strict\"; (a) => 00", "Invalid number (1:21)", {ecmaVersion: 6}) testFail("() <= 42", "Unexpected token (1:1)", {ecmaVersion: 6}); -testFail("(10) => 00", "Unexpected token (1:1)", {ecmaVersion: 6}); +testFail("(10) => 00", "Assigning to rvalue (1:1)", {ecmaVersion: 6}); -testFail("(10, 20) => 00", "Unexpected token (1:1)", {ecmaVersion: 6}); +testFail("(10, 20) => 00", "Assigning to rvalue (1:1)", {ecmaVersion: 6}); testFail("yield v", "Unexpected token (1:6)", {ecmaVersion: 6}); @@ -14037,7 +14037,7 @@ testFail("\"use strict\"; function x(a, ...[a]){}", "Argument name clash in stri testFail("(...a, b) => {}", "Unexpected token (1:5)", {ecmaVersion: 6}); -testFail("([ 5 ]) => {}", "Unexpected token (1:3)", {ecmaVersion: 6}); +testFail("([ 5 ]) => {}", "Assigning to rvalue (1:3)", {ecmaVersion: 6}); testFail("({ 5 }) => {}", "Unexpected token (1:5)", {ecmaVersion: 6}); @@ -14045,7 +14045,7 @@ testFail("(...[ 5 ]) => {}", "Unexpected token (1:6)", {ecmaVersion: 6}); testFail("[...{ a }] = b", "Unexpected token (1:4)", {ecmaVersion: 6}); -testFail("[...a, b] = c", "Unexpected token (1:1)", {ecmaVersion: 6}); +testFail("[...a, b] = c", "Assigning to rvalue (1:1)", {ecmaVersion: 6}); testFail("({ t(eval) { \"use strict\"; } });", "Defining 'eval' in strict mode (1:5)", {ecmaVersion: 6}); @@ -14120,7 +14120,7 @@ testFail("\"use strict\"; (eval) => 42", "Defining 'eval' in strict mode (1:15)" testFail("(eval) => { \"use strict\"; 42 }", "Defining 'eval' in strict mode (1:1)", {ecmaVersion: 6}); -testFail("({ get test() { } }) => 42", "Unexpected token (1:7)", {ecmaVersion: 6}); +testFail("({ get test() { } }) => 42", "Object pattern can't contain getter or setter (1:7)", {ecmaVersion: 6}); /* Regression tests */ @@ -14509,6 +14509,259 @@ test("var [localVar = defaultValue] = obj", { loose: false }); +test("({x = 0} = obj)", { + type: "Program", + range: [0, 15], + body: [{ + type: "ExpressionStatement", + range: [0, 15], + expression: { + type: "AssignmentExpression", + range: [1, 14], + operator: "=", + left: { + type: "ObjectPattern", + range: [1, 8], + properties: [{ + type: "Property", + range: [2, 7], + method: false, + shorthand: true, + computed: false, + key: { + type: "Identifier", + range: [2, 3], + name: "x" + }, + kind: "init", + value: { + type: "AssignmentPattern", + range: [6, 7], + operator: "=", + left: { + type: "Identifier", + range: [2, 3], + name: "x" + }, + right: { + type: "Literal", + range: [6, 7], + value: 0 + } + } + }] + }, + right: { + type: "Identifier", + range: [11, 14], + name: "obj" + } + } + }] +}, { + ecmaVersion: 6, + ranges: true, + loose: false +}); + +test("({x = 0}) => x", { + type: "Program", + range: [0, 14], + body: [{ + type: "ExpressionStatement", + range: [0, 14], + expression: { + type: "ArrowFunctionExpression", + range: [0, 14], + id: null, + generator: false, + expression: true, + params: [{ + type: "ObjectPattern", + range: [1, 8], + properties: [{ + type: "Property", + range: [2, 7], + method: false, + shorthand: true, + computed: false, + key: { + type: "Identifier", + range: [2, 3], + name: "x" + }, + kind: "init", + value: { + type: "AssignmentPattern", + range: [6, 7], + operator: "=", + left: { + type: "Identifier", + range: [2, 3], + name: "x" + }, + right: { + type: "Literal", + range: [6, 7], + value: 0 + } + } + }] + }], + body: { + type: "Identifier", + range: [13, 14], + name: "x" + } + } + }] +}, { + ecmaVersion: 6, + ranges: true, + loose: false +}); + +test("[a, {b: {c = 1}}] = arr", { + type: "Program", + range: [0, 23], + body: [{ + type: "ExpressionStatement", + range: [0, 23], + expression: { + type: "AssignmentExpression", + range: [0, 23], + operator: "=", + left: { + type: "ArrayPattern", + range: [0, 17], + elements: [ + { + type: "Identifier", + range: [1, 2], + name: "a" + }, + { + type: "ObjectPattern", + range: [4, 16], + properties: [{ + type: "Property", + range: [5, 15], + method: false, + shorthand: false, + computed: false, + key: { + type: "Identifier", + range: [5, 6], + name: "b" + }, + value: { + type: "ObjectPattern", + range: [8, 15], + properties: [{ + type: "Property", + range: [9, 14], + method: false, + shorthand: true, + computed: false, + key: { + type: "Identifier", + range: [9, 10], + name: "c" + }, + kind: "init", + value: { + type: "AssignmentPattern", + range: [13, 14], + operator: "=", + left: { + type: "Identifier", + range: [9, 10], + name: "c" + }, + right: { + type: "Literal", + range: [13, 14], + value: 1 + } + } + }] + }, + kind: "init" + }] + } + ] + }, + right: { + type: "Identifier", + range: [20, 23], + name: "arr" + } + } + }] +}, { + ecmaVersion: 6, + ranges: true, + loose: false +}); + +test("for ({x = 0} in arr);", { + type: "Program", + range: [0, 21], + body: [{ + type: "ForInStatement", + range: [0, 21], + left: { + type: "ObjectPattern", + range: [5, 12], + properties: [{ + type: "Property", + range: [6, 11], + method: false, + shorthand: true, + computed: false, + key: { + type: "Identifier", + range: [6, 7], + name: "x" + }, + kind: "init", + value: { + type: "AssignmentPattern", + range: [10, 11], + operator: "=", + left: { + type: "Identifier", + range: [6, 7], + name: "x" + }, + right: { + type: "Literal", + range: [10, 11], + value: 0 + } + } + }] + }, + right: { + type: "Identifier", + range: [16, 19], + name: "arr" + }, + body: { + type: "EmptyStatement", + range: [20, 21] + } + }] +}, { + ecmaVersion: 6, + ranges: true, + loose: false +}); + +testFail("obj = {x = 0}", "Unexpected token (1:9)", {ecmaVersion: 6}); + +testFail("f({x = 0})", "Unexpected token (1:5)", {ecmaVersion: 6}); + // https://github.com/marijnh/acorn/issues/191 test("try {} catch ({message}) {}", { From cdd444eff12a4e6eb0744606ce12d6b6043133b6 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sat, 24 Jan 2015 02:24:55 +0200 Subject: [PATCH 4/5] Speed-up reading words, strings and templates. Now identifiers and strings even with escaped chars are read in optimized way by reading entire chunks delimited by escape chars (and not bailing to deopt mode on first one). --- acorn.js | 61 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/acorn.js b/acorn.js index 01f7037033..91284e04ac 100644 --- a/acorn.js +++ b/acorn.js @@ -1119,36 +1119,33 @@ } function readString(quote) { - ++tokPos; - var out = ""; + var out = "", chunkStart = ++tokPos; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant"); var ch = input.charCodeAt(tokPos); - if (ch === quote) { - ++tokPos; - return finishToken(_string, out); - } + if (ch === quote) break; if (ch === 92) { // '\' + out += input.slice(chunkStart, tokPos); out += readEscapedChar(); + chunkStart = tokPos; } else { + if (isNewLine(ch)) raise(tokStart, "Unterminated string constant"); ++tokPos; - if (isNewLine(ch)) { - raise(tokStart, "Unterminated string constant"); - } - out += String.fromCharCode(ch); // '\' } } + out += input.slice(chunkStart, tokPos++); + return finishToken(_string, out); } // Reads template string tokens. function readTmplToken() { - var out = "", start = tokPos; + var out = "", chunkStart = tokPos; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated template"); var ch = input.charCodeAt(tokPos); if (ch === 96 || ch === 36 && input.charCodeAt(tokPos + 1) === 123) { // '`', '${' - if (tokPos === start && tokType === _template) { + if (tokPos === tokStart && tokType === _template) { if (ch === 36) { tokPos += 2; return finishToken(_dollarBraceL); @@ -1157,23 +1154,29 @@ return finishToken(_backQuote); } } + out += input.slice(chunkStart, tokPos); return finishToken(_template, out); } if (ch === 92) { // '\' + out += input.slice(chunkStart, tokPos); out += readEscapedChar(); + chunkStart = tokPos; + } else if (isNewLine(ch)) { + out += input.slice(chunkStart, tokPos); + ++tokPos; + if (ch === 13 && input.charCodeAt(tokPos) === 10) { + ++tokPos; + out += "\n"; + } else { + out += String.fromCharCode(ch); + } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + chunkStart = tokPos; } else { ++tokPos; - if (isNewLine(ch)) { - if (ch === 13 && input.charCodeAt(tokPos) === 10) { - ++tokPos; - ch = 10; - } - if (options.locations) { - ++tokCurLine; - tokLineStart = tokPos; - } - } - out += String.fromCharCode(ch); } } } @@ -1228,20 +1231,19 @@ // Read an identifier, and return it as a string. Sets `containsEsc` // to whether the word contained a '\u' escape. // - // Only builds up the word character-by-character when it actually - // containeds an escape, as a micro-optimization. + // Incrementally adds only escaped chars, adding other chunks as-is + // as a micro-optimization. function readWord1() { containsEsc = false; - var word, first = true, start = tokPos; + var word = "", first = true, chunkStart = tokPos; for (;;) { var ch = input.charCodeAt(tokPos); if (isIdentifierChar(ch)) { - if (containsEsc) word += input.charAt(tokPos); ++tokPos; } else if (ch === 92) { // "\" - if (!containsEsc) word = input.slice(start, tokPos); containsEsc = true; + word += input.slice(chunkStart, tokPos); if (input.charCodeAt(++tokPos) != 117) // "u" raise(tokPos, "Expecting Unicode escape sequence \\uXXXX"); ++tokPos; @@ -1251,12 +1253,13 @@ if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc))) raise(tokPos - 4, "Invalid Unicode escape"); word += escStr; + chunkStart = tokPos; } else { break; } first = false; } - return containsEsc ? word : input.slice(start, tokPos); + return word + input.slice(chunkStart, tokPos); } // Read an identifier or keyword token. Will check for reserved From 5d96bbd781538b14d61bb1259a446317520fc0ba Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sat, 24 Jan 2015 13:38:14 +0200 Subject: [PATCH 5/5] Simplify & fix rest argument validity checks. --- acorn.js | 23 +++++++++++------------ acorn_loose.js | 3 --- test/tests-harmony.js | 3 +++ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/acorn.js b/acorn.js index 91284e04ac..847a6ba67c 100644 --- a/acorn.js +++ b/acorn.js @@ -315,7 +315,7 @@ function initParserState() { lastStart = lastEnd = tokPos; if (options.locations) lastEndLoc = curPosition(); - inFunction = inGenerator = strict = false; + inFunction = inGenerator = false; labels = []; skipSpace(); readToken(); @@ -616,6 +616,7 @@ tokType = _eof; tokContext = [b_stat]; tokExprAllowed = true; + strict = false; if (tokPos === 0 && options.allowHashBang && input.slice(0, 2) === '#!') { skipLineComment(2); } @@ -1523,8 +1524,10 @@ break; case "SpreadElement": last.type = "RestElement"; - toAssignable(last.argument); - checkSpreadAssign(last.argument); + var arg = last.argument; + toAssignable(arg); + if (arg.type !== "Identifier" && arg.type !== "ArrayPattern") + unexpected(arg.start); break; default: toAssignable(last); @@ -1545,8 +1548,7 @@ function parseRest() { var node = startNode(); next(); - node.argument = parseAssignableAtom(); - checkSpreadAssign(node.argument); + node.argument = tokType === _name || tokType === _bracketL ? parseAssignableAtom() : unexpected(); return finishNode(node, "RestElement"); } @@ -1598,13 +1600,6 @@ return finishNode(node, "AssignmentPattern"); } - // Checks if node can be assignable spread argument. - - function checkSpreadAssign(node) { - if (node.type !== "Identifier" && node.type !== "ArrayPattern") - unexpected(node.start); - } - // Verify that argument names are not repeated, and it does not // try to bind the words `eval` or `arguments`. @@ -1691,7 +1686,11 @@ break; case "AssignmentPattern": + checkLVal(expr.left); + break; + case "RestElement": + checkLVal(expr.argument); break; default: diff --git a/acorn_loose.js b/acorn_loose.js index 297589a498..ab0c6ea709 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -325,9 +325,6 @@ } function parseStatement() { - if (token.type === tt.slash || token.type === tt.assign && token.value === "/=") - next(true); - var starttype = token.type, node = startNode(); switch (starttype) { diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 641f18657f..596e5f7ab0 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -15013,3 +15013,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}); + +testFail("'use strict'; [...eval] = arr", "Assigning to eval in strict mode (1:18)", {ecmaVersion: 6}); +testFail("'use strict'; ({eval = defValue} = obj)", "Assigning to eval in strict mode (1:16)", {ecmaVersion: 6});