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) {
|
export function JSXOpeningElement(node: Object) {
|
||||||
this.token("<");
|
this.token("<");
|
||||||
this.print(node.name, node);
|
this.print(node.name, node);
|
||||||
|
this.print(node.typeParameters, node); // TS
|
||||||
if (node.attributes.length > 0) {
|
if (node.attributes.length > 0) {
|
||||||
this.space();
|
this.space();
|
||||||
this.printJoin(node.attributes, node, { separator: spaceSeparator });
|
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
|
// @flow
|
||||||
|
|
||||||
import type { Options } from "../options";
|
import type { Options } from "../options";
|
||||||
import type { File } from "../types";
|
import type { File, JSXOpeningElement } from "../types";
|
||||||
import type { PluginList } from "../plugin-utils";
|
import type { PluginList } from "../plugin-utils";
|
||||||
import { getOptions } from "../options";
|
import { getOptions } from "../options";
|
||||||
import StatementParser from "./statement";
|
import StatementParser from "./statement";
|
||||||
@ -11,6 +11,11 @@ export type PluginsMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class Parser extends StatementParser {
|
export default class Parser extends StatementParser {
|
||||||
|
// Forward-declaration so typescript plugin can override jsx plugin
|
||||||
|
+jsxParseOpeningElementAfterName: (
|
||||||
|
node: JSXOpeningElement,
|
||||||
|
) => JSXOpeningElement;
|
||||||
|
|
||||||
constructor(options: ?Options, input: string) {
|
constructor(options: ?Options, input: string) {
|
||||||
options = getOptions(options);
|
options = getOptions(options);
|
||||||
super(options, input);
|
super(options, input);
|
||||||
|
|||||||
@ -358,11 +358,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
|||||||
this.expect(tt.jsxTagEnd);
|
this.expect(tt.jsxTagEnd);
|
||||||
return this.finishNode(node, "JSXOpeningFragment");
|
return this.finishNode(node, "JSXOpeningFragment");
|
||||||
}
|
}
|
||||||
node.attributes = [];
|
|
||||||
node.name = this.jsxParseElementName();
|
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)) {
|
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);
|
node.selfClosing = this.eat(tt.slash);
|
||||||
this.expect(tt.jsxTagEnd);
|
this.expect(tt.jsxTagEnd);
|
||||||
return this.finishNode(node, "JSXOpeningElement");
|
return this.finishNode(node, "JSXOpeningElement");
|
||||||
|
|||||||
@ -836,10 +836,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
|||||||
return this.finishNode(node, "TSTypeAssertion");
|
return this.finishNode(node, "TSTypeAssertion");
|
||||||
}
|
}
|
||||||
|
|
||||||
tsTryParseTypeArgumentsInExpression(): ?N.TsTypeParameterInstantiation {
|
tsTryParseTypeArgumentsInExpression(
|
||||||
|
eatNextToken: boolean,
|
||||||
|
): ?N.TsTypeParameterInstantiation {
|
||||||
return this.tsTryParseAndCatch(() => {
|
return this.tsTryParseAndCatch(() => {
|
||||||
const res = this.tsParseTypeArguments();
|
const res = this.tsParseTypeArguments();
|
||||||
this.expect(tt.parenL);
|
if (eatNextToken) this.expect(tt.parenL);
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -887,6 +889,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
|||||||
return this.finishNode(node, "TSTypeAliasDeclaration");
|
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.
|
* Runs `cb` in a type context.
|
||||||
* This should be called one token *before* the first type token,
|
* 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 {
|
tsParseTypeArguments(): N.TsTypeParameterInstantiation {
|
||||||
const node = this.startNode();
|
const node = this.startNode();
|
||||||
node.params = this.tsInType(() => {
|
node.params = this.tsInType(() =>
|
||||||
this.expectRelational("<");
|
// Temporarily remove a JSX parsing context, which makes us scan different tokens.
|
||||||
return this.tsParseDelimitedList(
|
this.tsInNoContext(() => {
|
||||||
"TypeParametersOrArguments",
|
this.expectRelational("<");
|
||||||
this.tsParseType.bind(this),
|
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(">");
|
this.expectRelational(">");
|
||||||
return this.finishNode(node, "TSTypeParameterInstantiation");
|
return this.finishNode(node, "TSTypeParameterInstantiation");
|
||||||
}
|
}
|
||||||
@ -1375,7 +1393,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
|||||||
node.callee = base;
|
node.callee = base;
|
||||||
|
|
||||||
// May be passing type arguments. But may just be the `<` operator.
|
// 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) {
|
if (typeArguments) {
|
||||||
// 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)
|
||||||
@ -2102,4 +2123,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
|
|||||||
// Avoid unnecessary lookahead in checking for abstract class unless needed!
|
// Avoid unnecessary lookahead in checking for abstract class unless needed!
|
||||||
return super.canHaveLeadingDecorator() || this.isAbstractClass();
|
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 {
|
readToken_slash(): void {
|
||||||
// '/'
|
// '/'
|
||||||
if (this.state.exprAllowed) {
|
if (this.state.exprAllowed && !this.state.inType) {
|
||||||
++this.state.pos;
|
++this.state.pos;
|
||||||
this.readRegexp();
|
this.readRegexp();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -573,7 +573,7 @@ export type TemplateLiteral = NodeBase & {
|
|||||||
expressions: $ReadOnlyArray<Expression>,
|
expressions: $ReadOnlyArray<Expression>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TaggedTmplateExpression = NodeBase & {
|
export type TaggedTemplateExpression = NodeBase & {
|
||||||
type: "TaggedTemplateExpression",
|
type: "TaggedTemplateExpression",
|
||||||
tag: Expression,
|
tag: Expression,
|
||||||
quasi: TemplateLiteral,
|
quasi: TemplateLiteral,
|
||||||
@ -820,7 +820,13 @@ export type JSXEmptyExpression = Node;
|
|||||||
export type JSXSpreadChild = Node;
|
export type JSXSpreadChild = Node;
|
||||||
export type JSXExpressionContainer = Node;
|
export type JSXExpressionContainer = Node;
|
||||||
export type JSXAttribute = 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 JSXClosingElement = Node;
|
||||||
export type JSXElement = Node;
|
export type JSXElement = Node;
|
||||||
export type JSXOpeningFragment = 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) {
|
NewExpression(path) {
|
||||||
path.node.typeParameters = null;
|
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")),
|
assertEach(assertNodeType("JSXAttribute", "JSXSpreadAttribute")),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
typeParameters: {
|
||||||
|
validate: assertNodeType(
|
||||||
|
"TypeParameterInstantiation",
|
||||||
|
"TSTypeParameterInstantiation",
|
||||||
|
),
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user