From 3d5964ceed12c8ed97cd99230055d1c8b8b6878b Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 27 Jan 2015 15:10:25 +0200 Subject: [PATCH 1/4] Fix locations for AssignmentPatterns. --- acorn.js | 10 ++++--- test/tests-harmony.js | 68 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/acorn.js b/acorn.js index 847a6ba67c..ae2e33e18d 100644 --- a/acorn.js +++ b/acorn.js @@ -1591,9 +1591,10 @@ // Parses assignment pattern around given atom if possible. function parseMaybeDefault(startPos, left) { + startPos = startPos || storeCurrentPos(); left = left || parseAssignableAtom(); if (!eat(_eq)) return left; - var node = startPos ? startNodeAt(startPos) : startNode(); + var node = startNodeAt(startPos); node.operator = "="; node.left = left; node.right = parseMaybeAssign(); @@ -2437,15 +2438,16 @@ if (options.ecmaVersion >= 6) { prop.method = false; prop.shorthand = false; - if (isPattern) { + if (isPattern || refShorthandDefaultPos) { start = storeCurrentPos(); - } else { + } + if (!isPattern) { isGenerator = eat(_star); } } parsePropertyName(prop); if (eat(_colon)) { - prop.value = isPattern ? parseMaybeDefault(start) : parseMaybeAssign(false, refShorthandDefaultPos); + prop.value = isPattern ? parseMaybeDefault() : parseMaybeAssign(false, refShorthandDefaultPos); prop.kind = "init"; } else if (options.ecmaVersion >= 6 && tokType === _parenL) { if (isPattern) unexpected(); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 596e5f7ab0..17df5d68f2 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -1806,6 +1806,10 @@ test("(x=1) => x * x", { start: {line: 1, column: 3}, end: {line: 1, column: 4} } + }, + loc: { + start: {line: 1, column: 1}, + end: {line: 1, column: 4} } }], body: { @@ -2073,6 +2077,10 @@ test("(eval = 10) => 42", { start: {line: 1, column: 8}, end: {line: 1, column: 10} } + }, + loc: { + start: {line: 1, column: 1}, + end: {line: 1, column: 10} } }], body: { @@ -2140,6 +2148,10 @@ test("(eval, a = 10) => 42", { start: {line: 1, column: 11}, end: {line: 1, column: 13} } + }, + loc: { + start: {line: 1, column: 7}, + end: {line: 1, column: 13} } } ], @@ -9589,6 +9601,10 @@ test("function f([x] = [1]) {}", { start: {line: 1, column: 17}, end: {line: 1, column: 20} } + }, + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 20} } }], body: { @@ -9698,6 +9714,10 @@ test("function f({x} = {x: 10}) {}", { start: {line: 1, column: 17}, end: {line: 1, column: 24} } + }, + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 24} } }], body: { @@ -9813,6 +9833,10 @@ test("f = function({x} = {x: 10}) {}", { start: {line: 1, column: 19}, end: {line: 1, column: 26} } + }, + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 26} } }], body: { @@ -9939,6 +9963,10 @@ test("({f: function({x} = {x: 10}) {}})", { start: {line: 1, column: 20}, end: {line: 1, column: 27} } + }, + loc: { + start: {line: 1, column: 14}, + end: {line: 1, column: 27} } }], body: { @@ -10074,6 +10102,10 @@ test("({f({x} = {x: 10}) {}})", { start: {line: 1, column: 10}, end: {line: 1, column: 17} } + }, + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 17} } }], body: { @@ -10213,6 +10245,10 @@ test("(class {f({x} = {x: 10}) {}})", { start: {line: 1, column: 16}, end: {line: 1, column: 23} } + }, + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 23} } }], body: { @@ -10339,6 +10375,10 @@ test("(({x} = {x: 10}) => {})", { start: {line: 1, column: 8}, end: {line: 1, column: 15} } + }, + loc: { + start: {line: 1, column: 2}, + end: {line: 1, column: 15} } }], body: { @@ -10407,6 +10447,10 @@ test("x = function(y = 1) {}", { start: {line: 1, column: 17}, end: {line: 1, column: 18} } + }, + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 18} } }], body: { @@ -10474,6 +10518,10 @@ test("function f(a = 1) {}", { start: {line: 1, column: 15}, end: {line: 1, column: 16} } + }, + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 16} } }], body: { @@ -10549,6 +10597,10 @@ test("x = { f: function(a=1) {} }", { start: {line: 1, column: 20}, end: {line: 1, column: 21} } + }, + loc: { + start: {line: 1, column: 18}, + end: {line: 1, column: 21} } }], body: { @@ -10648,6 +10700,10 @@ test("x = { f(a=1) {} }", { start: {line: 1, column: 10}, end: {line: 1, column: 11} } + }, + loc: { + start: {line: 1, column: 8}, + end: {line: 1, column: 11} } }], body: { @@ -14379,7 +14435,7 @@ test("var {propName: localVar = defaultValue} = obj", { }, value: { type: "AssignmentPattern", - range: [5, 38], + range: [15, 38], operator: "=", left: { type: "Identifier", @@ -14480,7 +14536,7 @@ test("var [localVar = defaultValue] = obj", { range: [4, 29], elements: [{ type: "AssignmentPattern", - range: [16, 28], + range: [5, 28], operator: "=", left: { type: "Identifier", @@ -14536,7 +14592,7 @@ test("({x = 0} = obj)", { kind: "init", value: { type: "AssignmentPattern", - range: [6, 7], + range: [2, 7], operator: "=", left: { type: "Identifier", @@ -14593,7 +14649,7 @@ test("({x = 0}) => x", { kind: "init", value: { type: "AssignmentPattern", - range: [6, 7], + range: [2, 7], operator: "=", left: { type: "Identifier", @@ -14671,7 +14727,7 @@ test("[a, {b: {c = 1}}] = arr", { kind: "init", value: { type: "AssignmentPattern", - range: [13, 14], + range: [9, 14], operator: "=", left: { type: "Identifier", @@ -14727,7 +14783,7 @@ test("for ({x = 0} in arr);", { kind: "init", value: { type: "AssignmentPattern", - range: [10, 11], + range: [6, 11], operator: "=", left: { type: "Identifier", From 6660a21b7992be2f23e6a2a19872619ac50dac3f Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 27 Jan 2015 15:23:09 +0200 Subject: [PATCH 2/4] Disallow MemberExpression in bindings; clarify function namings. --- acorn.js | 40 +++++++++++++------------ test/tests-harmony.js | 69 +------------------------------------------ 2 files changed, 22 insertions(+), 87 deletions(-) diff --git a/acorn.js b/acorn.js index ae2e33e18d..38a233bddd 100644 --- a/acorn.js +++ b/acorn.js @@ -1472,11 +1472,10 @@ // Convert existing expression atom to assignable pattern // if possible. - function toAssignable(node) { + function toAssignable(node, isBinding) { if (options.ecmaVersion >= 6 && node) { switch (node.type) { case "Identifier": - case "MemberExpression": case "ObjectPattern": case "ArrayPattern": case "AssignmentPattern": @@ -1487,13 +1486,13 @@ for (var i = 0; i < node.properties.length; i++) { var prop = node.properties[i]; if (prop.kind !== "init") raise(prop.key.start, "Object pattern can't contain getter or setter"); - toAssignable(prop.value); + toAssignable(prop.value, isBinding); } break; case "ArrayExpression": node.type = "ArrayPattern"; - toAssignableList(node.elements); + toAssignableList(node.elements, isBinding); break; case "AssignmentExpression": @@ -1504,6 +1503,9 @@ } break; + case "MemberExpression": + if (!isBinding) break; + default: raise(node.start, "Assigning to rvalue"); } @@ -1513,10 +1515,10 @@ // Convert list of expression atoms to binding list. - function toAssignableList(exprList) { + function toAssignableList(exprList, isBinding) { if (exprList.length) { for (var i = 0; i < exprList.length - 1; i++) { - toAssignable(exprList[i]); + toAssignable(exprList[i], isBinding); } var last = exprList[exprList.length - 1]; switch (last.type) { @@ -1525,12 +1527,12 @@ case "SpreadElement": last.type = "RestElement"; var arg = last.argument; - toAssignable(arg); - if (arg.type !== "Identifier" && arg.type !== "ArrayPattern") + toAssignable(arg, isBinding); + if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") unexpected(arg.start); break; default: - toAssignable(last); + toAssignable(last, isBinding); } } return exprList; @@ -1548,13 +1550,13 @@ function parseRest() { var node = startNode(); next(); - node.argument = tokType === _name || tokType === _bracketL ? parseAssignableAtom() : unexpected(); + node.argument = tokType === _name || tokType === _bracketL ? parseBindingAtom() : unexpected(); return finishNode(node, "RestElement"); } // Parses lvalue (assignable) atom. - function parseAssignableAtom() { + function parseBindingAtom() { if (options.ecmaVersion < 6) return parseIdent(); switch (tokType) { case _name: @@ -1563,7 +1565,7 @@ case _bracketL: var node = startNode(); next(); - node.elements = parseAssignableList(_bracketR, true); + node.elements = parseBindingList(_bracketR, true); return finishNode(node, "ArrayPattern"); case _braceL: @@ -1574,7 +1576,7 @@ } } - function parseAssignableList(close, allowEmpty) { + function parseBindingList(close, allowEmpty) { var elts = [], first = true; while (!eat(close)) { first ? first = false : expect(_comma); @@ -1592,7 +1594,7 @@ function parseMaybeDefault(startPos, left) { startPos = startPos || storeCurrentPos(); - left = left || parseAssignableAtom(); + left = left || parseBindingAtom(); if (!eat(_eq)) return left; var node = startNodeAt(startPos); node.operator = "="; @@ -1934,7 +1936,7 @@ var clause = startNode(); next(); expect(_parenL); - clause.param = parseAssignableAtom(); + clause.param = parseBindingAtom(); checkLVal(clause.param, true); expect(_parenR); clause.guard = null; @@ -2062,7 +2064,7 @@ node.kind = kind; for (;;) { var decl = startNode(); - decl.id = parseAssignableAtom(); + decl.id = parseBindingAtom(); checkLVal(decl.id, true); decl.init = eat(_eq) ? parseMaybeAssign(noIn) : (kind === _const.keyword ? unexpected() : null); node.declarations.push(finishNode(decl, "VariableDeclarator")); @@ -2517,7 +2519,7 @@ node.id = parseIdent(); } expect(_parenL); - node.params = parseAssignableList(_parenR, false); + node.params = parseBindingList(_parenR, false); parseFunctionBody(node, allowExpressionBody); return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); } @@ -2528,7 +2530,7 @@ var node = startNode(); initFunction(node); expect(_parenL); - node.params = parseAssignableList(_parenR, false); + node.params = parseBindingList(_parenR, false); var allowExpressionBody; if (options.ecmaVersion >= 6) { node.generator = isGenerator; @@ -2812,7 +2814,7 @@ var block = startNode(); next(); expect(_parenL); - block.left = parseAssignableAtom(); + block.left = parseBindingAtom(); checkLVal(block.left, true); expectContextual("of"); block.right = parseExpression(); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 17df5d68f2..a1a9ffae89 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -1712,74 +1712,7 @@ test("([a, , b]) => 42", { locations: true }); -test("([a.a]) => 42", { - type: "Program", - body: [{ - type: "ExpressionStatement", - expression: { - type: "ArrowFunctionExpression", - id: null, - params: [{ - type: "ArrayPattern", - elements: [{ - type: "MemberExpression", - computed: false, - object: { - type: "Identifier", - name: "a", - loc: { - start: {line: 1, column: 2}, - end: {line: 1, column: 3} - } - }, - property: { - type: "Identifier", - name: "a", - loc: { - start: {line: 1, column: 4}, - end: {line: 1, column: 5} - } - }, - loc: { - start: {line: 1, column: 2}, - end: {line: 1, column: 5} - } - }], - loc: { - start: {line: 1, column: 1}, - end: {line: 1, column: 6} - } - }], - body: { - type: "Literal", - value: 42, - raw: "42", - loc: { - start: {line: 1, column: 11}, - end: {line: 1, column: 13} - } - }, - generator: false, - expression: true, - loc: { - start: {line: 1, column: 0}, - end: {line: 1, column: 13} - } - }, - loc: { - start: {line: 1, column: 0}, - end: {line: 1, column: 13} - } - }], - loc: { - start: {line: 1, column: 0}, - end: {line: 1, column: 13} - } -}, { - ecmaVersion: 6, - ranges: true, - locations: true -}); +testFail("([a.a]) => 42", "Assigning to rvalue (1:2)", {ecmaVersion: 6}); test("(x=1) => x * x", { type: "Program", From e4a97ab87700c1f05d4effe15213cc60a5189b19 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 27 Jan 2015 15:53:16 +0200 Subject: [PATCH 3/4] Finalized destructuring support in loose parser; fixed startNodeAt. --- acorn_loose.js | 24 +++++++++++++++++++----- test/tests-harmony.js | 25 ++++++++----------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index ab0c6ea709..035f22395a 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -235,13 +235,14 @@ if (options.locations) { node = new Node(pos[0]); node.loc = new SourceLocation(pos[1]); + pos = pos[0]; } else { node = new Node(pos); } if (options.directSourceFile) node.sourceFile = options.directSourceFile; if (options.ranges) - node.range = [pos[0], 0]; + node.range = [pos, 0]; return node; } @@ -365,7 +366,7 @@ } var init = parseExpression(true); if (token.type === tt._in || isContextual("of")) { - return parseForIn(node, checkLVal(init)); + return parseForIn(node, toAssignable(init)); } return parseFor(node, init); @@ -432,7 +433,7 @@ var clause = startNode(); next(); expect(tt.parenL); - clause.param = parseIdent(); + clause.param = toAssignable(parseExprAtom()); expect(tt.parenR); clause.guard = null; clause.body = parseBlock(); @@ -857,11 +858,12 @@ if (curIndent + 1 < indent) { indent = curIndent; line = curLineStart; } while (!closes(tt.braceR, indent, line)) { if (isClass && semicolon()) continue; - var prop = startNode(), isGenerator; + var prop = startNode(), isGenerator, start; if (options.ecmaVersion >= 6) { if (isClass) { prop['static'] = false; } else { + start = storeCurrentPos(); prop.method = false; prop.shorthand = false; } @@ -901,7 +903,19 @@ prop.value = parseMethod(isGenerator); } else { prop.kind = "init"; - prop.value = options.ecmaVersion >= 6 ? prop.key : dummyIdent(); + if (options.ecmaVersion >= 6) { + if (eat(tt.eq)) { + var assign = startNodeAt(start); + assign.operator = "="; + assign.left = prop.key; + assign.right = parseMaybeAssign(); + prop.value = finishNode(assign, "AssignmentExpression"); + } else { + prop.value = prop.key; + } + } else { + prop.value = dummyIdent(); + } prop.shorthand = true; } diff --git a/test/tests-harmony.js b/test/tests-harmony.js index a1a9ffae89..7ccb49aab3 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14395,8 +14395,7 @@ test("var {propName: localVar = defaultValue} = obj", { }, { ecmaVersion: 6, ranges: true, - locations: true, - loose: false + locations: true }); test("var {propName = defaultValue} = obj", { @@ -14451,8 +14450,7 @@ test("var {propName = defaultValue} = obj", { }, { ecmaVersion: 6, ranges: true, - locations: true, - loose: false + locations: true }); test("var [localVar = defaultValue] = obj", { @@ -14494,8 +14492,7 @@ test("var [localVar = defaultValue] = obj", { }, { ecmaVersion: 6, ranges: true, - locations: true, - loose: false + locations: true }); test("({x = 0} = obj)", { @@ -14549,8 +14546,7 @@ test("({x = 0} = obj)", { }] }, { ecmaVersion: 6, - ranges: true, - loose: false + ranges: true }); test("({x = 0}) => x", { @@ -14606,8 +14602,7 @@ test("({x = 0}) => x", { }] }, { ecmaVersion: 6, - ranges: true, - loose: false + ranges: true }); test("[a, {b: {c = 1}}] = arr", { @@ -14689,8 +14684,7 @@ test("[a, {b: {c = 1}}] = arr", { }] }, { ecmaVersion: 6, - ranges: true, - loose: false + ranges: true }); test("for ({x = 0} in arr);", { @@ -14743,8 +14737,7 @@ test("for ({x = 0} in arr);", { }] }, { ecmaVersion: 6, - ranges: true, - loose: false + ranges: true }); testFail("obj = {x = 0}", "Unexpected token (1:9)", {ecmaVersion: 6}); @@ -14796,14 +14789,12 @@ test("try {} catch ({message}) {}", { body: [] } }, - guardedHandlers: [], finalizer: null }] }, { ecmaVersion: 6, ranges: true, - locations: true, - loose: false + locations: true }); // https://github.com/marijnh/acorn/issues/192 From 6eb177582d4c779f4d5f07ae7e14e0f4a8e0c8e1 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 27 Jan 2015 16:21:41 +0200 Subject: [PATCH 4/4] Fix export default declarations. Closes #184. --- acorn.js | 9 +++- acorn_loose.js | 10 +++- test/tests-harmony.js | 104 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/acorn.js b/acorn.js index 38a233bddd..9d11afe5ff 100644 --- a/acorn.js +++ b/acorn.js @@ -2683,7 +2683,14 @@ } else // export default ...; if (eat(_default)) { - node.declaration = parseMaybeAssign(); + var expr = parseMaybeAssign(); + if (expr.id) { + switch (expr.type) { + case "FunctionExpression": expr.type = "FunctionDeclaration"; break; + case "ClassExpression": expr.type = "ClassDeclaration"; break; + } + } + node.declaration = expr; node['default'] = true; node.specifiers = null; node.source = null; diff --git a/acorn_loose.js b/acorn_loose.js index 035f22395a..0f52fcc47a 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -846,6 +846,7 @@ next(); if (token.type === tt.name) node.id = parseIdent(); else if (isStatement) node.id = dummyIdent(); + else node.id = null; node.superClass = eat(tt._extends) ? parseExpression() : null; node.body = startNode(); node.body.body = []; @@ -1058,7 +1059,14 @@ node['default'] = eat(tt._default); node.specifiers = node.source = null; if (node['default']) { - node.declaration = parseExpression(); + var expr = parseMaybeAssign(); + if (expr.id) { + switch (expr.type) { + case "FunctionExpression": expr.type = "FunctionDeclaration"; break; + case "ClassExpression": expr.type = "ClassDeclaration"; break; + } + } + node.declaration = expr; semicolon(); } else if (token.type.keyword) { node.declaration = parseStatement(); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 7ccb49aab3..1b45b380cd 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -4826,6 +4826,110 @@ test("export default 42", { locations: true }); +test("export default function () {}", { + type: "Program", + range: [0, 29], + body: [{ + type: "ExportDeclaration", + range: [0, 29], + declaration: { + type: "FunctionExpression", + range: [15, 29], + id: null, + generator: false, + expression: false, + params: [], + body: { + type: "BlockStatement", + range: [27, 29], + body: [] + } + }, + default: true, + specifiers: null, + source: null + }] +}, {ecmaVersion: 6, ranges: true}); + +test("export default function f() {}", { + type: "Program", + range: [0, 30], + body: [{ + type: "ExportDeclaration", + range: [0, 30], + declaration: { + type: "FunctionDeclaration", + range: [15, 30], + id: { + type: "Identifier", + range: [24, 25], + name: "f" + }, + generator: false, + expression: false, + params: [], + body: { + type: "BlockStatement", + range: [28, 30], + body: [] + } + }, + default: true, + specifiers: null, + source: null + }] +}, {ecmaVersion: 6, ranges: true}); + +test("export default class {}", { + type: "Program", + range: [0, 23], + body: [{ + type: "ExportDeclaration", + range: [0, 23], + declaration: { + type: "ClassExpression", + range: [15, 23], + id: null, + superClass: null, + body: { + type: "ClassBody", + range: [21, 23], + body: [] + } + }, + default: true, + specifiers: null, + source: null + }] +}, {ecmaVersion: 6, ranges: true}); + +test("export default class A {}", { + type: "Program", + range: [0, 25], + body: [{ + type: "ExportDeclaration", + range: [0, 25], + declaration: { + type: "ClassDeclaration", + range: [15, 25], + id: { + type: "Identifier", + range: [21, 22], + name: "A" + }, + superClass: null, + body: { + type: "ClassBody", + range: [23, 25], + body: [] + } + }, + default: true, + specifiers: null, + source: null + }] +}, {ecmaVersion: 6, ranges: true}); + testFail("export *", "Unexpected token (1:8)", {ecmaVersion: 6}); test("export * from \"crypto\"", {