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
This commit is contained in:
Nicolò Ribaudo 2020-07-15 00:23:09 +02:00 committed by Huáng Jùnliàng
parent 9e6663f125
commit eba4c3b6ed
31 changed files with 736 additions and 17 deletions

View File

@ -262,6 +262,14 @@ export function TSRestType(node) {
this.print(node.typeAnnotation, 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) { export function TSUnionType(node) {
this.tsPrintUnionOrIntersectionType(node, "|"); this.tsPrintUnionOrIntersectionType(node, "|");
} }

View File

@ -0,0 +1 @@
type T = [x: A, y?: B, ...z: C];

View File

@ -0,0 +1 @@
type T = [x: A, y?: B, ...z: C];

View File

@ -74,6 +74,10 @@ const TSErrors = Object.freeze({
IndexSignatureHasAccessibility: IndexSignatureHasAccessibility:
"Index signatures cannot have an accessibility modifier ('%0')", "Index signatures cannot have an accessibility modifier ('%0')",
IndexSignatureHasStatic: "Index signatures cannot have the 'static' modifier", 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: OptionalTypeBeforeRequired:
"A required element cannot follow an optional element.", "A required element cannot follow an optional element.",
PatternIsOptional: PatternIsOptional:
@ -633,33 +637,87 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// Validate the elementTypes to ensure that no mandatory elements // Validate the elementTypes to ensure that no mandatory elements
// follow optional elements // follow optional elements
let seenOptionalElement = false; let seenOptionalElement = false;
let labeledElements = null;
node.elementTypes.forEach(elementNode => { node.elementTypes.forEach(elementNode => {
if (elementNode.type === "TSOptionalType") { let { type } = elementNode;
seenOptionalElement = true;
} else if (seenOptionalElement && elementNode.type !== "TSRestType") { if (
seenOptionalElement &&
type !== "TSRestType" &&
type !== "TSOptionalType" &&
!(type === "TSNamedTupleMember" && elementNode.optional)
) {
this.raise(elementNode.start, TSErrors.OptionalTypeBeforeRequired); 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"); return this.finishNode(node, "TSTupleType");
} }
tsParseTupleElementType(): N.TsType { tsParseTupleElementType(): N.TsType | N.TsNamedTupleMember {
// parses `...TsType[]` // 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(); const { start: startPos, startLoc } = this.state;
// parses `TsType?`
if (this.eat(tt.question)) { 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); const optionalTypeNode: N.TsOptionalType = this.startNodeAtNode(type);
optionalTypeNode.typeAnnotation = 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; return type;
} }

View File

@ -1264,7 +1264,14 @@ export type TsArrayType = TsTypeBase & {
export type TsTupleType = TsTypeBase & { export type TsTupleType = TsTypeBase & {
type: "TSTupleType", type: "TSTupleType",
elementTypes: $ReadOnlyArray<TsType>, elementTypes: $ReadOnlyArray<TsType | TsNamedTupleMember>,
};
export type TsNamedTupleMember = NodeBase & {
type: "TSNamedTupleMember",
label: Identifier,
optional: boolean,
elementType: TsType,
}; };
export type TsOptionalType = TsTypeBase & { export type TsOptionalType = TsTypeBase & {
@ -1274,7 +1281,7 @@ export type TsOptionalType = TsTypeBase & {
export type TsRestType = TsTypeBase & { export type TsRestType = TsTypeBase & {
type: "TSRestType", type: "TSRestType",
typeAnnotation: TsType, typeAnnotation: TsType | TsNamedTupleMember,
}; };
export type TsUnionOrIntersectionType = TsUnionType | TsIntersectionType; export type TsUnionOrIntersectionType = TsUnionType | TsIntersectionType;

View File

@ -0,0 +1 @@
type T = [x.y: A];

View File

@ -0,0 +1,6 @@
{
"sourceType": "module",
"plugins": [
"typescript"
]
}

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [x<y>: A];

View File

@ -0,0 +1,6 @@
{
"sourceType": "module",
"plugins": [
"typescript"
]
}

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [A, y: B];

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [x: A, B];

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [x: A?];

View File

@ -0,0 +1,7 @@
{
"sourceType": "module",
"plugins": [
"typescript"
],
"throws": "Unexpected token, expected \",\" (1:14)"
}

View File

@ -0,0 +1 @@
let x: [A: string, ...B: number[]]

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [foo: string, bar?: number];

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [x?: A, y: B];

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [x: A, ...B];

View File

@ -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": []
}
}

View File

@ -0,0 +1 @@
type T = [...B, x: A];

View File

@ -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": []
}
}

View File

@ -923,6 +923,12 @@ export function assertTSOptionalType(node: Object, opts?: Object = {}): void {
export function assertTSRestType(node: Object, opts?: Object = {}): void { export function assertTSRestType(node: Object, opts?: Object = {}): void {
assert("TSRestType", node, opts); assert("TSRestType", node, opts);
} }
export function assertTSNamedTupleMember(
node: Object,
opts?: Object = {},
): void {
assert("TSNamedTupleMember", node, opts);
}
export function assertTSUnionType(node: Object, opts?: Object = {}): void { export function assertTSUnionType(node: Object, opts?: Object = {}): void {
assert("TSUnionType", node, opts); assert("TSUnionType", node, opts);
} }

View File

@ -894,6 +894,11 @@ export function tsRestType(...args: Array<any>): Object {
} }
export { tsRestType as TSRestType }; export { tsRestType as TSRestType };
export { tsRestType as tSRestType }; export { tsRestType as tSRestType };
export function tsNamedTupleMember(...args: Array<any>): Object {
return builder("TSNamedTupleMember", ...args);
}
export { tsNamedTupleMember as TSNamedTupleMember };
export { tsNamedTupleMember as tSNamedTupleMember };
export function tsUnionType(...args: Array<any>): Object { export function tsUnionType(...args: Array<any>): Object {
return builder("TSUnionType", ...args); return builder("TSUnionType", ...args);
} }

View File

@ -214,7 +214,7 @@ defineType("TSTupleType", {
aliases: ["TSType"], aliases: ["TSType"],
visitor: ["elementTypes"], visitor: ["elementTypes"],
fields: { fields: {
elementTypes: validateArrayOfType("TSType"), elementTypes: validateArrayOfType(["TSType", "TSNamedTupleMember"]),
}, },
}); });
@ -234,6 +234,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 = { const unionOrIntersection = {
aliases: ["TSType"], aliases: ["TSType"],
visitor: ["types"], visitor: ["types"],

View File

@ -3041,6 +3041,20 @@ export function isTSRestType(node: ?Object, opts?: Object): boolean {
return false; 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 { export function isTSUnionType(node: ?Object, opts?: Object): boolean {
if (!node) return false; if (!node) return false;