diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 57ba5d25a35e..59221e8ca91e 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -278,7 +278,7 @@ export default class ExpressionParser extends LValParser { node.operator = operator; if (this.match(tt.eq)) { - node.left = this.toAssignable(left); + node.left = this.toAssignable(left, /* isLHS */ true); refExpressionErrors.doubleProto = -1; // reset because double __proto__ is valid in assignment expression } else { node.left = left; @@ -842,7 +842,6 @@ export default class ExpressionParser extends LValParser { nodeForExtra?: ?N.Node, ): $ReadOnlyArray { const elts = []; - let innerParenStart; let first = true; const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; this.state.inFSharpPipelineDirectBody = false; @@ -875,12 +874,6 @@ export default class ExpressionParser extends LValParser { } } - // we need to make sure that if this is an async arrow functions, - // that we don't allow inner parens inside the params - if (this.match(tt.parenL) && !innerParenStart) { - innerParenStart = this.state.start; - } - elts.push( this.parseExprListItem( false, @@ -891,11 +884,6 @@ export default class ExpressionParser extends LValParser { ); } - // we found an async arrow function so let's not allow any inner parens - if (possibleAsyncArrow && innerParenStart && this.shouldParseAsyncArrow()) { - this.unexpected(); - } - this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; return elts; @@ -2056,7 +2044,7 @@ export default class ExpressionParser extends LValParser { params: N.Expression[], trailingCommaPos: ?number, ): void { - node.params = this.toAssignableList(params, trailingCommaPos); + node.params = this.toAssignableList(params, trailingCommaPos, false); } parseFunctionBodyAndFinish( diff --git a/packages/babel-parser/src/parser/lval.js b/packages/babel-parser/src/parser/lval.js index 42c094b76203..7aaac489018d 100644 --- a/packages/babel-parser/src/parser/lval.js +++ b/packages/babel-parser/src/parser/lval.js @@ -51,18 +51,32 @@ export default class LValParser extends NodeUtils { +parseDecorator: () => Decorator; */ - // Convert existing expression atom to assignable pattern - // if possible. - // NOTE: There is a corresponding "isAssignable" method in flow.js. - // When this one is updated, please check if also that one needs to be updated. + /** + * Convert existing expression atom to assignable pattern + * if possible. Also checks invalid destructuring targets: - toAssignable(node: Node): Node { + - Parenthesized Destructuring patterns + - RestElement is not the last element + - Missing `=` in assignment pattern + + NOTE: There is a corresponding "isAssignable" method in flow.js. + When this one is updated, please check if also that one needs to be updated. + + * @param {Node} node The expression atom + * @param {boolean} [isLHS=false] Whether we are parsing a LeftHandSideExpression. If isLHS is `true`, the following cases are allowed: + `[(a)] = [0]`, `[(a.b)] = [0]` + + * @returns {Node} The converted assignable pattern + * @memberof LValParser + */ + toAssignable(node: Node, isLHS: boolean = false): Node { let parenthesized = undefined; if (node.type === "ParenthesizedExpression" || node.extra?.parenthesized) { parenthesized = unwrapParenthesizedExpression(node); if ( - parenthesized.type !== "Identifier" && - parenthesized.type !== "MemberExpression" + !isLHS || + (parenthesized.type !== "Identifier" && + parenthesized.type !== "MemberExpression") ) { this.raise(node.start, Errors.InvalidParenthesizedAssignment); } @@ -84,7 +98,7 @@ export default class LValParser extends NodeUtils { ) { const prop = node.properties[i]; const isLast = i === last; - this.toAssignableObjectExpressionProp(prop, isLast); + this.toAssignableObjectExpressionProp(prop, isLast, isLHS); if ( isLast && @@ -97,7 +111,7 @@ export default class LValParser extends NodeUtils { break; case "ObjectProperty": - this.toAssignable(node.value); + this.toAssignable(node.value, isLHS); break; case "SpreadElement": { @@ -105,13 +119,13 @@ export default class LValParser extends NodeUtils { node.type = "RestElement"; const arg = node.argument; - this.toAssignable(arg); + this.toAssignable(arg, isLHS); break; } case "ArrayExpression": node.type = "ArrayPattern"; - this.toAssignableList(node.elements, node.extra?.trailingComma); + this.toAssignableList(node.elements, node.extra?.trailingComma, isLHS); break; case "AssignmentExpression": @@ -121,11 +135,11 @@ export default class LValParser extends NodeUtils { node.type = "AssignmentPattern"; delete node.operator; - this.toAssignable(node.left); + this.toAssignable(node.left, isLHS); break; case "ParenthesizedExpression": - this.toAssignable(((parenthesized: any): Expression)); + this.toAssignable(((parenthesized: any): Expression), isLHS); break; default: @@ -135,7 +149,11 @@ export default class LValParser extends NodeUtils { return node; } - toAssignableObjectExpressionProp(prop: Node, isLast: boolean) { + toAssignableObjectExpressionProp( + prop: Node, + isLast: boolean, + isLHS: boolean, + ) { if (prop.type === "ObjectMethod") { const error = prop.kind === "get" || prop.kind === "set" @@ -148,7 +166,7 @@ export default class LValParser extends NodeUtils { } else if (prop.type === "SpreadElement" && !isLast) { this.raiseRestNotLast(prop.start); } else { - this.toAssignable(prop); + this.toAssignable(prop, isLHS); } } @@ -157,6 +175,7 @@ export default class LValParser extends NodeUtils { toAssignableList( exprList: Expression[], trailingCommaPos?: ?number, + isLHS: boolean, ): $ReadOnlyArray { let end = exprList.length; if (end) { @@ -166,7 +185,7 @@ export default class LValParser extends NodeUtils { } else if (last?.type === "SpreadElement") { last.type = "RestElement"; const arg = last.argument; - this.toAssignable(arg); + this.toAssignable(arg, isLHS); if ( arg.type !== "Identifier" && arg.type !== "MemberExpression" && @@ -186,7 +205,7 @@ export default class LValParser extends NodeUtils { for (let i = 0; i < end; i++) { const elt = exprList[i]; if (elt) { - this.toAssignable(elt); + this.toAssignable(elt, isLHS); if (elt.type === "RestElement") { this.raiseRestNotLast(elt.start); } diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 27df1ad74148..7c313e77f12d 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -341,23 +341,27 @@ export default (superClass: Class): Class => return (node: any); } - toAssignable(node: N.Node): N.Node { + toAssignable(node: N.Node, isLHS: boolean = false): N.Node { if (isSimpleProperty(node)) { this.toAssignable(node.value); return node; } - return super.toAssignable(node); + return super.toAssignable(node, isLHS); } - toAssignableObjectExpressionProp(prop: N.Node, isLast: boolean) { + toAssignableObjectExpressionProp( + prop: N.Node, + isLast: boolean, + isLHS: boolean, + ) { if (prop.kind === "get" || prop.kind === "set") { throw this.raise(prop.key.start, Errors.PatternHasAccessor); } else if (prop.method) { throw this.raise(prop.key.start, Errors.PatternHasMethod); } else { - super.toAssignableObjectExpressionProp(prop, isLast); + super.toAssignableObjectExpressionProp(prop, isLast, isLHS); } } diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index 53dc48f3067e..e14638301a63 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -2192,11 +2192,11 @@ export default (superClass: Class): Class => } } - toAssignable(node: N.Node): N.Node { + toAssignable(node: N.Node, isLHS: boolean = false): N.Node { if (node.type === "TypeCastExpression") { - return super.toAssignable(this.typeCastToParameter(node)); + return super.toAssignable(this.typeCastToParameter(node), isLHS); } else { - return super.toAssignable(node); + return super.toAssignable(node, isLHS); } } @@ -2204,6 +2204,7 @@ export default (superClass: Class): Class => toAssignableList( exprList: N.Expression[], trailingCommaPos?: ?number, + isLHS: boolean = true, ): $ReadOnlyArray { for (let i = 0; i < exprList.length; i++) { const expr = exprList[i]; @@ -2211,7 +2212,7 @@ export default (superClass: Class): Class => exprList[i] = this.typeCastToParameter(expr); } } - return super.toAssignableList(exprList, trailingCommaPos); + return super.toAssignableList(exprList, trailingCommaPos, isLHS); } // this is a list of nodes, from something like a call expression, we need to filter the diff --git a/packages/babel-parser/test/fixtures/es2015/arrow-functions/invalid-parenthesized-in-params/input.js b/packages/babel-parser/test/fixtures/es2015/arrow-functions/invalid-parenthesized-in-params/input.js new file mode 100644 index 000000000000..63159238e7b9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/arrow-functions/invalid-parenthesized-in-params/input.js @@ -0,0 +1 @@ +([(a)]) => {} diff --git a/packages/babel-parser/test/fixtures/es2015/arrow-functions/invalid-parenthesized-in-params/output.json b/packages/babel-parser/test/fixtures/es2015/arrow-functions/invalid-parenthesized-in-params/output.json new file mode 100644 index 000000000000..c2220d23a6d6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/arrow-functions/invalid-parenthesized-in-params/output.json @@ -0,0 +1,50 @@ +{ + "type": "File", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}}, + "errors": [ + "SyntaxError: Invalid parenthesized assignment pattern (1:3)" + ], + "program": { + "type": "Program", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}}, + "expression": { + "type": "ArrowFunctionExpression", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}}, + "id": null, + "generator": false, + "async": false, + "params": [ + { + "type": "ArrayPattern", + "start":1,"end":6,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":6}}, + "elements": [ + { + "type": "Identifier", + "start":3,"end":4,"loc":{"start":{"line":1,"column":3},"end":{"line":1,"column":4},"identifierName":"a"}, + "extra": { + "parenthesized": true, + "parenStart": 2 + }, + "name": "a" + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start":11,"end":13,"loc":{"start":{"line":1,"column":11},"end":{"line":1,"column":13}}, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/parenthesized-lhs-in-arrow-param/input.js b/packages/babel-parser/test/fixtures/es2015/destructuring/parenthesized-lhs-in-arrow-param/input.js new file mode 100644 index 000000000000..dcf47493a4fd --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/destructuring/parenthesized-lhs-in-arrow-param/input.js @@ -0,0 +1 @@ +(a = function (a) { [(a)] = [0] }) => {} diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/parenthesized-lhs-in-arrow-param/output.json b/packages/babel-parser/test/fixtures/es2015/destructuring/parenthesized-lhs-in-arrow-param/output.json new file mode 100644 index 000000000000..ecb394e8b96e --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/destructuring/parenthesized-lhs-in-arrow-param/output.json @@ -0,0 +1,101 @@ +{ + "type": "File", + "start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}}, + "program": { + "type": "Program", + "start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}}, + "expression": { + "type": "ArrowFunctionExpression", + "start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}}, + "id": null, + "generator": false, + "async": false, + "params": [ + { + "type": "AssignmentPattern", + "start":1,"end":33,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":33}}, + "left": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":2},"identifierName":"a"}, + "name": "a" + }, + "right": { + "type": "FunctionExpression", + "start":5,"end":33,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":33}}, + "id": null, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"a"}, + "name": "a" + } + ], + "body": { + "type": "BlockStatement", + "start":18,"end":33,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":33}}, + "body": [ + { + "type": "ExpressionStatement", + "start":20,"end":31,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":31}}, + "expression": { + "type": "AssignmentExpression", + "start":20,"end":31,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":31}}, + "operator": "=", + "left": { + "type": "ArrayPattern", + "start":20,"end":25,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":25}}, + "elements": [ + { + "type": "Identifier", + "start":22,"end":23,"loc":{"start":{"line":1,"column":22},"end":{"line":1,"column":23},"identifierName":"a"}, + "extra": { + "parenthesized": true, + "parenStart": 21 + }, + "name": "a" + } + ] + }, + "right": { + "type": "ArrayExpression", + "start":28,"end":31,"loc":{"start":{"line":1,"column":28},"end":{"line":1,"column":31}}, + "elements": [ + { + "type": "NumericLiteral", + "start":29,"end":30,"loc":{"start":{"line":1,"column":29},"end":{"line":1,"column":30}}, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + ] + } + } + } + ], + "directives": [] + } + } + } + ], + "body": { + "type": "BlockStatement", + "start":38,"end":40,"loc":{"start":{"line":1,"column":38},"end":{"line":1,"column":40}}, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/3/options.json b/packages/babel-parser/test/fixtures/es2017/async-functions/3/options.json deleted file mode 100644 index fa579aa83145..000000000000 --- a/packages/babel-parser/test/fixtures/es2017/async-functions/3/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "Unexpected token (1:24)" -} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/3/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-identifier/input.js similarity index 100% rename from packages/babel-parser/test/fixtures/es2017/async-functions/3/input.js rename to packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-identifier/input.js diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-identifier/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-identifier/output.json new file mode 100644 index 000000000000..984ab4dbde4c --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-identifier/output.json @@ -0,0 +1,56 @@ +{ + "type": "File", + "start":0,"end":30,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":30}}, + "errors": [ + "SyntaxError: Invalid parenthesized assignment pattern (1:18)" + ], + "program": { + "type": "Program", + "start":0,"end":30,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":30}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":30,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":30}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":29,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":29}}, + "id": { + "type": "Identifier", + "start":4,"end":7,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":7},"identifierName":"foo"}, + "name": "foo" + }, + "init": { + "type": "ArrowFunctionExpression", + "start":10,"end":29,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":29}}, + "id": null, + "generator": false, + "async": true, + "params": [ + { + "type": "Identifier", + "start":18,"end":21,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":21},"identifierName":"foo"}, + "extra": { + "parenthesized": true, + "parenStart": 17 + }, + "name": "foo" + } + ], + "body": { + "type": "BlockStatement", + "start":27,"end":29,"loc":{"start":{"line":1,"column":27},"end":{"line":1,"column":29}}, + "body": [], + "directives": [] + } + } + } + ], + "kind": "var" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-pattern/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-pattern/input.js new file mode 100644 index 000000000000..3cd89cca4326 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-pattern/input.js @@ -0,0 +1 @@ +var foo = async ([(foo)]) => {}; diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-pattern/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-pattern/output.json new file mode 100644 index 000000000000..2113ddb5a8da --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/parenthesized-async-arrow-binding-pattern/output.json @@ -0,0 +1,62 @@ +{ + "type": "File", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":32}}, + "errors": [ + "SyntaxError: Invalid parenthesized assignment pattern (1:19)" + ], + "program": { + "type": "Program", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":32}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":32}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":31,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":31}}, + "id": { + "type": "Identifier", + "start":4,"end":7,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":7},"identifierName":"foo"}, + "name": "foo" + }, + "init": { + "type": "ArrowFunctionExpression", + "start":10,"end":31,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":31}}, + "id": null, + "generator": false, + "async": true, + "params": [ + { + "type": "ArrayPattern", + "start":17,"end":24,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":24}}, + "elements": [ + { + "type": "Identifier", + "start":19,"end":22,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":22},"identifierName":"foo"}, + "extra": { + "parenthesized": true, + "parenStart": 18 + }, + "name": "foo" + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start":29,"end":31,"loc":{"start":{"line":1,"column":29},"end":{"line":1,"column":31}}, + "body": [], + "directives": [] + } + } + } + ], + "kind": "var" + } + ], + "directives": [] + } +} \ No newline at end of file