diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index afe13976d1..a23ef19ebe 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -2082,7 +2082,21 @@ export default (superClass: Class): Class => return this.finishNode(nonNullExpression, "TSNonNullExpression"); } + let isOptionalCall = false; + if ( + this.match(tt.questionDot) && + this.lookaheadCharCode() === charCodes.lessThan + ) { + if (noCalls) { + state.stop = true; + return base; + } + state.optionalChainMember = isOptionalCall = true; + this.next(); + } + if (this.isRelational("<")) { + let missingParenErrorPos; // tsTryParseAndCatch is expensive, so avoid if not necessary. // There are number of things we are going to "maybe" parse, like type arguments on // tagged template expressions. If any of them fail, walk it back and continue. @@ -2105,6 +2119,11 @@ export default (superClass: Class): Class => const typeArguments = this.tsParseTypeArguments(); if (typeArguments) { + if (isOptionalCall && !this.match(tt.parenL)) { + missingParenErrorPos = this.state.pos; + this.unexpected(); + } + if (!noCalls && this.eat(tt.parenL)) { // possibleAsync always false here, because we would have handled it above. // $FlowIgnore (won't be any undefined arguments) @@ -2119,8 +2138,9 @@ export default (superClass: Class): Class => node.typeParameters = typeArguments; if (state.optionalChainMember) { // $FlowIgnore - node.optional = false; + node.optional = isOptionalCall; } + return this.finishCallExpression(node, state.optionalChainMember); } else if (this.match(tt.backQuote)) { const result = this.parseTaggedTemplateExpression( @@ -2137,6 +2157,10 @@ export default (superClass: Class): Class => this.unexpected(); }); + if (missingParenErrorPos) { + this.unexpected(missingParenErrorPos, tt.parenL); + } + if (result) return result; } diff --git a/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/input.js b/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/input.js index 666a0756ab..998e39643a 100644 --- a/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/input.js +++ b/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/input.js @@ -1 +1,2 @@ foo?.foo(); +foo?.foo?.(); diff --git a/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/output.json b/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/output.json index 9d340ceecc..47edad4486 100644 --- a/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/output.json +++ b/packages/babel-parser/test/fixtures/estree/typescript/optional-chaining/output.json @@ -1,9 +1,9 @@ { "type": "File", - "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":16}}, "program": { "type": "Program", - "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":16}}, "sourceType": "script", "interpreter": null, "body": [ @@ -51,6 +51,51 @@ "optional": false } } + }, + { + "type": "ExpressionStatement", + "start":15,"end":31,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":16}}, + "expression": { + "type": "ChainExpression", + "start":15,"end":30,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":15}}, + "expression": { + "type": "CallExpression", + "start":15,"end":30,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":15}}, + "callee": { + "type": "MemberExpression", + "start":15,"end":23,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":8}}, + "object": { + "type": "Identifier", + "start":15,"end":18,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":3},"identifierName":"foo"}, + "name": "foo" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":20,"end":23,"loc":{"start":{"line":2,"column":5},"end":{"line":2,"column":8},"identifierName":"foo"}, + "name": "foo" + }, + "optional": true + }, + "arguments": [], + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start":25,"end":28,"loc":{"start":{"line":2,"column":10},"end":{"line":2,"column":13}}, + "params": [ + { + "type": "TSTypeReference", + "start":26,"end":27,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":12}}, + "typeName": { + "type": "Identifier", + "start":26,"end":27,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":12},"identifierName":"T"}, + "name": "T" + } + } + ] + }, + "optional": true + } + } } ] } diff --git a/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain-invalid/input.ts b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain-invalid/input.ts new file mode 100644 index 0000000000..ba51adb7c5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain-invalid/input.ts @@ -0,0 +1 @@ +f?.[1]; diff --git a/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain-invalid/options.json b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain-invalid/options.json new file mode 100644 index 0000000000..2a2a608b0e --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain-invalid/options.json @@ -0,0 +1,7 @@ +{ + "sourceType": "module", + "plugins": [ + "typescript" + ], + "throws": "Unexpected token, expected \"(\" (1:12)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain/input.ts b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain/input.ts new file mode 100644 index 0000000000..1f2bdf0dc5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain/input.ts @@ -0,0 +1,4 @@ +f?.(); +f?.(); +f +?.(); diff --git a/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain/output.json b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain/output.json new file mode 100644 index 0000000000..832b616726 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/type-arguments/call-optional-chain/output.json @@ -0,0 +1,112 @@ +{ + "type": "File", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":8}}, + "program": { + "type": "Program", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":8}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}}, + "expression": { + "type": "OptionalCallExpression", + "start":0,"end":8,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":8}}, + "callee": { + "type": "Identifier", + "start":0,"end":1,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":1},"identifierName":"f"}, + "name": "f" + }, + "arguments": [], + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start":3,"end":6,"loc":{"start":{"line":1,"column":3},"end":{"line":1,"column":6}}, + "params": [ + { + "type": "TSTypeReference", + "start":4,"end":5,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":5}}, + "typeName": { + "type": "Identifier", + "start":4,"end":5,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":5},"identifierName":"Q"}, + "name": "Q" + } + } + ] + }, + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start":10,"end":22,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":12}}, + "expression": { + "type": "OptionalCallExpression", + "start":10,"end":21,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":11}}, + "callee": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":1},"identifierName":"f"}, + "name": "f" + }, + "arguments": [], + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start":13,"end":19,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":9}}, + "params": [ + { + "type": "TSTypeReference", + "start":14,"end":15,"loc":{"start":{"line":2,"column":4},"end":{"line":2,"column":5}}, + "typeName": { + "type": "Identifier", + "start":14,"end":15,"loc":{"start":{"line":2,"column":4},"end":{"line":2,"column":5},"identifierName":"Q"}, + "name": "Q" + } + }, + { + "type": "TSTypeReference", + "start":17,"end":18,"loc":{"start":{"line":2,"column":7},"end":{"line":2,"column":8}}, + "typeName": { + "type": "Identifier", + "start":17,"end":18,"loc":{"start":{"line":2,"column":7},"end":{"line":2,"column":8},"identifierName":"W"}, + "name": "W" + } + } + ] + }, + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start":23,"end":33,"loc":{"start":{"line":3,"column":0},"end":{"line":4,"column":8}}, + "expression": { + "type": "OptionalCallExpression", + "start":23,"end":32,"loc":{"start":{"line":3,"column":0},"end":{"line":4,"column":7}}, + "callee": { + "type": "Identifier", + "start":23,"end":24,"loc":{"start":{"line":3,"column":0},"end":{"line":3,"column":1},"identifierName":"f"}, + "name": "f" + }, + "arguments": [], + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start":27,"end":30,"loc":{"start":{"line":4,"column":2},"end":{"line":4,"column":5}}, + "params": [ + { + "type": "TSTypeReference", + "start":28,"end":29,"loc":{"start":{"line":4,"column":3},"end":{"line":4,"column":4}}, + "typeName": { + "type": "Identifier", + "start":28,"end":29,"loc":{"start":{"line":4,"column":3},"end":{"line":4,"column":4},"identifierName":"Q"}, + "name": "Q" + } + } + ] + }, + "optional": true + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/input.ts index 362521c1ed..b4ecd52efe 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/input.ts +++ b/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/input.ts @@ -1 +1,3 @@ -x?.f(); \ No newline at end of file +x?.f(); +x?.f?.(); +f?.(); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/output.js b/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/output.js index 720f644dbc..06feb0462c 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/output.js +++ b/packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/optional-call/output.js @@ -1 +1,3 @@ -x?.f(); \ No newline at end of file +x?.f(); +x?.f?.(); +f?.();