diff --git a/packages/babel-core/src/index.ts b/packages/babel-core/src/index.ts
index f938b3c7d3d4..8abd7cc17489 100644
--- a/packages/babel-core/src/index.ts
+++ b/packages/babel-core/src/index.ts
@@ -2,6 +2,7 @@ declare const PACKAGE_JSON: { name: string; version: string };
export const version = PACKAGE_JSON.version;
export { default as File } from "./transformation/file/file";
+export type { default as PluginPass } from "./transformation/plugin-pass";
export { default as buildExternalHelpers } from "./tools/build-external-helpers";
export { resolvePlugin, resolvePreset } from "./config/files";
diff --git a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__self-as-jsx-attribute/input.js b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__self-as-jsx-attribute/input.js
new file mode 100644
index 000000000000..b7438e2124c8
--- /dev/null
+++ b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__self-as-jsx-attribute/input.js
@@ -0,0 +1 @@
+var x =
;
diff --git a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__self-as-jsx-attribute/options.json b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__self-as-jsx-attribute/options.json
new file mode 100644
index 000000000000..09c46c7f6b2e
--- /dev/null
+++ b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__self-as-jsx-attribute/options.json
@@ -0,0 +1,4 @@
+{
+ "throws": "Duplicate __self prop found. You are most likely using the deprecated transform-react-jsx-self Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.",
+ "plugins": ["transform-react-jsx-development"]
+}
diff --git a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__source-as-jsx-attribute/input.js b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__source-as-jsx-attribute/input.js
new file mode 100644
index 000000000000..6c7b703aa861
--- /dev/null
+++ b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__source-as-jsx-attribute/input.js
@@ -0,0 +1 @@
+var x = ;
diff --git a/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__source-as-jsx-attribute/options.json b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__source-as-jsx-attribute/options.json
new file mode 100644
index 000000000000..1fcae672734d
--- /dev/null
+++ b/packages/babel-plugin-transform-react-jsx-development/test/fixtures/cross-platform/disallow-__source-as-jsx-attribute/options.json
@@ -0,0 +1,4 @@
+{
+ "throws": "Duplicate __source prop found. You are most likely using the deprecated transform-react-jsx-source Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.",
+ "plugins": ["transform-react-jsx-development"]
+}
diff --git a/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts b/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts
index 40974d740bc8..d4afe577f25c 100644
--- a/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts
+++ b/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts
@@ -1,8 +1,28 @@
import jsx from "@babel/plugin-syntax-jsx";
import { declare } from "@babel/helper-plugin-utils";
import { types as t } from "@babel/core";
+import type { PluginPass } from "@babel/core";
+import type { NodePath, Visitor } from "@babel/traverse";
import { addNamed, addNamespace, isModule } from "@babel/helper-module-imports";
import annotateAsPure from "@babel/helper-annotate-as-pure";
+import type {
+ ArrowFunctionExpression,
+ CallExpression,
+ Class,
+ Expression,
+ FunctionParent,
+ Identifier,
+ JSXAttribute,
+ JSXElement,
+ JSXFragment,
+ JSXOpeningElement,
+ JSXSpreadAttribute,
+ MemberExpression,
+ ObjectExpression,
+ Program,
+} from "@babel/types";
+
+type Diff = T extends U ? never : T;
const DEFAULT = {
importSource: "react",
@@ -17,8 +37,10 @@ const JSX_RUNTIME_ANNOTATION_REGEX = /\*?\s*@jsxRuntime\s+([^\s]+)/;
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;
-const get = (pass, name) => pass.get(`@babel/plugin-react-jsx/${name}`);
-const set = (pass, name, v) => pass.set(`@babel/plugin-react-jsx/${name}`, v);
+const get = (pass: PluginPass, name: string) =>
+ pass.get(`@babel/plugin-react-jsx/${name}`);
+const set = (pass: PluginPass, name: string, v: any) =>
+ pass.set(`@babel/plugin-react-jsx/${name}`, v);
export default function createPlugin({ name, development }) {
return declare((api, options) => {
@@ -90,19 +112,8 @@ export default function createPlugin({ name, development }) {
}
}
- const injectMetaPropertiesVisitor = {
+ const injectMetaPropertiesVisitor: Visitor = {
JSXOpeningElement(path, state) {
- for (const attr of path.get("attributes")) {
- if (!attr.isJSXElement()) continue;
-
- const { name } = attr.node.name;
- if (name === "__source" || name === "__self") {
- throw path.buildCodeFrameError(
- `__source and __self should not be defined in props and are reserved for internal usage.`,
- );
- }
- }
-
const attributes = [];
if (isThisAllowed(path)) {
attributes.push(
@@ -144,11 +155,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
Program: {
enter(path, state) {
const { file } = state;
- let runtime = RUNTIME_DEFAULT;
+ let runtime: string = RUNTIME_DEFAULT;
- let source = IMPORT_SOURCE_DEFAULT;
- let pragma = PRAGMA_DEFAULT;
- let pragmaFrag = PRAGMA_FRAG_DEFAULT;
+ let source: string = IMPORT_SOURCE_DEFAULT;
+ let pragma: string = PRAGMA_DEFAULT;
+ let pragmaFrag: string = PRAGMA_FRAG_DEFAULT;
let sourceSet = !!options.importSource;
let pragmaSet = !!options.pragma;
@@ -208,7 +219,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
);
}
- const define = (name, id) =>
+ const define = (name: string, id: string) =>
set(state, name, createImportLazily(state, path, id, source));
define("id/jsx", development ? "jsxDEV" : "jsx");
@@ -280,20 +291,23 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
path.node.value = t.jsxExpressionContainer(path.node.value);
}
},
- },
+ } as Visitor,
};
// Finds the closest parent function that provides `this`. Specifically, this looks for
// the first parent function that isn't an arrow function.
//
// Derived from `Scope#getFunctionParent`
- function getThisFunctionParent(path) {
+ function getThisFunctionParent(
+ path: NodePath,
+ ): NodePath> | null {
let scope = path.scope;
do {
if (
scope.path.isFunctionParent() &&
!scope.path.isArrowFunctionExpression()
) {
+ // @ts-expect-error ts can not infer scope.path is Diff
return scope.path;
}
} while ((scope = scope.parent));
@@ -301,12 +315,12 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
// Returns whether the class has specified a superclass.
- function isDerivedClass(classPath) {
+ function isDerivedClass(classPath: NodePath) {
return classPath.node.superClass !== null;
}
// Returns whether `this` is allowed at given path.
- function isThisAllowed(path) {
+ function isThisAllowed(path: NodePath) {
// This specifically skips arrow functions as they do not rewrite `this`.
const parentMethodOrFunction = getThisFunctionParent(path);
if (parentMethodOrFunction === null) {
@@ -323,10 +337,16 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
return true;
}
// Now we are in a constructor. If it is a derived class, we do not reference `this`.
- return !isDerivedClass(parentMethodOrFunction.parentPath.parentPath);
+ return !isDerivedClass(
+ parentMethodOrFunction.parentPath.parentPath as NodePath,
+ );
}
- function call(pass, name, args) {
+ function call(
+ pass: PluginPass,
+ name: string,
+ args: CallExpression["arguments"],
+ ) {
const node = t.callExpression(get(pass, `id/${name}`)(), args);
if (PURE_ANNOTATION ?? get(pass, "defaultPure")) annotateAsPure(node);
return node;
@@ -337,7 +357,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
// from . This is an intermediary
// step while we deprecate key spread from props. Afterwards,
// we will stop using createElement in the transform.
- function shouldUseCreateElement(path) {
+ function shouldUseCreateElement(path: NodePath) {
const openingPath = path.get("openingElement");
const attributes = openingPath.node.attributes;
@@ -391,7 +411,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
}
- function accumulateAttribute(array, attribute) {
+ function accumulateAttribute(
+ array: ObjectExpression["properties"],
+ attribute: NodePath,
+ ) {
if (t.isJSXSpreadAttribute(attribute.node)) {
const arg = attribute.node.argument;
// Collect properties into props array if spreading object expression
@@ -426,27 +449,34 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
if (t.isJSXNamespacedName(attribute.node.name)) {
+ // @ts-expect-error mutating AST
attribute.node.name = t.stringLiteral(
attribute.node.name.namespace.name +
":" +
attribute.node.name.name.name,
);
} else if (t.isValidIdentifier(attribute.node.name.name, false)) {
+ // @ts-expect-error mutating AST
attribute.node.name.type = "Identifier";
} else {
+ // @ts-expect-error mutating AST
attribute.node.name = t.stringLiteral(attribute.node.name.name);
}
array.push(
t.inherits(
- t.objectProperty(attribute.node.name, value),
+ t.objectProperty(
+ // @ts-expect-error The attribute.node.name is an Identifier now
+ attribute.node.name,
+ value,
+ ),
attribute.node,
),
);
return array;
}
- function buildChildrenProperty(children) {
+ function buildChildrenProperty(children: Expression[]) {
let childrenNode;
if (children.length === 1) {
childrenNode = children[0];
@@ -462,7 +492,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
// Builds JSX into:
// Production: React.jsx(type, arguments, key)
// Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
- function buildJSXElementCall(path, file) {
+ function buildJSXElementCall(path: NodePath, file: PluginPass) {
const openingPath = path.get("openingElement");
const args = [getTag(openingPath)];
@@ -507,7 +537,8 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
if (attribsArray.length || children.length) {
attribs = buildJSXOpeningElementAttributes(
attribsArray,
- file,
+ //@ts-expect-error The children here contains JSXSpreadChild,
+ // which will be thrown later
children,
);
} else {
@@ -536,7 +567,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
// Builds props for React.jsx. This function adds children into the props
// and ensures that props is always an object
- function buildJSXOpeningElementAttributes(attribs, file, children) {
+ function buildJSXOpeningElementAttributes(
+ attribs: NodePath[],
+ children: Expression[],
+ ) {
const props = attribs.reduce(accumulateAttribute, []);
// In React.jsx, children is no longer a separate argument, but passed in
@@ -551,14 +585,25 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
// Builds JSX Fragment <>> into
// Production: React.jsx(type, arguments)
// Development: React.jsxDEV(type, { children })
- function buildJSXFragmentCall(path, file) {
+ function buildJSXFragmentCall(
+ path: NodePath,
+ file: PluginPass,
+ ) {
const args = [get(file, "id/fragment")()];
const children = t.react.buildChildren(path.node);
args.push(
t.objectExpression(
- children.length > 0 ? [buildChildrenProperty(children)] : [],
+ children.length > 0
+ ? [
+ buildChildrenProperty(
+ //@ts-expect-error The children here contains JSXSpreadChild,
+ // which will be thrown later
+ children,
+ ),
+ ]
+ : [],
),
);
@@ -574,7 +619,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
// Builds JSX Fragment <>> into
// React.createElement(React.Fragment, null, ...children)
- function buildCreateElementFragmentCall(path, file) {
+ function buildCreateElementFragmentCall(
+ path: NodePath,
+ file: PluginPass,
+ ) {
if (filter && !filter(path.node, file)) return;
return call(file, "createElement", [
@@ -587,7 +635,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
// Builds JSX into:
// Production: React.createElement(type, arguments, children)
// Development: React.createElement(type, arguments, children, source, self)
- function buildCreateElementCall(path, file) {
+ function buildCreateElementCall(
+ path: NodePath,
+ file: PluginPass,
+ ) {
const openingPath = path.get("openingElement");
return call(file, "createElement", [
@@ -601,7 +652,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
]);
}
- function getTag(openingPath) {
+ function getTag(openingPath: NodePath) {
const tagExpr = convertJSXIdentifier(
openingPath.node.name,
openingPath.node,
@@ -628,7 +679,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
* breaking on spreads, we then push a new object containing
* all prior attributes to an array for later processing.
*/
- function buildCreateElementOpeningElementAttributes(file, path, attribs) {
+ function buildCreateElementOpeningElementAttributes(
+ file: PluginPass,
+ path: NodePath,
+ attribs: NodePath[],
+ ) {
const runtime = get(file, "runtime");
if (!process.env.BABEL_8_BREAKING) {
if (runtime !== "automatic") {
@@ -704,7 +759,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
});
- function getSource(source, importName) {
+ function getSource(source: string, importName: string) {
switch (importName) {
case "Fragment":
return `${source}/${development ? "jsx-dev-runtime" : "jsx-runtime"}`;
@@ -718,7 +773,12 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
}
- function createImportLazily(pass, path, importName, source) {
+ function createImportLazily(
+ pass: PluginPass,
+ path: NodePath,
+ importName: string,
+ source: string,
+ ): () => Identifier | MemberExpression {
return () => {
const actualSource = getSource(source, importName);
if (isModule(path)) {
@@ -749,20 +809,25 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`,
}
}
-function toMemberExpression(id) {
- return id
- .split(".")
- .map(name => t.identifier(name))
- .reduce((object, property) => t.memberExpression(object, property));
+function toMemberExpression(id: string): Identifier | MemberExpression {
+ return (
+ id
+ .split(".")
+ .map(name => t.identifier(name))
+ // @ts-expect-error - The Array#reduce does not have a signature
+ // where the type of initialial value differs from callback return type
+ .reduce((object, property) => t.memberExpression(object, property))
+ );
}
-function makeSource(path, state) {
+function makeSource(path: NodePath, state: PluginPass) {
const location = path.node.loc;
if (!location) {
// the element was generated and doesn't have location information
return path.scope.buildUndefinedNode();
}
+ // @ts-expect-error todo: avoid mutating PluginPass
if (!state.fileNameIdentifier) {
const { filename = "" } = state;
@@ -774,17 +839,25 @@ function makeSource(path, state) {
init: t.stringLiteral(filename),
});
}
+ // @ts-expect-error todo: avoid mutating PluginPass
state.fileNameIdentifier = fileNameIdentifier;
}
return makeTrace(
- t.cloneNode(state.fileNameIdentifier),
+ t.cloneNode(
+ // @ts-expect-error todo: avoid mutating PluginPass
+ state.fileNameIdentifier,
+ ),
location.start.line,
location.start.column,
);
}
-function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
+function makeTrace(
+ fileNameIdentifier: Identifier,
+ lineNumber?: number,
+ column0Based?: number,
+) {
const fileLineLiteral =
lineNumber != null ? t.numericLiteral(lineNumber) : t.nullLiteral();
@@ -810,7 +883,7 @@ function makeTrace(fileNameIdentifier, lineNumber, column0Based) {
]);
}
-function sourceSelfError(path, name) {
+function sourceSelfError(path: NodePath, name: string) {
const pluginName = `transform-react-jsx-${name.slice(2)}`;
return path.buildCodeFrameError(
diff --git a/packages/babel-types/src/builders/react/buildChildren.ts b/packages/babel-types/src/builders/react/buildChildren.ts
index 0893a8bb301d..90f9328853e6 100644
--- a/packages/babel-types/src/builders/react/buildChildren.ts
+++ b/packages/babel-types/src/builders/react/buildChildren.ts
@@ -7,22 +7,14 @@ import cleanJSXElementLiteralChild from "../../utils/react/cleanJSXElementLitera
import type * as t from "../..";
type ReturnedChild =
- | t.JSXExpressionContainer
| t.JSXSpreadChild
| t.JSXElement
| t.JSXFragment
| t.Expression;
-export default function buildChildren(node: {
- children: ReadonlyArray<
- | t.JSXText
- | t.JSXExpressionContainer
- | t.JSXSpreadChild
- | t.JSXElement
- | t.JSXFragment
- | t.JSXEmptyExpression
- >;
-}): ReturnedChild[] {
+export default function buildChildren(
+ node: t.JSXElement | t.JSXFragment,
+): ReturnedChild[] {
const elements = [];
for (let i = 0; i < node.children.length; i++) {