From 309536aad0fd036893e437cff0f3982341d27e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 15 Jul 2020 00:23:09 +0200 Subject: [PATCH] TypeScript 4.0: Support labeled tuple elements (#11754) * TypeScript 4.0: Support labeled tuple elements * More tests * Disallow mixing labeled and unlabeled elements * Update AST shape * Enable test after rebase * Allow labeled spread types * Fix flow * Add types and generator suport * Update packages/babel-parser/src/plugins/typescript/index.js * Prettier --- .../src/generators/typescript.js | 8 ++ .../typescript/tuple-labeled/input.js | 1 + .../typescript/tuple-labeled/output.js | 1 + .../src/plugins/typescript/index.js | 86 ++++++++++++++++--- packages/babel-parser/src/types.js | 11 ++- .../types/tuple-invalid-label-1/input.ts | 1 + .../types/tuple-invalid-label-1/options.json | 6 ++ .../types/tuple-invalid-label-1/output.json | 63 ++++++++++++++ .../types/tuple-invalid-label-2/input.ts | 1 + .../types/tuple-invalid-label-2/options.json | 6 ++ .../types/tuple-invalid-label-2/output.json | 69 +++++++++++++++ .../tuple-labeled-after-unlabeled/input.ts | 1 + .../tuple-labeled-after-unlabeled/output.json | 59 +++++++++++++ .../tuple-labeled-before-unlabeled/input.ts | 1 + .../output.json | 59 +++++++++++++ .../tuple-labeled-invalid-optional/input.ts | 1 + .../options.json | 7 ++ .../types/tuple-labeled-spread/input.ts | 1 + .../types/tuple-labeled-spread/output.json | 76 ++++++++++++++++ .../typescript/types/tuple-labeled/input.ts | 1 + .../types/tuple-labeled/output.json | 56 ++++++++++++ .../input.ts | 1 + .../output.json | 69 +++++++++++++++ .../input.ts | 1 + .../output.json | 63 ++++++++++++++ .../input.ts | 1 + .../output.json | 63 ++++++++++++++ .../src/asserts/generated/index.js | 6 ++ .../src/builders/generated/index.js | 5 ++ .../babel-types/src/definitions/typescript.js | 15 +++- .../src/validators/generated/index.js | 14 +++ 31 files changed, 736 insertions(+), 17 deletions(-) create mode 100644 packages/babel-generator/test/fixtures/typescript/tuple-labeled/input.js create mode 100644 packages/babel-generator/test/fixtures/typescript/tuple-labeled/output.js create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/options.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/options.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/output.json diff --git a/packages/babel-generator/src/generators/typescript.js b/packages/babel-generator/src/generators/typescript.js index 39c580d74d2f..ea3e2218437d 100644 --- a/packages/babel-generator/src/generators/typescript.js +++ b/packages/babel-generator/src/generators/typescript.js @@ -262,6 +262,14 @@ export function TSRestType(node) { this.print(node.typeAnnotation, node); } +export function TSNamedTupleMember(node) { + this.print(node.label, node); + if (node.optional) this.token("?"); + this.token(":"); + this.space(); + this.print(node.elementType, node); +} + export function TSUnionType(node) { this.tsPrintUnionOrIntersectionType(node, "|"); } diff --git a/packages/babel-generator/test/fixtures/typescript/tuple-labeled/input.js b/packages/babel-generator/test/fixtures/typescript/tuple-labeled/input.js new file mode 100644 index 000000000000..a8d132c09bd3 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple-labeled/input.js @@ -0,0 +1 @@ +type T = [x: A, y?: B, ...z: C]; diff --git a/packages/babel-generator/test/fixtures/typescript/tuple-labeled/output.js b/packages/babel-generator/test/fixtures/typescript/tuple-labeled/output.js new file mode 100644 index 000000000000..b9111dbd9aa6 --- /dev/null +++ b/packages/babel-generator/test/fixtures/typescript/tuple-labeled/output.js @@ -0,0 +1 @@ +type T = [x: A, y?: B, ...z: C]; \ No newline at end of file diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 7ba2b6abae08..bb3a5a10ccb7 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -74,6 +74,10 @@ const TSErrors = Object.freeze({ IndexSignatureHasAccessibility: "Index signatures cannot have an accessibility modifier ('%0')", IndexSignatureHasStatic: "Index signatures cannot have the 'static' modifier", + InvalidTupleMemberLabel: + "Tuple members must be labeled with a simple identifier.", + MixedLabeledAndUnlabeledElements: + "Tuple members must all have names or all not have names.", OptionalTypeBeforeRequired: "A required element cannot follow an optional element.", PatternIsOptional: @@ -633,33 +637,87 @@ export default (superClass: Class): Class => // Validate the elementTypes to ensure that no mandatory elements // follow optional elements let seenOptionalElement = false; + let labeledElements = null; node.elementTypes.forEach(elementNode => { - if (elementNode.type === "TSOptionalType") { - seenOptionalElement = true; - } else if (seenOptionalElement && elementNode.type !== "TSRestType") { + let { type } = elementNode; + + if ( + seenOptionalElement && + type !== "TSRestType" && + type !== "TSOptionalType" && + !(type === "TSNamedTupleMember" && elementNode.optional) + ) { this.raise(elementNode.start, TSErrors.OptionalTypeBeforeRequired); } + + // Flow doesn't support ||= + seenOptionalElement = + seenOptionalElement || + (type === "TSNamedTupleMember" && elementNode.optional) || + type === "TSOptionalType"; + + // When checking labels, check the argument of the spread operator + if (type === "TSRestType") { + elementNode = elementNode.typeAnnotation; + type = elementNode.type; + } + + const isLabeled = type === "TSNamedTupleMember"; + // Flow doesn't support ??= + labeledElements = labeledElements ?? isLabeled; + if (labeledElements !== isLabeled) { + this.raise( + elementNode.start, + TSErrors.MixedLabeledAndUnlabeledElements, + ); + } }); return this.finishNode(node, "TSTupleType"); } - tsParseTupleElementType(): N.TsType { + tsParseTupleElementType(): N.TsType | N.TsNamedTupleMember { // parses `...TsType[]` - if (this.match(tt.ellipsis)) { - const restNode: N.TsRestType = this.startNode(); - this.next(); // skips ellipsis - restNode.typeAnnotation = this.tsParseType(); - return this.finishNode(restNode, "TSRestType"); - } - const type = this.tsParseType(); - // parses `TsType?` - if (this.eat(tt.question)) { + const { start: startPos, startLoc } = this.state; + + const rest = this.eat(tt.ellipsis); + let type = this.tsParseType(); + const optional = this.eat(tt.question); + const labeled = this.eat(tt.colon); + + if (labeled) { + const labeledNode: N.TsNamedTupleMember = this.startNodeAtNode(type); + labeledNode.optional = optional; + + if ( + type.type === "TSTypeReference" && + !type.typeParameters && + type.typeName.type === "Identifier" + ) { + labeledNode.label = (type.typeName: N.Identifier); + } else { + this.raise(type.start, TSErrors.InvalidTupleMemberLabel); + // This produces an invalid AST, but at least we don't drop + // nodes representing the invalid source. + // $FlowIgnore + labeledNode.label = type; + } + + labeledNode.elementType = this.tsParseType(); + type = this.finishNode(labeledNode, "TSNamedTupleMember"); + } else if (optional) { const optionalTypeNode: N.TsOptionalType = this.startNodeAtNode(type); optionalTypeNode.typeAnnotation = type; - return this.finishNode(optionalTypeNode, "TSOptionalType"); + type = this.finishNode(optionalTypeNode, "TSOptionalType"); } + + if (rest) { + const restNode: N.TsRestType = this.startNodeAt(startPos, startLoc); + restNode.typeAnnotation = type; + type = this.finishNode(restNode, "TSRestType"); + } + return type; } diff --git a/packages/babel-parser/src/types.js b/packages/babel-parser/src/types.js index 17f96dc4934c..3566baaafbe2 100644 --- a/packages/babel-parser/src/types.js +++ b/packages/babel-parser/src/types.js @@ -1264,7 +1264,14 @@ export type TsArrayType = TsTypeBase & { export type TsTupleType = TsTypeBase & { type: "TSTupleType", - elementTypes: $ReadOnlyArray, + elementTypes: $ReadOnlyArray, +}; + +export type TsNamedTupleMember = NodeBase & { + type: "TSNamedTupleMember", + label: Identifier, + optional: boolean, + elementType: TsType, }; export type TsOptionalType = TsTypeBase & { @@ -1274,7 +1281,7 @@ export type TsOptionalType = TsTypeBase & { export type TsRestType = TsTypeBase & { type: "TSRestType", - typeAnnotation: TsType, + typeAnnotation: TsType | TsNamedTupleMember, }; export type TsUnionOrIntersectionType = TsUnionType | TsIntersectionType; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/input.ts new file mode 100644 index 000000000000..8b4d80dddbfb --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/input.ts @@ -0,0 +1 @@ +type T = [x.y: A]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/options.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/options.json new file mode 100644 index 000000000000..d1b7c8ebeace --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/options.json @@ -0,0 +1,6 @@ +{ + "sourceType": "module", + "plugins": [ + "typescript" + ] +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/output.json new file mode 100644 index 000000000000..f78fc791b541 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-1/output.json @@ -0,0 +1,63 @@ +{ + "type": "File", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":18}}, + "errors": [ + "SyntaxError: Tuple members must be labeled with a simple identifier. (1:10)" + ], + "program": { + "type": "Program", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":18}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":18}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":17,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":17}}, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start":10,"end":16,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":16}}, + "optional": false, + "label": { + "type": "TSTypeReference", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13}}, + "typeName": { + "type": "TSQualifiedName", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13}}, + "left": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11},"identifierName":"x"}, + "name": "x" + }, + "right": { + "type": "Identifier", + "start":12,"end":13,"loc":{"start":{"line":1,"column":12},"end":{"line":1,"column":13},"identifierName":"y"}, + "name": "y" + } + } + }, + "elementType": { + "type": "TSTypeReference", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16}}, + "typeName": { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"A"}, + "name": "A" + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/input.ts new file mode 100644 index 000000000000..dc499f225690 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/input.ts @@ -0,0 +1 @@ +type T = [x: A]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/options.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/options.json new file mode 100644 index 000000000000..d1b7c8ebeace --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/options.json @@ -0,0 +1,6 @@ +{ + "sourceType": "module", + "plugins": [ + "typescript" + ] +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/output.json new file mode 100644 index 000000000000..37710a11ffc3 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-invalid-label-2/output.json @@ -0,0 +1,69 @@ +{ + "type": "File", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "errors": [ + "SyntaxError: Tuple members must be labeled with a simple identifier. (1:10)" + ], + "program": { + "type": "Program", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":18,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":18}}, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start":10,"end":17,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":17}}, + "optional": false, + "label": { + "type": "TSTypeReference", + "start":10,"end":14,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":14}}, + "typeName": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11},"identifierName":"x"}, + "name": "x" + }, + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start":11,"end":14,"loc":{"start":{"line":1,"column":11},"end":{"line":1,"column":14}}, + "params": [ + { + "type": "TSTypeReference", + "start":12,"end":13,"loc":{"start":{"line":1,"column":12},"end":{"line":1,"column":13}}, + "typeName": { + "type": "Identifier", + "start":12,"end":13,"loc":{"start":{"line":1,"column":12},"end":{"line":1,"column":13},"identifierName":"y"}, + "name": "y" + } + } + ] + } + }, + "elementType": { + "type": "TSTypeReference", + "start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17}}, + "typeName": { + "type": "Identifier", + "start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17},"identifierName":"A"}, + "name": "A" + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/input.ts new file mode 100644 index 000000000000..938829fbc562 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/input.ts @@ -0,0 +1 @@ +type T = [A, y: B]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/output.json new file mode 100644 index 000000000000..c0deaecdd5f3 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-after-unlabeled/output.json @@ -0,0 +1,59 @@ +{ + "type": "File", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "errors": [ + "SyntaxError: Tuple members must all have names or all not have names. (1:13)" + ], + "program": { + "type": "Program", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":18,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":18}}, + "elementTypes": [ + { + "type": "TSTypeReference", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11}}, + "typeName": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11},"identifierName":"A"}, + "name": "A" + } + }, + { + "type": "TSNamedTupleMember", + "start":13,"end":17,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":17}}, + "optional": false, + "label": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14},"identifierName":"y"}, + "name": "y" + }, + "elementType": { + "type": "TSTypeReference", + "start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17}}, + "typeName": { + "type": "Identifier", + "start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17},"identifierName":"B"}, + "name": "B" + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/input.ts new file mode 100644 index 000000000000..c0191826d929 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/input.ts @@ -0,0 +1 @@ +type T = [x: A, B]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/output.json new file mode 100644 index 000000000000..083db448c753 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-before-unlabeled/output.json @@ -0,0 +1,59 @@ +{ + "type": "File", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "errors": [ + "SyntaxError: Tuple members must all have names or all not have names. (1:16)" + ], + "program": { + "type": "Program", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":18,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":18}}, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start":10,"end":14,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":14}}, + "optional": false, + "label": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11},"identifierName":"x"}, + "name": "x" + }, + "elementType": { + "type": "TSTypeReference", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14}}, + "typeName": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14},"identifierName":"A"}, + "name": "A" + } + } + }, + { + "type": "TSTypeReference", + "start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17}}, + "typeName": { + "type": "Identifier", + "start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17},"identifierName":"B"}, + "name": "B" + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/input.ts new file mode 100644 index 000000000000..aa8784ffd62f --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/input.ts @@ -0,0 +1 @@ +type T = [x: A?]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json new file mode 100644 index 000000000000..32484cd670a1 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json @@ -0,0 +1,7 @@ +{ + "sourceType": "module", + "plugins": [ + "typescript" + ], + "throws": "Unexpected token, expected \",\" (1:14)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/input.ts new file mode 100644 index 000000000000..1f84d8117f3b --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/input.ts @@ -0,0 +1 @@ +let x: [A: string, ...B: number[]] diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/output.json new file mode 100644 index 000000000000..e0a6607cfd8e --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-spread/output.json @@ -0,0 +1,76 @@ +{ + "type": "File", + "start":0,"end":34,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":34}}, + "program": { + "type": "Program", + "start":0,"end":34,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":34}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":34,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":34}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":34,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":34}}, + "id": { + "type": "Identifier", + "start":4,"end":34,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":34},"identifierName":"x"}, + "name": "x", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start":5,"end":34,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":34}}, + "typeAnnotation": { + "type": "TSTupleType", + "start":7,"end":34,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":34}}, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start":8,"end":17,"loc":{"start":{"line":1,"column":8},"end":{"line":1,"column":17}}, + "optional": false, + "label": { + "type": "Identifier", + "start":8,"end":9,"loc":{"start":{"line":1,"column":8},"end":{"line":1,"column":9},"identifierName":"A"}, + "name": "A" + }, + "elementType": { + "type": "TSStringKeyword", + "start":11,"end":17,"loc":{"start":{"line":1,"column":11},"end":{"line":1,"column":17}} + } + }, + { + "type": "TSRestType", + "start":19,"end":33,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":33}}, + "typeAnnotation": { + "type": "TSNamedTupleMember", + "start":22,"end":33,"loc":{"start":{"line":1,"column":22},"end":{"line":1,"column":33}}, + "optional": false, + "label": { + "type": "Identifier", + "start":22,"end":23,"loc":{"start":{"line":1,"column":22},"end":{"line":1,"column":23},"identifierName":"B"}, + "name": "B" + }, + "elementType": { + "type": "TSArrayType", + "start":25,"end":33,"loc":{"start":{"line":1,"column":25},"end":{"line":1,"column":33}}, + "elementType": { + "type": "TSNumberKeyword", + "start":25,"end":31,"loc":{"start":{"line":1,"column":25},"end":{"line":1,"column":31}} + } + } + } + } + ] + } + } + }, + "init": null + } + ], + "kind": "let" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/input.ts new file mode 100644 index 000000000000..4ded9c4935fa --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/input.ts @@ -0,0 +1 @@ +type T = [foo: string, bar?: number]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/output.json new file mode 100644 index 000000000000..f8ddae722eb3 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled/output.json @@ -0,0 +1,56 @@ +{ + "type": "File", + "start":0,"end":37,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":37}}, + "program": { + "type": "Program", + "start":0,"end":37,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":37}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":37,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":37}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":36,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":36}}, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start":10,"end":21,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":21}}, + "optional": false, + "label": { + "type": "Identifier", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13},"identifierName":"foo"}, + "name": "foo" + }, + "elementType": { + "type": "TSStringKeyword", + "start":15,"end":21,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":21}} + } + }, + { + "type": "TSNamedTupleMember", + "start":23,"end":35,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":35}}, + "optional": true, + "label": { + "type": "Identifier", + "start":23,"end":26,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":26},"identifierName":"bar"}, + "name": "bar" + }, + "elementType": { + "type": "TSNumberKeyword", + "start":29,"end":35,"loc":{"start":{"line":1,"column":29},"end":{"line":1,"column":35}} + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/input.ts new file mode 100644 index 000000000000..65546562d1c2 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/input.ts @@ -0,0 +1 @@ +type T = [x?: A, y: B]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/output.json new file mode 100644 index 000000000000..6675981261a0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-required-after-labeled-optional/output.json @@ -0,0 +1,69 @@ +{ + "type": "File", + "start":0,"end":23,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":23}}, + "errors": [ + "SyntaxError: A required element cannot follow an optional element. (1:17)" + ], + "program": { + "type": "Program", + "start":0,"end":23,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":23}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":23,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":23}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":22,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":22}}, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start":10,"end":15,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":15}}, + "optional": true, + "label": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11},"identifierName":"x"}, + "name": "x" + }, + "elementType": { + "type": "TSTypeReference", + "start":14,"end":15,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":15}}, + "typeName": { + "type": "Identifier", + "start":14,"end":15,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":15},"identifierName":"A"}, + "name": "A" + } + } + }, + { + "type": "TSNamedTupleMember", + "start":17,"end":21,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":21}}, + "optional": false, + "label": { + "type": "Identifier", + "start":17,"end":18,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":18},"identifierName":"y"}, + "name": "y" + }, + "elementType": { + "type": "TSTypeReference", + "start":20,"end":21,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":21}}, + "typeName": { + "type": "Identifier", + "start":20,"end":21,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":21},"identifierName":"B"}, + "name": "B" + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/input.ts new file mode 100644 index 000000000000..82ac1034fda4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/input.ts @@ -0,0 +1 @@ +type T = [x: A, ...B]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/output.json new file mode 100644 index 000000000000..24e7d290ecd6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-after-labeled/output.json @@ -0,0 +1,63 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "errors": [ + "SyntaxError: Tuple members must all have names or all not have names. (1:19)" + ], + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":21,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":21}}, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start":10,"end":14,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":14}}, + "optional": false, + "label": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11},"identifierName":"x"}, + "name": "x" + }, + "elementType": { + "type": "TSTypeReference", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14}}, + "typeName": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14},"identifierName":"A"}, + "name": "A" + } + } + }, + { + "type": "TSRestType", + "start":16,"end":20,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":20}}, + "typeAnnotation": { + "type": "TSTypeReference", + "start":19,"end":20,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":20}}, + "typeName": { + "type": "Identifier", + "start":19,"end":20,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":20},"identifierName":"B"}, + "name": "B" + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/input.ts new file mode 100644 index 000000000000..a45bda9425f3 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/input.ts @@ -0,0 +1 @@ +type T = [...B, x: A]; diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/output.json new file mode 100644 index 000000000000..5115e0744bd4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-unlabeled-spread-before-labeled/output.json @@ -0,0 +1,63 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "errors": [ + "SyntaxError: Tuple members must all have names or all not have names. (1:16)" + ], + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"T"}, + "name": "T" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start":9,"end":21,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":21}}, + "elementTypes": [ + { + "type": "TSRestType", + "start":10,"end":14,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":14}}, + "typeAnnotation": { + "type": "TSTypeReference", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14}}, + "typeName": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14},"identifierName":"B"}, + "name": "B" + } + } + }, + { + "type": "TSNamedTupleMember", + "start":16,"end":20,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":20}}, + "optional": false, + "label": { + "type": "Identifier", + "start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17},"identifierName":"x"}, + "name": "x" + }, + "elementType": { + "type": "TSTypeReference", + "start":19,"end":20,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":20}}, + "typeName": { + "type": "Identifier", + "start":19,"end":20,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":20},"identifierName":"A"}, + "name": "A" + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-types/src/asserts/generated/index.js b/packages/babel-types/src/asserts/generated/index.js index 457103a16b71..2c24ad6a5496 100644 --- a/packages/babel-types/src/asserts/generated/index.js +++ b/packages/babel-types/src/asserts/generated/index.js @@ -920,6 +920,12 @@ export function assertTSOptionalType(node: Object, opts?: Object = {}): void { export function assertTSRestType(node: Object, opts?: Object = {}): void { assert("TSRestType", node, opts); } +export function assertTSNamedTupleMember( + node: Object, + opts?: Object = {}, +): void { + assert("TSNamedTupleMember", 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 676ff2c0d4dc..96203a1370d9 100644 --- a/packages/babel-types/src/builders/generated/index.js +++ b/packages/babel-types/src/builders/generated/index.js @@ -888,6 +888,11 @@ export function TSRestType(...args: Array): Object { } export { TSRestType as tsRestType }; export { TSRestType as tSRestType }; +export function TSNamedTupleMember(...args: Array): Object { + return builder("TSNamedTupleMember", ...args); +} +export { TSNamedTupleMember as tsNamedTupleMember }; +export { TSNamedTupleMember as tSNamedTupleMember }; 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 e4ec9872d918..b74c350c0f3f 100644 --- a/packages/babel-types/src/definitions/typescript.js +++ b/packages/babel-types/src/definitions/typescript.js @@ -212,7 +212,7 @@ defineType("TSTupleType", { aliases: ["TSType"], visitor: ["elementTypes"], fields: { - elementTypes: validateArrayOfType("TSType"), + elementTypes: validateArrayOfType(["TSType", "TSNamedTupleMember"]), }, }); @@ -232,6 +232,19 @@ defineType("TSRestType", { }, }); +defineType("TSNamedTupleMember", { + visitor: ["label", "elementType"], + builder: ["label", "elementType", "optional"], + fields: { + label: validateType("Identifier"), + optional: { + validate: bool, + default: false, + }, + elementType: 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 746034390aaf..1e353f0970c5 100644 --- a/packages/babel-types/src/validators/generated/index.js +++ b/packages/babel-types/src/validators/generated/index.js @@ -3027,6 +3027,20 @@ export function isTSRestType(node: ?Object, opts?: Object): boolean { return false; } +export function isTSNamedTupleMember(node: ?Object, opts?: Object): boolean { + if (!node) return false; + + const nodeType = node.type; + if (nodeType === "TSNamedTupleMember") { + 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;