From c47981c14fc07563cdbfce1331c3145f4377f206 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Thu, 25 Jan 2018 14:22:07 +0900 Subject: [PATCH 1/5] add rest/spread properties --- src/expression.js | 21 + src/loose/expression.js | 13 +- test/run.js | 1 + test/tests-rest-spread-properties.js | 825 +++++++++++++++++++++++++++ 4 files changed, 857 insertions(+), 3 deletions(-) create mode 100644 test/tests-rest-spread-properties.js diff --git a/src/expression.js b/src/expression.js index 29f39f41e..50f58679f 100644 --- a/src/expression.js +++ b/src/expression.js @@ -29,6 +29,8 @@ const pp = Parser.prototype // strict mode, init properties are also not allowed to be repeated. pp.checkPropClash = function(prop, propHash, refDestructuringErrors) { + if (this.options.ecmaVersion >= 9 && prop.type === "SpreadElement") + return if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand)) return let {key} = prop, name @@ -565,6 +567,25 @@ pp.parseObj = function(isPattern, refDestructuringErrors) { pp.parseProperty = function(isPattern, refDestructuringErrors) { let prop = this.startNode(), isGenerator, isAsync, startPos, startLoc + if (this.options.ecmaVersion >= 9 && this.eat(tt.ellipsis)) { + // To disallow parenthesized identifier. + if (this.type === tt.parenL && refDestructuringErrors) { + if (refDestructuringErrors.parenthesizedAssign < 0) { + refDestructuringErrors.parenthesizedAssign = this.start + } + if (refDestructuringErrors.parenthesizedBind < 0) { + refDestructuringErrors.parenthesizedBind = this.start + } + } + // Parse argument. + prop.argument = isPattern ? this.parseIdent(false) : this.parseMaybeAssign(false, refDestructuringErrors) + // To disallow trailing comma. + if (this.type === tt.comma && refDestructuringErrors && refDestructuringErrors.trailingComma < 0) { + refDestructuringErrors.trailingComma = this.start + } + // Finish + return this.finishNode(prop, isPattern ? "RestElement" : "SpreadElement") + } if (this.options.ecmaVersion >= 6) { prop.method = false prop.shorthand = false diff --git a/src/loose/expression.js b/src/loose/expression.js index 21c2fd1fa..865d0c822 100644 --- a/src/loose/expression.js +++ b/src/loose/expression.js @@ -368,6 +368,12 @@ lp.parseObj = function() { if (this.curIndent + 1 < indent) { indent = this.curIndent; line = this.curLineStart } while (!this.closes(tt.braceR, indent, line)) { let prop = this.startNode(), isGenerator, isAsync, start + if (this.options.ecmaVersion >= 9 && this.eat(tt.ellipsis)) { + prop.argument = this.parseMaybeAssign() + node.properties.push(this.finishNode(prop, "SpreadElement")) + this.eat(tt.comma) + continue + } if (this.options.ecmaVersion >= 6) { start = this.storeCurrentPos() prop.method = false @@ -476,12 +482,13 @@ lp.toAssignable = function(node, binding) { return this.dummyIdent() } else if (node.type == "ObjectExpression") { node.type = "ObjectPattern" - let props = node.properties - for (let prop of props) - this.toAssignable(prop.value, binding) + for (let prop of node.properties) + this.toAssignable(prop, binding) } else if (node.type == "ArrayExpression") { node.type = "ArrayPattern" this.toAssignableList(node.elements, binding) + } else if (node.type == "Property") { + this.toAssignable(node.value, binding) } else if (node.type == "SpreadElement") { node.type = "RestElement" this.toAssignable(node.argument, binding) diff --git a/test/run.js b/test/run.js index dce46c624..4f0ce54dd 100644 --- a/test/run.js +++ b/test/run.js @@ -10,6 +10,7 @@ require("./tests-trailing-commas-in-func.js"); require("./tests-template-literal-revision.js"); require("./tests-directive.js"); + require("./tests-rest-spread-properties.js"); acorn = require("../dist/acorn") require("../dist/acorn_loose") } else { diff --git a/test/tests-rest-spread-properties.js b/test/tests-rest-spread-properties.js new file mode 100644 index 000000000..d5d11356e --- /dev/null +++ b/test/tests-rest-spread-properties.js @@ -0,0 +1,825 @@ + +if (typeof exports != "undefined") { + var driver = require("./driver.js"); + var test = driver.test, testFail = driver.testFail, testAssert = driver.testAssert, misMatch = driver.misMatch; + var acorn = require(".."); +} + +//------------------------------------------------------------------------------ +// Spread Properties +//------------------------------------------------------------------------------ + +test("({...obj})", { + "type": "Program", + "start": 0, + "end": 10, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 10, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 9, + "properties": [ + { + "type": "SpreadElement", + "start": 2, + "end": 8, + "argument": { + "type": "Identifier", + "start": 5, + "end": 8, + "name": "obj" + } + } + ] + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({...obj1,})", { + "type": "Program", + "start": 0, + "end": 12, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 12, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 11, + "properties": [ + { + "type": "SpreadElement", + "start": 2, + "end": 9, + "argument": { + "type": "Identifier", + "start": 5, + "end": 9, + "name": "obj1" + } + } + ] + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({...obj1,...obj2})", { + "type": "Program", + "start": 0, + "end": 19, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 19, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 18, + "properties": [ + { + "type": "SpreadElement", + "start": 2, + "end": 9, + "argument": { + "type": "Identifier", + "start": 5, + "end": 9, + "name": "obj1" + } + }, + { + "type": "SpreadElement", + "start": 10, + "end": 17, + "argument": { + "type": "Identifier", + "start": 13, + "end": 17, + "name": "obj2" + } + } + ] + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({a,...obj1,b:1,...obj2,c:2})", { + "type": "Program", + "start": 0, + "end": 29, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 29, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 28, + "properties": [ + { + "type": "Property", + "start": 2, + "end": 3, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + } + }, + { + "type": "SpreadElement", + "start": 4, + "end": 11, + "argument": { + "type": "Identifier", + "start": 7, + "end": 11, + "name": "obj1" + } + }, + { + "type": "Property", + "start": 12, + "end": 15, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 12, + "end": 13, + "name": "b" + }, + "value": { + "type": "Literal", + "start": 14, + "end": 15, + "value": 1, + "raw": "1" + }, + "kind": "init" + }, + { + "type": "SpreadElement", + "start": 16, + "end": 23, + "argument": { + "type": "Identifier", + "start": 19, + "end": 23, + "name": "obj2" + } + }, + { + "type": "Property", + "start": 24, + "end": 27, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 24, + "end": 25, + "name": "c" + }, + "value": { + "type": "Literal", + "start": 26, + "end": 27, + "value": 2, + "raw": "2" + }, + "kind": "init" + } + ] + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({...(obj)})", { + "type": "Program", + "start": 0, + "end": 12, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 12, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 11, + "properties": [ + { + "type": "SpreadElement", + "start": 2, + "end": 10, + "argument": { + "type": "Identifier", + "start": 6, + "end": 9, + "name": "obj" + } + } + ] + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({...a,b,c})", { + "type": "Program", + "start": 0, + "end": 12, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 12, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 11, + "properties": [ + { + "type": "SpreadElement", + "start": 2, + "end": 6, + "argument": { + "type": "Identifier", + "start": 5, + "end": 6, + "name": "a" + } + }, + { + "type": "Property", + "start": 7, + "end": 8, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 7, + "end": 8, + "name": "b" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 7, + "end": 8, + "name": "b" + } + }, + { + "type": "Property", + "start": 9, + "end": 10, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 9, + "end": 10, + "name": "c" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 9, + "end": 10, + "name": "c" + } + } + ] + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({...(a,b),c})", { + "type": "Program", + "start": 0, + "end": 14, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 14, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 13, + "properties": [ + { + "type": "SpreadElement", + "start": 2, + "end": 10, + "argument": { + "type": "SequenceExpression", + "start": 6, + "end": 9, + "expressions": [ + { + "type": "Identifier", + "start": 6, + "end": 7, + "name": "a" + }, + { + "type": "Identifier", + "start": 8, + "end": 9, + "name": "b" + } + ] + } + }, + { + "type": "Property", + "start": 11, + "end": 12, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "c" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "c" + } + } + ] + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) + +testFail("({...})", "Unexpected token (1:5)", { ecmaVersion: 9 }) +testFail("({...obj})", "Unexpected token (1:2)", { ecmaVersion: 8 }) + +//------------------------------------------------------------------------------ +// Rest Properties +//------------------------------------------------------------------------------ + +test("({...obj} = foo)", { + "type": "Program", + "start": 0, + "end": 16, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 16, + "expression": { + "type": "AssignmentExpression", + "start": 1, + "end": 15, + "operator": "=", + "left": { + "type": "ObjectPattern", + "start": 1, + "end": 9, + "properties": [ + { + "type": "RestElement", + "start": 2, + "end": 8, + "argument": { + "type": "Identifier", + "start": 5, + "end": 8, + "name": "obj" + } + } + ] + }, + "right": { + "type": "Identifier", + "start": 12, + "end": 15, + "name": "foo" + } + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({a,...obj} = foo)", { + "type": "Program", + "start": 0, + "end": 18, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 18, + "expression": { + "type": "AssignmentExpression", + "start": 1, + "end": 17, + "operator": "=", + "left": { + "type": "ObjectPattern", + "start": 1, + "end": 11, + "properties": [ + { + "type": "Property", + "start": 2, + "end": 3, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + } + }, + { + "type": "RestElement", + "start": 4, + "end": 10, + "argument": { + "type": "Identifier", + "start": 7, + "end": 10, + "name": "obj" + } + } + ] + }, + "right": { + "type": "Identifier", + "start": 14, + "end": 17, + "name": "foo" + } + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({a:b,...obj} = foo)", { + "type": "Program", + "start": 0, + "end": 20, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 20, + "expression": { + "type": "AssignmentExpression", + "start": 1, + "end": 19, + "operator": "=", + "left": { + "type": "ObjectPattern", + "start": 1, + "end": 13, + "properties": [ + { + "type": "Property", + "start": 2, + "end": 5, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + }, + "value": { + "type": "Identifier", + "start": 4, + "end": 5, + "name": "b" + }, + "kind": "init" + }, + { + "type": "RestElement", + "start": 6, + "end": 12, + "argument": { + "type": "Identifier", + "start": 9, + "end": 12, + "name": "obj" + } + } + ] + }, + "right": { + "type": "Identifier", + "start": 16, + "end": 19, + "name": "foo" + } + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({...obj}) => {}", { + "type": "Program", + "start": 0, + "end": 16, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 16, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 16, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "ObjectPattern", + "start": 1, + "end": 9, + "properties": [ + { + "type": "RestElement", + "start": 2, + "end": 8, + "argument": { + "type": "Identifier", + "start": 5, + "end": 8, + "name": "obj" + } + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start": 14, + "end": 16, + "body": [] + } + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({...obj} = {}) => {}", { + "type": "Program", + "start": 0, + "end": 21, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 21, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 21, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "AssignmentPattern", + "start": 1, + "end": 14, + "left": { + "type": "ObjectPattern", + "start": 1, + "end": 9, + "properties": [ + { + "type": "RestElement", + "start": 2, + "end": 8, + "argument": { + "type": "Identifier", + "start": 5, + "end": 8, + "name": "obj" + } + } + ] + }, + "right": { + "type": "ObjectExpression", + "start": 12, + "end": 14, + "properties": [] + } + } + ], + "body": { + "type": "BlockStatement", + "start": 19, + "end": 21, + "body": [] + } + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({a,...obj}) => {}", { + "type": "Program", + "start": 0, + "end": 18, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 18, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 18, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "ObjectPattern", + "start": 1, + "end": 11, + "properties": [ + { + "type": "Property", + "start": 2, + "end": 3, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + } + }, + { + "type": "RestElement", + "start": 4, + "end": 10, + "argument": { + "type": "Identifier", + "start": 7, + "end": 10, + "name": "obj" + } + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start": 16, + "end": 18, + "body": [] + } + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) +test("({a:b,...obj}) => {}", { + "type": "Program", + "start": 0, + "end": 20, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 20, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 20, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "ObjectPattern", + "start": 1, + "end": 13, + "properties": [ + { + "type": "Property", + "start": 2, + "end": 5, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "a" + }, + "value": { + "type": "Identifier", + "start": 4, + "end": 5, + "name": "b" + }, + "kind": "init" + }, + { + "type": "RestElement", + "start": 6, + "end": 12, + "argument": { + "type": "Identifier", + "start": 9, + "end": 12, + "name": "obj" + } + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start": 18, + "end": 20, + "body": [] + } + } + } + ], + "sourceType": "script" +}, { ecmaVersion: 9 }) + +testFail("({...obj1,} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) +testFail("({...obj1,a} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) +testFail("({...obj1,...obj2} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) +testFail("({...(obj)} = foo)", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) +testFail("({...(a,b)} = foo)", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) +testFail("({...obj} = foo)", "Unexpected token (1:2)", { ecmaVersion: 8 }) +testFail("({...(obj)}) => {}", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) +testFail("({...(a,b)}) => {}", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) +testFail("({...obj}) => {}", "Unexpected token (1:2)", { ecmaVersion: 8 }) From 3fd6f9e164e157960adb0305aad56ff8616f8435 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Thu, 25 Jan 2018 18:26:27 +0900 Subject: [PATCH 2/5] update for destructuring bindings and exports --- src/expression.js | 11 +- src/statement.js | 6 +- test/tests-rest-spread-properties.js | 585 +++++++++++++++++++++++++++ 3 files changed, 598 insertions(+), 4 deletions(-) diff --git a/src/expression.js b/src/expression.js index 50f58679f..31f97dd5e 100644 --- a/src/expression.js +++ b/src/expression.js @@ -568,7 +568,7 @@ pp.parseObj = function(isPattern, refDestructuringErrors) { pp.parseProperty = function(isPattern, refDestructuringErrors) { let prop = this.startNode(), isGenerator, isAsync, startPos, startLoc if (this.options.ecmaVersion >= 9 && this.eat(tt.ellipsis)) { - // To disallow parenthesized identifier. + // To disallow parenthesized identifier via `this.toAssignable()`. if (this.type === tt.parenL && refDestructuringErrors) { if (refDestructuringErrors.parenthesizedAssign < 0) { refDestructuringErrors.parenthesizedAssign = this.start @@ -580,8 +580,13 @@ pp.parseProperty = function(isPattern, refDestructuringErrors) { // Parse argument. prop.argument = isPattern ? this.parseIdent(false) : this.parseMaybeAssign(false, refDestructuringErrors) // To disallow trailing comma. - if (this.type === tt.comma && refDestructuringErrors && refDestructuringErrors.trailingComma < 0) { - refDestructuringErrors.trailingComma = this.start + if (this.type === tt.comma) { + if (isPattern) { + this.raise(this.start, "Comma is not permitted after the rest element") + } else if (refDestructuringErrors && refDestructuringErrors.trailingComma < 0) { + // via `this.toAssignable()` + refDestructuringErrors.trailingComma = this.start + } } // Finish return this.finishNode(prop, isPattern ? "RestElement" : "SpreadElement") diff --git a/src/statement.js b/src/statement.js index db2fb8b8d..18aece8bd 100644 --- a/src/statement.js +++ b/src/statement.js @@ -665,13 +665,17 @@ pp.checkPatternExport = function(exports, pat) { this.checkExport(exports, pat.name, pat.start) else if (type == "ObjectPattern") for (let prop of pat.properties) - this.checkPatternExport(exports, prop.value) + this.checkPatternExport(exports, prop) else if (type == "ArrayPattern") for (let elt of pat.elements) { if (elt) this.checkPatternExport(exports, elt) } + else if (type == "Property") + this.checkPatternExport(exports, pat.value) else if (type == "AssignmentPattern") this.checkPatternExport(exports, pat.left) + else if (type == "RestElement") + this.checkPatternExport(exports, pat.argument) else if (type == "ParenthesizedExpression") this.checkPatternExport(exports, pat.expression) } diff --git a/test/tests-rest-spread-properties.js b/test/tests-rest-spread-properties.js index d5d11356e..573ea8940 100644 --- a/test/tests-rest-spread-properties.js +++ b/test/tests-rest-spread-properties.js @@ -814,6 +814,11 @@ test("({a:b,...obj}) => {}", { "sourceType": "script" }, { ecmaVersion: 9 }) +testFail("let {...obj1,} = foo", "Comma is not permitted after the rest element (1:12)", { ecmaVersion: 9 }) +testFail("let {...obj1,a} = foo", "Comma is not permitted after the rest element (1:12)", { ecmaVersion: 9 }) +testFail("let {...obj1,...obj2} = foo", "Comma is not permitted after the rest element (1:12)", { ecmaVersion: 9 }) +testFail("let {...(obj)} = foo", "Unexpected token (1:8)", { ecmaVersion: 9 }) +testFail("let {...(a,b)} = foo", "Unexpected token (1:8)", { ecmaVersion: 9 }) testFail("({...obj1,} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) testFail("({...obj1,a} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) testFail("({...obj1,...obj2} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) @@ -823,3 +828,583 @@ testFail("({...obj} = foo)", "Unexpected token (1:2)", { ecmaVersion: 8 }) testFail("({...(obj)}) => {}", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) testFail("({...(a,b)}) => {}", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) testFail("({...obj}) => {}", "Unexpected token (1:2)", { ecmaVersion: 8 }) + +//------------------------------------------------------------------------------ +// From https://github.com/adrianheine/acorn5-object-spread/tree/49839ac662fe34e1b4ad56767115f54747db2e7c/test +//------------------------------------------------------------------------------ + +test("let z = {...x}", { + "type": "Program", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 14, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 14, + "id": { + "type": "Identifier", + "start": 4, + "end": 5, + "name": "z" + }, + "init": { + "type": "ObjectExpression", + "start": 8, + "end": 14, + "properties": [ + { + "type": "SpreadElement", + "start": 9, + "end": 13, + "argument": { + "type": "Identifier", + "start": 12, + "end": 13, + "name": "x" + } + } + ] + } + } + ], + "kind": "let" + } + ], + "start": 0, + "end": 14, + "sourceType": "script" +}, { "ecmaVersion": 9 }) +test("z = {x, ...y}", { + "type": "Program", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 13, + "expression": { + "type": "AssignmentExpression", + "start": 0, + "end": 13, + "operator": "=", + "left": { + "type": "Identifier", + "start": 0, + "end": 1, + "name": "z" + }, + "right": { + "type": "ObjectExpression", + "start": 4, + "end": 13, + "properties": [ + { + "type": "Property", + "start": 5, + "end": 6, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 5, + "end": 6, + "name": "x" + }, + "value": { + "type": "Identifier", + "start": 5, + "end": 6, + "name": "x" + }, + "kind": "init" + }, + { + "type": "SpreadElement", + "start": 8, + "end": 12, + "argument": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "y" + } + } + ] + } + } + } + ], + "start": 0, + "end": 13, + "sourceType": "script" +}, { "ecmaVersion": 9 }) +test("({x, ...y, a, ...b, c})", { + "type": "Program", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 23, + "expression": { + "type": "ObjectExpression", + "start": 1, + "end": 22, + "properties": [ + { + "type": "Property", + "start": 2, + "end": 3, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "x" + }, + "value": { + "type": "Identifier", + "start": 2, + "end": 3, + "name": "x" + }, + "kind": "init" + }, + { + "type": "SpreadElement", + "start": 5, + "end": 9, + "argument": { + "type": "Identifier", + "start": 8, + "end": 9, + "name": "y" + } + }, + { + "type": "Property", + "start": 11, + "end": 12, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "a" + }, + "value": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "a" + }, + "kind": "init" + }, + { + "type": "SpreadElement", + "start": 14, + "end": 18, + "argument": { + "type": "Identifier", + "start": 17, + "end": 18, + "name": "b" + } + }, + { + "type": "Property", + "start": 20, + "end": 21, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 20, + "end": 21, + "name": "c" + }, + "value": { + "type": "Identifier", + "start": 20, + "end": 21, + "name": "c" + }, + "kind": "init" + } + ] + } + } + ], + "start": 0, + "end": 23, + "sourceType": "script" +}, { "ecmaVersion": 9 }) +test("var someObject = { someKey: { ...mapGetters([ 'some_val_1', 'some_val_2' ]) } }", { + "type": "Program", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 79, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 79, + "id": { + "type": "Identifier", + "start": 4, + "end": 14, + "name": "someObject" + }, + "init": { + "type": "ObjectExpression", + "start": 17, + "end": 79, + "properties": [ + { + "type": "Property", + "start": 19, + "end": 77, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 19, + "end": 26, + "name": "someKey" + }, + "value": { + "type": "ObjectExpression", + "start": 28, + "end": 77, + "properties": [ + { + "type": "SpreadElement", + "start": 30, + "end": 75, + "argument": { + "type": "CallExpression", + "start": 33, + "end": 75, + "callee": { + "type": "Identifier", + "start": 33, + "end": 43, + "name": "mapGetters" + }, + "arguments": [ + { + "type": "ArrayExpression", + "start": 44, + "end": 74, + "elements": [ + { + "type": "Literal", + "start": 46, + "end": 58, + "value": "some_val_1", + "raw": "'some_val_1'" + }, + { + "type": "Literal", + "start": 60, + "end": 72, + "value": "some_val_2", + "raw": "'some_val_2'" + } + ] + } + ] + } + } + ] + }, + "kind": "init" + } + ] + } + } + ], + "kind": "var" + } + ], + "start": 0, + "end": 79, + "sourceType": "script" +}, { "ecmaVersion": 9 }) +test("let {x, ...y} = v", { + "type": "Program", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 17, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 17, + "id": { + "type": "ObjectPattern", + "start": 4, + "end": 13, + "properties": [ + { + "type": "Property", + "start": 5, + "end": 6, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 5, + "end": 6, + "name": "x" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 5, + "end": 6, + "name": "x" + } + }, + { + "type": "RestElement", + "start": 8, + "end": 12, + "argument": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "y" + } + } + ] + }, + "init": { + "type": "Identifier", + "start": 16, + "end": 17, + "name": "v" + } + } + ], + "kind": "let" + } + ], + "start": 0, + "end": 17, + "sourceType": "script" +}, { "ecmaVersion": 9 }) +test("(function({x, ...y}) {})", { + "type": "Program", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 24, + "expression": { + "type": "FunctionExpression", + "start": 1, + "end": 23, + "id": null, + "generator": false, + "expression": false, + "params": [ + { + "type": "ObjectPattern", + "start": 10, + "end": 19, + "properties": [ + { + "type": "Property", + "start": 11, + "end": 12, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "x" + }, + "kind": "init", + "value": { + "type": "Identifier", + "start": 11, + "end": 12, + "name": "x" + } + }, + { + "type": "RestElement", + "start": 14, + "end": 18, + "argument": { + "type": "Identifier", + "start": 17, + "end": 18, + "name": "y" + } + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start": 21, + "end": 23, + "body": [] + } + } + } + ], + "start": 0, + "end": 24, + "sourceType": "script" +}, { "ecmaVersion": 9 }) +test("const fn = ({text = \"default\", ...props}) => text + props.children", { + "type": "Program", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 66, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 6, + "end": 66, + "id": { + "type": "Identifier", + "start": 6, + "end": 8, + "name": "fn" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 11, + "end": 66, + "id": null, + "generator": false, + "expression": true, + "params": [ + { + "type": "ObjectPattern", + "start": 12, + "end": 40, + "properties": [ + { + "type": "Property", + "start": 13, + "end": 29, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 13, + "end": 17, + "name": "text" + }, + "kind": "init", + "value": { + "type": "AssignmentPattern", + "start": 13, + "end": 29, + "left": { + "type": "Identifier", + "start": 13, + "end": 17, + "name": "text" + }, + "right": { + "type": "Literal", + "start": 20, + "end": 29, + "value": "default", + "raw": "\"default\"" + } + } + }, + { + "type": "RestElement", + "start": 31, + "end": 39, + "argument": { + "type": "Identifier", + "start": 34, + "end": 39, + "name": "props" + } + } + ] + } + ], + "body": { + "type": "BinaryExpression", + "start": 45, + "end": 66, + "left": { + "type": "Identifier", + "start": 45, + "end": 49, + "name": "text" + }, + "operator": "+", + "right": { + "type": "MemberExpression", + "start": 52, + "end": 66, + "object": { + "type": "Identifier", + "start": 52, + "end": 57, + "name": "props" + }, + "property": { + "type": "Identifier", + "start": 58, + "end": 66, + "name": "children" + }, + "computed": false + } + } + } + } + ], + "kind": "const" + } + ], + "start": 0, + "end": 66, + "sourceType": "script" +}, { "ecmaVersion": 9 }) + +testFail("({get x() {}}) => {}", "Object pattern can't contain getter or setter (1:6)", { ecmaVersion: 9 }) +testFail("let {...x, ...y} = {}", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) +testFail("({...x,}) => z", "Comma is not permitted after the rest element (1:6)", { ecmaVersion: 9 }) +testFail("export const { foo, ...bar } = baz;\nexport const bar = 1;\n", "Identifier 'bar' has already been declared (2:13)", { + ecmaVersion: 9, + sourceType: "module" +}) +testFail("function ({...x,}) { z }", "Unexpected token (1:9)", { ecmaVersion: 9 }) +testFail("let {...{x, y}} = {}", "Unexpected token (1:8)", { ecmaVersion: 9 }) +testFail("let {...{...{x, y}}} = {}", "Unexpected token (1:8)", { ecmaVersion: 9 }) +testFail("0, {...rest, b} = {}", "Comma is not permitted after the rest element (1:11)", { ecmaVersion: 9 }) +testFail("(([a, ...b = 0]) => {})", "Rest elements cannot have a default value (1:9)", { ecmaVersion: 9 }) +testFail("(({a, ...b = 0}) => {})", "Rest elements cannot have a default value (1:9)", { ecmaVersion: 9 }) From 267cd06292e9e6a459067f9ce47961a7ec60df07 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Fri, 26 Jan 2018 05:26:56 +0900 Subject: [PATCH 3/5] add missing early error --- src/lval.js | 14 +++++++++++++- test/tests-rest-spread-properties.js | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/lval.js b/src/lval.js index c5da84af1..0fc1bc528 100644 --- a/src/lval.js +++ b/src/lval.js @@ -23,8 +23,20 @@ pp.toAssignable = function(node, isBinding, refDestructuringErrors) { case "ObjectExpression": node.type = "ObjectPattern" if (refDestructuringErrors) this.checkPatternErrors(refDestructuringErrors, true) - for (let prop of node.properties) + for (let prop of node.properties) { this.toAssignable(prop, isBinding) + // Early error: + // AssignmentRestProperty[Yield, Await] : + // `...` DestructuringAssignmentTarget[Yield, Await] + // + // It is a Syntax Error if |DestructuringAssignmentTarget| is an |ArrayLiteral| or an |ObjectLiteral|. + if ( + prop.type === "RestElement" && + (prop.argument.type === "ArrayPattern" || prop.argument.type === "ObjectPattern") + ) { + this.raise(prop.argument.start, "Unexpected token") + } + } break case "Property": diff --git a/test/tests-rest-spread-properties.js b/test/tests-rest-spread-properties.js index 573ea8940..28e36ddd9 100644 --- a/test/tests-rest-spread-properties.js +++ b/test/tests-rest-spread-properties.js @@ -819,14 +819,20 @@ testFail("let {...obj1,a} = foo", "Comma is not permitted after the rest element testFail("let {...obj1,...obj2} = foo", "Comma is not permitted after the rest element (1:12)", { ecmaVersion: 9 }) testFail("let {...(obj)} = foo", "Unexpected token (1:8)", { ecmaVersion: 9 }) testFail("let {...(a,b)} = foo", "Unexpected token (1:8)", { ecmaVersion: 9 }) +testFail("let {...{a,b}} = foo", "Unexpected token (1:8)", { ecmaVersion: 9 }) +testFail("let {...[a,b]} = foo", "Unexpected token (1:8)", { ecmaVersion: 9 }) testFail("({...obj1,} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) testFail("({...obj1,a} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) testFail("({...obj1,...obj2} = foo)", "Comma is not permitted after the rest element (1:9)", { ecmaVersion: 9 }) testFail("({...(obj)} = foo)", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) testFail("({...(a,b)} = foo)", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) +testFail("({...{a,b}} = foo)", "Unexpected token (1:5)", { ecmaVersion: 9 }) +testFail("({...[a,b]} = foo)", "Unexpected token (1:5)", { ecmaVersion: 9 }) testFail("({...obj} = foo)", "Unexpected token (1:2)", { ecmaVersion: 8 }) testFail("({...(obj)}) => {}", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) testFail("({...(a,b)}) => {}", "Parenthesized pattern (1:5)", { ecmaVersion: 9 }) +testFail("({...{a,b}}) => {}", "Unexpected token (1:5)", { ecmaVersion: 9 }) +testFail("({...[a,b]}) => {}", "Unexpected token (1:5)", { ecmaVersion: 9 }) testFail("({...obj}) => {}", "Unexpected token (1:2)", { ecmaVersion: 8 }) //------------------------------------------------------------------------------ From 6492a297728585218932b5487273ee483a0100d4 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Fri, 26 Jan 2018 05:38:06 +0900 Subject: [PATCH 4/5] refactor separating rest and spread --- src/expression.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/expression.js b/src/expression.js index 31f97dd5e..36b1a0b47 100644 --- a/src/expression.js +++ b/src/expression.js @@ -568,6 +568,13 @@ pp.parseObj = function(isPattern, refDestructuringErrors) { pp.parseProperty = function(isPattern, refDestructuringErrors) { let prop = this.startNode(), isGenerator, isAsync, startPos, startLoc if (this.options.ecmaVersion >= 9 && this.eat(tt.ellipsis)) { + if (isPattern) { + prop.argument = this.parseIdent(false) + if (this.type === tt.comma) { + this.raise(this.start, "Comma is not permitted after the rest element") + } + return this.finishNode(prop, "RestElement") + } // To disallow parenthesized identifier via `this.toAssignable()`. if (this.type === tt.parenL && refDestructuringErrors) { if (refDestructuringErrors.parenthesizedAssign < 0) { @@ -578,18 +585,13 @@ pp.parseProperty = function(isPattern, refDestructuringErrors) { } } // Parse argument. - prop.argument = isPattern ? this.parseIdent(false) : this.parseMaybeAssign(false, refDestructuringErrors) - // To disallow trailing comma. - if (this.type === tt.comma) { - if (isPattern) { - this.raise(this.start, "Comma is not permitted after the rest element") - } else if (refDestructuringErrors && refDestructuringErrors.trailingComma < 0) { - // via `this.toAssignable()` - refDestructuringErrors.trailingComma = this.start - } + prop.argument = this.parseMaybeAssign(false, refDestructuringErrors) + // To disallow trailing comma via `this.toAssignable()`. + if (this.type === tt.comma && refDestructuringErrors && refDestructuringErrors.trailingComma < 0) { + refDestructuringErrors.trailingComma = this.start } // Finish - return this.finishNode(prop, isPattern ? "RestElement" : "SpreadElement") + return this.finishNode(prop, "SpreadElement") } if (this.options.ecmaVersion >= 6) { prop.method = false From 438914977a258cdac8170f7bd51e70c041c8d335 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Fri, 26 Jan 2018 05:50:26 +0900 Subject: [PATCH 5/5] update test262 --- bin/run_test262.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/run_test262.js b/bin/run_test262.js index b0e4e8fac..74b81184e 100644 --- a/bin/run_test262.js +++ b/bin/run_test262.js @@ -9,8 +9,6 @@ const unsupportedFeatures = [ "class-fields", "class-fields-private", "class-fields-public", - "object-rest", - "object-spread", "optional-catch-binding", "regexp-lookbehind", "regexp-named-groups",