Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript 4.0: Support labeled tuple elements #11754

Merged
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";
Comment on lines +654 to +657
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
seenOptionalElement =
seenOptionalElement ||
(type === "TSNamedTupleMember" && elementNode.optional) ||
type === "TSOptionalType";
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
labeledElements = labeledElements ?? isLabeled;
labeledElements ?? (labeledElements = isLabeled);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it's one operation more, I find the = ... ?? more readable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😂 But that's not the intended semantics of ||=. They are just nitpicks, feel free to ignore them if current code looks good to you.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, without with or MemberExpression, it has exactly the same behavior 😛

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];