Skip to content

Commit

Permalink
TypeScript 4.0: Support labeled tuple elements (#11754)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nicolo-ribaudo authored and JLHwung committed Jul 29, 2020
1 parent 9e6663f commit eba4c3b
Show file tree
Hide file tree
Showing 31 changed files with 736 additions and 17 deletions.
8 changes: 8 additions & 0 deletions packages/babel-generator/src/generators/typescript.js
Expand Up @@ -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, "|");
}
Expand Down
@@ -0,0 +1 @@
type T = [x: A, y?: B, ...z: C];
@@ -0,0 +1 @@
type T = [x: A, y?: B, ...z: C];
86 changes: 72 additions & 14 deletions packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -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:
Expand Down Expand Up @@ -633,33 +637,87 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// 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;
}

Expand Down
11 changes: 9 additions & 2 deletions packages/babel-parser/src/types.js
Expand Up @@ -1264,7 +1264,14 @@ export type TsArrayType = TsTypeBase & {

export type TsTupleType = TsTypeBase & {
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 & {
Expand All @@ -1274,7 +1281,7 @@ export type TsOptionalType = TsTypeBase & {

export type TsRestType = TsTypeBase & {
type: "TSRestType",
typeAnnotation: TsType,
typeAnnotation: TsType | TsNamedTupleMember,
};

export type TsUnionOrIntersectionType = TsUnionType | TsIntersectionType;
Expand Down
@@ -0,0 +1 @@
type T = [x.y: A];
@@ -0,0 +1,6 @@
{
"sourceType": "module",
"plugins": [
"typescript"
]
}
@@ -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": []
}
}
@@ -0,0 +1 @@
type T = [x<y>: A];
@@ -0,0 +1,6 @@
{
"sourceType": "module",
"plugins": [
"typescript"
]
}
@@ -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": []
}
}
@@ -0,0 +1 @@
type T = [A, y: B];
@@ -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": []
}
}
@@ -0,0 +1 @@
type T = [x: A, B];

0 comments on commit eba4c3b

Please sign in to comment.