diff --git a/packages/babel-helper-builder-react-jsx/src/index.ts b/packages/babel-helper-builder-react-jsx/src/index.ts index e9f436afb6fa..f51975543fbc 100644 --- a/packages/babel-helper-builder-react-jsx/src/index.ts +++ b/packages/babel-helper-builder-react-jsx/src/index.ts @@ -9,7 +9,6 @@ import { isJSXMemberExpression, isJSXNamespacedName, isJSXSpreadAttribute, - isLiteral, isObjectExpression, isReferenced, isStringLiteral, @@ -24,10 +23,12 @@ import { thisExpression, } from "@babel/types"; import annotateAsPure from "@babel/helper-annotate-as-pure"; -import type { Visitor } from "@babel/traverse"; +import type { NodePath, Visitor } from "@babel/traverse"; +import type { PluginPass, File } from "@babel/core"; +import type * as t from "@babel/types"; type ElementState = { - tagExpr: any; // tag node, + tagExpr: t.Expression; // tag node, tagName: string | undefined | null; // raw string tag name, args: Array; // array of call arguments, call?: any; // optional call property that can be set to override the call expression returned, @@ -35,8 +36,17 @@ type ElementState = { callee?: any; }; -export default function (opts) { - const visitor: Visitor = {}; +export interface Options { + filter?: (node: t.Node, file: File) => boolean; + pre?: (state: ElementState, file: File) => void; + post?: (state: ElementState, file: File) => void; + compat?: boolean; + pure?: string; + throwIfNamespace?: boolean; +} + +export default function (opts: Options) { + const visitor: Visitor = {}; visitor.JSXNamespacedName = function (path) { if (opts.throwIfNamespace) { @@ -54,8 +64,8 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, }; visitor.JSXElement = { - exit(path, file) { - const callExpr = buildElementCall(path, file); + exit(path, state) { + const callExpr = buildElementCall(path, state.file); if (callExpr) { path.replaceWith(inherits(callExpr, path.node)); } @@ -63,13 +73,13 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, }; visitor.JSXFragment = { - exit(path, file) { + exit(path, state) { if (opts.compat) { throw path.buildCodeFrameError( "Fragment tags are only supported in React 16 and up.", ); } - const callExpr = buildFragmentCall(path, file); + const callExpr = buildFragmentCall(path, state.file); if (callExpr) { path.replaceWith(inherits(callExpr, path.node)); } @@ -78,7 +88,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return visitor; - function convertJSXIdentifier(node, parent) { + function convertJSXIdentifier( + node: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName, + parent: t.JSXOpeningElement | t.JSXMemberExpression, + ): t.ThisExpression | t.StringLiteral | t.MemberExpression | t.Identifier { if (isJSXIdentifier(node)) { if (node.name === "this" && isReferenced(node, parent)) { return thisExpression(); @@ -101,10 +114,13 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return stringLiteral(`${node.namespace.name}:${node.name.name}`); } + // @ts-expect-error return node; } - function convertAttributeValue(node) { + function convertAttributeValue( + node: t.JSXAttribute["value"] | t.BooleanLiteral, + ) { if (isJSXExpressionContainer(node)) { return node.expression; } else { @@ -112,12 +128,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } } - function convertAttribute(node) { - const value = convertAttributeValue(node.value || booleanLiteral(true)); - + function convertAttribute(node: t.JSXAttribute | t.JSXSpreadAttribute) { if (isJSXSpreadAttribute(node)) { return spreadElement(node.argument); } + const value = convertAttributeValue(node.value || booleanLiteral(true)); if (isStringLiteral(value) && !isJSXExpressionContainer(node.value)) { value.value = value.value.replace(/\n\s+/g, " "); @@ -127,35 +142,45 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } if (isJSXNamespacedName(node.name)) { + // @ts-expect-error Mutating AST nodes node.name = stringLiteral( node.name.namespace.name + ":" + node.name.name.name, ); } else if (isValidIdentifier(node.name.name, false)) { + // @ts-expect-error Mutating AST nodes node.name.type = "Identifier"; } else { + // @ts-expect-error Mutating AST nodes node.name = stringLiteral(node.name.name); } - return inherits(objectProperty(node.name, value), node); + return inherits( + objectProperty( + // @ts-expect-error Mutating AST nodes + node.name, + value, + ), + node, + ); } - function buildElementCall(path, file) { + function buildElementCall(path: NodePath, file: File) { if (opts.filter && !opts.filter(path.node, file)) return; const openingPath = path.get("openingElement"); - openingPath.parent.children = react.buildChildren(openingPath.parent); + // @ts-expect-error mutating AST nodes + path.node.children = react.buildChildren(path.node); const tagExpr = convertJSXIdentifier( openingPath.node.name, openingPath.node, ); - const args = []; + const args: (t.Expression | t.JSXElement | t.JSXFragment)[] = []; - let tagName; + let tagName: string; if (isIdentifier(tagExpr)) { tagName = tagExpr.name; - } else if (isLiteral(tagExpr)) { - // @ts-expect-error todo(flow->ts) NullLiteral + } else if (isStringLiteral(tagExpr)) { tagName = tagExpr.value; } @@ -170,18 +195,23 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, opts.pre(state, file); } - let attribs = openingPath.node.attributes; + const attribs = openingPath.node.attributes; + let convertedAttributes: t.Expression; if (attribs.length) { if (process.env.BABEL_8_BREAKING) { - attribs = objectExpression(attribs.map(convertAttribute)); + convertedAttributes = objectExpression(attribs.map(convertAttribute)); } else { - attribs = buildOpeningElementAttributes(attribs, file); + convertedAttributes = buildOpeningElementAttributes(attribs, file); } } else { - attribs = nullLiteral(); + convertedAttributes = nullLiteral(); } - args.push(attribs, ...path.node.children); + args.push( + convertedAttributes, + // @ts-expect-error JSXExpressionContainer has been transformed by convertAttributeValue + ...path.node.children, + ); if (opts.post) { opts.post(state, file); @@ -193,7 +223,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return call; } - function pushProps(_props, objs) { + function pushProps( + _props: (t.ObjectProperty | t.SpreadElement)[], + objs: t.Expression[], + ) { if (!_props.length) return _props; objs.push(objectExpression(_props)); @@ -207,9 +240,12 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, * all prior attributes to an array for later processing. */ - function buildOpeningElementAttributes(attribs, file) { - let _props = []; - const objs = []; + function buildOpeningElementAttributes( + attribs: (t.JSXAttribute | t.JSXSpreadAttribute)[], + file: File, + ): t.Expression { + let _props: (t.ObjectProperty | t.SpreadElement)[] = []; + const objs: t.Expression[] = []; const { useSpread = false } = file.opts; if (typeof useSpread !== "boolean") { @@ -250,10 +286,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } pushProps(_props, objs); + let convertedAttribs: t.Expression; if (objs.length === 1) { // only one object - attribs = objs[0]; + convertedAttribs = objs[0]; } else { // looks like we have multiple objects if (!isObjectExpression(objs[0])) { @@ -265,20 +302,20 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, : file.addHelper("extends"); // spread it - attribs = callExpression(helper, objs); + convertedAttribs = callExpression(helper, objs); } - return attribs; + return convertedAttribs; } - function buildFragmentCall(path, file) { + function buildFragmentCall(path: NodePath, file: File) { if (opts.filter && !opts.filter(path.node, file)) return; - const openingPath = path.get("openingElement"); - openingPath.parent.children = react.buildChildren(openingPath.parent); + // @ts-expect-error mutating AST nodes + path.node.children = react.buildChildren(path.node); - const args = []; - const tagName = null; + const args: t.Expression[] = []; + const tagName: null = null; const tagExpr = file.get("jsxFragIdentifier")(); const state: ElementState = { @@ -293,7 +330,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } // no attributes are allowed with <> syntax - args.push(nullLiteral(), ...path.node.children); + args.push( + nullLiteral(), + // @ts-expect-error JSXExpressionContainer has been transformed by convertAttributeValue + ...path.node.children, + ); if (opts.post) { opts.post(state, file); diff --git a/packages/babel-plugin-transform-react-display-name/src/index.ts b/packages/babel-plugin-transform-react-display-name/src/index.ts index f19c4dd0555f..91d9c85f5a27 100644 --- a/packages/babel-plugin-transform-react-display-name/src/index.ts +++ b/packages/babel-plugin-transform-react-display-name/src/index.ts @@ -2,17 +2,24 @@ import { declare } from "@babel/helper-plugin-utils"; import path from "path"; import { types as t } from "@babel/core"; +type ReactCreateClassCall = t.CallExpression & { + arguments: [t.ObjectExpression]; +}; + export default declare(api => { api.assertVersion(7); - function addDisplayName(id, call) { + function addDisplayName(id: string, call: ReactCreateClassCall) { const props = call.arguments[0].properties; let safe = true; for (let i = 0; i < props.length; i++) { const prop = props[i]; + if (t.isSpreadElement(prop)) { + continue; + } const key = t.toComputedKey(prop); - if (t.isLiteral(key, { value: "displayName" })) { + if (t.isStringLiteral(key, { value: "displayName" })) { safe = false; break; } @@ -27,9 +34,10 @@ export default declare(api => { const isCreateClassCallExpression = t.buildMatchMemberExpression("React.createClass"); - const isCreateClassAddon = callee => callee.name === "createReactClass"; + const isCreateClassAddon = (callee: t.CallExpression["callee"]) => + t.isIdentifier(callee, { name: "createReactClass" }); - function isCreateClass(node) { + function isCreateClass(node?: t.Node): node is ReactCreateClassCall { if (!node || !t.isCallExpression(node)) return false; // not createReactClass nor React.createClass call member object @@ -74,7 +82,7 @@ export default declare(api => { const { node } = path; if (!isCreateClass(node)) return; - let id; + let id: t.LVal | t.Expression | t.PrivateName | null; // crawl up the ancestry looking for possible candidates for displayName inference path.find(function (path) { diff --git a/packages/babel-plugin-transform-react-inline-elements/src/index.ts b/packages/babel-plugin-transform-react-inline-elements/src/index.ts index 46199b940b2b..119971a4f392 100644 --- a/packages/babel-plugin-transform-react-inline-elements/src/index.ts +++ b/packages/babel-plugin-transform-react-inline-elements/src/index.ts @@ -5,7 +5,7 @@ import { types as t } from "@babel/core"; export default declare(api => { api.assertVersion(7); - function hasRefOrSpread(attrs) { + function hasRefOrSpread(attrs: t.JSXOpeningElement["attributes"]) { for (let i = 0; i < attrs.length; i++) { const attr = attrs[i]; if (t.isJSXSpreadAttribute(attr)) return true; @@ -14,7 +14,7 @@ export default declare(api => { return false; } - function isJSXAttributeOfName(attr, name) { + function isJSXAttributeOfName(attr: t.JSXAttribute, name: string) { return ( t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: name }) ); @@ -23,9 +23,8 @@ export default declare(api => { const visitor = helper({ filter(node) { return ( - // Regular JSX nodes have an `openingElement`. JSX fragments, however, don't have an - // `openingElement` which causes `node.openingElement.attributes` to throw. - node.openingElement && !hasRefOrSpread(node.openingElement.attributes) + node.type === "JSXElement" && + !hasRefOrSpread(node.openingElement.attributes) ); }, pre(state) { diff --git a/packages/babel-plugin-transform-react-jsx-compat/src/index.ts b/packages/babel-plugin-transform-react-jsx-compat/src/index.ts index 294607ba79ec..510f20480628 100644 --- a/packages/babel-plugin-transform-react-jsx-compat/src/index.ts +++ b/packages/babel-plugin-transform-react-jsx-compat/src/index.ts @@ -8,7 +8,7 @@ export default declare(api => { return { name: "transform-react-jsx-compat", - manipulateOptions(opts, parserOpts) { + manipulateOptions(_, parserOpts) { parserOpts.plugins.push("jsx"); }, diff --git a/packages/babel-plugin-transform-react-jsx-source/src/index.ts b/packages/babel-plugin-transform-react-jsx-source/src/index.ts index 0f5bee8a3bf9..08602e94a1ee 100644 --- a/packages/babel-plugin-transform-react-jsx-source/src/index.ts +++ b/packages/babel-plugin-transform-react-jsx-source/src/index.ts @@ -13,15 +13,23 @@ * */ import { declare } from "@babel/helper-plugin-utils"; -import { types as t } from "@babel/core"; +import { type PluginPass, types as t } from "@babel/core"; +import type { Visitor } from "@babel/traverse"; const TRACE_ID = "__source"; const FILE_NAME_VAR = "_jsxFileName"; -export default declare(api => { +type State = { + fileNameIdentifier: t.Identifier; +}; +export default declare(api => { api.assertVersion(7); - function makeTrace(fileNameIdentifier, lineNumber, column0Based) { + function makeTrace( + fileNameIdentifier: t.Identifier, + lineNumber: number, + column0Based: number, + ) { const fileLineLiteral = lineNumber != null ? t.numericLiteral(lineNumber) : t.nullLiteral(); const fileColumnLiteral = @@ -47,18 +55,21 @@ export default declare(api => { ]); } - const visitor = { + const visitor: Visitor = { JSXOpeningElement(path, state) { const id = t.jsxIdentifier(TRACE_ID); - const location = path.container.openingElement.loc; + const location = (path.container as t.JSXElement).openingElement.loc; if (!location) { // the element was generated and doesn't have location information return; } - const attributes = path.container.openingElement.attributes; + const attributes = (path.container as t.JSXElement).openingElement + .attributes; for (let i = 0; i < attributes.length; i++) { - const name = attributes[i].name; + // @ts-expect-error .name is not defined in JSXSpreadElement + const name = attributes[i].name as t.JSXAttribute["name"] | void; + // @ts-expect-error TS can not narrow down optional chain if (name?.name === TRACE_ID) { // The __source attribute already exists return; 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 2fb18e6ddb9c..3e3a93963030 100644 --- a/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts +++ b/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts @@ -50,8 +50,14 @@ export interface Options { useBuiltIns: boolean; useSpread?: boolean; } -export default function createPlugin({ name, development }) { - return declare((api, options: Options) => { +export default function createPlugin({ + name, + development, +}: { + name: string; + development: boolean; +}) { + return declare((_, options: Options) => { const { pure: PURE_ANNOTATION, @@ -369,7 +375,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return false; } - function convertJSXIdentifier(node, parent) { + function convertJSXIdentifier( + node: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName, + parent: t.JSXOpeningElement | t.JSXMemberExpression, + ): t.ThisExpression | t.StringLiteral | t.MemberExpression | t.Identifier { if (t.isJSXIdentifier(node)) { if (node.name === "this" && t.isReferenced(node, parent)) { return t.thisExpression(); @@ -392,10 +401,13 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return t.stringLiteral(`${node.namespace.name}:${node.name.name}`); } + // @ts-expect-error return node; } - function convertAttributeValue(node) { + function convertAttributeValue( + node: t.JSXAttribute["value"] | t.BooleanLiteral, + ) { if (t.isJSXExpressionContainer(node)) { return node.expression; } else { @@ -486,7 +498,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, // Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self) function buildJSXElementCall(path: NodePath, file: PluginPass) { const openingPath = path.get("openingElement"); - const args = [getTag(openingPath)]; + const args: t.Expression[] = [getTag(openingPath)]; const attribsArray = []; const extracted = Object.create(null); @@ -640,6 +652,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, path, openingPath.get("attributes"), ), + // @ts-expect-error JSXSpreadChild has been transformed in convertAttributeValue ...t.react.buildChildren(path.node), ]); } @@ -650,11 +663,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, openingPath.node, ); - let tagName; + let tagName: string; if (t.isIdentifier(tagExpr)) { tagName = tagExpr.name; - } else if (t.isLiteral(tagExpr)) { - // @ts-expect-error todo(flow->ts) value in missing for NullLiteral + } else if (t.isStringLiteral(tagExpr)) { tagName = tagExpr.value; } @@ -723,7 +735,7 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } } - const props = []; + const props: ObjectExpression["properties"] = []; const found = Object.create(null); for (const attr of attribs) {