Skip to content

Commit

Permalink
Reinterpret << when parsing TS type arguments (#14145)
Browse files Browse the repository at this point in the history
* refactor: split bitShift to bitShiftL and bitShiftR

* fix: reinterpret bitShiftL to lt when parsing type arguments

* test: update typescript allowlist

* add Babel 8 test cases

* test: simplify testcase

* add more testcases
  • Loading branch information
JLHwung committed Jan 24, 2022
1 parent 48cf642 commit 5861002
Show file tree
Hide file tree
Showing 44 changed files with 1,360 additions and 15 deletions.
48 changes: 36 additions & 12 deletions packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -1974,6 +1974,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
);
}

// Used when parsing type arguments from ES productions, where the first token
// has been created without state.inType. Thus we need to rescan the lt token.
tsParseTypeArgumentsInExpression(): N.TsTypeParameterInstantiation | void {
if (this.reScan_lt() !== tt.lt) {
return undefined;
}
return this.tsParseTypeArguments();
}

tsParseTypeArguments(): N.TsTypeParameterInstantiation {
const node = this.startNode();
node.params = this.tsInType(() =>
Expand Down Expand Up @@ -2172,7 +2181,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.next();
}

if (this.match(tt.lt)) {
// handles 'f<<T>'
if (this.match(tt.lt) || this.match(tt.bitShiftL)) {
let missingParenErrorLoc;
// tsTryParseAndCatch is expensive, so avoid if not necessary.
// There are number of things we are going to "maybe" parse, like type arguments on
Expand All @@ -2193,7 +2203,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const node: N.CallExpression = this.startNodeAt(startPos, startLoc);
node.callee = base;

const typeArguments = this.tsParseTypeArguments();
const typeArguments = this.tsParseTypeArgumentsInExpression();

if (typeArguments) {
if (isOptionalCall && !this.match(tt.parenL)) {
Expand Down Expand Up @@ -2245,11 +2255,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}

parseNewArguments(node: N.NewExpression): void {
if (this.match(tt.lt)) {
// tsTryParseAndCatch is expensive, so avoid if not necessary.
// 99% certain this is `new C<T>();`. But may be `new C < T;`, which is also legal.
// tsTryParseAndCatch is expensive, so avoid if not necessary.
// 99% certain this is `new C<T>();`. But may be `new C < T;`, which is also legal.
// Also handles `new C<<T>`
if (this.match(tt.lt) || this.match(tt.bitShiftL)) {
const typeParameters = this.tsTryParseAndCatch(() => {
const args = this.tsParseTypeArguments();
const args = this.tsParseTypeArgumentsInExpression();
if (!this.match(tt.parenL)) this.unexpected();
return args;
});
Expand Down Expand Up @@ -2832,8 +2843,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>

parseClassSuper(node: N.Class): void {
super.parseClassSuper(node);
if (node.superClass && this.match(tt.lt)) {
node.superTypeParameters = this.tsParseTypeArguments();
// handle `extends f<<T>
if (node.superClass && (this.match(tt.lt) || this.match(tt.bitShiftL))) {
node.superTypeParameters = this.tsParseTypeArgumentsInExpression();
}
if (this.eatContextual(tt._implements)) {
node.implements = this.tsParseHeritageClause("implements");
Expand Down Expand Up @@ -3146,8 +3158,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}

parseMaybeDecoratorArguments(expr: N.Expression): N.Expression {
if (this.match(tt.lt)) {
const typeArguments = this.tsParseTypeArguments();
// handles `@f<<T>`
if (this.match(tt.lt) || this.match(tt.bitShiftL)) {
const typeArguments = this.tsParseTypeArgumentsInExpression();

if (this.match(tt.parenL)) {
const call = super.parseMaybeDecoratorArguments(expr);
Expand Down Expand Up @@ -3230,6 +3243,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
}

reScan_lt() {
const { type } = this.state;
if (type === tt.bitShiftL) {
this.state.pos -= 2;
this.finishOp(tt.lt, 1);
return tt.lt;
}
return type;
}

toAssignableList(exprList: N.Expression[]): $ReadOnlyArray<N.Pattern> {
for (let i = 0; i < exprList.length; i++) {
const expr = exprList[i];
Expand Down Expand Up @@ -3280,9 +3303,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
jsxParseOpeningElementAfterName(
node: N.JSXOpeningElement,
): N.JSXOpeningElement {
if (this.match(tt.lt)) {
// handles `<Component<<T>`
if (this.match(tt.lt) || this.match(tt.bitShiftL)) {
const typeArguments = this.tsTryParseAndCatch(() =>
this.tsParseTypeArguments(),
this.tsParseTypeArgumentsInExpression(),
);
if (typeArguments) node.typeParameters = typeArguments;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -755,7 +755,7 @@ export default class Tokenizer extends ParserErrors {
this.finishOp(tt.assign, 3);
return;
}
this.finishOp(tt.bitShift, 2);
this.finishOp(tt.bitShiftL, 2);
return;
}

Expand All @@ -780,7 +780,7 @@ export default class Tokenizer extends ParserErrors {
this.finishOp(tt.assign, size + 1);
return;
}
this.finishOp(tt.bitShift, size);
this.finishOp(tt.bitShiftR, size);
return;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/tokenizer/types.js
Expand Up @@ -208,6 +208,8 @@ export const tt: { [name: string]: TokenType } = {
gt: createBinop("</>/<=/>=", 7),
relational: createBinop("</>/<=/>=", 7),
bitShift: createBinop("<</>>/>>>", 8),
bitShiftL: createBinop("<</>>/>>>", 8),
bitShiftR: createBinop("<</>>/>>>", 8),
plusMin: createToken("+/-", { beforeExpr, binop: 9, prefix, startsExpr }),
// startsExpr: required by v8intrinsic plugin
modulo: createToken("%", { binop: 10, startsExpr }),
Expand Down
@@ -0,0 +1 @@
f<<<T>(x)
@@ -0,0 +1,49 @@
{
"type": "File",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"program": {
"type": "Program",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"left": {
"type": "Identifier",
"start":0,"end":1,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":1},"identifierName":"f"},
"name": "f"
},
"operator": "<<",
"right": {
"type": "TSTypeAssertion",
"start":3,"end":9,"loc":{"start":{"line":1,"column":3},"end":{"line":1,"column":9}},
"typeAnnotation": {
"type": "TSTypeReference",
"start":4,"end":5,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":5}},
"typeName": {
"type": "Identifier",
"start":4,"end":5,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":5},"identifierName":"T"},
"name": "T"
}
},
"expression": {
"type": "Identifier",
"start":7,"end":8,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":8},"identifierName":"x"},
"name": "x",
"extra": {
"parenthesized": true,
"parenStart": 6
}
}
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
f<<T>(v: T) => void>();
@@ -0,0 +1,76 @@
{
"type": "File",
"start":0,"end":23,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":23}},
"program": {
"type": "Program",
"start":0,"end":23,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":23}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":23,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":23}},
"expression": {
"type": "CallExpression",
"start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}},
"callee": {
"type": "Identifier",
"start":0,"end":1,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":1},"identifierName":"f"},
"name": "f"
},
"arguments": [],
"typeParameters": {
"type": "TSTypeParameterInstantiation",
"start":1,"end":20,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":20}},
"params": [
{
"type": "TSFunctionType",
"start":2,"end":19,"loc":{"start":{"line":1,"column":2},"end":{"line":1,"column":19}},
"typeParameters": {
"type": "TSTypeParameterDeclaration",
"start":2,"end":5,"loc":{"start":{"line":1,"column":2},"end":{"line":1,"column":5}},
"params": [
{
"type": "TSTypeParameter",
"start":3,"end":4,"loc":{"start":{"line":1,"column":3},"end":{"line":1,"column":4}},
"name": "T"
}
]
},
"parameters": [
{
"type": "Identifier",
"start":6,"end":10,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":10},"identifierName":"v"},
"name": "v",
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":7,"end":10,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":10}},
"typeAnnotation": {
"type": "TSTypeReference",
"start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10}},
"typeName": {
"type": "Identifier",
"start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10},"identifierName":"T"},
"name": "T"
}
}
}
}
],
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":12,"end":19,"loc":{"start":{"line":1,"column":12},"end":{"line":1,"column":19}},
"typeAnnotation": {
"type": "TSVoidKeyword",
"start":15,"end":19,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":19}}
}
}
}
]
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
(class extends f<<T>(v: T) => void> {});
@@ -0,0 +1,85 @@
{
"type": "File",
"start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}},
"program": {
"type": "Program",
"start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}},
"expression": {
"type": "ClassExpression",
"start":1,"end":38,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":38}},
"id": null,
"superClass": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"f"},
"name": "f"
},
"superTypeParameters": {
"type": "TSTypeParameterInstantiation",
"start":16,"end":35,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":35}},
"params": [
{
"type": "TSFunctionType",
"start":17,"end":34,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":34}},
"typeParameters": {
"type": "TSTypeParameterDeclaration",
"start":17,"end":20,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":20}},
"params": [
{
"type": "TSTypeParameter",
"start":18,"end":19,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":19}},
"name": "T"
}
]
},
"parameters": [
{
"type": "Identifier",
"start":21,"end":25,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":25},"identifierName":"v"},
"name": "v",
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":22,"end":25,"loc":{"start":{"line":1,"column":22},"end":{"line":1,"column":25}},
"typeAnnotation": {
"type": "TSTypeReference",
"start":24,"end":25,"loc":{"start":{"line":1,"column":24},"end":{"line":1,"column":25}},
"typeName": {
"type": "Identifier",
"start":24,"end":25,"loc":{"start":{"line":1,"column":24},"end":{"line":1,"column":25},"identifierName":"T"},
"name": "T"
}
}
}
}
],
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":27,"end":34,"loc":{"start":{"line":1,"column":27},"end":{"line":1,"column":34}},
"typeAnnotation": {
"type": "TSVoidKeyword",
"start":30,"end":34,"loc":{"start":{"line":1,"column":30},"end":{"line":1,"column":34}}
}
}
}
]
},
"body": {
"type": "ClassBody",
"start":36,"end":38,"loc":{"start":{"line":1,"column":36},"end":{"line":1,"column":38}},
"body": []
},
"extra": {
"parenthesized": true,
"parenStart": 0
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
(@f<<T>(v: T) => void>() class {});
@@ -0,0 +1,3 @@
{
"plugins": [["decorators", { "decoratorsBeforeExport": true }], "typescript"]
}

0 comments on commit 5861002

Please sign in to comment.