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

@@ -563,22 +563,41 @@ export default class ExpressionParser extends LValParser {
}
return node;
} else if (this.match(tt.backQuote)) {
const node = this.startNodeAt(startPos, startLoc);
node.tag = base;
node.quasi = this.parseTemplate(true);
if (state.optionalChainMember) {
this.raise(
startPos,
"Tagged Template Literals are not allowed in optionalChain",
);
}
return this.finishNode(node, "TaggedTemplateExpression");
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.quasi = this.parseTemplate(true);
if (typeArguments) node.typeParameters = typeArguments;
if (state.optionalChainMember) {
this.raise(
startPos,
"Tagged Template Literals are not allowed in optionalChain",
);
}
return this.finishNode(node, "TaggedTemplateExpression");
}
atPossibleAsync(base: N.Expression): boolean {
return (
!this.state.containsEsc &&

View File

@@ -836,16 +836,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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> {
return this.tsParseDelimitedList(
"HeritageClauseElement",
@@ -1376,38 +1366,53 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.finishNode(nonNullExpression, "TSNonNullExpression");
}
if (!noCalls && this.isRelational("<")) {
if (this.atPossibleAsync(base)) {
// Almost certainly this is a generic async function `async <T>() => ...
// But it might be a call with a type argument `async<T>();`
const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction(
startPos,
startLoc,
);
if (asyncArrowFn) {
return asyncArrowFn;
// There are number of things we are going to "maybe" parse, like type arguments on
// 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>() => ...
// But it might be a call with a type argument `async<T>();`
const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction(
startPos,
startLoc,
);
if (asyncArrowFn) {
return asyncArrowFn;
}
}
const node: N.CallExpression = this.startNodeAt(startPos, startLoc);
node.callee = base;
const typeArguments = this.tsParseTypeArguments();
if (typeArguments) {
if (!noCalls && this.eat(tt.parenL)) {
// possibleAsync always false here, because we would have handled it above.
// $FlowIgnore (won't be any undefined arguments)
node.arguments = this.parseCallExpressionArguments(
tt.parenR,
/* possibleAsync */ false,
);
node.typeParameters = typeArguments;
return this.finishCallExpression(node);
} else if (this.match(tt.backQuote)) {
return this.parseTaggedTemplateExpression(
startPos,
startLoc,
base,
state,
typeArguments,
);
}
}
}
const node: N.CallExpression = this.startNodeAt(startPos, startLoc);
node.callee = base;
this.unexpected();
});
// May be passing type arguments. But may just be the `<` operator.
// Note: With `/*eatNextToken*/ true` this also eats the `(` following the type arguments
const typeArguments = this.tsTryParseTypeArgumentsInExpression(
/*eatNextToken*/ true,
);
if (typeArguments) {
// possibleAsync always false here, because we would have handled it above.
// $FlowIgnore (won't be any undefined arguments)
node.arguments = this.parseCallExpressionArguments(
tt.parenR,
/* possibleAsync */ false,
);
node.typeParameters = typeArguments;
return this.finishCallExpression(node);
}
}
if (result) return result;
return super.parseSubscript(base, startPos, startLoc, noCalls, state);
}
@@ -2127,8 +2132,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
jsxParseOpeningElementAfterName(
node: N.JSXOpeningElement,
): N.JSXOpeningElement {
const typeArguments = this.tsTryParseTypeArgumentsInExpression(
/*eatNextToken*/ false,
const typeArguments = this.tsTryParseAndCatch(() =>
this.tsParseTypeArguments(),
);
if (typeArguments) node.typeParameters = typeArguments;
return super.jsxParseOpeningElementAfterName(node);

View File

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