TypeScript: Support type arguments on JSX opening and self-closing tags (#7799)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import type { Options } from "../options";
|
||||
import type { File } from "../types";
|
||||
import type { File, JSXOpeningElement } from "../types";
|
||||
import type { PluginList } from "../plugin-utils";
|
||||
import { getOptions } from "../options";
|
||||
import StatementParser from "./statement";
|
||||
@@ -11,6 +11,11 @@ export type PluginsMap = {
|
||||
};
|
||||
|
||||
export default class Parser extends StatementParser {
|
||||
// Forward-declaration so typescript plugin can override jsx plugin
|
||||
+jsxParseOpeningElementAfterName: (
|
||||
node: JSXOpeningElement,
|
||||
) => JSXOpeningElement;
|
||||
|
||||
constructor(options: ?Options, input: string) {
|
||||
options = getOptions(options);
|
||||
super(options, input);
|
||||
|
||||
@@ -358,11 +358,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXOpeningFragment");
|
||||
}
|
||||
node.attributes = [];
|
||||
node.name = this.jsxParseElementName();
|
||||
return this.jsxParseOpeningElementAfterName(node);
|
||||
}
|
||||
|
||||
jsxParseOpeningElementAfterName(
|
||||
node: N.JSXOpeningElement,
|
||||
): N.JSXOpeningElement {
|
||||
const attributes: N.JSXAttribute[] = [];
|
||||
while (!this.match(tt.slash) && !this.match(tt.jsxTagEnd)) {
|
||||
node.attributes.push(this.jsxParseAttribute());
|
||||
attributes.push(this.jsxParseAttribute());
|
||||
}
|
||||
node.attributes = attributes;
|
||||
node.selfClosing = this.eat(tt.slash);
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXOpeningElement");
|
||||
|
||||
@@ -836,10 +836,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return this.finishNode(node, "TSTypeAssertion");
|
||||
}
|
||||
|
||||
tsTryParseTypeArgumentsInExpression(): ?N.TsTypeParameterInstantiation {
|
||||
tsTryParseTypeArgumentsInExpression(
|
||||
eatNextToken: boolean,
|
||||
): ?N.TsTypeParameterInstantiation {
|
||||
return this.tsTryParseAndCatch(() => {
|
||||
const res = this.tsParseTypeArguments();
|
||||
this.expect(tt.parenL);
|
||||
if (eatNextToken) this.expect(tt.parenL);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
@@ -887,6 +889,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
return this.finishNode(node, "TSTypeAliasDeclaration");
|
||||
}
|
||||
|
||||
tsInNoContext<T>(cb: () => T): T {
|
||||
const oldContext = this.state.context;
|
||||
this.state.context = [oldContext[0]];
|
||||
try {
|
||||
return cb();
|
||||
} finally {
|
||||
this.state.context = oldContext;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `cb` in a type context.
|
||||
* This should be called one token *before* the first type token,
|
||||
@@ -1241,13 +1253,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
|
||||
tsParseTypeArguments(): N.TsTypeParameterInstantiation {
|
||||
const node = this.startNode();
|
||||
node.params = this.tsInType(() => {
|
||||
this.expectRelational("<");
|
||||
return this.tsParseDelimitedList(
|
||||
"TypeParametersOrArguments",
|
||||
this.tsParseType.bind(this),
|
||||
);
|
||||
});
|
||||
node.params = this.tsInType(() =>
|
||||
// Temporarily remove a JSX parsing context, which makes us scan different tokens.
|
||||
this.tsInNoContext(() => {
|
||||
this.expectRelational("<");
|
||||
return this.tsParseDelimitedList(
|
||||
"TypeParametersOrArguments",
|
||||
this.tsParseType.bind(this),
|
||||
);
|
||||
}),
|
||||
);
|
||||
// This reads the next token after the `>` too, so do this in the enclosing context.
|
||||
// But be sure not to parse a regex in the jsx expression `<C<number> />`, so set exprAllowed = false
|
||||
this.state.exprAllowed = false;
|
||||
this.expectRelational(">");
|
||||
return this.finishNode(node, "TSTypeParameterInstantiation");
|
||||
}
|
||||
@@ -1375,7 +1393,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
node.callee = base;
|
||||
|
||||
// May be passing type arguments. But may just be the `<` operator.
|
||||
const typeArguments = this.tsTryParseTypeArgumentsInExpression(); // Also eats the "("
|
||||
// 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)
|
||||
@@ -2102,4 +2123,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
||||
// Avoid unnecessary lookahead in checking for abstract class unless needed!
|
||||
return super.canHaveLeadingDecorator() || this.isAbstractClass();
|
||||
}
|
||||
|
||||
jsxParseOpeningElementAfterName(
|
||||
node: N.JSXOpeningElement,
|
||||
): N.JSXOpeningElement {
|
||||
const typeArguments = this.tsTryParseTypeArgumentsInExpression(
|
||||
/*eatNextToken*/ false,
|
||||
);
|
||||
if (typeArguments) node.typeParameters = typeArguments;
|
||||
return super.jsxParseOpeningElementAfterName(node);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -423,7 +423,7 @@ export default class Tokenizer extends LocationParser {
|
||||
|
||||
readToken_slash(): void {
|
||||
// '/'
|
||||
if (this.state.exprAllowed) {
|
||||
if (this.state.exprAllowed && !this.state.inType) {
|
||||
++this.state.pos;
|
||||
this.readRegexp();
|
||||
return;
|
||||
|
||||
@@ -573,7 +573,7 @@ export type TemplateLiteral = NodeBase & {
|
||||
expressions: $ReadOnlyArray<Expression>,
|
||||
};
|
||||
|
||||
export type TaggedTmplateExpression = NodeBase & {
|
||||
export type TaggedTemplateExpression = NodeBase & {
|
||||
type: "TaggedTemplateExpression",
|
||||
tag: Expression,
|
||||
quasi: TemplateLiteral,
|
||||
@@ -820,7 +820,13 @@ export type JSXEmptyExpression = Node;
|
||||
export type JSXSpreadChild = Node;
|
||||
export type JSXExpressionContainer = Node;
|
||||
export type JSXAttribute = Node;
|
||||
export type JSXOpeningElement = Node;
|
||||
export type JSXOpeningElement = NodeBase & {
|
||||
type: "JSXOpeningElement",
|
||||
name: JSXNamespacedName | JSXMemberExpression,
|
||||
typeParameters?: ?TypeParameterInstantiationBase, // TODO: Not in spec
|
||||
attributes: $ReadOnlyArray<JSXAttribute>,
|
||||
selfClosing: boolean,
|
||||
};
|
||||
export type JSXClosingElement = Node;
|
||||
export type JSXElement = Node;
|
||||
export type JSXOpeningFragment = Node;
|
||||
|
||||
Reference in New Issue
Block a user