diff --git a/packages/babel-generator/src/generators/typescript.js b/packages/babel-generator/src/generators/typescript.js index d628617171af..1dca58c2adfd 100644 --- a/packages/babel-generator/src/generators/typescript.js +++ b/packages/babel-generator/src/generators/typescript.js @@ -256,6 +256,28 @@ export function tsPrintUnionOrIntersectionType(node, sep) { }); } +export function TSConditionalType(node) { + this.print(node.checkType); + this.space(); + this.word("extends"); + this.space(); + this.print(node.extendsType); + this.space(); + this.token("?"); + this.space(); + this.print(node.trueType); + this.space(); + this.token(":"); + this.space(); + this.print(node.falseType); +} + +export function TSInferType(node) { + this.token("infer"); + this.space(); + this.print(node.typeParameter); +} + export function TSParenthesizedType(node) { this.token("("); this.print(node.typeAnnotation, node); diff --git a/packages/babel-generator/test/fixtures/typescript/types-conditional-infer/input.js b/packages/babel-generator/test/fixtures/typescript/types-conditional-infer/input.js new file mode 100644 index 000000000000..fc9bc48546e6 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/types-conditional-infer/input.js @@ -0,0 +1 @@ +type Element = T extends (infer U)[] ? U : T; diff --git a/packages/babel-generator/test/fixtures/typescript/types-conditional-infer/output.js b/packages/babel-generator/test/fixtures/typescript/types-conditional-infer/output.js new file mode 100644 index 000000000000..aab1028e5036 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/types-conditional-infer/output.js @@ -0,0 +1 @@ +type Element = T extends (infer U)[] ? U : T; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/typescript/types-conditional/input.js b/packages/babel-generator/test/fixtures/typescript/types-conditional/input.js new file mode 100644 index 000000000000..c77648689818 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/types-conditional/input.js @@ -0,0 +1 @@ +let x: number extends string ? boolean : null; diff --git a/packages/babel-generator/test/fixtures/typescript/types-conditional/output.js b/packages/babel-generator/test/fixtures/typescript/types-conditional/output.js new file mode 100644 index 000000000000..79c94be2c94e --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/types-conditional/output.js @@ -0,0 +1 @@ +let x: number extends string ? boolean : null; \ No newline at end of file diff --git a/packages/babel-types/README.md b/packages/babel-types/README.md index b465146f791c..927ca0d4d22a 100644 --- a/packages/babel-types/README.md +++ b/packages/babel-types/README.md @@ -1875,6 +1875,22 @@ Aliases: `TSTypeElement` --- +### tSConditionalType +```javascript +t.tsConditionalType(checkType, extendsType, trueType, falseType) +``` + +See also `t.isTSConditionalType(node, opts)` and `t.assertTSConditionalType(node, opts)`. + +Aliases: `TSType` + + - `checkType`: `TSType` (required) + - `extendsType`: `TSType` (required) + - `trueType`: `TSType` (required) + - `falseType`: `TSType` (required) + +--- + ### tSConstructSignatureDeclaration ```javascript t.tsConstructSignatureDeclaration(typeParameters, parameters, typeAnnotation) @@ -2074,6 +2090,19 @@ Aliases: `TSType` --- +### tSInferType +```javascript +t.tsInferType(typeParameter) +``` + +See also `t.isTSInferType(node, opts)` and `t.assertTSInferType(node, opts)`. + +Aliases: `TSType` + + - `typeParameter`: `TSType` (required) + +--- + ### tSInterfaceBody ```javascript t.tsInterfaceBody(body) diff --git a/packages/babel-types/src/asserts/generated/index.js b/packages/babel-types/src/asserts/generated/index.js index 093d1e7701b0..efae53080e0e 100644 --- a/packages/babel-types/src/asserts/generated/index.js +++ b/packages/babel-types/src/asserts/generated/index.js @@ -801,6 +801,15 @@ export function assertTSIntersectionType( ): void { assert("TSIntersectionType", node, opts); } +export function assertTSConditionalType( + node: Object, + opts?: Object = {}, +): void { + assert("TSConditionalType", node, opts); +} +export function assertTSInferType(node: Object, opts?: Object = {}): void { + assert("TSInferType", node, opts); +} export function assertTSParenthesizedType( node: Object, opts?: Object = {}, diff --git a/packages/babel-types/src/builders/generated/index.js b/packages/babel-types/src/builders/generated/index.js index ee003c27e812..1ec3b1776682 100644 --- a/packages/babel-types/src/builders/generated/index.js +++ b/packages/babel-types/src/builders/generated/index.js @@ -774,6 +774,16 @@ export function TSIntersectionType(...args: Array): Object { } export { TSIntersectionType as tsIntersectionType }; export { TSIntersectionType as tSIntersectionType }; +export function TSConditionalType(...args: Array): Object { + return builder("TSConditionalType", ...args); +} +export { TSConditionalType as tsConditionalType }; +export { TSConditionalType as tSConditionalType }; +export function TSInferType(...args: Array): Object { + return builder("TSInferType", ...args); +} +export { TSInferType as tsInferType }; +export { TSInferType as tSInferType }; export function TSParenthesizedType(...args: Array): Object { return builder("TSParenthesizedType", ...args); } diff --git a/packages/babel-types/src/definitions/typescript.js b/packages/babel-types/src/definitions/typescript.js index 8f4ff25bdafd..3aaba9ec6b07 100644 --- a/packages/babel-types/src/definitions/typescript.js +++ b/packages/babel-types/src/definitions/typescript.js @@ -223,6 +223,25 @@ const unionOrIntersection = { defineType("TSUnionType", unionOrIntersection); defineType("TSIntersectionType", unionOrIntersection); +defineType("TSConditionalType", { + aliases: ["TSType"], + visitor: ["checkType", "extendsType", "trueType", "falseType"], + fields: { + checkType: validateType("TSType"), + extendsType: validateType("TSType"), + trueType: validateType("TSType"), + falseType: validateType("TSType"), + }, +}); + +defineType("TSInferType", { + aliases: ["TSType"], + visitor: ["typeParameter"], + fields: { + typeParameter: validateType("TSType"), + }, +}); + defineType("TSParenthesizedType", { aliases: ["TSType"], visitor: ["typeAnnotation"], diff --git a/packages/babel-types/src/validators/generated/index.js b/packages/babel-types/src/validators/generated/index.js index 4a2215a50b11..874d8f65086e 100644 --- a/packages/babel-types/src/validators/generated/index.js +++ b/packages/babel-types/src/validators/generated/index.js @@ -608,6 +608,12 @@ export function isTSUnionType(node: Object, opts?: Object): boolean { export function isTSIntersectionType(node: Object, opts?: Object): boolean { return is("TSIntersectionType", node, opts); } +export function isTSConditionalType(node: Object, opts?: Object): boolean { + return is("TSConditionalType", node, opts); +} +export function isTSInferType(node: Object, opts?: Object): boolean { + return is("TSInferType", node, opts); +} export function isTSParenthesizedType(node: Object, opts?: Object): boolean { return is("TSParenthesizedType", node, opts); } diff --git a/packages/babylon/src/plugins/typescript.js b/packages/babylon/src/plugins/typescript.js index 693edfe59f2a..69086d55424e 100644 --- a/packages/babylon/src/plugins/typescript.js +++ b/packages/babylon/src/plugins/typescript.js @@ -617,11 +617,22 @@ export default (superClass: Class): Class => return this.finishNode(node, "TSTypeOperator"); } + tsParseInferType(): N.TsInferType { + const node = this.startNode(); + this.expectContextual("infer"); + const typeParameter = this.startNode(); + typeParameter.name = this.parseIdentifierName(typeParameter.start); + node.typeParameter = this.finishNode(typeParameter, "TypeParameter"); + return this.finishNode(node, "TSInferType"); + } + tsParseTypeOperatorOrHigher(): N.TsType { const operator = ["keyof", "unique"].find(kw => this.isContextual(kw)); return operator ? this.tsParseTypeOperator(operator) - : this.tsParseArrayTypeOrHigher(); + : this.isContextual("infer") + ? this.tsParseInferType() + : this.tsParseArrayTypeOrHigher(); } tsParseUnionOrIntersectionType( @@ -774,6 +785,21 @@ export default (superClass: Class): Class => tsParseType(): N.TsType { // Need to set `state.inType` so that we don't parse JSX in a type context. assert(this.state.inType); + const type = this.tsParseNonConditionalType(); + if (this.hasPrecedingLineBreak() || !this.eat(tt._extends)) { + return type; + } + const node: N.TsConditionalType = this.startNodeAtNode(type); + node.checkType = type; + node.extendsType = this.tsParseNonConditionalType(); + this.expect(tt.question); + node.trueType = this.tsParseType(); + this.expect(tt.colon); + node.falseType = this.tsParseType(); + return this.finishNode(node, "TSConditionalType"); + } + + tsParseNonConditionalType(): N.TsType { if (this.tsIsStartOfFunctionType()) { return this.tsParseFunctionOrConstructorType("TSFunctionType"); } diff --git a/packages/babylon/src/types.js b/packages/babylon/src/types.js index 201c4f35c718..835f36b6d25c 100644 --- a/packages/babylon/src/types.js +++ b/packages/babylon/src/types.js @@ -1154,6 +1154,19 @@ export type TsIntersectionType = TsUnionOrIntersectionTypeBase & { type: "TSIntersectionType", }; +export type TsConditionalType = TsTypeBase & { + type: "TSConditionalType", + checkType: TsType, + extendsType: TsType, + trueType: TsType, + falseType: tsType, +}; + +export type InferType = TsTypeBase & { + type: "TSInferType", + typeParameter: TypeParameter, +}; + export type TsParenthesizedType = TsTypeBase & { type: "TSParenthesizedType", typeAnnotation: TsType, diff --git a/packages/babylon/test/fixtures/typescript/types/conditional-infer/input.js b/packages/babylon/test/fixtures/typescript/types/conditional-infer/input.js new file mode 100644 index 000000000000..fc9bc48546e6 --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/types/conditional-infer/input.js @@ -0,0 +1 @@ +type Element = T extends (infer U)[] ? U : T; diff --git a/packages/babylon/test/fixtures/typescript/types/conditional-infer/output.json b/packages/babylon/test/fixtures/typescript/types/conditional-infer/output.json new file mode 100644 index 000000000000..dd94232cb832 --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/types/conditional-infer/output.json @@ -0,0 +1,271 @@ +{ + "type": "File", + "start": 0, + "end": 48, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 48 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 48, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 48 + } + }, + "sourceType": "module", + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start": 0, + "end": 48, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 48 + } + }, + "id": { + "type": "Identifier", + "start": 5, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 12 + }, + "identifierName": "Element" + }, + "name": "Element" + }, + "typeParameters": { + "type": "TSTypeParameterDeclaration", + "start": 12, + "end": 15, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 15 + } + }, + "params": [ + { + "type": "TSTypeParameter", + "start": 13, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 13 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "T" + } + ] + }, + "typeAnnotation": { + "type": "TSConditionalType", + "start": 18, + "end": 47, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 47 + } + }, + "checkType": { + "type": "TSTypeReference", + "start": 18, + "end": 19, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 19 + } + }, + "typeName": { + "type": "Identifier", + "start": 18, + "end": 19, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 19 + }, + "identifierName": "T" + }, + "name": "T" + } + }, + "extendsType": { + "type": "TSArrayType", + "start": 28, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 28 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "elementType": { + "type": "TSParenthesizedType", + "start": 28, + "end": 37, + "loc": { + "start": { + "line": 1, + "column": 28 + }, + "end": { + "line": 1, + "column": 37 + } + }, + "typeAnnotation": { + "type": "TSInferType", + "start": 29, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 29 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "typeParameter": { + "type": "TypeParameter", + "start": 35, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 35 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "name": "U" + } + } + } + }, + "trueType": { + "type": "TSTypeReference", + "start": 42, + "end": 43, + "loc": { + "start": { + "line": 1, + "column": 42 + }, + "end": { + "line": 1, + "column": 43 + } + }, + "typeName": { + "type": "Identifier", + "start": 42, + "end": 43, + "loc": { + "start": { + "line": 1, + "column": 42 + }, + "end": { + "line": 1, + "column": 43 + }, + "identifierName": "U" + }, + "name": "U" + } + }, + "falseType": { + "type": "TSTypeReference", + "start": 46, + "end": 47, + "loc": { + "start": { + "line": 1, + "column": 46 + }, + "end": { + "line": 1, + "column": 47 + } + }, + "typeName": { + "type": "Identifier", + "start": 46, + "end": 47, + "loc": { + "start": { + "line": 1, + "column": 46 + }, + "end": { + "line": 1, + "column": 47 + }, + "identifierName": "T" + }, + "name": "T" + } + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/typescript/types/conditional/input.js b/packages/babylon/test/fixtures/typescript/types/conditional/input.js new file mode 100644 index 000000000000..c77648689818 --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/types/conditional/input.js @@ -0,0 +1 @@ +let x: number extends string ? boolean : null; diff --git a/packages/babylon/test/fixtures/typescript/types/conditional/output.json b/packages/babylon/test/fixtures/typescript/types/conditional/output.json new file mode 100644 index 000000000000..d9e73ef0a364 --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/types/conditional/output.json @@ -0,0 +1,175 @@ +{ + "type": "File", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 46 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 46 + } + }, + "sourceType": "module", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 46 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 45 + } + }, + "id": { + "type": "Identifier", + "start": 4, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 45 + }, + "identifierName": "x" + }, + "name": "x", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 5, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 45 + } + }, + "typeAnnotation": { + "type": "TSConditionalType", + "start": 7, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 45 + } + }, + "checkType": { + "type": "TSNumberKeyword", + "start": 7, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 13 + } + } + }, + "extendsType": { + "type": "TSStringKeyword", + "start": 22, + "end": 28, + "loc": { + "start": { + "line": 1, + "column": 22 + }, + "end": { + "line": 1, + "column": 28 + } + } + }, + "trueType": { + "type": "TSBooleanKeyword", + "start": 31, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 31 + }, + "end": { + "line": 1, + "column": 38 + } + } + }, + "falseType": { + "type": "TSNullKeyword", + "start": 41, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 41 + }, + "end": { + "line": 1, + "column": 45 + } + } + } + } + } + }, + "init": null + } + ], + "kind": "let" + } + ], + "directives": [] + } +} \ No newline at end of file