From 3fd963fdc8e0f73a2f6645eee8fbc3fc43227572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 14 Oct 2020 20:15:42 +0200 Subject: [PATCH] [ts] Add support for template interpolations in types (#12131) Co-authored-by: Brian Ng --- Makefile | 2 +- .../babel-parser/src/parser/expression.js | 7 +- .../src/plugins/typescript/index.js | 16 ++-- .../types/literal-string-2/output.json | 13 ++-- .../types/literal-string-3/input.ts | 1 + .../types/literal-string-3/options.json | 3 + .../types/literal-string-4/input.ts | 1 + .../types/literal-string-4/output.json | 73 +++++++++++++++++++ packages/babel-types/src/definitions/core.js | 8 +- scripts/parser-tests/typescript/allowlist.txt | 1 + 10 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/typescript/types/literal-string-3/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/literal-string-3/options.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/literal-string-4/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/literal-string-4/output.json diff --git a/Makefile b/Makefile index 6537e089b8..2063f2445c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ FLOW_COMMIT = a1f9a4c709dcebb27a5084acf47755fbae699c25 TEST262_COMMIT = 058adfed86b1d4129996faaf50a85ea55379a66a -TYPESCRIPT_COMMIT = d779a190535e52896cfe5100101173c00b6b8625 +TYPESCRIPT_COMMIT = da8633212023517630de5f3620a23736b63234b1 FORCE_PUBLISH = "@babel/runtime,@babel/runtime-corejs2,@babel/runtime-corejs3,@babel/standalone" diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 3e48b00945..1f7069d99c 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -1608,7 +1608,7 @@ export default class ExpressionParser extends LValParser { node.quasis = [curElt]; while (!curElt.tail) { this.expect(tt.dollarBraceL); - node.expressions.push(this.parseExpression()); + node.expressions.push(this.parseTemplateSubstitution()); this.expect(tt.braceR); node.quasis.push((curElt = this.parseTemplateElement(isTagged))); } @@ -1616,6 +1616,11 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, "TemplateLiteral"); } + // This is overwritten by the TypeScript plugin to parse template types + parseTemplateSubstitution(): N.Expression { + return this.parseExpression(); + } + // Parse an object literal, binding pattern, or record. parseObjectLike( diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index e91043d6bb..8ff2cce4c2 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -94,8 +94,6 @@ const TSErrors = Object.freeze({ "Private elements cannot have the 'abstract' modifier.", PrivateElementHasAccessibility: "Private elements cannot have an accessibility modifier ('%0')", - TemplateTypeHasSubstitution: - "Template literal types cannot have any substitution", TypeAnnotationAfterAssign: "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`", UnexpectedParameterModifier: @@ -774,17 +772,15 @@ export default (superClass: Class): Class => tsParseTemplateLiteralType(): N.TsType { const node: N.TsLiteralType = this.startNode(); - const templateNode = this.parseTemplate(false); - if (templateNode.expressions.length > 0) { - this.raise( - templateNode.expressions[0].start, - TSErrors.TemplateTypeHasSubstitution, - ); - } - node.literal = templateNode; + node.literal = this.parseTemplate(false); return this.finishNode(node, "TSLiteralType"); } + parseTemplateSubstitution(): N.TsType { + if (this.state.inType) return this.tsParseType(); + return super.parseTemplateSubstitution(); + } + tsParseThisTypeOrThisTypePredicate(): N.TsThisType | N.TsTypePredicate { const thisKeyword = this.tsParseThisTypeNode(); if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { diff --git a/packages/babel-parser/test/fixtures/typescript/types/literal-string-2/output.json b/packages/babel-parser/test/fixtures/typescript/types/literal-string-2/output.json index 1b6b7291f9..3459f5976e 100644 --- a/packages/babel-parser/test/fixtures/typescript/types/literal-string-2/output.json +++ b/packages/babel-parser/test/fixtures/typescript/types/literal-string-2/output.json @@ -1,9 +1,6 @@ { "type": "File", "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, - "errors": [ - "SyntaxError: Template literal types cannot have any substitution (1:14)" - ], "program": { "type": "Program", "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, @@ -32,9 +29,13 @@ "start":7,"end":19,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":19}}, "expressions": [ { - "type": "Identifier", - "start":14,"end":17,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":17},"identifierName":"bar"}, - "name": "bar" + "type": "TSTypeReference", + "start":14,"end":17,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":17}}, + "typeName": { + "type": "Identifier", + "start":14,"end":17,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":17},"identifierName":"bar"}, + "name": "bar" + } } ], "quasis": [ diff --git a/packages/babel-parser/test/fixtures/typescript/types/literal-string-3/input.ts b/packages/babel-parser/test/fixtures/typescript/types/literal-string-3/input.ts new file mode 100644 index 0000000000..994ef1af2a --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/literal-string-3/input.ts @@ -0,0 +1 @@ +let x: `foo-${bar + baz}`; diff --git a/packages/babel-parser/test/fixtures/typescript/types/literal-string-3/options.json b/packages/babel-parser/test/fixtures/typescript/types/literal-string-3/options.json new file mode 100644 index 0000000000..92ccb389f6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/literal-string-3/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected token, expected \"}\" (1:18)" +} diff --git a/packages/babel-parser/test/fixtures/typescript/types/literal-string-4/input.ts b/packages/babel-parser/test/fixtures/typescript/types/literal-string-4/input.ts new file mode 100644 index 0000000000..020b98d485 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/literal-string-4/input.ts @@ -0,0 +1 @@ +let x: `foo-${infer bar}`; diff --git a/packages/babel-parser/test/fixtures/typescript/types/literal-string-4/output.json b/packages/babel-parser/test/fixtures/typescript/types/literal-string-4/output.json new file mode 100644 index 0000000000..5bc58ae9d4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/literal-string-4/output.json @@ -0,0 +1,73 @@ +{ + "type": "File", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "program": { + "type": "Program", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":26,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":26}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":25,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":25}}, + "id": { + "type": "Identifier", + "start":4,"end":25,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":25},"identifierName":"x"}, + "name": "x", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start":5,"end":25,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":25}}, + "typeAnnotation": { + "type": "TSLiteralType", + "start":7,"end":25,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":25}}, + "literal": { + "type": "TemplateLiteral", + "start":7,"end":25,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":25}}, + "expressions": [ + { + "type": "TSInferType", + "start":14,"end":23,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":23}}, + "typeParameter": { + "type": "TSTypeParameter", + "start":20,"end":23,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":23}}, + "name": "bar" + } + } + ], + "quasis": [ + { + "type": "TemplateElement", + "start":8,"end":12,"loc":{"start":{"line":1,"column":8},"end":{"line":1,"column":12}}, + "value": { + "raw": "foo-", + "cooked": "foo-" + }, + "tail": false + }, + { + "type": "TemplateElement", + "start":24,"end":24,"loc":{"start":{"line":1,"column":24},"end":{"line":1,"column":24}}, + "value": { + "raw": "", + "cooked": "" + }, + "tail": true + } + ] + } + } + } + }, + "init": null + } + ], + "kind": "let" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-types/src/definitions/core.js b/packages/babel-types/src/definitions/core.js index 02bee8745b..51be45bfd7 100644 --- a/packages/babel-types/src/definitions/core.js +++ b/packages/babel-types/src/definitions/core.js @@ -1841,7 +1841,13 @@ defineType("TemplateLiteral", { expressions: { validate: chain( assertValueType("array"), - assertEach(assertNodeType("Expression")), + assertEach( + assertNodeType( + "Expression", + // For TypeScript template literal types + "TSType", + ), + ), function (node, key, val) { if (node.quasis.length !== val.length + 1) { throw new TypeError( diff --git a/scripts/parser-tests/typescript/allowlist.txt b/scripts/parser-tests/typescript/allowlist.txt index a773587592..6f73e1c8fc 100644 --- a/scripts/parser-tests/typescript/allowlist.txt +++ b/scripts/parser-tests/typescript/allowlist.txt @@ -226,6 +226,7 @@ gettersAndSettersErrors.ts giant.ts globalThisDeclarationEmit.ts globalThisDeclarationEmit2.ts +hugeDeclarationOutputGetsTruncatedWithError.ts implementClausePrecedingExtends.ts implementsClauseAlreadySeen.ts importAndVariableDeclarationConflict1.ts