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 4 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
69 changes: 54 additions & 15 deletions packages/babel-parser/src/plugins/typescript/index.ts
Expand Up @@ -1099,34 +1099,73 @@ 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);
if (isWord && this.lookaheadCharCode() === charCodes.colon) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we cache the next char code when isWord is true? So we don't have to look ahead twice.

labeled = true;
optional = false;
label = this.parseIdentifier(true);
this.expect(tt.colon);
type = this.tsParseType();
} else if (
isWord &&
this.lookaheadCharCode() === 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;
} 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,4 @@
type FuncWithDescriptionBabel7 = [
function: (...args: any[]) => any,
string: string
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you augment the test case to include optional keyword labeled tuple? such as void?: string

]
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": false
}
@@ -0,0 +1,87 @@
{
"type": "File",
"start":0,"end":90,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":90}},
"program": {
"type": "Program",
"start":0,"end":90,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":90}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSTypeAliasDeclaration",
"start":0,"end":90,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":90}},
"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":90,"loc":{"start":{"line":1,"column":33,"index":33},"end":{"line":4,"column":1,"index":90}},
"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}}
}
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
type FuncWithDescription = [
function: (...args: any[]) => any,
string: string
]
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": true
}
@@ -0,0 +1,87 @@
{
"type": "File",
"start":0,"end":84,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":84}},
"program": {
"type": "Program",
"start":0,"end":84,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":84}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSTypeAliasDeclaration",
"start":0,"end":84,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":84}},
"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":84,"loc":{"start":{"line":1,"column":27,"index":27},"end":{"line":4,"column":1,"index":84}},
"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}}
}
}
]
}
}
],
"directives": []
}
}