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

[ts] Allow keywords in tuple labels #15423

Merged
merged 6 commits into from Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 61 additions & 17 deletions packages/babel-parser/src/plugins/typescript/index.ts
Expand Up @@ -214,6 +214,8 @@ const TSErrors = ParseErrorEnum`typescript`({
`Single type parameter ${typeParameterName} should have a trailing comma. Example usage: <${typeParameterName},>.`,
StaticBlockCannotHaveModifier:
"Static class blocks cannot have any modifier.",
TupleOptionalAfterType:
"A labeled tuple optional element must be declared using a question mark after the name and before the colon (`name?: type`), rather than after the type (`name: type?`).",
TypeAnnotationAfterAssign:
"Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.",
TypeImportCannotSpecifyDefaultAndNamed:
Expand Down Expand Up @@ -460,8 +462,6 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
case "TypeParametersOrArguments":
return this.match(tt.gt);
}

throw new Error("Unreachable");
}

tsParseList<T extends N.Node>(
Expand Down Expand Up @@ -1099,34 +1099,78 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
return this.finishNode(node, "TSTupleType");
}

tsParseTupleElementType(): N.TsType | N.TsNamedTupleMember {
tsParseTupleElementType(): N.TsNamedTupleMember | N.TsType {
// parses `...TsType[]`

const { startLoc } = this.state;

const rest = this.eat(tt.ellipsis);
let type: N.TsType | N.TsNamedTupleMember = this.tsParseType();
const optional = this.eat(tt.question);
const labeled = this.eat(tt.colon);

if (labeled) {
const labeledNode = this.startNodeAtNode<N.TsNamedTupleMember>(type);
labeledNode.optional = optional;
let labeled: boolean;
let label: N.Identifier;
let optional: boolean;
let type: N.TsNamedTupleMember | N.TsType;

const isWord = tokenIsKeywordOrIdentifier(this.state.type);
const chAfterWord = isWord ? this.lookaheadCharCode() : null;
if (chAfterWord === charCodes.colon) {
labeled = true;
optional = false;
label = this.parseIdentifier(true);
this.expect(tt.colon);
type = this.tsParseType();
} else if (chAfterWord === charCodes.questionMark) {
optional = true;
const startLoc = this.state.startLoc;
const wordName = this.state.value;
const typeOrLabel = this.tsParseNonArrayType();

if (this.lookaheadCharCode() === charCodes.colon) {
labeled = true;
label = this.createIdentifier(
this.startNodeAt<N.Identifier>(startLoc),
wordName,
);
this.expect(tt.question);
this.expect(tt.colon);
type = this.tsParseType();
} else {
labeled = false;
type = typeOrLabel;
this.expect(tt.question);
}
} else {
type = this.tsParseType();
optional = this.eat(tt.question);
// In this case (labeled === true) could be only in invalid label.
// E.g. [x.y:type]
// An error is raised while processing node.
labeled = this.eat(tt.colon);
Copy link
Member

Choose a reason for hiding this comment

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

When can this be followed by a colon?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In case we have invalid label in tuple followed by colon, which fails tokenIsKeywordOrIdentifier check?

I'm sorry to bother you, this issue seemed easier than it really is for me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking about the case described in "tuple-invalid-label-1" test: type T = [x.y: A]; which should throw error TSErrors.InvalidTupleMemberLabel to be passed

Copy link
Member

Choose a reason for hiding this comment

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

Ok 👍 Maybe add a comment in the code with that example, explaining that it can only happen with invalid input.
Also, probably we can move the TSErrors.InvalidTupleMemberLabel inside this block so that it's throw in the same place as where we parse the invalid code.

}

if (
type.type === "TSTypeReference" &&
!type.typeParameters &&
type.typeName.type === "Identifier"
) {
labeledNode.label = type.typeName;
if (labeled) {
let labeledNode: Undone<N.TsNamedTupleMember>;
if (label) {
labeledNode = this.startNodeAtNode<N.TsNamedTupleMember>(label);
labeledNode.optional = optional;
labeledNode.label = label;
labeledNode.elementType = type;

if (this.eat(tt.question)) {
labeledNode.optional = true;
this.raise(TSErrors.TupleOptionalAfterType, {
at: this.state.lastTokStartLoc,
});
}
} else {
labeledNode = this.startNodeAtNode<N.TsNamedTupleMember>(type);
labeledNode.optional = optional;
this.raise(TSErrors.InvalidTupleMemberLabel, { at: type });
// @ts-expect-error This produces an invalid AST, but at least we don't drop
// nodes representing the invalid source.
labeledNode.label = type;
labeledNode.elementType = this.tsParseType();
}

labeledNode.elementType = this.tsParseType();
type = this.finishNode(labeledNode, "TSNamedTupleMember");
} else if (optional) {
const optionalTypeNode = this.startNodeAtNode<N.TsOptionalType>(type);
Expand Down
@@ -0,0 +1,5 @@
type FuncWithDescriptionBabel7 = [
function: (...args: any[]) => any,
string: string,
void?: number
]
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": false
}
@@ -0,0 +1,101 @@
{
"type": "File",
"start":0,"end":107,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":107}},
"program": {
"type": "Program",
"start":0,"end":107,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":107}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSTypeAliasDeclaration",
"start":0,"end":107,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":107}},
"id": {
"type": "Identifier",
"start":5,"end":30,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":30,"index":30},"identifierName":"FuncWithDescriptionBabel7"},
"name": "FuncWithDescriptionBabel7"
},
"typeAnnotation": {
"type": "TSTupleType",
"start":33,"end":107,"loc":{"start":{"line":1,"column":33,"index":33},"end":{"line":5,"column":1,"index":107}},
"elementTypes": [
{
"type": "TSNamedTupleMember",
"start":37,"end":70,"loc":{"start":{"line":2,"column":2,"index":37},"end":{"line":2,"column":35,"index":70}},
"optional": false,
"label": {
"type": "Identifier",
"start":37,"end":45,"loc":{"start":{"line":2,"column":2,"index":37},"end":{"line":2,"column":10,"index":45},"identifierName":"function"},
"name": "function"
},
"elementType": {
"type": "TSFunctionType",
"start":47,"end":70,"loc":{"start":{"line":2,"column":12,"index":47},"end":{"line":2,"column":35,"index":70}},
"parameters": [
{
"type": "RestElement",
"start":48,"end":62,"loc":{"start":{"line":2,"column":13,"index":48},"end":{"line":2,"column":27,"index":62}},
"argument": {
"type": "Identifier",
"start":51,"end":55,"loc":{"start":{"line":2,"column":16,"index":51},"end":{"line":2,"column":20,"index":55},"identifierName":"args"},
"name": "args"
},
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":55,"end":62,"loc":{"start":{"line":2,"column":20,"index":55},"end":{"line":2,"column":27,"index":62}},
"typeAnnotation": {
"type": "TSArrayType",
"start":57,"end":62,"loc":{"start":{"line":2,"column":22,"index":57},"end":{"line":2,"column":27,"index":62}},
"elementType": {
"type": "TSAnyKeyword",
"start":57,"end":60,"loc":{"start":{"line":2,"column":22,"index":57},"end":{"line":2,"column":25,"index":60}}
}
}
}
}
],
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":64,"end":70,"loc":{"start":{"line":2,"column":29,"index":64},"end":{"line":2,"column":35,"index":70}},
"typeAnnotation": {
"type": "TSAnyKeyword",
"start":67,"end":70,"loc":{"start":{"line":2,"column":32,"index":67},"end":{"line":2,"column":35,"index":70}}
}
}
}
},
{
"type": "TSNamedTupleMember",
"start":74,"end":88,"loc":{"start":{"line":3,"column":2,"index":74},"end":{"line":3,"column":16,"index":88}},
"optional": false,
"label": {
"type": "Identifier",
"start":74,"end":80,"loc":{"start":{"line":3,"column":2,"index":74},"end":{"line":3,"column":8,"index":80},"identifierName":"string"},
"name": "string"
},
"elementType": {
"type": "TSStringKeyword",
"start":82,"end":88,"loc":{"start":{"line":3,"column":10,"index":82},"end":{"line":3,"column":16,"index":88}}
}
},
{
"type": "TSNamedTupleMember",
"start":92,"end":105,"loc":{"start":{"line":4,"column":2,"index":92},"end":{"line":4,"column":15,"index":105}},
"optional": true,
"label": {
"type": "Identifier",
"start":92,"end":96,"loc":{"start":{"line":4,"column":2,"index":92},"end":{"line":4,"column":6,"index":96},"identifierName":"void"},
"name": "void"
},
"elementType": {
"type": "TSNumberKeyword",
"start":99,"end":105,"loc":{"start":{"line":4,"column":9,"index":99},"end":{"line":4,"column":15,"index":105}}
}
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,5 @@
type FuncWithDescription = [
function: (...args: any[]) => any,
string: string,
void?: number
]
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": true
}
@@ -0,0 +1,101 @@
{
"type": "File",
"start":0,"end":101,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":101}},
"program": {
"type": "Program",
"start":0,"end":101,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":101}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSTypeAliasDeclaration",
"start":0,"end":101,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":101}},
"id": {
"type": "Identifier",
"start":5,"end":24,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":24,"index":24},"identifierName":"FuncWithDescription"},
"name": "FuncWithDescription"
},
"typeAnnotation": {
"type": "TSTupleType",
"start":27,"end":101,"loc":{"start":{"line":1,"column":27,"index":27},"end":{"line":5,"column":1,"index":101}},
"elementTypes": [
{
"type": "TSNamedTupleMember",
"start":31,"end":64,"loc":{"start":{"line":2,"column":2,"index":31},"end":{"line":2,"column":35,"index":64}},
"optional": false,
"label": {
"type": "Identifier",
"start":31,"end":39,"loc":{"start":{"line":2,"column":2,"index":31},"end":{"line":2,"column":10,"index":39},"identifierName":"function"},
"name": "function"
},
"elementType": {
"type": "TSFunctionType",
"start":41,"end":64,"loc":{"start":{"line":2,"column":12,"index":41},"end":{"line":2,"column":35,"index":64}},
"params": [
{
"type": "RestElement",
"start":42,"end":56,"loc":{"start":{"line":2,"column":13,"index":42},"end":{"line":2,"column":27,"index":56}},
"argument": {
"type": "Identifier",
"start":45,"end":49,"loc":{"start":{"line":2,"column":16,"index":45},"end":{"line":2,"column":20,"index":49},"identifierName":"args"},
"name": "args"
},
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":49,"end":56,"loc":{"start":{"line":2,"column":20,"index":49},"end":{"line":2,"column":27,"index":56}},
"typeAnnotation": {
"type": "TSArrayType",
"start":51,"end":56,"loc":{"start":{"line":2,"column":22,"index":51},"end":{"line":2,"column":27,"index":56}},
"elementType": {
"type": "TSAnyKeyword",
"start":51,"end":54,"loc":{"start":{"line":2,"column":22,"index":51},"end":{"line":2,"column":25,"index":54}}
}
}
}
}
],
"returnType": {
"type": "TSTypeAnnotation",
"start":58,"end":64,"loc":{"start":{"line":2,"column":29,"index":58},"end":{"line":2,"column":35,"index":64}},
"typeAnnotation": {
"type": "TSAnyKeyword",
"start":61,"end":64,"loc":{"start":{"line":2,"column":32,"index":61},"end":{"line":2,"column":35,"index":64}}
}
}
}
},
{
"type": "TSNamedTupleMember",
"start":68,"end":82,"loc":{"start":{"line":3,"column":2,"index":68},"end":{"line":3,"column":16,"index":82}},
"optional": false,
"label": {
"type": "Identifier",
"start":68,"end":74,"loc":{"start":{"line":3,"column":2,"index":68},"end":{"line":3,"column":8,"index":74},"identifierName":"string"},
"name": "string"
},
"elementType": {
"type": "TSStringKeyword",
"start":76,"end":82,"loc":{"start":{"line":3,"column":10,"index":76},"end":{"line":3,"column":16,"index":82}}
}
},
{
"type": "TSNamedTupleMember",
"start":86,"end":99,"loc":{"start":{"line":4,"column":2,"index":86},"end":{"line":4,"column":15,"index":99}},
"optional": true,
"label": {
"type": "Identifier",
"start":86,"end":90,"loc":{"start":{"line":4,"column":2,"index":86},"end":{"line":4,"column":6,"index":90},"identifierName":"void"},
"name": "void"
},
"elementType": {
"type": "TSNumberKeyword",
"start":93,"end":99,"loc":{"start":{"line":4,"column":9,"index":93},"end":{"line":4,"column":15,"index":99}}
}
}
]
}
}
],
"directives": []
}
}
Expand Up @@ -2,6 +2,5 @@
"sourceType": "module",
"plugins": [
"typescript"
],
"throws": "Unexpected token, expected \",\" (1:14)"
}
]
}
@@ -0,0 +1,50 @@
{
"type": "File",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":17,"index":17}},
"errors": [
"SyntaxError: A labeled tuple optional element must be declared using a question mark after the name and before the colon (`name?: type`), rather than after the type (`name: type?`). (1:14)"
],
"program": {
"type": "Program",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":17,"index":17}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSTypeAliasDeclaration",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":17,"index":17}},
"id": {
"type": "Identifier",
"start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"T"},
"name": "T"
},
"typeAnnotation": {
"type": "TSTupleType",
"start":9,"end":16,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":16,"index":16}},
"elementTypes": [
{
"type": "TSNamedTupleMember",
"start":10,"end":15,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":15,"index":15}},
"optional": true,
"label": {
"type": "Identifier",
"start":10,"end":11,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":11,"index":11},"identifierName":"x"},
"name": "x"
},
"elementType": {
"type": "TSTypeReference",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14}},
"typeName": {
"type": "Identifier",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"A"},
"name": "A"
}
}
}
]
}
}
],
"directives": []
}
}