diff --git a/codemods/react/update-react-imports/__testfixtures__/any-import.input.tsx b/codemods/react/update-react-imports/__testfixtures__/any-import.input.tsx new file mode 100644 index 00000000..bb4f11e3 --- /dev/null +++ b/codemods/react/update-react-imports/__testfixtures__/any-import.input.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; + +function MyComponent() { + const [count, setCount] = React.useState(0); + + const increment = (event: React.MouseEvent) => { + console.log(event); + setCount(count + 1); + }; + + return React.createElement( + 'div', + null, + React.createElement(React.Fragment, null, 'Hello'), + ); +} diff --git a/codemods/react/update-react-imports/__testfixtures__/any-import.output.tsx b/codemods/react/update-react-imports/__testfixtures__/any-import.output.tsx new file mode 100644 index 00000000..a06bed39 --- /dev/null +++ b/codemods/react/update-react-imports/__testfixtures__/any-import.output.tsx @@ -0,0 +1,17 @@ +import type { MouseEvent } from 'react'; +import { useState, createElement, Fragment } from 'react'; + +function MyComponent() { + const [count, setCount] = useState(0); + + const increment = (event: MouseEvent) => { + console.log(event); + setCount(count + 1); + }; + + return createElement( + 'div', + null, + createElement(Fragment, null, 'Hello'), + ); +} \ No newline at end of file diff --git a/codemods/react/update-react-imports/__testfixtures__/default-import.input.tsx b/codemods/react/update-react-imports/__testfixtures__/default-import.input.tsx index 9fcb1075..e7cd30d5 100644 --- a/codemods/react/update-react-imports/__testfixtures__/default-import.input.tsx +++ b/codemods/react/update-react-imports/__testfixtures__/default-import.input.tsx @@ -1,9 +1,9 @@ -import React from "react"; +import React from 'react'; function MyComponent() { - return React.createElement( - "div", - null, - React.createElement(React.Fragment, null, "Hello"), - ); + return React.createElement( + 'div', + null, + React.createElement(React.Fragment, null, 'Hello'), + ); } diff --git a/codemods/react/update-react-imports/__testfixtures__/default-import.output.tsx b/codemods/react/update-react-imports/__testfixtures__/default-import.output.tsx index 2f7d56bb..8459a13d 100644 --- a/codemods/react/update-react-imports/__testfixtures__/default-import.output.tsx +++ b/codemods/react/update-react-imports/__testfixtures__/default-import.output.tsx @@ -1,5 +1,9 @@ -import { createElement, Fragment } from "react"; +import { createElement, Fragment } from 'react'; function MyComponent() { - return createElement("div", null, createElement(Fragment, null, "Hello")); -} + return createElement( + 'div', + null, + createElement(Fragment, null, 'Hello'), + ); +} \ No newline at end of file diff --git a/codemods/react/update-react-imports/__testfixtures__/type-import.output.tsx b/codemods/react/update-react-imports/__testfixtures__/type-import.output.tsx index c691afbc..560afccf 100644 --- a/codemods/react/update-react-imports/__testfixtures__/type-import.output.tsx +++ b/codemods/react/update-react-imports/__testfixtures__/type-import.output.tsx @@ -1,6 +1,7 @@ -import * as React from "react"; +import type { ComponentProps, ReactElement } from "react"; +import { createElement } from "react"; -type Props = React.ComponentProps<"div">; -type Element = React.ReactElement; +type Props = ComponentProps<"div">; +type Element = ReactElement; -const element = React.createElement("div"); +const element = createElement("div"); diff --git a/codemods/react/update-react-imports/src/index.ts b/codemods/react/update-react-imports/src/index.ts index c33b8258..34b47d83 100644 --- a/codemods/react/update-react-imports/src/index.ts +++ b/codemods/react/update-react-imports/src/index.ts @@ -4,416 +4,466 @@ * @format */ -"use strict"; +'use strict'; import type { - API, - FileInfo, - Options, - ImportSpecifier, - Identifier, - StringLiteral, -} from "jscodeshift"; + API, + FileInfo, + Options, + ImportSpecifier, + Identifier, + StringLiteral, +} from 'jscodeshift'; interface ExtendedImportSpecifier extends ImportSpecifier { - importKind?: "type" | "value"; + importKind?: 'type' | 'value'; } export default function transform( - file: FileInfo, - api: API, - options?: Options, + file: FileInfo, + api: API, + options?: Options, ): string | undefined | null { - const j = api.jscodeshift; - const printOptions = options?.printOptions || {}; - const root = j(file.source); - - const destructureNamespaceImports = options?.destructureNamespaceImports; - - // https://github.com/facebook/jscodeshift/blob/master/recipes/retain-first-comment.md - function getFirstNode() { - return root.find(j.Program).get("body", 0).node; - } - - // Save the comments attached to the first node - const firstNode = getFirstNode(); - const { comments } = firstNode; - - function isVariableDeclared(variable: string) { - return ( - root - .find(j.Identifier, { - name: variable, + const j = api.jscodeshift; + const printOptions = options?.printOptions || {}; + const root = j(file.source); + + const destructureNamespaceImports = options?.destructureNamespaceImports; + + // https://github.com/facebook/jscodeshift/blob/master/recipes/retain-first-comment.md + function getFirstNode() { + return root.find(j.Program).get('body', 0).node; + } + + // Save the comments attached to the first node + const firstNode = getFirstNode(); + const { comments } = firstNode; + + function isVariableDeclared(variable: string) { + return ( + root + .find(j.Identifier, { + name: variable, + }) + .filter( + (path) => + path.parent.value.type !== 'MemberExpression' && + path.parent.value.type !== 'QualifiedTypeIdentifier' && + path.parent.value.type !== 'JSXMemberExpression' && + path.parent.value.type !== 'TSQualifiedName', // Add support for TypeScript qualified names + ) + .size() > 0 + ); + } + + // Get all paths that import from React + const reactImportPaths = root + .find(j.ImportDeclaration, { + type: 'ImportDeclaration', }) - .filter( - (path) => - path.parent.value.type !== "MemberExpression" && - path.parent.value.type !== "QualifiedTypeIdentifier" && - path.parent.value.type !== "JSXMemberExpression", - ) - .size() > 0 - ); - } - - // Get all paths that import from React - const reactImportPaths = root - .find(j.ImportDeclaration, { - type: "ImportDeclaration", - }) - .filter((path) => { - return ( - (path.value.source.type === "Literal" || - path.value.source.type === "StringLiteral") && - (path.value.source.value === "React" || - path.value.source.value === "react") - ); + .filter((path) => { + return ( + (path.value.source.type === 'Literal' || + path.value.source.type === 'StringLiteral') && + (path.value.source.value === 'React' || + path.value.source.value === 'react') + ); + }); + + // get all namespace/default React imports + const reactPaths = reactImportPaths.filter((path) => { + return ( + path.value.specifiers!.length > 0 && + path.value.importKind === 'value' && + path.value.specifiers!.some( + (specifier) => specifier.local?.name === 'React', + ) + ); }); - // get all namespace/default React imports - const reactPaths = reactImportPaths.filter((path) => { - return ( - path.value.specifiers!.length > 0 && - path.value.importKind === "value" && - path.value.specifiers!.some( - (specifier) => specifier.local?.name === "React", - ) - ); - }); + if (reactPaths.size() > 1) { + throw Error( + 'There should only be one React import. Please remove the duplicate import and try again.', + ); + } - if (reactPaths.size() > 1) { - throw Error( - "There should only be one React import. Please remove the duplicate import and try again.", + if (reactPaths.size() === 0) { + return null; + } + + const reactPath = reactPaths.paths()[0]; + // Reuse the node so that we can preserve quoting style. + const reactLiteral = reactPath?.value.source as StringLiteral; + + const isDefaultOrNamespaceImport = reactPath?.value.specifiers?.some( + (specifier) => + (specifier.type === 'ImportDefaultSpecifier' || + specifier.type === 'ImportNamespaceSpecifier') && + specifier.local?.name === 'React', ); - } - - if (reactPaths.size() === 0) { - return null; - } - - const reactPath = reactPaths.paths()[0]; - // Reuse the node so that we can preserve quoting style. - const reactLiteral = reactPath?.value.source as StringLiteral; - - const isDefaultImport = reactPath?.value.specifiers?.some( - (specifier) => - specifier.type === "ImportDefaultSpecifier" && - specifier.local?.name === "React", - ); - - // Check to see if we should keep the React import - const isReactImportUsed = - root - .find(j.Identifier, { - name: "React", - }) - .filter((path) => { - return path.parent.parent.value.type !== "ImportDeclaration"; - }) - .size() > 0; - - // local: imported - const reactIdentifiers: Record = {}; - const reactTypeIdentifiers: Record = {}; - let canDestructureReactVariable = false; - if (isReactImportUsed && (isDefaultImport || destructureNamespaceImports)) { - // Checks to see if the react variable is used itself (rather than used to access its properties) - canDestructureReactVariable = - root - .find(j.Identifier, { - name: "React", - }) - .filter((path) => { - return path.parent.parent.value.type !== "ImportDeclaration"; - }) - .filter( - (path) => - !( - path.parent.value.type === "MemberExpression" && - path.parent.value.object.name === "React" - ) && - !( - path.parent.value.type === "QualifiedTypeIdentifier" && - path.parent.value.qualification.name === "React" - ) && - !( - path.parent.value.type === "JSXMemberExpression" && - path.parent.value.object.name === "React" - ), - ) - .size() === 0; - if (canDestructureReactVariable) { - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a type variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Element is declared and we are using React.Element) - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: "Identifier", - name: "React", - }, - }) - .forEach((path) => { - const id = path.value.id.name; - if (path.parent.parent.value.type === "TypeofTypeAnnotation") { - // This is a typeof import so it isn't actually a type - reactIdentifiers[id] = id; - - if (reactTypeIdentifiers[id]) { - canDestructureReactVariable = false; - } - } else { - reactTypeIdentifiers[id] = id; + // Check to see if we should keep the React import + const isReactImportUsed = + root + .find(j.Identifier, { + name: 'React', + }) + .filter((path) => { + return path.parent.parent.value.type !== 'ImportDeclaration'; + }) + .size() > 0; + + // local: imported + const reactIdentifiers: Record = {}; + const reactTypeIdentifiers: Record = {}; + let canDestructureReactVariable = false; + if ( + isReactImportUsed && + (isDefaultOrNamespaceImport || destructureNamespaceImports) + ) { + // Checks to see if the react variable is used itself (rather than used to access its properties) + canDestructureReactVariable = + root + .find(j.Identifier, { + name: 'React', + }) + .filter((path) => { + return ( + path.parent.parent.value.type !== 'ImportDeclaration' + ); + }) + .filter( + (path) => + !( + path.parent.value.type === 'MemberExpression' && + path.parent.value.object.name === 'React' + ) && + !( + path.parent.value.type === + 'QualifiedTypeIdentifier' && + path.parent.value.qualification.name === 'React' + ) && + !( + path.parent.value.type === 'JSXMemberExpression' && + path.parent.value.object.name === 'React' + ) && + !( + path.parent.value.type === 'TSQualifiedName' && + path.parent.value.left.name === 'React' + ) + ) + .size() === 0; + + if (canDestructureReactVariable) { + // Add React identifiers to separate object so we can destructure the imports + // later if we can. If a type variable that we are trying to import has already + // been declared, do not try to destructure imports + // (ex. Element is declared and we are using React.Element) + root.find(j.QualifiedTypeIdentifier, { + qualification: { + type: 'Identifier', + name: 'React', + }, + }).forEach((path) => { + const id = path.value.id.name; + if (path.parent.parent.value.type === 'TypeofTypeAnnotation') { + // This is a typeof import so it isn't actually a type + reactIdentifiers[id] = id; + + if (reactTypeIdentifiers[id]) { + canDestructureReactVariable = false; + } + } else { + reactTypeIdentifiers[id] = id; + + if (reactIdentifiers[id]) { + canDestructureReactVariable = false; + } + } + + if (isVariableDeclared(id)) { + canDestructureReactVariable = false; + } + }); + + // Support TypeScript qualified names (React.MouseEvent etc) + root.find(j.TSQualifiedName, { + left: { + type: 'Identifier', + name: 'React', + }, + }).forEach((path) => { + const right = 'name' in path.value.right + ? path.value.right.name + : (path.value.right as any).name; + reactTypeIdentifiers[right] = right; + + if (reactIdentifiers[right] || isVariableDeclared(right)) { + canDestructureReactVariable = false; + } + }); + + // Add React identifiers to separate object so we can destructure the imports + // later if we can. If a variable that we are trying to import has already + // been declared, do not try to destructure imports + // (ex. createElement is declared and we are using React.createElement) + root.find(j.MemberExpression, { + object: { + type: 'Identifier', + name: 'React', + }, + }).forEach((path) => { + const property = (path.value.property as Identifier).name; + reactIdentifiers[property] = property; + + if ( + isVariableDeclared(property) || + reactTypeIdentifiers[property] + ) { + canDestructureReactVariable = false; + } + }); + + // Add React identifiers to separate object so we can destructure the imports + // later if we can. If a JSX variable that we are trying to import has already + // been declared, do not try to destructure imports + // (ex. Fragment is declared and we are using React.Fragment) + root.find(j.JSXMemberExpression, { + object: { + type: 'JSXIdentifier', + name: 'React', + }, + }).forEach((path) => { + const property = path.value.property.name; + reactIdentifiers[property] = property; + + if ( + isVariableDeclared(property) || + reactTypeIdentifiers[property] + ) { + canDestructureReactVariable = false; + } + }); + } + } - if (reactIdentifiers[id]) { - canDestructureReactVariable = false; - } - } + if (canDestructureReactVariable) { + // replace react identifiers + root.find(j.QualifiedTypeIdentifier, { + qualification: { + type: 'Identifier', + name: 'React', + }, + }).forEach((path) => { + const id = path.value.id.name; + + j(path).replaceWith(j.identifier(id)); + }); - if (isVariableDeclared(id)) { - canDestructureReactVariable = false; - } + // Replace TypeScript qualified names + root.find(j.TSQualifiedName, { + left: { + type: 'Identifier', + name: 'React', + }, + }).forEach((path) => { + const right = 'name' in path.value.right + ? path.value.right.name + : (path.value.right as any).name; + j(path).replaceWith(j.identifier(right)); }); - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. createElement is declared and we are using React.createElement) - root - .find(j.MemberExpression, { - object: { - type: "Identifier", - name: "React", - }, - }) - .forEach((path) => { - const property = (path.value.property as Identifier).name; - reactIdentifiers[property] = property; + root.find(j.MemberExpression, { + object: { + type: 'Identifier', + name: 'React', + }, + }).forEach((path) => { + const property = (path.value.property as Identifier).name; - if (isVariableDeclared(property) || reactTypeIdentifiers[property]) { - canDestructureReactVariable = false; - } + j(path).replaceWith(j.identifier(property)); }); - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a JSX variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Fragment is declared and we are using React.Fragment) - root - .find(j.JSXMemberExpression, { - object: { - type: "JSXIdentifier", - name: "React", - }, - }) - .forEach((path) => { - const property = path.value.property.name; - reactIdentifiers[property] = property; + root.find(j.JSXMemberExpression, { + object: { + type: 'JSXIdentifier', + name: 'React', + }, + }).forEach((path) => { + const property = path.value.property.name; - if (isVariableDeclared(property) || reactTypeIdentifiers[property]) { - canDestructureReactVariable = false; - } + j(path).replaceWith(j.jsxIdentifier(property)); }); - } - } - - if (canDestructureReactVariable) { - // replace react identifiers - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: "Identifier", - name: "React", - }, - }) - .forEach((path) => { - const id = path.value.id.name; - - j(path).replaceWith(j.identifier(id)); - }); - - root - .find(j.MemberExpression, { - object: { - type: "Identifier", - name: "React", - }, - }) - .forEach((path) => { - const property = (path.value.property as Identifier).name; - - j(path).replaceWith(j.identifier(property)); - }); - - root - .find(j.JSXMemberExpression, { - object: { - type: "JSXIdentifier", - name: "React", - }, - }) - .forEach((path) => { - const property = path.value.property.name; - - j(path).replaceWith(j.jsxIdentifier(property)); - }); - - // Add exisiting React imports to map - reactImportPaths.forEach((path) => { - const specifiers = path.value.specifiers; - if (!specifiers) return; - - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i] as ImportSpecifier; - // get all type and regular imports that are imported - // from React - if (specifier.type === "ImportSpecifier") { - if ( - path.value.importKind === "type" || - (specifier as unknown as ExtendedImportSpecifier).importKind === - "type" - ) { - if (specifier.local && specifier.imported) { - reactTypeIdentifiers[specifier.local.name] = ( - specifier.imported as Identifier - ).name; - } - } else { - if (specifier.local && specifier.imported) { - reactIdentifiers[specifier.local.name] = ( - specifier.imported as Identifier - ).name; - } - } - } - } - }); - - const regularImports: ImportSpecifier[] = []; - Object.keys(reactIdentifiers).forEach((local) => { - const imported = reactIdentifiers[local]!; - regularImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); - const typeImports: ImportSpecifier[] = []; - Object.keys(reactTypeIdentifiers).forEach((local) => { - const imported = reactTypeIdentifiers[local]!; - typeImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); + // Add exisiting React imports to map + reactImportPaths.forEach((path) => { + const specifiers = path.value.specifiers; + if (!specifiers) return; + + for (let i = 0; i < specifiers.length; i++) { + const specifier = specifiers[i] as ImportSpecifier; + // get all type and regular imports that are imported + // from React + if (specifier.type === 'ImportSpecifier') { + if ( + path.value.importKind === 'type' || + (specifier as unknown as ExtendedImportSpecifier) + .importKind === 'type' + ) { + if (specifier.local && specifier.imported) { + reactTypeIdentifiers[specifier.local.name] = ( + specifier.imported as Identifier + ).name; + } + } else { + if (specifier.local && specifier.imported) { + reactIdentifiers[specifier.local.name] = ( + specifier.imported as Identifier + ).name; + } + } + } + } + }); - if (regularImports.length > 0 && reactPath) { - j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), - ); - } - if (typeImports.length > 0 && reactPath) { - j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, "type"), - ); - } + const regularImports: ImportSpecifier[] = []; + Object.keys(reactIdentifiers).forEach((local) => { + const imported = reactIdentifiers[local]!; + regularImports.push( + j.importSpecifier(j.identifier(imported), j.identifier(local)), + ); + }); - // remove all old react imports - reactImportPaths.forEach((path) => { - // This is for import type React from 'react' which shouldn't - // be removed - if ( - path.value.specifiers?.some( - (specifier) => - specifier.type === "ImportDefaultSpecifier" && - specifier.local?.name === "React" && - ((specifier as unknown as ExtendedImportSpecifier).importKind === - "type" || - path.value.importKind === "type"), - ) - ) { - j(path).insertAfter( - j.importDeclaration( - [j.importDefaultSpecifier(j.identifier("React"))], - reactLiteral, - "type", - ), - ); - } - j(path).remove(); - }); - } else { - // Remove the import because it's not being used - // If we should keep the React import, just convert - // default imports to named imports - let isImportRemoved = false; - if (!reactPath) return null; - - const specifiers = reactPath.value.specifiers; - if (!specifiers) return null; - - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - if (!specifier) continue; - - if (specifier.type === "ImportNamespaceSpecifier") { - if (!isReactImportUsed) { - isImportRemoved = true; - j(reactPath).remove(); - } - } else if (specifier.type === "ImportDefaultSpecifier") { - if (isReactImportUsed) { - j(reactPath).insertAfter( - j.importDeclaration( - [j.importNamespaceSpecifier(j.identifier("React"))], - reactLiteral, - ), - ); - } + const typeImports: ImportSpecifier[] = []; + Object.keys(reactTypeIdentifiers).forEach((local) => { + const imported = reactTypeIdentifiers[local]!; + typeImports.push( + j.importSpecifier(j.identifier(imported), j.identifier(local)), + ); + }); - if (specifiers.length > 1) { - const typeImports: ImportSpecifier[] = []; - const regularImports: ImportSpecifier[] = []; - for (let x = 0; x < specifiers.length; x++) { - const spec = specifiers[x]; - if (!spec) continue; - - if (spec.type !== "ImportDefaultSpecifier") { - if ( - (spec as unknown as ExtendedImportSpecifier).importKind === - "type" - ) { - typeImports.push(spec as ImportSpecifier); - } else { - regularImports.push(spec as ImportSpecifier); - } - } - } - if (regularImports.length > 0) { + if (regularImports.length > 0 && reactPath) { j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), + j.importDeclaration(regularImports, reactLiteral), ); - } - if (typeImports.length > 0) { + } + if (typeImports.length > 0 && reactPath) { j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, "type"), + j.importDeclaration(typeImports, reactLiteral, 'type'), ); - } } - isImportRemoved = true; - j(reactPath).remove(); - } - } + // remove all old react imports + reactImportPaths.forEach((path) => { + // This is for import type React from 'react' which shouldn't + // be removed + if ( + path.value.specifiers?.some( + (specifier) => + specifier.type === 'ImportDefaultSpecifier' && + specifier.local?.name === 'React' && + ((specifier as unknown as ExtendedImportSpecifier) + .importKind === 'type' || + path.value.importKind === 'type'), + ) + ) { + j(path).insertAfter( + j.importDeclaration( + [j.importDefaultSpecifier(j.identifier('React'))], + reactLiteral, + 'type', + ), + ); + } + j(path).remove(); + }); + } else { + // Remove the import because it's not being used + // If we should keep the React import, just convert + // default imports to named imports + let isImportRemoved = false; + if (!reactPath) return null; + + const specifiers = reactPath.value.specifiers; + if (!specifiers) return null; + + for (let i = 0; i < specifiers.length; i++) { + const specifier = specifiers[i]; + if (!specifier) continue; + + if (specifier.type === 'ImportNamespaceSpecifier') { + if (!isReactImportUsed) { + isImportRemoved = true; + j(reactPath).remove(); + } else if (destructureNamespaceImports) { + // If we can destructure namespace imports, handle it like default imports + j(reactPath).insertAfter( + j.importDeclaration( + [j.importNamespaceSpecifier(j.identifier('React'))], + reactLiteral, + ), + ); + isImportRemoved = true; + j(reactPath).remove(); + } + } else if (specifier.type === 'ImportDefaultSpecifier') { + if (isReactImportUsed) { + j(reactPath).insertAfter( + j.importDeclaration( + [j.importNamespaceSpecifier(j.identifier('React'))], + reactLiteral, + ), + ); + } + + if (specifiers.length > 1) { + const typeImports: ImportSpecifier[] = []; + const regularImports: ImportSpecifier[] = []; + for (let x = 0; x < specifiers.length; x++) { + const spec = specifiers[x]; + if (!spec) continue; + + if (spec.type !== 'ImportDefaultSpecifier') { + if ( + (spec as unknown as ExtendedImportSpecifier) + .importKind === 'type' + ) { + typeImports.push(spec as ImportSpecifier); + } else { + regularImports.push(spec as ImportSpecifier); + } + } + } + if (regularImports.length > 0) { + j(reactPath).insertAfter( + j.importDeclaration(regularImports, reactLiteral), + ); + } + if (typeImports.length > 0) { + j(reactPath).insertAfter( + j.importDeclaration( + typeImports, + reactLiteral, + 'type', + ), + ); + } + } + + isImportRemoved = true; + j(reactPath).remove(); + } + } - if (!isImportRemoved) { - return null; + if (!isImportRemoved) { + return null; + } } - } - // If the first node has been modified or deleted, reattach the comments - const firstNode2 = getFirstNode(); - if (firstNode2 !== firstNode) { - firstNode2.comments = comments; - } + // If the first node has been modified or deleted, reattach the comments + const firstNode2 = getFirstNode(); + if (firstNode2 !== firstNode) { + firstNode2.comments = comments; + } - return root.toSource(printOptions); + return root.toSource(printOptions); } diff --git a/codemods/react/update-react-imports/test/test.ts b/codemods/react/update-react-imports/test/test.ts index 3c45ae34..866989a6 100644 --- a/codemods/react/update-react-imports/test/test.ts +++ b/codemods/react/update-react-imports/test/test.ts @@ -32,6 +32,7 @@ describe("react/update-react-imports", () => { {}, ); + assert.strictEqual(actualOutput?.trim(), OUTPUT.trim()); }); @@ -100,4 +101,26 @@ describe("react/update-react-imports", () => { assert.strictEqual(actualOutput?.trim(), OUTPUT.trim()); }); + + it("Check imports like * as React", async () => { + const INPUT = await readFile( + join(__dirname, "..", "__testfixtures__/any-import.input.tsx"), + "utf-8", + ); + const OUTPUT = await readFile( + join(__dirname, "..", "__testfixtures__/any-import.output.tsx"), + "utf-8", + ); + + const actualOutput = transform( + { + path: "test.tsx", + source: INPUT, + }, + buildApi("tsx"), + {}, + ); + + assert.strictEqual(actualOutput?.trim(), OUTPUT.trim()); + }) });