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
Changes from all commits
fe268bc
6b56234
a80ddaf
0882548
016add4
1d5c524
4021a4b
9156d4b
0354898
8238731
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
type T = [x: A, y?: B, ...z: C]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
type T = [x: A, y?: B, ...z: C]; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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: | ||||||
|
@@ -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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if it's one operation more, I find the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😂 But that's not the intended semantics of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, without |
||||||
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; | ||||||
} | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
type T = [x.y: A]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"sourceType": "module", | ||
"plugins": [ | ||
"typescript" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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": [] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
type T = [x<y>: A]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"sourceType": "module", | ||
"plugins": [ | ||
"typescript" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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": [] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
type T = [A, y: B]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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": [] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
type T = [x: A, B]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.