From 8b0be7cec9387711296d9d457e46d23c00f68e24 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 25 Jul 2014 14:53:19 +0300 Subject: [PATCH] Improved destruction assignment guards. --- acorn.js | 49 ++++++++++++++++----- test/tests-harmony.js | 100 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 118 insertions(+), 31 deletions(-) diff --git a/acorn.js b/acorn.js index dee01b4765..25638b3da7 100644 --- a/acorn.js +++ b/acorn.js @@ -1154,12 +1154,35 @@ // Verify that a node is an lval — something that can be assigned // to. - function checkLVal(expr) { - if (expr.type !== "Identifier" && expr.type !== "MemberExpression" && - expr.type !== "ObjectPattern" && expr.type !== "ArrayPattern") - raise(expr.start, "Assigning to rvalue"); - if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name)) - raise(expr.start, "Assigning to " + expr.name + " in strict mode"); + function checkLVal(expr, isBinding) { + switch (expr.type) { + case "Identifier": + if (strict && isStrictBadIdWord(expr.name)) + raise(expr.start, isBinding + ? "Binding " + expr.name + " in strict mode" + : "Assigning to " + expr.name + " in strict mode" + ); + break; + + case "MemberExpression": + if (!isBinding) break; + + case "ObjectPattern": + for (var i = 0; i < expr.properties.length; i++) + checkLVal(expr.properties[i].value, isBinding); + break; + + case "ArrayPattern": + for (var i = 0; i < expr.elements.length; i++) + checkLVal(expr.elements[i], isBinding); + break; + + case "SpreadElement": + break; + + default: + raise(expr.start, "Assigning to rvalue"); + } } // ### Statement parsing @@ -1510,8 +1533,7 @@ for (;;) { var decl = startNode(); decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent(); - if (strict && decl.id.type === "Identifier" && isStrictBadIdWord(decl.id.name)) - raise(decl.id.start, "Binding " + decl.id.name + " in strict mode"); + checkLVal(decl.id, true); decl.init = eat(_eq) ? parseExpression(true, noIn) : (kind === _const.keyword ? unexpected() : null); node.declarations.push(finishNode(decl, "VariableDeclarator")); if (!eat(_comma)) break; @@ -1550,10 +1572,10 @@ if (tokType.isAssign) { var node = startNodeFrom(left); node.operator = tokVal; - node.left = toAssignable(left); + node.left = tokVal === '=' ? toAssignable(left) : left; + checkLVal(left); next(); node.right = parseMaybeAssign(noIn); - checkLVal(left); return finishNode(node, "AssignmentExpression"); } return left; @@ -2074,12 +2096,15 @@ if (options.ecmaVersion >= 6 && node) { switch (node.type) { case "Identifier": - break; + case "MemberExpression": + break; case "ObjectExpression": node.type = "ObjectPattern"; for (var i = 0; i < node.properties.length; i++) { - toAssignable(node.properties[i].value); + var prop = node.properties[i]; + if (prop.kind !== "init") unexpected(prop.key.start); + toAssignable(prop.value); } break; diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 74f4593c98..c688adece4 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -11361,7 +11361,7 @@ test("var x = {*[test]() { yield *v; }}", { locations: true }); -testFail("({[x]})", "Unexpected token (1:6)", {ecmaVersion: 6}); +testFail("({[x]})", "Unexpected token (1:5)", {ecmaVersion: 6}); // ES6: Default parameters @@ -11971,10 +11971,10 @@ test("({f({x} = {x: 10}) {}})", { end: {line: 1, column: 21} } }], - range: [1, 22], + range: [0, 23], loc: { - start: {line: 1, column: 1}, - end: {line: 1, column: 22} + start: {line: 1, column: 0}, + end: {line: 1, column: 23} } }, range: [0, 23], @@ -12126,10 +12126,10 @@ test("(class {f({x} = {x: 10}) {}})", { end: {line: 1, column: 28} } }, - range: [1, 28], + range: [0, 29], loc: { - start: {line: 1, column: 1}, - end: {line: 1, column: 28} + start: {line: 1, column: 0}, + end: {line: 1, column: 29} } }, range: [0, 29], @@ -15891,13 +15891,13 @@ testFail("\"\\u{FFFF\"", "Bad character escape sequence (1:0)", {ecmaVersion: 6} testFail("\"\\u{FFZ}\"", "Bad character escape sequence (1:0)", {ecmaVersion: 6}); -testFail("[v] += ary", "Unexpected token (1:4)", {ecmaVersion: 6}); +testFail("[v] += ary", "Assigning to rvalue (1:0)", {ecmaVersion: 6}); -testFail("[2] = 42", "Unexpected token (1:4)", {ecmaVersion: 6}); +testFail("[2] = 42", "Unexpected token (1:1)", {ecmaVersion: 6}); -testFail("({ obj:20 }) = 42", "Unexpected token (1:13)", {ecmaVersion: 6}); +testFail("({ obj:20 }) = 42", "Unexpected token (1:7)", {ecmaVersion: 6}); -testFail("( { get x() {} } ) = 0", "Unexpected token (1:19)", {ecmaVersion: 6}); +testFail("( { get x() {} } ) = 0", "Unexpected token (1:8)", {ecmaVersion: 6}); testFail("x \n is y", "Unexpected token (2:5)", {ecmaVersion: 6}); @@ -15917,9 +15917,9 @@ testFail("let default", "Unexpected token (1:4)", {ecmaVersion: 6}); testFail("const default", "Unexpected token (1:6)", {ecmaVersion: 6}); -testFail("({ v: eval }) = obj", "Unexpected token (1:14)", {ecmaVersion: 6}); +testFail("\"use strict\"; ({ v: eval }) = obj", "Assigning to eval in strict mode (1:20)", {ecmaVersion: 6}); -testFail("({ v: arguments }) = obj", "Unexpected token (1:19)", {ecmaVersion: 6}); +testFail("\"use strict\"; ({ v: arguments }) = obj", "Assigning to arguments in strict mode (1:20)", {ecmaVersion: 6}); testFail("for (var i = function() { return 10 in [] } in list) process(x);", "Unexpected token (1:45)", {ecmaVersion: 6}); @@ -15965,7 +15965,7 @@ testFail("\"use strict\"; (a, a) => 42", "Argument name clash in strict mode (1: testFail("\"use strict\"; (a) => 00", "Invalid number (1:21)", {ecmaVersion: 6}); -testFail("() <= 42", "Unexpected token (1:4)", {ecmaVersion: 6}); +testFail("() <= 42", "Unexpected token (1:1)", {ecmaVersion: 6}); testFail("(10) => 00", "Unexpected token (1:1)", {ecmaVersion: 6}); @@ -16153,10 +16153,10 @@ test("(function () { yield* 10 })", { rest: null, generator: false, expression: false, - range: [1, 26], + range: [0, 27], loc: { - start: {line: 1, column: 1}, - end: {line: 1, column: 26} + start: {line: 1, column: 0}, + end: {line: 1, column: 27} } }, range: [0, 27], @@ -16244,7 +16244,69 @@ testFail("({ t(eval) { \"use strict\"; } });", "Defining 'eval' in strict mode ( testFail("\"use strict\"; `${test}\\02`;", "Unexpected token (1:22)", {ecmaVersion: 6}); -testFail("[...a, ] = b", "Unexpected token (1:6)", {ecmaVersion: 6}); +test("[...a, ] = b", { + type: "Program", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 12} + }, + range: [0, 12], + body: [{ + type: "ExpressionStatement", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 12} + }, + range: [0, 12], + expression: { + type: "AssignmentExpression", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 12} + }, + range: [0, 12], + operator: "=", + left: { + type: "ArrayPattern", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 8} + }, + range: [0, 8], + elements: [{ + type: "SpreadElement", + loc: { + start: {line: 1, column: 1}, + end: {line: 1, column: 5} + }, + range: [1, 5], + argument: { + type: "Identifier", + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 5} + }, + range: [4, 5], + name: "a" + } + }] + }, + right: { + type: "Identifier", + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 12} + }, + range: [11, 12], + name: "b" + } + } + }] +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); testFail("if (b,...a, );", "Unexpected token (1:11)", {ecmaVersion: 6}); @@ -16258,4 +16320,4 @@ 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:21)", {ecmaVersion: 6}); +testFail("({ get test() { } }) => 42", "Unexpected token (1:7)", {ecmaVersion: 6});