TypeScript: Support type arguments on tagged templates (#7754)

| Q                        | A
| ------------------------ | ---
| Fixed Issues?            | #7747 (partly)
| Patch: Bug Fix?          | 
| Major: Breaking Change?  | 
| Minor: New Feature?      | Yes
| Tests Added + Pass?      | Yes
| Documentation PR         |
| Any Dependency Changes?  |
| License                  | MIT

@JamesHenry This changes the AST format. CC @DanielRosenwasser for review.
Supports parsing type arguments on tagged template calls.
Should wait on Microsoft/TypeScript#23430 to be merged so we're sure we have the final syntax.
This commit is contained in:
Andy 2018-07-26 08:44:43 -07:00 committed by Brian Ng
parent db2a9fc96e
commit 8ee24fdfc0
14 changed files with 448 additions and 50 deletions

View File

@ -1,5 +1,6 @@
export function TaggedTemplateExpression(node: Object) { export function TaggedTemplateExpression(node: Object) {
this.print(node.tag, node); this.print(node.tag, node);
this.print(node.typeParameters, node); // TS
this.print(node.quasi, node); this.print(node.quasi, node);
} }

View File

@ -563,9 +563,32 @@ export default class ExpressionParser extends LValParser {
} }
return node; return node;
} else if (this.match(tt.backQuote)) { } else if (this.match(tt.backQuote)) {
const node = this.startNodeAt(startPos, startLoc); return this.parseTaggedTemplateExpression(
startPos,
startLoc,
base,
state,
);
} else {
state.stop = true;
return base;
}
}
parseTaggedTemplateExpression(
startPos: number,
startLoc: Position,
base: N.Expression,
state: N.ParseSubscriptState,
typeArguments?: ?N.TsTypeParameterInstantiation,
): N.TaggedTemplateExpression {
const node: N.TaggedTemplateExpression = this.startNodeAt(
startPos,
startLoc,
);
node.tag = base; node.tag = base;
node.quasi = this.parseTemplate(true); node.quasi = this.parseTemplate(true);
if (typeArguments) node.typeParameters = typeArguments;
if (state.optionalChainMember) { if (state.optionalChainMember) {
this.raise( this.raise(
startPos, startPos,
@ -573,10 +596,6 @@ export default class ExpressionParser extends LValParser {
); );
} }
return this.finishNode(node, "TaggedTemplateExpression"); return this.finishNode(node, "TaggedTemplateExpression");
} else {
state.stop = true;
return base;
}
} }
atPossibleAsync(base: N.Expression): boolean { atPossibleAsync(base: N.Expression): boolean {

View File

@ -836,16 +836,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.finishNode(node, "TSTypeAssertion"); return this.finishNode(node, "TSTypeAssertion");
} }
tsTryParseTypeArgumentsInExpression(
eatNextToken: boolean,
): ?N.TsTypeParameterInstantiation {
return this.tsTryParseAndCatch(() => {
const res = this.tsParseTypeArguments();
if (eatNextToken) this.expect(tt.parenL);
return res;
});
}
tsParseHeritageClause(): $ReadOnlyArray<N.TsExpressionWithTypeArguments> { tsParseHeritageClause(): $ReadOnlyArray<N.TsExpressionWithTypeArguments> {
return this.tsParseDelimitedList( return this.tsParseDelimitedList(
"HeritageClauseElement", "HeritageClauseElement",
@ -1376,8 +1366,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.finishNode(nonNullExpression, "TSNonNullExpression"); return this.finishNode(nonNullExpression, "TSNonNullExpression");
} }
if (!noCalls && this.isRelational("<")) { // There are number of things we are going to "maybe" parse, like type arguments on
if (this.atPossibleAsync(base)) { // tagged template expressions. If any of them fail, walk it back and continue.
const result = this.tsTryParseAndCatch(() => {
if (this.isRelational("<")) {
if (!noCalls && this.atPossibleAsync(base)) {
// Almost certainly this is a generic async function `async <T>() => ... // Almost certainly this is a generic async function `async <T>() => ...
// But it might be a call with a type argument `async<T>();` // But it might be a call with a type argument `async<T>();`
const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction( const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction(
@ -1392,12 +1385,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const node: N.CallExpression = this.startNodeAt(startPos, startLoc); const node: N.CallExpression = this.startNodeAt(startPos, startLoc);
node.callee = base; node.callee = base;
// May be passing type arguments. But may just be the `<` operator. const typeArguments = this.tsParseTypeArguments();
// Note: With `/*eatNextToken*/ true` this also eats the `(` following the type arguments
const typeArguments = this.tsTryParseTypeArgumentsInExpression(
/*eatNextToken*/ true,
);
if (typeArguments) { if (typeArguments) {
if (!noCalls && this.eat(tt.parenL)) {
// possibleAsync always false here, because we would have handled it above. // possibleAsync always false here, because we would have handled it above.
// $FlowIgnore (won't be any undefined arguments) // $FlowIgnore (won't be any undefined arguments)
node.arguments = this.parseCallExpressionArguments( node.arguments = this.parseCallExpressionArguments(
@ -1406,8 +1397,22 @@ export default (superClass: Class<Parser>): Class<Parser> =>
); );
node.typeParameters = typeArguments; node.typeParameters = typeArguments;
return this.finishCallExpression(node); return this.finishCallExpression(node);
} else if (this.match(tt.backQuote)) {
return this.parseTaggedTemplateExpression(
startPos,
startLoc,
base,
state,
typeArguments,
);
} }
} }
}
this.unexpected();
});
if (result) return result;
return super.parseSubscript(base, startPos, startLoc, noCalls, state); return super.parseSubscript(base, startPos, startLoc, noCalls, state);
} }
@ -2127,8 +2132,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
jsxParseOpeningElementAfterName( jsxParseOpeningElementAfterName(
node: N.JSXOpeningElement, node: N.JSXOpeningElement,
): N.JSXOpeningElement { ): N.JSXOpeningElement {
const typeArguments = this.tsTryParseTypeArgumentsInExpression( const typeArguments = this.tsTryParseAndCatch(() =>
/*eatNextToken*/ false, this.tsParseTypeArguments(),
); );
if (typeArguments) node.typeParameters = typeArguments; if (typeArguments) node.typeParameters = typeArguments;
return super.jsxParseOpeningElementAfterName(node); return super.jsxParseOpeningElementAfterName(node);

View File

@ -577,6 +577,7 @@ export type TaggedTemplateExpression = NodeBase & {
type: "TaggedTemplateExpression", type: "TaggedTemplateExpression",
tag: Expression, tag: Expression,
quasi: TemplateLiteral, quasi: TemplateLiteral,
typeParameters?: ?TypeParameterInstantiationBase, // TODO: Not in spec
}; };
export type TemplateElement = NodeBase & { export type TemplateElement = NodeBase & {

View File

@ -0,0 +1,2 @@
new C<T>
``

View File

@ -0,0 +1,185 @@
{
"type": "File",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 2
}
},
"program": {
"type": "Program",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 2
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 2
}
},
"expression": {
"type": "NewExpression",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 2
}
},
"callee": {
"type": "TaggedTemplateExpression",
"start": 4,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 2,
"column": 2
}
},
"tag": {
"type": "Identifier",
"start": 4,
"end": 5,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 5
},
"identifierName": "C"
},
"name": "C"
},
"quasi": {
"type": "TemplateLiteral",
"start": 9,
"end": 11,
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 2
}
},
"expressions": [],
"quasis": [
{
"type": "TemplateElement",
"start": 10,
"end": 10,
"loc": {
"start": {
"line": 2,
"column": 1
},
"end": {
"line": 2,
"column": 1
}
},
"value": {
"raw": "",
"cooked": ""
},
"tail": true
}
]
},
"typeParameters": {
"type": "TSTypeParameterInstantiation",
"start": 5,
"end": 8,
"loc": {
"start": {
"line": 1,
"column": 5
},
"end": {
"line": 1,
"column": 8
}
},
"params": [
{
"type": "TSTypeReference",
"start": 6,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 7
}
},
"typeName": {
"type": "Identifier",
"start": 6,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 7
},
"identifierName": "T"
},
"name": "T"
}
}
]
}
},
"arguments": []
}
}
],
"directives": []
}
}

View File

@ -0,0 +1,169 @@
{
"type": "File",
"start": 0,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 7
}
},
"program": {
"type": "Program",
"start": 0,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 7
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 7
}
},
"expression": {
"type": "TaggedTemplateExpression",
"start": 0,
"end": 6,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 6
}
},
"tag": {
"type": "Identifier",
"start": 0,
"end": 1,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 1
},
"identifierName": "f"
},
"name": "f"
},
"quasi": {
"type": "TemplateLiteral",
"start": 4,
"end": 6,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 6
}
},
"expressions": [],
"quasis": [
{
"type": "TemplateElement",
"start": 5,
"end": 5,
"loc": {
"start": {
"line": 1,
"column": 5
},
"end": {
"line": 1,
"column": 5
}
},
"value": {
"raw": "",
"cooked": ""
},
"tail": true
}
]
},
"typeParameters": {
"type": "TSTypeParameterInstantiation",
"start": 1,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 4
}
},
"params": [
{
"type": "TSTypeReference",
"start": 2,
"end": 3,
"loc": {
"start": {
"line": 1,
"column": 2
},
"end": {
"line": 1,
"column": 3
}
},
"typeName": {
"type": "Identifier",
"start": 2,
"end": 3,
"loc": {
"start": {
"line": 1,
"column": 2
},
"end": {
"line": 1,
"column": 3
},
"identifierName": "T"
},
"name": "T"
}
}
]
}
}
}
],
"directives": []
}
}

View File

@ -268,6 +268,10 @@ export default declare((api, { jsxPragma = "React" }) => {
JSXOpeningElement(path) { JSXOpeningElement(path) {
path.node.typeParameters = null; path.node.typeParameters = null;
}, },
TaggedTemplateExpression(path) {
path.node.typeParameters = null;
},
}, },
}; };

View File

@ -510,6 +510,13 @@ defineType("TaggedTemplateExpression", {
quasi: { quasi: {
validate: assertNodeType("TemplateLiteral"), validate: assertNodeType("TemplateLiteral"),
}, },
typeParameters: {
validate: assertNodeType(
"TypeParameterInstantiation",
"TSTypeParameterInstantiation",
),
optional: true,
},
}, },
}); });