From 5c0d9a0e6194f2ce0fac37449616dae897461957 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Fri, 9 Jan 2015 05:54:16 +1100 Subject: [PATCH] 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). Conflicts: acorn.js acorn_loose.js --- acorn.js | 75 +++++++++++++++++++++++---- acorn_loose.js | 4 ++ test/tests-harmony.js | 117 +++++++++++++++++++++++++++++++++++------- 3 files changed, 166 insertions(+), 30 deletions(-) diff --git a/acorn.js b/acorn.js index 225b588c3f..47767ddc94 100644 --- a/acorn.js +++ b/acorn.js @@ -1828,7 +1828,8 @@ function has(obj, propName) { return Object.prototype.hasOwnProperty.call(obj, propName); } -// Convert existing expression atom to assignable pattern + + // Convert existing expression atom to assignable pattern // if possible. function toAssignable(node, allowSpread, checkType) { @@ -1878,6 +1879,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) { @@ -2361,7 +2409,7 @@ node.kind = kind; for (;;) { var decl = startNode(); - decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent(); + decl.id = parseAssignableAtom(); checkLVal(decl.id, true); if (tokType === _colon) { @@ -2788,7 +2836,7 @@ // Parse an object literal. - function parseObj() { + function parseObj(isPattern) { var node = startNode(), first = true, propHash = {}; node.properties = []; next(); @@ -2798,7 +2846,7 @@ if (options.allowTrailingCommas && eat(_braceR)) break; } else first = false; - var prop = startNode(), isGenerator = false, isAsync = false; + var prop = startNode(), start, isGenerator = false, isAsync = false; if (options.ecmaVersion >= 7 && tokType === _ellipsis) { prop = parseMaybeUnary(); prop.type = "SpreadProperty"; @@ -2808,7 +2856,11 @@ if (options.ecmaVersion >= 6) { prop.method = false; prop.shorthand = false; - isGenerator = eat(_star); + if (isPattern) { + start = storeCurrentPos(); + } else { + isGenerator = eat(_star); + } } if (options.ecmaVersion >= 7 && tokType === _name && tokVal === "async") { var asyncId = parseIdent(); @@ -2827,22 +2879,23 @@ if (tokType !== _parenL) unexpected(); } 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, isAsync); } else if (options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set"|| (options.playground && prop.key.name === "memo")) && (tokType != _comma && tokType != _braceR)) { - if (isGenerator || isAsync) unexpected(); + if (isGenerator || isAsync || isPattern) unexpected(); prop.kind = prop.key.name; parsePropertyName(prop); prop.value = parseMethod(false, 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(); @@ -2850,7 +2903,7 @@ checkPropClash(prop, propHash); node.properties.push(finishNode(prop, "Property")); } - return finishNode(node, "ObjectExpression"); + return finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression"); } function parsePropertyName(prop) { @@ -2960,7 +3013,7 @@ if (eat(_parenR)) { break; } else if (options.ecmaVersion >= 6 && eat(_ellipsis)) { - node.rest = toAssignable(parseExprAtom(), false, true); + node.rest = parseAssignableAtom(); checkSpreadAssign(node.rest); parseFunctionParam(node.rest); expect(_parenR); @@ -3358,7 +3411,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 867e21fa19..2f41edd588 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -983,6 +983,10 @@ case "SpreadElement": node.argument = toAssignable(node.argument); break; + + case "AssignmentExpression": + node.type = "AssignmentPattern"; + break; } } return checkLVal(node); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 72153b0a43..fa19a011b6 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14393,30 +14393,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" } }, @@ -14425,44 +14434,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 +});