diff --git a/packages/babel-generator/src/generators/typescript.js b/packages/babel-generator/src/generators/typescript.js index 4ec6736077..1fabf7c421 100644 --- a/packages/babel-generator/src/generators/typescript.js +++ b/packages/babel-generator/src/generators/typescript.js @@ -241,6 +241,11 @@ export function TSTupleType(node) { this.token("]"); } +export function TSOptionalType(node) { + this.print(node.typeAnnotation, node); + this.token("?"); +} + export function TSUnionType(node) { this.tsPrintUnionOrIntersectionType(node, "|"); } diff --git a/packages/babel-generator/test/fixtures/typescript/tuple-empty/input.js b/packages/babel-generator/test/fixtures/typescript/tuple-empty/input.js new file mode 100644 index 0000000000..f7cd7b5a0e --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple-empty/input.js @@ -0,0 +1 @@ +let x: []; diff --git a/packages/babel-generator/test/fixtures/typescript/tuple-empty/output.js b/packages/babel-generator/test/fixtures/typescript/tuple-empty/output.js new file mode 100644 index 0000000000..a602e22037 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple-empty/output.js @@ -0,0 +1 @@ +let x: []; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/typescript/tuple-optional/input.js b/packages/babel-generator/test/fixtures/typescript/tuple-optional/input.js new file mode 100644 index 0000000000..3b8d21ba95 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple-optional/input.js @@ -0,0 +1 @@ +let x: [string, number?, (string | number)?] diff --git a/packages/babel-generator/test/fixtures/typescript/tuple-optional/output.js b/packages/babel-generator/test/fixtures/typescript/tuple-optional/output.js new file mode 100644 index 0000000000..c32cf4f129 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple-optional/output.js @@ -0,0 +1 @@ +let x: [string, number?, (string | number)?]; diff --git a/packages/babel-generator/test/fixtures/typescript/tuple/input.js b/packages/babel-generator/test/fixtures/typescript/tuple/input.js new file mode 100644 index 0000000000..53c8e72ec9 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple/input.js @@ -0,0 +1 @@ +let x: [number, number, number]; diff --git a/packages/babel-generator/test/fixtures/typescript/tuple/output.js b/packages/babel-generator/test/fixtures/typescript/tuple/output.js new file mode 100644 index 0000000000..07edd6a752 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple/output.js @@ -0,0 +1 @@ +let x: [number, number, number]; \ No newline at end of file diff --git a/packages/babel-parser/src/plugins/typescript.js b/packages/babel-parser/src/plugins/typescript.js index f298521b63..57d67bbff4 100644 --- a/packages/babel-parser/src/plugins/typescript.js +++ b/packages/babel-parser/src/plugins/typescript.js @@ -503,13 +503,23 @@ export default (superClass: Class): Class => const node: N.TsTupleType = this.startNode(); node.elementTypes = this.tsParseBracketedList( "TupleElementTypes", - this.tsParseType.bind(this), + this.tsParseTupleElementType.bind(this), /* bracket */ true, /* skipFirstToken */ false, ); return this.finishNode(node, "TSTupleType"); } + tsParseTupleElementType(): N.TsType { + const type = this.tsParseType(); + if (this.eat(tt.question)) { + const optionalTypeNode: N.TsOptionalType = this.startNodeAtNode(type); + optionalTypeNode.typeAnnotation = type; + return this.finishNode(optionalTypeNode, "TSOptionalType"); + } + return type; + } + tsParseParenthesizedType(): N.TsParenthesizedType { const node = this.startNode(); this.expect(tt.parenL); diff --git a/packages/babel-parser/src/types.js b/packages/babel-parser/src/types.js index 4f44c9fffe..51e2435f0a 100644 --- a/packages/babel-parser/src/types.js +++ b/packages/babel-parser/src/types.js @@ -1104,6 +1104,7 @@ export type TsType = | TsTypeLiteral | TsArrayType | TsTupleType + | TsOptionalType | TsUnionOrIntersectionType | TsConditionalType | TsInferType @@ -1184,6 +1185,11 @@ export type TsTupleType = TsTypeBase & { elementTypes: $ReadOnlyArray, }; +export type TsOptionalType = TsTypeBase & { + type: "TSOptionalType", + typeAnnotation: TsType, +}; + export type TsUnionOrIntersectionType = TsUnionType | TsIntersectionType; export type TsUnionOrIntersectionTypeBase = TsTypeBase & { diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-empty/input.js b/packages/babel-parser/test/fixtures/typescript/types/tuple-empty/input.js new file mode 100644 index 0000000000..f7cd7b5a0e --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-empty/input.js @@ -0,0 +1 @@ +let x: []; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-empty/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-empty/output.json new file mode 100644 index 0000000000..667ffcb301 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-empty/output.json @@ -0,0 +1,117 @@ +{ + "type": "File", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "id": { + "type": "Identifier", + "start": 4, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 9 + }, + "identifierName": "x" + }, + "name": "x", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 5, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "typeAnnotation": { + "type": "TSTupleType", + "start": 7, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "elementTypes": [] + } + } + }, + "init": null + } + ], + "kind": "let" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-optional/input.js b/packages/babel-parser/test/fixtures/typescript/types/tuple-optional/input.js new file mode 100644 index 0000000000..3b8d21ba95 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-optional/input.js @@ -0,0 +1 @@ +let x: [string, number?, (string | number)?] diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-optional/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-optional/output.json new file mode 100644 index 0000000000..90aaa9bc8a --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-optional/output.json @@ -0,0 +1,240 @@ +{ + "type": "File", + "start": 0, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 4, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "id": { + "type": "Identifier", + "start": 4, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 44 + }, + "identifierName": "x" + }, + "name": "x", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 5, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "typeAnnotation": { + "type": "TSTupleType", + "start": 7, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "elementTypes": [ + { + "type": "TSStringKeyword", + "start": 8, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 14 + } + } + }, + { + "type": "TSOptionalType", + "start": 16, + "end": 23, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 23 + } + }, + "typeAnnotation": { + "type": "TSNumberKeyword", + "start": 16, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 22 + } + } + } + }, + { + "type": "TSOptionalType", + "start": 25, + "end": 43, + "loc": { + "start": { + "line": 1, + "column": 25 + }, + "end": { + "line": 1, + "column": 43 + } + }, + "typeAnnotation": { + "type": "TSParenthesizedType", + "start": 25, + "end": 42, + "loc": { + "start": { + "line": 1, + "column": 25 + }, + "end": { + "line": 1, + "column": 42 + } + }, + "typeAnnotation": { + "type": "TSUnionType", + "start": 26, + "end": 41, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 41 + } + }, + "types": [ + { + "type": "TSStringKeyword", + "start": 26, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 32 + } + } + }, + { + "type": "TSNumberKeyword", + "start": 35, + "end": 41, + "loc": { + "start": { + "line": 1, + "column": 35 + }, + "end": { + "line": 1, + "column": 41 + } + } + } + ] + } + } + } + ] + } + } + }, + "init": null + } + ], + "kind": "let" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple/input.js b/packages/babel-parser/test/fixtures/typescript/types/tuple/input.js new file mode 100644 index 0000000000..53c8e72ec9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple/input.js @@ -0,0 +1 @@ +let x: [number, number, number]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple/output.json new file mode 100644 index 0000000000..5b6c0e2cff --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple/output.json @@ -0,0 +1,163 @@ +{ + "type": "File", + "start": 0, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "sourceType": "module", + "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": 31, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 31 + }, + "identifierName": "x" + }, + "name": "x", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 5, + "end": 31, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 31 + } + }, + "typeAnnotation": { + "type": "TSTupleType", + "start": 7, + "end": 31, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 31 + } + }, + "elementTypes": [ + { + "type": "TSNumberKeyword", + "start": 8, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 14 + } + } + }, + { + "type": "TSNumberKeyword", + "start": 16, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 22 + } + } + }, + { + "type": "TSNumberKeyword", + "start": 24, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 24 + }, + "end": { + "line": 1, + "column": 30 + } + } + } + ] + } + } + }, + "init": null + } + ], + "kind": "let" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/function/tuple-parameter/input.js b/packages/babel-plugin-transform-typescript/test/fixtures/function/tuple-parameter/input.js new file mode 100644 index 0000000000..9d8fadb133 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/function/tuple-parameter/input.js @@ -0,0 +1 @@ +function foo(...args: [number, string?]) {} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/function/tuple-parameter/output.js b/packages/babel-plugin-transform-typescript/test/fixtures/function/tuple-parameter/output.js new file mode 100644 index 0000000000..3b940d1057 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/function/tuple-parameter/output.js @@ -0,0 +1 @@ +function foo(...args) {} diff --git a/packages/babel-types/src/asserts/generated/index.js b/packages/babel-types/src/asserts/generated/index.js index e1b2ad3313..725c0e7dba 100644 --- a/packages/babel-types/src/asserts/generated/index.js +++ b/packages/babel-types/src/asserts/generated/index.js @@ -825,6 +825,9 @@ export function assertTSArrayType(node: Object, opts?: Object = {}): void { export function assertTSTupleType(node: Object, opts?: Object = {}): void { assert("TSTupleType", node, opts); } +export function assertTSOptionalType(node: Object, opts?: Object = {}): void { + assert("TSOptionalType", node, opts); +} export function assertTSUnionType(node: Object, opts?: Object = {}): void { assert("TSUnionType", node, opts); } diff --git a/packages/babel-types/src/builders/generated/index.js b/packages/babel-types/src/builders/generated/index.js index 6d976f42cb..8692bf7ba0 100644 --- a/packages/babel-types/src/builders/generated/index.js +++ b/packages/babel-types/src/builders/generated/index.js @@ -793,6 +793,11 @@ export function TSTupleType(...args: Array): Object { } export { TSTupleType as tsTupleType }; export { TSTupleType as tSTupleType }; +export function TSOptionalType(...args: Array): Object { + return builder("TSOptionalType", ...args); +} +export { TSOptionalType as tsOptionalType }; +export { TSOptionalType as tSOptionalType }; export function TSUnionType(...args: Array): Object { return builder("TSUnionType", ...args); } diff --git a/packages/babel-types/src/definitions/typescript.js b/packages/babel-types/src/definitions/typescript.js index 46bb444269..ab1d7eb202 100644 --- a/packages/babel-types/src/definitions/typescript.js +++ b/packages/babel-types/src/definitions/typescript.js @@ -213,6 +213,14 @@ defineType("TSTupleType", { }, }); +defineType("TSOptionalType", { + aliases: ["TSType"], + visitor: ["typeAnnotation"], + fields: { + typeAnnotation: validateType("TSType"), + }, +}); + const unionOrIntersection = { aliases: ["TSType"], visitor: ["types"], diff --git a/packages/babel-types/src/validators/generated/index.js b/packages/babel-types/src/validators/generated/index.js index 74e50870be..c677379fef 100644 --- a/packages/babel-types/src/validators/generated/index.js +++ b/packages/babel-types/src/validators/generated/index.js @@ -2672,6 +2672,20 @@ export function isTSTupleType(node: Object, opts?: Object): boolean { return false; } +export function isTSOptionalType(node: Object, opts?: Object): boolean { + if (!node) return false; + + const nodeType = node.type; + if (nodeType === "TSOptionalType") { + if (typeof opts === "undefined") { + return true; + } else { + return shallowEqual(node, opts); + } + } + + return false; +} export function isTSUnionType(node: Object, opts?: Object): boolean { if (!node) return false; @@ -4149,6 +4163,7 @@ export function isTSType(node: Object, opts?: Object): boolean { "TSTypeLiteral" === nodeType || "TSArrayType" === nodeType || "TSTupleType" === nodeType || + "TSOptionalType" === nodeType || "TSUnionType" === nodeType || "TSIntersectionType" === nodeType || "TSConditionalType" === nodeType ||