diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 9eb7a15fa4c5..4b7a4558426b 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -582,63 +582,46 @@ export default class ExpressionParser extends LValParser { startLoc, noCalls, ); - } else if (this.match(tt.questionDot)) { + } + let optional = false; + if (this.match(tt.questionDot)) { this.expectPlugin("optionalChaining"); - state.optionalChainMember = true; + state.optionalChainMember = optional = true; if (noCalls && this.lookaheadCharCode() === charCodes.leftParenthesis) { state.stop = true; return base; } this.next(); - - const node = this.startNodeAt(startPos, startLoc); - - if (this.eat(tt.bracketL)) { - node.object = base; - node.property = this.parseExpression(); - node.computed = true; - node.optional = true; - this.expect(tt.bracketR); - return this.finishNode(node, "OptionalMemberExpression"); - } else if (this.eat(tt.parenL)) { - node.callee = base; - node.arguments = this.parseCallExpressionArguments(tt.parenR, false); - node.optional = true; - return this.finishCallExpression(node, /* optional */ true); - } else { - node.object = base; - node.property = this.parseIdentifier(true); - node.computed = false; - node.optional = true; - return this.finishNode(node, "OptionalMemberExpression"); - } - } else if (this.eat(tt.dot)) { + } + const computed = this.eat(tt.bracketL); + if ( + (optional && !this.match(tt.parenL) && !this.match(tt.backQuote)) || + computed || + this.eat(tt.dot) + ) { const node = this.startNodeAt(startPos, startLoc); node.object = base; - node.property = this.parseMaybePrivateName(); - node.computed = false; + node.property = computed + ? this.parseExpression() + : optional + ? this.parseIdentifier(true) + : this.parseMaybePrivateName(); + node.computed = computed; if ( node.property.type === "PrivateName" && node.object.type === "Super" ) { this.raise(startPos, "Private fields can't be accessed on super"); } - if (state.optionalChainMember) { - node.optional = false; - return this.finishNode(node, "OptionalMemberExpression"); + if (computed) { + this.expect(tt.bracketR); } - return this.finishNode(node, "MemberExpression"); - } else if (this.eat(tt.bracketL)) { - const node = this.startNodeAt(startPos, startLoc); - node.object = base; - node.property = this.parseExpression(); - node.computed = true; - this.expect(tt.bracketR); if (state.optionalChainMember) { - node.optional = false; + node.optional = optional; return this.finishNode(node, "OptionalMemberExpression"); + } else { + return this.finishNode(node, "MemberExpression"); } - return this.finishNode(node, "MemberExpression"); } else if (!noCalls && this.match(tt.parenL)) { const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; const oldYieldPos = this.state.yieldPos; @@ -652,16 +635,21 @@ export default class ExpressionParser extends LValParser { let node = this.startNodeAt(startPos, startLoc); node.callee = base; - node.arguments = this.parseCallExpressionArguments( - tt.parenR, - state.maybeAsyncArrow, - base.type === "Import", - base.type !== "Super", - node, - ); + if (optional) { + node.optional = true; + node.arguments = this.parseCallExpressionArguments(tt.parenR, false); + } else { + node.arguments = this.parseCallExpressionArguments( + tt.parenR, + state.maybeAsyncArrow, + base.type === "Import", + base.type !== "Super", + node, + ); + } this.finishCallExpression(node, state.optionalChainMember); - if (state.maybeAsyncArrow && this.shouldParseAsyncArrow()) { + if (state.maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) { state.stop = true; node = this.parseAsyncArrowFromCallExpression( diff --git a/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-async-optional-calls/input.js b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-async-optional-calls/input.js new file mode 100644 index 000000000000..961846976c7a --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-async-optional-calls/input.js @@ -0,0 +1 @@ +async?.(bar: string) => {} diff --git a/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-async-optional-calls/options.json b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-async-optional-calls/options.json new file mode 100644 index 000000000000..3c7c9c589b44 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-async-optional-calls/options.json @@ -0,0 +1,8 @@ +{ + "sourceType": "module", + "plugins": [ + "flow", + "optionalChaining" + ], + "throws": "Unexpected token, expected \";\" (1:21)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/input.js b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/input.js new file mode 100644 index 000000000000..14df4e2b16c7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/input.js @@ -0,0 +1 @@ +funccall?.(a, b: string); diff --git a/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/options.json b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/options.json new file mode 100644 index 000000000000..0e7765c73ae7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/options.json @@ -0,0 +1,7 @@ +{ + "sourceType": "module", + "plugins": [ + "flow", + "optionalChaining" + ] +} diff --git a/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/output.json b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/output.json new file mode 100644 index 000000000000..84fcef591c74 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/typecasts/fail-in-optional-calls/output.json @@ -0,0 +1,167 @@ +{ + "type": "File", + "start": 0, + "end": 25, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 25 + } + }, + "errors": [ + "SyntaxError: The type cast expression is expected to be wrapped with parenthesis (1:15)" + ], + "program": { + "type": "Program", + "start": 0, + "end": 25, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 25 + } + }, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 25, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 25 + } + }, + "expression": { + "type": "OptionalCallExpression", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "callee": { + "type": "Identifier", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + }, + "identifierName": "funccall" + }, + "name": "funccall" + }, + "optional": true, + "arguments": [ + { + "type": "Identifier", + "start": 11, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 11 + }, + "end": { + "line": 1, + "column": 12 + }, + "identifierName": "a" + }, + "name": "a" + }, + { + "type": "TypeCastExpression", + "start": 14, + "end": 23, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 23 + } + }, + "expression": { + "type": "Identifier", + "start": 14, + "end": 15, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 15 + }, + "identifierName": "b" + }, + "name": "b" + }, + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 15, + "end": 23, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 23 + } + }, + "typeAnnotation": { + "type": "StringTypeAnnotation", + "start": 17, + "end": 23, + "loc": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 23 + } + } + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file