TypeScript: Support type arguments on JSX opening and self-closing tags (#7799)
This commit is contained in:
parent
19a1705293
commit
301db1b921
@ -73,6 +73,7 @@ function spaceSeparator() {
|
||||
export function JSXOpeningElement(node: Object) {
|
||||
this.token("<");
|
||||
this.print(node.name, node);
|
||||
this.print(node.typeParameters, node); // TS
|
||||
if (node.attributes.length > 0) {
|
||||
this.space();
|
||||
this.printJoin(node.attributes, node, { separator: spaceSeparator });
|
||||
|
||||
2
packages/babel-generator/test/fixtures/typescript/type-arguments-tsx/input.js
vendored
Normal file
2
packages/babel-generator/test/fixtures/typescript/type-arguments-tsx/input.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<C<number>></C>;
|
||||
<C<number>/>;
|
||||
4
packages/babel-generator/test/fixtures/typescript/type-arguments-tsx/options.json
vendored
Normal file
4
packages/babel-generator/test/fixtures/typescript/type-arguments-tsx/options.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": ["jsx", "typescript"],
|
||||
"sourceType": "module"
|
||||
}
|
||||
2
packages/babel-generator/test/fixtures/typescript/type-arguments-tsx/output.js
vendored
Normal file
2
packages/babel-generator/test/fixtures/typescript/type-arguments-tsx/output.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<C<number>></C>;
|
||||
<C<number> />;
|
||||
@ -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;
|
||||
|
||||
2
packages/babel-parser/test/fixtures/typescript/type-arguments/tsx/input.js
vendored
Normal file
2
packages/babel-parser/test/fixtures/typescript/type-arguments/tsx/input.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<C<number>></C>;
|
||||
<C<number>/>;
|
||||
3
packages/babel-parser/test/fixtures/typescript/type-arguments/tsx/options.json
vendored
Normal file
3
packages/babel-parser/test/fixtures/typescript/type-arguments/tsx/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["jsx", "typescript"]
|
||||
}
|
||||
259
packages/babel-parser/test/fixtures/typescript/type-arguments/tsx/output.json
vendored
Normal file
259
packages/babel-parser/test/fixtures/typescript/type-arguments/tsx/output.json
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
{
|
||||
"type": "File",
|
||||
"start": 0,
|
||||
"end": 30,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 13
|
||||
}
|
||||
},
|
||||
"program": {
|
||||
"type": "Program",
|
||||
"start": 0,
|
||||
"end": 30,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 13
|
||||
}
|
||||
},
|
||||
"sourceType": "module",
|
||||
"interpreter": null,
|
||||
"body": [
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"start": 0,
|
||||
"end": 16,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 16
|
||||
}
|
||||
},
|
||||
"expression": {
|
||||
"type": "JSXElement",
|
||||
"start": 0,
|
||||
"end": 15,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 15
|
||||
}
|
||||
},
|
||||
"openingElement": {
|
||||
"type": "JSXOpeningElement",
|
||||
"start": 0,
|
||||
"end": 11,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 11
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "JSXIdentifier",
|
||||
"start": 1,
|
||||
"end": 2,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 2
|
||||
}
|
||||
},
|
||||
"name": "C"
|
||||
},
|
||||
"typeParameters": {
|
||||
"type": "TSTypeParameterInstantiation",
|
||||
"start": 2,
|
||||
"end": 10,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 10
|
||||
}
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"type": "TSNumberKeyword",
|
||||
"start": 3,
|
||||
"end": 9,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 3
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 9
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"attributes": [],
|
||||
"selfClosing": false
|
||||
},
|
||||
"closingElement": {
|
||||
"type": "JSXClosingElement",
|
||||
"start": 11,
|
||||
"end": 15,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 11
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 15
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "JSXIdentifier",
|
||||
"start": 13,
|
||||
"end": 14,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 13
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 14
|
||||
}
|
||||
},
|
||||
"name": "C"
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"start": 17,
|
||||
"end": 30,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 13
|
||||
}
|
||||
},
|
||||
"expression": {
|
||||
"type": "JSXElement",
|
||||
"start": 17,
|
||||
"end": 29,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 12
|
||||
}
|
||||
},
|
||||
"openingElement": {
|
||||
"type": "JSXOpeningElement",
|
||||
"start": 17,
|
||||
"end": 29,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 12
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "JSXIdentifier",
|
||||
"start": 18,
|
||||
"end": 19,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"column": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 2
|
||||
}
|
||||
},
|
||||
"name": "C"
|
||||
},
|
||||
"typeParameters": {
|
||||
"type": "TSTypeParameterInstantiation",
|
||||
"start": 19,
|
||||
"end": 27,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"column": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 10
|
||||
}
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"type": "TSNumberKeyword",
|
||||
"start": 20,
|
||||
"end": 26,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"column": 3
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 9
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"attributes": [],
|
||||
"selfClosing": true
|
||||
},
|
||||
"closingElement": null,
|
||||
"children": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"directives": []
|
||||
}
|
||||
}
|
||||
@ -264,6 +264,10 @@ export default declare((api, { jsxPragma = "React" }) => {
|
||||
NewExpression(path) {
|
||||
path.node.typeParameters = null;
|
||||
},
|
||||
|
||||
JSXOpeningElement(path) {
|
||||
path.node.typeParameters = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
2
packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/tsx/input.js
vendored
Normal file
2
packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/tsx/input.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<C<number>></C>;
|
||||
<C<number>/>;
|
||||
3
packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/tsx/options.json
vendored
Normal file
3
packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/tsx/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": [["transform-typescript", { "isTSX": true }]]
|
||||
}
|
||||
2
packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/tsx/output.js
vendored
Normal file
2
packages/babel-plugin-transform-typescript/test/fixtures/type-arguments/tsx/output.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<C></C>;
|
||||
<C />;
|
||||
@ -142,6 +142,13 @@ defineType("JSXOpeningElement", {
|
||||
assertEach(assertNodeType("JSXAttribute", "JSXSpreadAttribute")),
|
||||
),
|
||||
},
|
||||
typeParameters: {
|
||||
validate: assertNodeType(
|
||||
"TypeParameterInstantiation",
|
||||
"TSTypeParameterInstantiation",
|
||||
),
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user