diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.input.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.input.tsx new file mode 100644 index 0000000000..363af35b13 --- /dev/null +++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.input.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { + Hydrate, + QueryClient, + QueryClientProvider, +} from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' + +export default function MyApp({ Component, pageProps }) { + const [queryClient] = React.useState(() => new QueryClient()) + + return ( + + + + + + + ) +} diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.output.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.output.tsx new file mode 100644 index 0000000000..cbedc7ca1a --- /dev/null +++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.output.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { + HydrationBoundary, + QueryClient, + QueryClientProvider, +} from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' + +export default function MyApp({ Component, pageProps }) { + const [queryClient] = React.useState(() => new QueryClient()) + + return ( + ( + + + + + ) + ); +} diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.input.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.input.tsx new file mode 100644 index 0000000000..00c9d988af --- /dev/null +++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.input.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { + Hydrate as RenamedHydrate, + QueryClient as RenamedQueryClient, + QueryClientProvider as RenamedQueryClientProvider, +} from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' + +export default function MyApp({ Component, pageProps }) { + const [queryClient] = React.useState(() => new RenamedQueryClient()) + + return ( + + + + + + + ) +} diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.output.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.output.tsx new file mode 100644 index 0000000000..f83abc1e4d --- /dev/null +++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.output.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { + HydrationBoundary as RenamedHydrate, + QueryClient as RenamedQueryClient, + QueryClientProvider as RenamedQueryClientProvider, +} from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' + +export default function MyApp({ Component, pageProps }) { + const [queryClient] = React.useState(() => new RenamedQueryClient()) + + return ( + + + + + + + ) +} diff --git a/packages/codemods/src/v5/rename-hydrate/__tests__/rename-hydrate.test.js b/packages/codemods/src/v5/rename-hydrate/__tests__/rename-hydrate.test.js new file mode 100644 index 0000000000..600e852553 --- /dev/null +++ b/packages/codemods/src/v5/rename-hydrate/__tests__/rename-hydrate.test.js @@ -0,0 +1,10 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const defineTest = require('jscodeshift/dist/testUtils').defineTest + +defineTest(__dirname, 'rename-hydrate', null, 'default-import', { + parser: 'tsx', +}) + +defineTest(__dirname, 'rename-hydrate', null, 'named-import', { + parser: 'tsx', +}) diff --git a/packages/codemods/src/v5/rename-hydrate/rename-hydrate.js b/packages/codemods/src/v5/rename-hydrate/rename-hydrate.js new file mode 100644 index 0000000000..488040d6a2 --- /dev/null +++ b/packages/codemods/src/v5/rename-hydrate/rename-hydrate.js @@ -0,0 +1,55 @@ +module.exports = (file, api) => { + const jscodeshift = api.jscodeshift + const root = jscodeshift(file.source) + + const importSpecifiers = root + .find(jscodeshift.ImportDeclaration, { + source: { + value: '@tanstack/react-query', + }, + }) + .find(jscodeshift.ImportSpecifier, { + imported: { + name: 'Hydrate', + }, + }) + + if (importSpecifiers.length > 0) { + const names = { + searched: 'Hydrate', // By default, we want to replace the `Hydrate` usages. + target: 'HydrationBoundary', // We want to replace them with `HydrationBoundary`. + } + + importSpecifiers.replaceWith(({ node: mutableNode }) => { + /** + * When the local and imported names match which means the code doesn't contain import aliases, we need + * to replace only the import specifier. + * @type {boolean} + */ + const usesDefaultImport = + mutableNode.local.name === mutableNode.imported.name + + if (!usesDefaultImport) { + // If the code uses import aliases, we must re-use the alias. + names.searched = mutableNode.local.name + names.target = mutableNode.local.name + } + + // Override the import specifier. + mutableNode.imported.name = 'HydrationBoundary' + + return mutableNode + }) + + root + .findJSXElements(names.searched) + .replaceWith(({ node: mutableNode }) => { + mutableNode.openingElement.name.name = names.target + mutableNode.closingElement.name.name = names.target + + return mutableNode + }) + } + + return root.toSource({ quote: 'single', lineTerminator: '\n' }) +}