Typescript: Validate tuple type element positions (#8828)
* feat: validate the positions of rest elements and optional elements in tuple types Adds a validation step to the parser which raises syntax errors if a rest param is not at the end of a tuple, or if a mandatory param follows an optional parameter * Fix spread after optional case; add test case
This commit is contained in:
parent
e3b2c1afff
commit
2194842d11
@ -507,6 +507,29 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
|||||||
/* bracket */ true,
|
/* bracket */ true,
|
||||||
/* skipFirstToken */ false,
|
/* skipFirstToken */ false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Validate the elementTypes to ensure:
|
||||||
|
// No mandatory elements may follow optional elements
|
||||||
|
// If there's a rest element, it must be at the end of the tuple
|
||||||
|
let seenOptionalElement = false;
|
||||||
|
node.elementTypes.forEach((elementNode, i) => {
|
||||||
|
if (elementNode.type === "TSRestType") {
|
||||||
|
if (i !== node.elementTypes.length - 1) {
|
||||||
|
this.raise(
|
||||||
|
elementNode.start,
|
||||||
|
"A rest element must be last in a tuple type.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (elementNode.type === "TSOptionalType") {
|
||||||
|
seenOptionalElement = true;
|
||||||
|
} else if (seenOptionalElement) {
|
||||||
|
this.raise(
|
||||||
|
elementNode.start,
|
||||||
|
"A required element cannot follow an optional element.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return this.finishNode(node, "TSTupleType");
|
return this.finishNode(node, "TSTupleType");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
packages/babel-parser/test/fixtures/typescript/types/tuple-optional-invalid/input.js
vendored
Normal file
1
packages/babel-parser/test/fixtures/typescript/types/tuple-optional-invalid/input.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
let x: [string?, number]
|
||||||
7
packages/babel-parser/test/fixtures/typescript/types/tuple-optional-invalid/options.json
vendored
Normal file
7
packages/babel-parser/test/fixtures/typescript/types/tuple-optional-invalid/options.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sourceType": "module",
|
||||||
|
"plugins": [
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"throws": "A required element cannot follow an optional element. (1:17)"
|
||||||
|
}
|
||||||
1
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-after-optional/input.js
vendored
Normal file
1
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-after-optional/input.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
function foo(...args: [number, string?, ...number[]]) {}
|
||||||
242
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-after-optional/output.json
vendored
Normal file
242
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-after-optional/output.json
vendored
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
{
|
||||||
|
"type": "File",
|
||||||
|
"start": 0,
|
||||||
|
"end": 56,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 56
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"program": {
|
||||||
|
"type": "Program",
|
||||||
|
"start": 0,
|
||||||
|
"end": 56,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 56
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceType": "module",
|
||||||
|
"interpreter": null,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "FunctionDeclaration",
|
||||||
|
"start": 0,
|
||||||
|
"end": 56,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 56
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"start": 9,
|
||||||
|
"end": 12,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 9
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 12
|
||||||
|
},
|
||||||
|
"identifierName": "foo"
|
||||||
|
},
|
||||||
|
"name": "foo"
|
||||||
|
},
|
||||||
|
"generator": false,
|
||||||
|
"async": false,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"type": "RestElement",
|
||||||
|
"start": 13,
|
||||||
|
"end": 52,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 13
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 52
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"argument": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"start": 16,
|
||||||
|
"end": 20,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 16
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 20
|
||||||
|
},
|
||||||
|
"identifierName": "args"
|
||||||
|
},
|
||||||
|
"name": "args"
|
||||||
|
},
|
||||||
|
"typeAnnotation": {
|
||||||
|
"type": "TSTypeAnnotation",
|
||||||
|
"start": 20,
|
||||||
|
"end": 52,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 20
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 52
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typeAnnotation": {
|
||||||
|
"type": "TSTupleType",
|
||||||
|
"start": 22,
|
||||||
|
"end": 52,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 22
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 52
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"elementTypes": [
|
||||||
|
{
|
||||||
|
"type": "TSNumberKeyword",
|
||||||
|
"start": 23,
|
||||||
|
"end": 29,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 23
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 29
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TSOptionalType",
|
||||||
|
"start": 31,
|
||||||
|
"end": 38,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 31
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 38
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typeAnnotation": {
|
||||||
|
"type": "TSStringKeyword",
|
||||||
|
"start": 31,
|
||||||
|
"end": 37,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 31
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 37
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TSRestType",
|
||||||
|
"start": 40,
|
||||||
|
"end": 51,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 40
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 51
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typeAnnotation": {
|
||||||
|
"type": "TSArrayType",
|
||||||
|
"start": 43,
|
||||||
|
"end": 51,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 43
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 51
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"elementType": {
|
||||||
|
"type": "TSNumberKeyword",
|
||||||
|
"start": 43,
|
||||||
|
"end": 49,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 43
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 49
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"type": "BlockStatement",
|
||||||
|
"start": 54,
|
||||||
|
"end": 56,
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 54
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 1,
|
||||||
|
"column": 56
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"body": [],
|
||||||
|
"directives": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"directives": []
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-invalid/input.js
vendored
Normal file
1
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-invalid/input.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
let x: [...number[], string]
|
||||||
7
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-invalid/options.json
vendored
Normal file
7
packages/babel-parser/test/fixtures/typescript/types/tuple-rest-invalid/options.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sourceType": "module",
|
||||||
|
"plugins": [
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"throws": "A rest element must be last in a tuple type. (1:8)"
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user