From e27a57bc7a1113a46ad4ae764e933329ef48ad8a Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Sat, 25 Feb 2023 11:55:05 +0100 Subject: [PATCH 1/8] feat(codemod): initial implementation of remove overloads codemod --- packages/codemods/src/utils/index.js | 19 + .../__testfixtures__/default-import.input.tsx | 187 ++++++++ .../default-import.output.tsx | 430 ++++++++++++++++++ .../__tests__/remove-overloads.test.js | 8 + .../v5/remove-overloads/remove-overloads.js | 211 +++++++++ .../src/v5/remove-overloads/utils/index.js | 84 ++++ .../utils/unknown-usage-error.js | 26 ++ 7 files changed, 965 insertions(+) create mode 100644 packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx create mode 100644 packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx create mode 100644 packages/codemods/src/v5/remove-overloads/__tests__/remove-overloads.test.js create mode 100644 packages/codemods/src/v5/remove-overloads/remove-overloads.js create mode 100644 packages/codemods/src/v5/remove-overloads/utils/index.js create mode 100644 packages/codemods/src/v5/remove-overloads/utils/unknown-usage-error.js diff --git a/packages/codemods/src/utils/index.js b/packages/codemods/src/utils/index.js index 152ce45ebe..0e5bb61c63 100644 --- a/packages/codemods/src/utils/index.js +++ b/packages/codemods/src/utils/index.js @@ -117,6 +117,21 @@ module.exports = ({ root, jscodeshift }) => { const isNewExpression = (node) => jscodeshift.match(node, { type: jscodeshift.NewExpression.name }) + const isArrayExpression = (node) => + jscodeshift.match(node, { type: jscodeshift.ArrayExpression.name }) + + const isObjectExpression = (node) => + jscodeshift.match(node, { type: jscodeshift.ObjectExpression.name }) + + const isObjectProperty = (node) => + jscodeshift.match(node, { type: jscodeshift.ObjectProperty.name }) + + const warn = (message) => { + if (process.env.NODE_ENV !== 'test') { + console.warn(message) + } + } + const isClassInstantiationOf = (node, selector) => { if (!isNewExpression(node)) { return false @@ -158,7 +173,11 @@ module.exports = ({ root, jscodeshift }) => { isFunctionCallOf, isIdentifier, isMemberExpression, + isArrayExpression, + isObjectExpression, + isObjectProperty, locateImports, + warn, queryClient: { findQueryClientIdentifiers, }, diff --git a/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx new file mode 100644 index 0000000000..b8ebb4c529 --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx @@ -0,0 +1,187 @@ +import * as React from 'react' +import { + useIsFetching, + useIsMutating, + useQueryClient, +} from '@tanstack/react-query' +import { queryKeysFromAnotherModule } from '../another/module' + +export const WithKnownParameters = () => { + useIsFetching(['foo', 'bar']) + useIsFetching(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + useIsFetching(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { context: undefined }) + useIsFetching(['foo', 'bar'], { type: 'all', exact: true }) + useIsFetching(['foo', 'bar'], { type: 'all', exact: true }, { context: undefined }) + useIsFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + useIsFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { context: undefined }) + + useIsMutating(['foo', 'bar']) + useIsMutating(['foo', 'bar'], { exact: true }) + useIsMutating(['foo', 'bar'], { exact: true }, { context: undefined }) + useIsMutating({ mutationKey: ['foo', 'bar'], exact: true }) + useIsMutating({ mutationKey: ['foo', 'bar'], exact: true }, { context: undefined }) + + // QueryClient methods + // --- Instantiated hook call. + const queryClient = useQueryClient() + queryClient.cancelQueries(['foo', 'bar']) + queryClient.cancelQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryClient.cancelQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { silent: true }) + queryClient.cancelQueries(['foo', 'bar'], { type: 'all', exact: true }) + queryClient.cancelQueries(['foo', 'bar'], { type: 'all', exact: true }, { silent: true }) + queryClient.cancelQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + queryClient.cancelQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { silent: true }) + + queryClient.getQueriesData(['foo', 'bar']) + queryClient.getQueriesData({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + queryClient.invalidateQueries(['foo', 'bar']) + queryClient.invalidateQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryClient.invalidateQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + queryClient.invalidateQueries(['foo', 'bar'], { type: 'all', exact: true }) + queryClient.invalidateQueries(['foo', 'bar'], { type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + queryClient.invalidateQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + queryClient.invalidateQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + + queryClient.isFetching(['foo', 'bar']) + queryClient.isFetching(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryClient.isFetching(['foo', 'bar'], { type: 'all', exact: true }) + queryClient.isFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + queryClient.refetchQueries(['foo', 'bar']) + queryClient.refetchQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryClient.refetchQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + queryClient.refetchQueries(['foo', 'bar'], { type: 'all', exact: true }) + queryClient.refetchQueries(['foo', 'bar'], { type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + queryClient.refetchQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + queryClient.refetchQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + + queryClient.removeQueries(['foo', 'bar']) + queryClient.removeQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryClient.removeQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + queryClient.resetQueries(['foo', 'bar']) + queryClient.resetQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryClient.resetQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + queryClient.resetQueries(['foo', 'bar'], { type: 'all', exact: true }) + queryClient.resetQueries(['foo', 'bar'], { type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + queryClient.resetQueries({ queryKey: ['foo', 'bar'], exact: true }) + queryClient.resetQueries({ queryKey: ['foo', 'bar'], exact: true }, { cancelRefetch: false, throwOnError: true }) + + queryClient.setQueriesData(['foo', 'bar'], null) + queryClient.setQueriesData(['foo', 'bar'], null, { updatedAt: 1000 }) + queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null) + queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null, { updatedAt: 1000 }) + + const queryCache = queryClient.getQueryCache() + + queryCache.find(['foo', 'bar']) + queryCache.find(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryCache.find(['foo', 'bar'], { type: 'all', exact: true }) + + queryCache.findAll(['foo', 'bar']) + queryCache.findAll(['foo', 'bar'], { type: 'all', exact: true }) + queryCache.findAll(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }) + queryCache.findAll({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + return
Example Component
+} + +const globalQueryKey = ['module', 'level'] + +export const WithIdentifiers = () => { + const queryKey = ['foo', 'bar'] + const mutationKey = ['posts', 'articles'] + const filters = { type: 'all', exact: true } as const + const options = { context: undefined } as const + const mutationOptions = { exact: true, fetching: false } as const + const cancelOptions = { silent: true } as const + const invalidateOptions = { cancelRefetch: true, throwOnError: true } as const + const refetchOptions = { cancelRefetch: false, throwOnError: true } as const + const resetOptions = { cancelRefetch: false, throwOnError: true } as const + + useIsFetching(queryKey) + useIsFetching(queryKey, filters) + useIsFetching(queryKey, filters, options) + useIsFetching(queryKey, { type: 'all', exact: true }) + useIsFetching(queryKey, { type: 'all', exact: true }, { context: undefined }) + useIsFetching(queryKey, { queryKey: ['todos'], ...filters }, options) + useIsFetching({ queryKey: queryKey, ...filters }) + useIsFetching({ queryKey: queryKey, ...filters }, { context: undefined }) + + useIsMutating(mutationKey) + useIsMutating(mutationKey, { exact: true, status: 'idle' }) + useIsMutating(mutationKey, { ...mutationOptions, exact: false }) + useIsMutating({ mutationKey, ...mutationOptions }) + useIsMutating({ mutationKey: ['foo', 'bar'], exact: true, status: 'idle' }) + + // QueryClient methods + // --- Instantiated hook call. + const queryClient = useQueryClient() + queryClient.cancelQueries(queryKey) + queryClient.cancelQueries(queryKey, filters) + queryClient.cancelQueries(queryKey, filters, cancelOptions) + queryClient.cancelQueries(queryKey, { type: 'all', exact: true }) + queryClient.cancelQueries(queryKey, { type: 'all', exact: true }, { revert: true }) + queryClient.cancelQueries(queryKey, { queryKey: ['todos'], ...filters }, cancelOptions) + queryClient.cancelQueries({ queryKey: queryKey, type: 'all', exact: true }) + queryClient.cancelQueries({ queryKey: ['foo', 'bar'], ...filters }, cancelOptions) + + queryClient.getQueriesData(globalQueryKey) + queryClient.getQueriesData({ queryKey: globalQueryKey, ...filters }) + queryClient.getQueriesData({ queryKey: ['foo', 'bar'], type: 'all' }) + + queryClient.invalidateQueries(queryKey) + queryClient.invalidateQueries(queryKey, filters) + queryClient.invalidateQueries(queryKey, filters, invalidateOptions) + queryClient.invalidateQueries(queryKey, { queryKey: ['todos'], stale: true, ...filters }) + queryClient.invalidateQueries(queryKey, { queryKey: ['todos'], stale: true, ...filters }, invalidateOptions) + queryClient.invalidateQueries({ queryKey: globalQueryKey, ...filters, stale: true }) + queryClient.invalidateQueries({ queryKey: globalQueryKey, ...filters, stale: true }, invalidateOptions) + + queryClient.isFetching(globalQueryKey) + queryClient.isFetching(globalQueryKey, filters) + queryClient.isFetching(globalQueryKey, { queryKey: ['todos'], type: 'all', exact: true }) + queryClient.isFetching(globalQueryKey, { queryKey: ['todos'], ...filters }) + queryClient.isFetching({ queryKey: globalQueryKey, ...filters, stale: true }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.isFetching(queryKeysFromAnotherModule) + + queryClient.refetchQueries(queryKey) + queryClient.refetchQueries(queryKey, filters) + queryClient.refetchQueries(queryKey, filters, refetchOptions) + queryClient.refetchQueries(queryKey, { queryKey: ['todos'], ...filters }, { ...refetchOptions, cancelRefetch: true }) + queryClient.refetchQueries({ queryKey: queryKey, ...filters }) + queryClient.refetchQueries({ queryKey: queryKey, ...filters }, { ...refetchOptions, cancelRefetch: true }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.refetchQueries(queryKeysFromAnotherModule) + queryClient.refetchQueries(queryKeysFromAnotherModule, filters) + queryClient.refetchQueries(queryKeysFromAnotherModule, filters, refetchOptions) + + queryClient.removeQueries(queryKey) + queryClient.removeQueries(queryKey, filters) + queryClient.removeQueries(queryKey, { queryKey: ['todos'], ...filters, stale: true }) + queryClient.removeQueries({ queryKey, ...filters, stale: true }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.removeQueries(queryKeysFromAnotherModule) + queryClient.removeQueries(queryKeysFromAnotherModule, filters) + + queryClient.resetQueries(queryKey) + queryClient.resetQueries(queryKey, filters) + queryClient.resetQueries(queryKey, filters, resetOptions) + queryClient.resetQueries(queryKey, { queryKey: ['todos'], ...filters, stale: true }) + queryClient.resetQueries(queryKey, { queryKey: ['todos'], ...filters, stale: true }, resetOptions) + queryClient.resetQueries({ queryKey, ...filters, stale: true }) + queryClient.resetQueries({ queryKey, ...filters, stale: true }, resetOptions) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.resetQueries(queryKeysFromAnotherModule) + queryClient.resetQueries(queryKeysFromAnotherModule, filters) + queryClient.resetQueries(queryKeysFromAnotherModule, filters, resetOptions) + + queryClient.fetchQuery(['foo', 'bar']) + queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], staleTime: 1000 }) + queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], queryFn: () => 'data', staleTime: 1000 }) + queryClient.fetchQuery(['foo', 'bar'], () => 'data', { queryKey: ['todos'], staleTime: 1000 }) + queryClient.fetchQuery(['foo', 'bar'], function myFn() { return 'data' }, { queryKey: ['todos'], staleTime: 1000 }) + queryClient.fetchQuery({ queryKey: ['foo', 'bar'], queryFn: () => 'data', retry: true }) +} diff --git a/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx new file mode 100644 index 0000000000..5de657da8c --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx @@ -0,0 +1,430 @@ +import * as React from 'react' +import { + useIsFetching, + useIsMutating, + useQueryClient, +} from '@tanstack/react-query' +import { queryKeysFromAnotherModule } from '../another/module' + +export const WithKnownParameters = () => { + useIsFetching({ + queryKey: ['foo', 'bar'] + }) + useIsFetching({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + useIsFetching({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { context: undefined }) + useIsFetching({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + useIsFetching({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { context: undefined }) + useIsFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + useIsFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { context: undefined }) + + useIsMutating({ + mutationKey: ['foo', 'bar'] + }) + useIsMutating({ + mutationKey: ['foo', 'bar'], + exact: true + }) + useIsMutating({ + mutationKey: ['foo', 'bar'], + exact: true + }, { context: undefined }) + useIsMutating({ mutationKey: ['foo', 'bar'], exact: true }) + useIsMutating({ mutationKey: ['foo', 'bar'], exact: true }, { context: undefined }) + + // QueryClient methods + // --- Instantiated hook call. + const queryClient = useQueryClient() + queryClient.cancelQueries({ + queryKey: ['foo', 'bar'] + }) + queryClient.cancelQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.cancelQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { silent: true }) + queryClient.cancelQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.cancelQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { silent: true }) + queryClient.cancelQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + queryClient.cancelQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { silent: true }) + + queryClient.getQueriesData({ + queryKey: ['foo', 'bar'] + }) + queryClient.getQueriesData({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + queryClient.invalidateQueries({ + queryKey: ['foo', 'bar'] + }) + queryClient.invalidateQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.invalidateQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { cancelRefetch: false, throwOnError: true }) + queryClient.invalidateQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.invalidateQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { cancelRefetch: false, throwOnError: true }) + queryClient.invalidateQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + queryClient.invalidateQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + + queryClient.isFetching({ + queryKey: ['foo', 'bar'] + }) + queryClient.isFetching({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.isFetching({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.isFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + queryClient.refetchQueries({ + queryKey: ['foo', 'bar'] + }) + queryClient.refetchQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.refetchQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { cancelRefetch: false, throwOnError: true }) + queryClient.refetchQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.refetchQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { cancelRefetch: false, throwOnError: true }) + queryClient.refetchQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + queryClient.refetchQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true }) + + queryClient.removeQueries({ + queryKey: ['foo', 'bar'] + }) + queryClient.removeQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.removeQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + queryClient.resetQueries({ + queryKey: ['foo', 'bar'] + }) + queryClient.resetQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.resetQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { cancelRefetch: false, throwOnError: true }) + queryClient.resetQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryClient.resetQueries({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }, { cancelRefetch: false, throwOnError: true }) + queryClient.resetQueries({ queryKey: ['foo', 'bar'], exact: true }) + queryClient.resetQueries({ queryKey: ['foo', 'bar'], exact: true }, { cancelRefetch: false, throwOnError: true }) + + queryClient.setQueriesData(['foo', 'bar'], null) + queryClient.setQueriesData(['foo', 'bar'], null, { updatedAt: 1000 }) + queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null) + queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null, { updatedAt: 1000 }) + + const queryCache = queryClient.getQueryCache() + + queryCache.find({ + queryKey: ['foo', 'bar'] + }) + queryCache.find({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryCache.find({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + + queryCache.findAll({ + queryKey: ['foo', 'bar'] + }) + queryCache.findAll({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryCache.findAll({ + queryKey: ['foo', 'bar'], + type: 'all', + exact: true + }) + queryCache.findAll({ queryKey: ['foo', 'bar'], type: 'all', exact: true }) + + return
Example Component
+} + +const globalQueryKey = ['module', 'level'] + +export const WithIdentifiers = () => { + const queryKey = ['foo', 'bar'] + const mutationKey = ['posts', 'articles'] + const filters = { type: 'all', exact: true } as const + const options = { context: undefined } as const + const mutationOptions = { exact: true, fetching: false } as const + const cancelOptions = { silent: true } as const + const invalidateOptions = { cancelRefetch: true, throwOnError: true } as const + const refetchOptions = { cancelRefetch: false, throwOnError: true } as const + const resetOptions = { cancelRefetch: false, throwOnError: true } as const + + useIsFetching({ + queryKey: queryKey + }) + useIsFetching({ + queryKey: queryKey, + ...filters + }) + useIsFetching({ + queryKey: queryKey, + ...filters + }, options) + useIsFetching({ + queryKey: queryKey, + type: 'all', + exact: true + }) + useIsFetching({ + queryKey: queryKey, + type: 'all', + exact: true + }, { context: undefined }) + useIsFetching({ + queryKey: queryKey, + ...filters + }, options) + useIsFetching({ queryKey: queryKey, ...filters }) + useIsFetching({ queryKey: queryKey, ...filters }, { context: undefined }) + + useIsMutating({ + mutationKey: mutationKey + }) + useIsMutating({ + mutationKey: mutationKey, + exact: true, + status: 'idle' + }) + useIsMutating({ + mutationKey: mutationKey, + ...mutationOptions, + exact: false + }) + useIsMutating({ mutationKey, ...mutationOptions }) + useIsMutating({ mutationKey: ['foo', 'bar'], exact: true, status: 'idle' }) + + // QueryClient methods + // --- Instantiated hook call. + const queryClient = useQueryClient() + queryClient.cancelQueries({ + queryKey: queryKey + }) + queryClient.cancelQueries({ + queryKey: queryKey, + ...filters + }) + queryClient.cancelQueries({ + queryKey: queryKey, + ...filters + }, cancelOptions) + queryClient.cancelQueries({ + queryKey: queryKey, + type: 'all', + exact: true + }) + queryClient.cancelQueries({ + queryKey: queryKey, + type: 'all', + exact: true + }, { revert: true }) + queryClient.cancelQueries({ + queryKey: queryKey, + ...filters + }, cancelOptions) + queryClient.cancelQueries({ queryKey: queryKey, type: 'all', exact: true }) + queryClient.cancelQueries({ queryKey: ['foo', 'bar'], ...filters }, cancelOptions) + + queryClient.getQueriesData({ + queryKey: globalQueryKey + }) + queryClient.getQueriesData({ queryKey: globalQueryKey, ...filters }) + queryClient.getQueriesData({ queryKey: ['foo', 'bar'], type: 'all' }) + + queryClient.invalidateQueries({ + queryKey: queryKey + }) + queryClient.invalidateQueries({ + queryKey: queryKey, + ...filters + }) + queryClient.invalidateQueries({ + queryKey: queryKey, + ...filters + }, invalidateOptions) + queryClient.invalidateQueries({ + queryKey: queryKey, + stale: true, + ...filters + }) + queryClient.invalidateQueries({ + queryKey: queryKey, + stale: true, + ...filters + }, invalidateOptions) + queryClient.invalidateQueries({ queryKey: globalQueryKey, ...filters, stale: true }) + queryClient.invalidateQueries({ queryKey: globalQueryKey, ...filters, stale: true }, invalidateOptions) + + queryClient.isFetching({ + queryKey: globalQueryKey + }) + queryClient.isFetching({ + queryKey: globalQueryKey, + ...filters + }) + queryClient.isFetching({ + queryKey: globalQueryKey, + type: 'all', + exact: true + }) + queryClient.isFetching({ + queryKey: globalQueryKey, + ...filters + }) + queryClient.isFetching({ queryKey: globalQueryKey, ...filters, stale: true }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.isFetching(queryKeysFromAnotherModule) + + queryClient.refetchQueries({ + queryKey: queryKey + }) + queryClient.refetchQueries({ + queryKey: queryKey, + ...filters + }) + queryClient.refetchQueries({ + queryKey: queryKey, + ...filters + }, refetchOptions) + queryClient.refetchQueries({ + queryKey: queryKey, + ...filters + }, { ...refetchOptions, cancelRefetch: true }) + queryClient.refetchQueries({ queryKey: queryKey, ...filters }) + queryClient.refetchQueries({ queryKey: queryKey, ...filters }, { ...refetchOptions, cancelRefetch: true }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.refetchQueries(queryKeysFromAnotherModule) + queryClient.refetchQueries(queryKeysFromAnotherModule, filters) + queryClient.refetchQueries(queryKeysFromAnotherModule, filters, refetchOptions) + + queryClient.removeQueries({ + queryKey: queryKey + }) + queryClient.removeQueries({ + queryKey: queryKey, + ...filters + }) + queryClient.removeQueries({ + queryKey: queryKey, + ...filters, + stale: true + }) + queryClient.removeQueries({ queryKey, ...filters, stale: true }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.removeQueries(queryKeysFromAnotherModule) + queryClient.removeQueries(queryKeysFromAnotherModule, filters) + + queryClient.resetQueries({ + queryKey: queryKey + }) + queryClient.resetQueries({ + queryKey: queryKey, + ...filters + }) + queryClient.resetQueries({ + queryKey: queryKey, + ...filters + }, resetOptions) + queryClient.resetQueries({ + queryKey: queryKey, + ...filters, + stale: true + }) + queryClient.resetQueries({ + queryKey: queryKey, + ...filters, + stale: true + }, resetOptions) + queryClient.resetQueries({ queryKey, ...filters, stale: true }) + queryClient.resetQueries({ queryKey, ...filters, stale: true }, resetOptions) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.resetQueries(queryKeysFromAnotherModule) + queryClient.resetQueries(queryKeysFromAnotherModule, filters) + queryClient.resetQueries(queryKeysFromAnotherModule, filters, resetOptions) +} diff --git a/packages/codemods/src/v5/remove-overloads/__tests__/remove-overloads.test.js b/packages/codemods/src/v5/remove-overloads/__tests__/remove-overloads.test.js new file mode 100644 index 0000000000..a8be29a69a --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/__tests__/remove-overloads.test.js @@ -0,0 +1,8 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const defineTest = require('jscodeshift/dist/testUtils').defineTest + +jest.autoMockOff() + +defineTest(__dirname, 'remove-overloads', null, 'default-import', { + parser: 'tsx', +}) diff --git a/packages/codemods/src/v5/remove-overloads/remove-overloads.js b/packages/codemods/src/v5/remove-overloads/remove-overloads.js new file mode 100644 index 0000000000..c48390b0f5 --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/remove-overloads.js @@ -0,0 +1,211 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createUtilsObject = require('../../utils') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createUseQueryLikeTransformer = require('../../utils/transformers/use-query-like-transformer') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createQueryClientTransformer = require('../../utils/transformers/query-client-transformer') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createQueryCacheTransformer = require('../../utils/transformers/query-cache-transformer') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const UnknownUsageError = require('./utils/unknown-usage-error') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createV5UtilsObject = require('./utils') + +const transformQueryFnAwareUsages = ({ + jscodeshift, + utils, + root, + filePath, + config, +}) => { + const replacer = (path) => { + const node = path.node + + try { + return node + } catch (error) { + utils.warn( + error.name === UnknownUsageError.name + ? error.message + : `An unknown error occurred while processing the "${filePath}" file. Please review this file, because the codemod couldn't be applied.`, + ) + + return node + } + } + + createQueryClientTransformer({ jscodeshift, utils, root }).execute( + config.queryClientMethods, + replacer, + ) +} + +/** + * + * @param {import('jscodeshift')} jscodeshift + * @param {Object} utils + * @param {import('jscodeshift').Collection} root + * @param {string} filePath + * @param {{keyName: "mutationKey"|"queryKey", queryClientMethods: ReadonlyArray, hooks: ReadonlyArray}} config + */ +const transformFilterAwareUsages = ({ + jscodeshift, + utils, + root, + filePath, + config, +}) => { + const v5Utils = createV5UtilsObject({ jscodeshift, utils }) + + /** + * @param {import('jscodeshift').CallExpression} node + * @param {"mutationKey"|"queryKey"} keyName + * @returns {boolean} + */ + const canSkipReplacement = (node, keyName) => { + const callArguments = node.arguments + + const hasKeyProperty = () => + callArguments[0].properties.some( + (property) => + utils.isObjectProperty(property) && property.key.name !== keyName, + ) + + /** + * This call has only one argument, which is an object expression. According to the new signature, this is a + * valid use case, so code changes are not needed. + */ + return ( + callArguments.length > 0 && + utils.isObjectExpression(callArguments[0]) && + hasKeyProperty() + ) + } + + const replacer = (path) => { + const node = path.node + + try { + // If the given method/function call matches certain criteria, the node doesn't need to be replaced, this step can be skipped. + if (canSkipReplacement(node, config.keyName)) { + return node + } + + const keyProperty = v5Utils.transformArgumentToKey( + path, + node.arguments[0], + config.keyName, + filePath, + ) + + if (!keyProperty) { + throw new UnknownUsageError(node, filePath) + } + + const parameters = [jscodeshift.objectExpression([keyProperty])] + const secondParameter = node.arguments[1] + + if (secondParameter) { + // If it has a second argument, and it's an object, then we get the properties of it, because it will be part of the + // first argument, otherwise we use an empty array, because we can spread it during the objectExpression creation. + if (utils.isObjectExpression(secondParameter)) { + secondParameter.properties.forEach((property) => { + const isSpreadElement = jscodeshift.match(property, { + type: jscodeshift.SpreadElement.name, + }) + const isObjectProperty = utils.isObjectProperty(property) + + if ( + isSpreadElement || + (isObjectProperty && property.key.name !== config.keyName) + ) { + parameters[0].properties.push(property) + } + }) + } else { + parameters[0].properties.push( + jscodeshift.spreadElement(secondParameter), + ) + } + } + + // The rest of the parameters can be simply pushed to the parameters object so all will be kept. + parameters.push(...node.arguments.slice(2)) + + return jscodeshift.callExpression(node.original.callee, parameters) + } catch (error) { + utils.warn( + error.name === UnknownUsageError.name + ? error.message + : `An unknown error occurred while processing the "${filePath}" file. Please review this file, because the codemod couldn't be applied.`, + ) + + return node + } + } + + createQueryClientTransformer({ jscodeshift, utils, root }).execute( + config.queryClientMethods, + replacer, + ) + + createUseQueryLikeTransformer({ jscodeshift, utils, root }).execute( + config.hooks, + replacer, + ) + + createQueryCacheTransformer({ jscodeshift, utils, root }).execute(replacer) +} + +module.exports = (file, api) => { + const jscodeshift = api.jscodeshift + const root = jscodeshift(file.source) + const utils = createUtilsObject({ root, jscodeshift }) + const filePath = file.path + + const dependencies = { jscodeshift, utils, root, filePath } + + transformFilterAwareUsages({ + ...dependencies, + config: { + keyName: 'queryKey', + queryClientMethods: [ + 'cancelQueries', + 'getQueriesData', + 'invalidateQueries', + 'isFetching', + 'refetchQueries', + 'removeQueries', + 'resetQueries', + // 'setQueriesData', + ], + hooks: ['useIsFetching'], + }, + }) + + transformFilterAwareUsages({ + ...dependencies, + config: { + keyName: 'mutationKey', + queryClientMethods: [], + hooks: ['useIsMutating'], + }, + }) + + transformQueryFnAwareUsages({ + ...dependencies, + config: { + keyName: 'queryKey', + queryClientMethods: [ + 'ensureQueryData', + 'fetchQuery', + 'prefetchQuery', + 'fetchInfiniteQuery', + 'prefetchInfiniteQuery', + ], + hooks: [], + }, + }) + + return root.toSource({ quote: 'single' }) +} diff --git a/packages/codemods/src/v5/remove-overloads/utils/index.js b/packages/codemods/src/v5/remove-overloads/utils/index.js new file mode 100644 index 0000000000..585c6fb89c --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/utils/index.js @@ -0,0 +1,84 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const UnknownUsageError = require('./unknown-usage-error') + +module.exports = ({ jscodeshift, utils }) => { + /** + * @param {import('jscodeshift').NodePath} path + * @param {string} argumentName + * @returns {*} + */ + const getBindingFromScope = (path, argumentName) => { + /** + * If the current scope contains the declaration then we can use the actual one else we attempt to find the + * binding from above. + */ + const scope = path.scope.declares(argumentName) + ? path.scope + : path.scope.lookup(argumentName) + + /** + * The declaration couldn't be found for some reason, time to move on. We warn the user it needs to be rewritten + * by themselves. + */ + if (!scope) { + return undefined + } + + return scope.bindings[argumentName] + .filter((item) => utils.isIdentifier(item.value)) + .map((item) => item.parentPath.value) + .at(0) + } + + /** + * @param {import('jscodeshift').Node} node + * @returns {boolean} + */ + const isArrayExpressionVariable = (node) => + jscodeshift.match(node, { + type: jscodeshift.VariableDeclarator.name, + init: { + type: jscodeshift.ArrayExpression.name, + }, + }) + + /** + * @param {import('jscodeshift').NodePath} path + * @param {import('jscodeshift').Node} node + * @param {"queryKey"|"mutationKey"} keyName + * @param {string} filePath + * @returns {import('jscodeshift').Property|undefined} + */ + const transformArgumentToKey = (path, node, keyName, filePath) => { + // If the first argument is an identifier we have to infer its type if possible. + if (utils.isIdentifier(node)) { + const binding = getBindingFromScope(path, node.name) + + if (!binding) { + throw new UnknownUsageError(path.node, filePath) + } + + if (isArrayExpressionVariable(binding)) { + return jscodeshift.property( + 'init', + jscodeshift.identifier(keyName), + jscodeshift.identifier(binding.id.name), + ) + } + } + + // If the first argument is an array, then it matches the following overload: + // methodName(queryKey?: QueryKey, firstObject?: TFirstObject, secondObject?: TSecondObject) + if (utils.isArrayExpression(node)) { + // Then we create the 'queryKey' property based on it, because it will be passed to the first argument + // that should be an object according to the new signature. + return jscodeshift.property('init', jscodeshift.identifier(keyName), node) + } + + return undefined + } + + return { + transformArgumentToKey, + } +} diff --git a/packages/codemods/src/v5/remove-overloads/utils/unknown-usage-error.js b/packages/codemods/src/v5/remove-overloads/utils/unknown-usage-error.js new file mode 100644 index 0000000000..96daa2e9d3 --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/utils/unknown-usage-error.js @@ -0,0 +1,26 @@ +class UnknownUsageError extends Error { + /** + * @param {import('jscodeshift').CallExpression} callExpression + */ + constructor(callExpression, filePath) { + super('') + this.message = this.#buildMessage(callExpression, filePath) + this.name = 'UnknownUsageError' + } + + /** + * + * @param {import('jscodeshift').CallExpression} callExpression + * @param {string} filePath + * @returns {string} + */ + #buildMessage(callExpression, filePath) { + const location = callExpression.callee.loc + const start = location.start.line + const end = location.end.line + + return `The usage in file "${filePath}" at line ${start}:${end} could not be transformed into the new syntax. Please do this manually.` + } +} + +module.exports = UnknownUsageError From b59bcf79364d0e80fce6a167e45b04299c27266b Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Thu, 2 Mar 2023 19:09:11 +0100 Subject: [PATCH 2/8] feat(codemod): add nearly working implementation of `transformQueryFnAwareUsages` function --- .../__testfixtures__/default-import.input.tsx | 24 ++- .../default-import.output.tsx | 54 +++++++ .../v5/remove-overloads/remove-overloads.js | 145 +++++++++++++++++- .../src/v5/remove-overloads/utils/index.js | 16 ++ 4 files changed, 232 insertions(+), 7 deletions(-) diff --git a/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx index b8ebb4c529..00e873e1be 100644 --- a/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx +++ b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.input.tsx @@ -73,6 +73,13 @@ export const WithKnownParameters = () => { queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null) queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null, { updatedAt: 1000 }) + queryClient.fetchQuery(['foo', 'bar']) + queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], staleTime: 1000 }) + queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], queryFn: () => 'data', staleTime: 1000 }) + queryClient.fetchQuery(['foo', 'bar'], () => 'data', { queryKey: ['todos'], staleTime: 1000 }) + queryClient.fetchQuery(['foo', 'bar'], function myFn() { return 'data' }, { queryKey: ['todos'], staleTime: 1000 }) + queryClient.fetchQuery({ queryKey: ['foo', 'bar'], queryFn: () => 'data', retry: true }) + const queryCache = queryClient.getQueryCache() queryCache.find(['foo', 'bar']) @@ -99,6 +106,8 @@ export const WithIdentifiers = () => { const invalidateOptions = { cancelRefetch: true, throwOnError: true } as const const refetchOptions = { cancelRefetch: false, throwOnError: true } as const const resetOptions = { cancelRefetch: false, throwOnError: true } as const + const fetchOptions = { queryFn: () => 'data', retry: true } as const + const queryFn = () => 'data' useIsFetching(queryKey) useIsFetching(queryKey, filters) @@ -178,10 +187,13 @@ export const WithIdentifiers = () => { queryClient.resetQueries(queryKeysFromAnotherModule, filters) queryClient.resetQueries(queryKeysFromAnotherModule, filters, resetOptions) - queryClient.fetchQuery(['foo', 'bar']) - queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], staleTime: 1000 }) - queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], queryFn: () => 'data', staleTime: 1000 }) - queryClient.fetchQuery(['foo', 'bar'], () => 'data', { queryKey: ['todos'], staleTime: 1000 }) - queryClient.fetchQuery(['foo', 'bar'], function myFn() { return 'data' }, { queryKey: ['todos'], staleTime: 1000 }) - queryClient.fetchQuery({ queryKey: ['foo', 'bar'], queryFn: () => 'data', retry: true }) + queryClient.fetchQuery(queryKey) + queryClient.fetchQuery(queryKey, fetchOptions) + queryClient.fetchQuery(queryKey, { networkMode: 'always', ...fetchOptions }) + queryClient.fetchQuery(queryKey, queryFn, fetchOptions) + queryClient.fetchQuery(queryKey, () => 'data', { networkMode: 'always', ...fetchOptions }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.fetchQuery(queryKeysFromAnotherModule) + queryClient.fetchQuery(queryKeysFromAnotherModule, fetchOptions) + queryClient.fetchQuery(queryKeysFromAnotherModule, queryFn, fetchOptions) } diff --git a/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx index 5de657da8c..2a5dc2bded 100644 --- a/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx +++ b/packages/codemods/src/v5/remove-overloads/__testfixtures__/default-import.output.tsx @@ -189,6 +189,30 @@ export const WithKnownParameters = () => { queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null) queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null, { updatedAt: 1000 }) + queryClient.fetchQuery({ + queryKey: ['foo', 'bar'] + }) + queryClient.fetchQuery({ + queryKey: ['foo', 'bar'], + staleTime: 1000 + }) + queryClient.fetchQuery({ + queryKey: ['foo', 'bar'], + queryFn: () => 'data', + staleTime: 1000 + }) + queryClient.fetchQuery({ + queryKey: ['foo', 'bar'], + queryFn: () => 'data', + staleTime: 1000 + }) + queryClient.fetchQuery({ + queryKey: ['foo', 'bar'], + queryFn: function myFn() { return 'data' }, + staleTime: 1000 + }) + queryClient.fetchQuery({ queryKey: ['foo', 'bar'], queryFn: () => 'data', retry: true }) + const queryCache = queryClient.getQueryCache() queryCache.find({ @@ -235,6 +259,8 @@ export const WithIdentifiers = () => { const invalidateOptions = { cancelRefetch: true, throwOnError: true } as const const refetchOptions = { cancelRefetch: false, throwOnError: true } as const const resetOptions = { cancelRefetch: false, throwOnError: true } as const + const fetchOptions = { queryFn: () => 'data', retry: true } as const + const queryFn = () => 'data' useIsFetching({ queryKey: queryKey @@ -427,4 +453,32 @@ export const WithIdentifiers = () => { queryClient.resetQueries(queryKeysFromAnotherModule) queryClient.resetQueries(queryKeysFromAnotherModule, filters) queryClient.resetQueries(queryKeysFromAnotherModule, filters, resetOptions) + + queryClient.fetchQuery({ + queryKey: queryKey + }) + queryClient.fetchQuery({ + queryKey: queryKey, + ...fetchOptions + }) + queryClient.fetchQuery({ + queryKey: queryKey, + networkMode: 'always', + ...fetchOptions + }) + queryClient.fetchQuery({ + queryKey: queryKey, + queryFn: queryFn, + ...fetchOptions + }) + queryClient.fetchQuery({ + queryKey: queryKey, + queryFn: () => 'data', + networkMode: 'always', + ...fetchOptions + }) + // Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier. + queryClient.fetchQuery(queryKeysFromAnotherModule) + queryClient.fetchQuery(queryKeysFromAnotherModule, fetchOptions) + queryClient.fetchQuery(queryKeysFromAnotherModule, queryFn, fetchOptions) } diff --git a/packages/codemods/src/v5/remove-overloads/remove-overloads.js b/packages/codemods/src/v5/remove-overloads/remove-overloads.js index c48390b0f5..5fc242f20a 100644 --- a/packages/codemods/src/v5/remove-overloads/remove-overloads.js +++ b/packages/codemods/src/v5/remove-overloads/remove-overloads.js @@ -18,11 +18,154 @@ const transformQueryFnAwareUsages = ({ filePath, config, }) => { + const v5Utils = createV5UtilsObject({ jscodeshift, utils }) + + /** + * @param {import('jscodeshift').CallExpression} node + * @returns {boolean} + */ + const canSkipReplacement = (node) => { + const callArguments = node.arguments + + const hasKeyProperty = () => + callArguments[0].properties.some( + (property) => + utils.isObjectProperty(property) && + [config.keyName, 'queryFn'].includes(property.key.name), + ) + + return ( + callArguments.length > 0 && + utils.isObjectExpression(callArguments[0]) && + hasKeyProperty() + ) + } + + const isFunctionDefinition = (node) => { + const isArrowFunctionExpression = jscodeshift.match(node, { + type: jscodeshift.ArrowFunctionExpression.name, + }) + const isFunctionExpression = jscodeshift.match(node, { + type: jscodeshift.FunctionExpression.name, + }) + + return isArrowFunctionExpression || isFunctionExpression + } + + const predicate = (property) => { + const isSpreadElement = jscodeshift.match(property, { + type: jscodeshift.SpreadElement.name, + }) + const isObjectProperty = utils.isObjectProperty(property) + + return ( + isSpreadElement || + (isObjectProperty && property.key.name !== config.keyName) + ) + } + + const myFnQueryFn = (path, node) => { + if (isFunctionDefinition(node)) { + return jscodeshift.property( + 'init', + jscodeshift.identifier('queryFn'), + node, + ) + } + + if (utils.isIdentifier(node)) { + const binding = v5Utils.getBindingFromScope(path, node.name) + + if (!binding) { + throw new UnknownUsageError(path.node, filePath) + } + + const isVariableDeclaration = jscodeshift.match(binding, { + type: jscodeshift.VariableDeclarator.name, + }) + + if (!isVariableDeclaration) { + return undefined + } + + const isTSAsExpression = jscodeshift.match(binding.init, { + type: jscodeshift.TSAsExpression.name, + }) + const initializer = isTSAsExpression + ? binding.init.expression + : binding.init + + if (isFunctionDefinition(initializer)) { + return jscodeshift.property( + 'init', + jscodeshift.identifier('queryFn'), + binding.id, + ) + } + } + + return undefined + } + const replacer = (path) => { const node = path.node try { - return node + // If the given method/function call matches certain criteria, the node doesn't need to be replaced, this step can be skipped. + if (canSkipReplacement(node)) { + return node + } + + const keyProperty = v5Utils.transformArgumentToKey( + path, + node.arguments[0], + config.keyName, + filePath, + ) + + if (!keyProperty) { + throw new UnknownUsageError(node, filePath) + } + + const parameters = [jscodeshift.objectExpression([keyProperty])] + const targetObject = parameters[0] + const secondParameter = node.arguments[1] + + if (secondParameter) { + const queryFnProperty = myFnQueryFn(path, secondParameter) + + if (queryFnProperty) { + targetObject.properties.push(queryFnProperty) + + const thirdParameter = node.arguments[2] + + if (utils.isObjectExpression(thirdParameter)) { + v5Utils.copyPropertiesFromSource( + thirdParameter, + targetObject, + predicate, + ) + } else { + targetObject.properties.push( + jscodeshift.spreadElement(thirdParameter), + ) + } + + return jscodeshift.callExpression(node.original.callee, parameters) + } + + if (utils.isObjectExpression(secondParameter)) { + v5Utils.copyPropertiesFromSource( + secondParameter, + targetObject, + predicate, + ) + } + + return jscodeshift.callExpression(node.original.callee, parameters) + } + + return jscodeshift.callExpression(node.original.callee, parameters) } catch (error) { utils.warn( error.name === UnknownUsageError.name diff --git a/packages/codemods/src/v5/remove-overloads/utils/index.js b/packages/codemods/src/v5/remove-overloads/utils/index.js index 585c6fb89c..24d21ae89f 100644 --- a/packages/codemods/src/v5/remove-overloads/utils/index.js +++ b/packages/codemods/src/v5/remove-overloads/utils/index.js @@ -2,6 +2,20 @@ const UnknownUsageError = require('./unknown-usage-error') module.exports = ({ jscodeshift, utils }) => { + /** + * + * @param {import('jscodeshift').ObjectExpression} source + * @param {import('jscodeshift').ObjectExpression} target + * @param {(node: import('jscodeshift').Node) => boolean} predicate + */ + const copyPropertiesFromSource = (source, target, predicate) => { + source.properties.forEach((property) => { + if (predicate(property)) { + target.properties.push(property) + } + }) + } + /** * @param {import('jscodeshift').NodePath} path * @param {string} argumentName @@ -79,6 +93,8 @@ module.exports = ({ jscodeshift, utils }) => { } return { + copyPropertiesFromSource, + getBindingFromScope, transformArgumentToKey, } } From 62d8621d99023f9008204ed17b0a035a229d97ee Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Fri, 3 Mar 2023 14:40:49 +0100 Subject: [PATCH 3/8] chore(codemod): move "transformer" functions under the `transformers` directory --- .../v5/remove-overloads/remove-overloads.js | 299 +----------------- .../filter-aware-usage-transformer.js | 129 ++++++++ .../query-fn-aware-usage-transformer.js | 188 +++++++++++ 3 files changed, 319 insertions(+), 297 deletions(-) create mode 100644 packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js create mode 100644 packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js diff --git a/packages/codemods/src/v5/remove-overloads/remove-overloads.js b/packages/codemods/src/v5/remove-overloads/remove-overloads.js index 5fc242f20a..34bd6dada8 100644 --- a/packages/codemods/src/v5/remove-overloads/remove-overloads.js +++ b/packages/codemods/src/v5/remove-overloads/remove-overloads.js @@ -1,304 +1,9 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const createUtilsObject = require('../../utils') // eslint-disable-next-line @typescript-eslint/no-var-requires -const createUseQueryLikeTransformer = require('../../utils/transformers/use-query-like-transformer') +const transformFilterAwareUsages = require('./transformers/filter-aware-usage-transformer') // eslint-disable-next-line @typescript-eslint/no-var-requires -const createQueryClientTransformer = require('../../utils/transformers/query-client-transformer') -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createQueryCacheTransformer = require('../../utils/transformers/query-cache-transformer') -// eslint-disable-next-line @typescript-eslint/no-var-requires -const UnknownUsageError = require('./utils/unknown-usage-error') -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createV5UtilsObject = require('./utils') - -const transformQueryFnAwareUsages = ({ - jscodeshift, - utils, - root, - filePath, - config, -}) => { - const v5Utils = createV5UtilsObject({ jscodeshift, utils }) - - /** - * @param {import('jscodeshift').CallExpression} node - * @returns {boolean} - */ - const canSkipReplacement = (node) => { - const callArguments = node.arguments - - const hasKeyProperty = () => - callArguments[0].properties.some( - (property) => - utils.isObjectProperty(property) && - [config.keyName, 'queryFn'].includes(property.key.name), - ) - - return ( - callArguments.length > 0 && - utils.isObjectExpression(callArguments[0]) && - hasKeyProperty() - ) - } - - const isFunctionDefinition = (node) => { - const isArrowFunctionExpression = jscodeshift.match(node, { - type: jscodeshift.ArrowFunctionExpression.name, - }) - const isFunctionExpression = jscodeshift.match(node, { - type: jscodeshift.FunctionExpression.name, - }) - - return isArrowFunctionExpression || isFunctionExpression - } - - const predicate = (property) => { - const isSpreadElement = jscodeshift.match(property, { - type: jscodeshift.SpreadElement.name, - }) - const isObjectProperty = utils.isObjectProperty(property) - - return ( - isSpreadElement || - (isObjectProperty && property.key.name !== config.keyName) - ) - } - - const myFnQueryFn = (path, node) => { - if (isFunctionDefinition(node)) { - return jscodeshift.property( - 'init', - jscodeshift.identifier('queryFn'), - node, - ) - } - - if (utils.isIdentifier(node)) { - const binding = v5Utils.getBindingFromScope(path, node.name) - - if (!binding) { - throw new UnknownUsageError(path.node, filePath) - } - - const isVariableDeclaration = jscodeshift.match(binding, { - type: jscodeshift.VariableDeclarator.name, - }) - - if (!isVariableDeclaration) { - return undefined - } - - const isTSAsExpression = jscodeshift.match(binding.init, { - type: jscodeshift.TSAsExpression.name, - }) - const initializer = isTSAsExpression - ? binding.init.expression - : binding.init - - if (isFunctionDefinition(initializer)) { - return jscodeshift.property( - 'init', - jscodeshift.identifier('queryFn'), - binding.id, - ) - } - } - - return undefined - } - - const replacer = (path) => { - const node = path.node - - try { - // If the given method/function call matches certain criteria, the node doesn't need to be replaced, this step can be skipped. - if (canSkipReplacement(node)) { - return node - } - - const keyProperty = v5Utils.transformArgumentToKey( - path, - node.arguments[0], - config.keyName, - filePath, - ) - - if (!keyProperty) { - throw new UnknownUsageError(node, filePath) - } - - const parameters = [jscodeshift.objectExpression([keyProperty])] - const targetObject = parameters[0] - const secondParameter = node.arguments[1] - - if (secondParameter) { - const queryFnProperty = myFnQueryFn(path, secondParameter) - - if (queryFnProperty) { - targetObject.properties.push(queryFnProperty) - - const thirdParameter = node.arguments[2] - - if (utils.isObjectExpression(thirdParameter)) { - v5Utils.copyPropertiesFromSource( - thirdParameter, - targetObject, - predicate, - ) - } else { - targetObject.properties.push( - jscodeshift.spreadElement(thirdParameter), - ) - } - - return jscodeshift.callExpression(node.original.callee, parameters) - } - - if (utils.isObjectExpression(secondParameter)) { - v5Utils.copyPropertiesFromSource( - secondParameter, - targetObject, - predicate, - ) - } - - return jscodeshift.callExpression(node.original.callee, parameters) - } - - return jscodeshift.callExpression(node.original.callee, parameters) - } catch (error) { - utils.warn( - error.name === UnknownUsageError.name - ? error.message - : `An unknown error occurred while processing the "${filePath}" file. Please review this file, because the codemod couldn't be applied.`, - ) - - return node - } - } - - createQueryClientTransformer({ jscodeshift, utils, root }).execute( - config.queryClientMethods, - replacer, - ) -} - -/** - * - * @param {import('jscodeshift')} jscodeshift - * @param {Object} utils - * @param {import('jscodeshift').Collection} root - * @param {string} filePath - * @param {{keyName: "mutationKey"|"queryKey", queryClientMethods: ReadonlyArray, hooks: ReadonlyArray}} config - */ -const transformFilterAwareUsages = ({ - jscodeshift, - utils, - root, - filePath, - config, -}) => { - const v5Utils = createV5UtilsObject({ jscodeshift, utils }) - - /** - * @param {import('jscodeshift').CallExpression} node - * @param {"mutationKey"|"queryKey"} keyName - * @returns {boolean} - */ - const canSkipReplacement = (node, keyName) => { - const callArguments = node.arguments - - const hasKeyProperty = () => - callArguments[0].properties.some( - (property) => - utils.isObjectProperty(property) && property.key.name !== keyName, - ) - - /** - * This call has only one argument, which is an object expression. According to the new signature, this is a - * valid use case, so code changes are not needed. - */ - return ( - callArguments.length > 0 && - utils.isObjectExpression(callArguments[0]) && - hasKeyProperty() - ) - } - - const replacer = (path) => { - const node = path.node - - try { - // If the given method/function call matches certain criteria, the node doesn't need to be replaced, this step can be skipped. - if (canSkipReplacement(node, config.keyName)) { - return node - } - - const keyProperty = v5Utils.transformArgumentToKey( - path, - node.arguments[0], - config.keyName, - filePath, - ) - - if (!keyProperty) { - throw new UnknownUsageError(node, filePath) - } - - const parameters = [jscodeshift.objectExpression([keyProperty])] - const secondParameter = node.arguments[1] - - if (secondParameter) { - // If it has a second argument, and it's an object, then we get the properties of it, because it will be part of the - // first argument, otherwise we use an empty array, because we can spread it during the objectExpression creation. - if (utils.isObjectExpression(secondParameter)) { - secondParameter.properties.forEach((property) => { - const isSpreadElement = jscodeshift.match(property, { - type: jscodeshift.SpreadElement.name, - }) - const isObjectProperty = utils.isObjectProperty(property) - - if ( - isSpreadElement || - (isObjectProperty && property.key.name !== config.keyName) - ) { - parameters[0].properties.push(property) - } - }) - } else { - parameters[0].properties.push( - jscodeshift.spreadElement(secondParameter), - ) - } - } - - // The rest of the parameters can be simply pushed to the parameters object so all will be kept. - parameters.push(...node.arguments.slice(2)) - - return jscodeshift.callExpression(node.original.callee, parameters) - } catch (error) { - utils.warn( - error.name === UnknownUsageError.name - ? error.message - : `An unknown error occurred while processing the "${filePath}" file. Please review this file, because the codemod couldn't be applied.`, - ) - - return node - } - } - - createQueryClientTransformer({ jscodeshift, utils, root }).execute( - config.queryClientMethods, - replacer, - ) - - createUseQueryLikeTransformer({ jscodeshift, utils, root }).execute( - config.hooks, - replacer, - ) - - createQueryCacheTransformer({ jscodeshift, utils, root }).execute(replacer) -} +const transformQueryFnAwareUsages = require('./transformers/query-fn-aware-usage-transformer') module.exports = (file, api) => { const jscodeshift = api.jscodeshift diff --git a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js new file mode 100644 index 0000000000..83b56ad1ed --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js @@ -0,0 +1,129 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createV5UtilsObject = require('../utils') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const UnknownUsageError = require('../utils/unknown-usage-error') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createQueryClientTransformer = require('../../../utils/transformers/query-client-transformer') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createQueryCacheTransformer = require('../../../utils/transformers/query-cache-transformer') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createUseQueryLikeTransformer = require('../../../utils/transformers/use-query-like-transformer') + +/** + * + * @param {import('jscodeshift')} jscodeshift + * @param {Object} utils + * @param {import('jscodeshift').Collection} root + * @param {string} filePath + * @param {{keyName: "mutationKey"|"queryKey", queryClientMethods: ReadonlyArray, hooks: ReadonlyArray}} config + */ +const transformFilterAwareUsages = ({ + jscodeshift, + utils, + root, + filePath, + config, +}) => { + const v5Utils = createV5UtilsObject({ jscodeshift, utils }) + + /** + * @param {import('jscodeshift').CallExpression} node + * @param {"mutationKey"|"queryKey"} keyName + * @returns {boolean} + */ + const canSkipReplacement = (node, keyName) => { + const callArguments = node.arguments + + const hasKeyProperty = () => + callArguments[0].properties.some( + (property) => + utils.isObjectProperty(property) && property.key.name !== keyName, + ) + + /** + * This call has only one argument, which is an object expression. According to the new signature, this is a + * valid use case, so code changes are not needed. + */ + return ( + callArguments.length > 0 && + utils.isObjectExpression(callArguments[0]) && + hasKeyProperty() + ) + } + + const replacer = (path) => { + const node = path.node + + try { + // If the given method/function call matches certain criteria, the node doesn't need to be replaced, this step can be skipped. + if (canSkipReplacement(node, config.keyName)) { + return node + } + + const keyProperty = v5Utils.transformArgumentToKey( + path, + node.arguments[0], + config.keyName, + filePath, + ) + + if (!keyProperty) { + throw new UnknownUsageError(node, filePath) + } + + const parameters = [jscodeshift.objectExpression([keyProperty])] + const secondParameter = node.arguments[1] + + if (secondParameter) { + // If it has a second argument, and it's an object, then we get the properties of it, because it will be part of the + // first argument, otherwise we use an empty array, because we can spread it during the objectExpression creation. + if (utils.isObjectExpression(secondParameter)) { + secondParameter.properties.forEach((property) => { + const isSpreadElement = jscodeshift.match(property, { + type: jscodeshift.SpreadElement.name, + }) + const isObjectProperty = utils.isObjectProperty(property) + + if ( + isSpreadElement || + (isObjectProperty && property.key.name !== config.keyName) + ) { + parameters[0].properties.push(property) + } + }) + } else { + parameters[0].properties.push( + jscodeshift.spreadElement(secondParameter), + ) + } + } + + // The rest of the parameters can be simply pushed to the parameters object so all will be kept. + parameters.push(...node.arguments.slice(2)) + + return jscodeshift.callExpression(node.original.callee, parameters) + } catch (error) { + utils.warn( + error.name === UnknownUsageError.name + ? error.message + : `An unknown error occurred while processing the "${filePath}" file. Please review this file, because the codemod couldn't be applied.`, + ) + + return node + } + } + + createQueryClientTransformer({ jscodeshift, utils, root }).execute( + config.queryClientMethods, + replacer, + ) + + createUseQueryLikeTransformer({ jscodeshift, utils, root }).execute( + config.hooks, + replacer, + ) + + createQueryCacheTransformer({ jscodeshift, utils, root }).execute(replacer) +} + +module.exports = transformFilterAwareUsages diff --git a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js new file mode 100644 index 0000000000..0eb327012f --- /dev/null +++ b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js @@ -0,0 +1,188 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createV5UtilsObject = require('../utils') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const UnknownUsageError = require('../utils/unknown-usage-error') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const createQueryClientTransformer = require('../../../utils/transformers/query-client-transformer') + +/** + * + * @param {import('jscodeshift')} jscodeshift + * @param {Object} utils + * @param {import('jscodeshift').Collection} root + * @param {string} filePath + * @param {{keyName: "mutationKey"|"queryKey", queryClientMethods: ReadonlyArray, hooks: ReadonlyArray}} config + */ +const transformQueryFnAwareUsages = ({ + jscodeshift, + utils, + root, + filePath, + config, +}) => { + const v5Utils = createV5UtilsObject({ jscodeshift, utils }) + + /** + * @param {import('jscodeshift').CallExpression} node + * @returns {boolean} + */ + const canSkipReplacement = (node) => { + const callArguments = node.arguments + + const hasKeyProperty = () => + callArguments[0].properties.some( + (property) => + utils.isObjectProperty(property) && + [config.keyName, 'queryFn'].includes(property.key.name), + ) + + return ( + callArguments.length > 0 && + utils.isObjectExpression(callArguments[0]) && + hasKeyProperty() + ) + } + + const isFunctionDefinition = (node) => { + const isArrowFunctionExpression = jscodeshift.match(node, { + type: jscodeshift.ArrowFunctionExpression.name, + }) + const isFunctionExpression = jscodeshift.match(node, { + type: jscodeshift.FunctionExpression.name, + }) + + return isArrowFunctionExpression || isFunctionExpression + } + + const predicate = (property) => { + const isSpreadElement = jscodeshift.match(property, { + type: jscodeshift.SpreadElement.name, + }) + const isObjectProperty = utils.isObjectProperty(property) + + return ( + isSpreadElement || + (isObjectProperty && property.key.name !== config.keyName) + ) + } + + const myFnQueryFn = (path, node) => { + if (isFunctionDefinition(node)) { + return jscodeshift.property( + 'init', + jscodeshift.identifier('queryFn'), + node, + ) + } + + if (utils.isIdentifier(node)) { + const binding = v5Utils.getBindingFromScope(path, node.name) + + if (!binding) { + throw new UnknownUsageError(path.node, filePath) + } + + const isVariableDeclaration = jscodeshift.match(binding, { + type: jscodeshift.VariableDeclarator.name, + }) + + if (!isVariableDeclaration) { + return undefined + } + + const isTSAsExpression = jscodeshift.match(binding.init, { + type: jscodeshift.TSAsExpression.name, + }) + const initializer = isTSAsExpression + ? binding.init.expression + : binding.init + + if (isFunctionDefinition(initializer)) { + return jscodeshift.property( + 'init', + jscodeshift.identifier('queryFn'), + binding.id, + ) + } + } + + return undefined + } + + const replacer = (path) => { + const node = path.node + + try { + // If the given method/function call matches certain criteria, the node doesn't need to be replaced, this step can be skipped. + if (canSkipReplacement(node)) { + return node + } + + const keyProperty = v5Utils.transformArgumentToKey( + path, + node.arguments[0], + config.keyName, + filePath, + ) + + if (!keyProperty) { + throw new UnknownUsageError(node, filePath) + } + + const parameters = [jscodeshift.objectExpression([keyProperty])] + const targetObject = parameters[0] + const secondParameter = node.arguments[1] + + if (secondParameter) { + const queryFnProperty = myFnQueryFn(path, secondParameter) + + if (queryFnProperty) { + targetObject.properties.push(queryFnProperty) + + const thirdParameter = node.arguments[2] + + if (utils.isObjectExpression(thirdParameter)) { + v5Utils.copyPropertiesFromSource( + thirdParameter, + targetObject, + predicate, + ) + } else { + targetObject.properties.push( + jscodeshift.spreadElement(thirdParameter), + ) + } + + return jscodeshift.callExpression(node.original.callee, parameters) + } + + if (utils.isObjectExpression(secondParameter)) { + v5Utils.copyPropertiesFromSource( + secondParameter, + targetObject, + predicate, + ) + } + + return jscodeshift.callExpression(node.original.callee, parameters) + } + + return jscodeshift.callExpression(node.original.callee, parameters) + } catch (error) { + utils.warn( + error.name === UnknownUsageError.name + ? error.message + : `An unknown error occurred while processing the "${filePath}" file. Please review this file, because the codemod couldn't be applied.`, + ) + + return node + } + } + + createQueryClientTransformer({ jscodeshift, utils, root }).execute( + config.queryClientMethods, + replacer, + ) +} + +module.exports = transformQueryFnAwareUsages From 02752621cc5b41498eb941ca449aae338c6f279a Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Fri, 3 Mar 2023 14:50:23 +0100 Subject: [PATCH 4/8] chore(codemod): add better type definitions for `jscodeshift` parameters --- .../transformers/filter-aware-usage-transformer.js | 3 +-- .../transformers/query-fn-aware-usage-transformer.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js index 83b56ad1ed..c5ac9ca2d6 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js @@ -10,8 +10,7 @@ const createQueryCacheTransformer = require('../../../utils/transformers/query-c const createUseQueryLikeTransformer = require('../../../utils/transformers/use-query-like-transformer') /** - * - * @param {import('jscodeshift')} jscodeshift + * @param {import('jscodeshift').api} jscodeshift * @param {Object} utils * @param {import('jscodeshift').Collection} root * @param {string} filePath diff --git a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js index 0eb327012f..ed76fce6f0 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js @@ -6,8 +6,7 @@ const UnknownUsageError = require('../utils/unknown-usage-error') const createQueryClientTransformer = require('../../../utils/transformers/query-client-transformer') /** - * - * @param {import('jscodeshift')} jscodeshift + * @param {import('jscodeshift').api} jscodeshift * @param {Object} utils * @param {import('jscodeshift').Collection} root * @param {string} filePath From adbe04b0394bd2dcf50bbdb69af33c82f271fa1e Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Fri, 3 Mar 2023 16:02:37 +0100 Subject: [PATCH 5/8] feat(codemod): finish the implementation of `transformQueryFnAwareUsages` function --- .../query-fn-aware-usage-transformer.js | 55 ++++++++++++++++--- .../src/v5/remove-overloads/utils/index.js | 17 +++--- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js index ed76fce6f0..28aca2861c 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js @@ -42,6 +42,10 @@ const transformQueryFnAwareUsages = ({ ) } + /** + * @param {import('jscodeshift').Node} node + * @returns {boolean} + */ const isFunctionDefinition = (node) => { const isArrowFunctionExpression = jscodeshift.match(node, { type: jscodeshift.ArrowFunctionExpression.name, @@ -65,7 +69,7 @@ const transformQueryFnAwareUsages = ({ ) } - const myFnQueryFn = (path, node) => { + const transformArgumentToQueryFunction = (path, node) => { if (isFunctionDefinition(node)) { return jscodeshift.property( 'init', @@ -75,11 +79,7 @@ const transformQueryFnAwareUsages = ({ } if (utils.isIdentifier(node)) { - const binding = v5Utils.getBindingFromScope(path, node.name) - - if (!binding) { - throw new UnknownUsageError(path.node, filePath) - } + const binding = v5Utils.getBindingFromScope(path, node.name, filePath) const isVariableDeclaration = jscodeshift.match(binding, { type: jscodeshift.VariableDeclarator.name, @@ -108,6 +108,33 @@ const transformQueryFnAwareUsages = ({ return undefined } + const transformArgumentToOptionsObject = (path, node) => { + if (utils.isIdentifier(node)) { + const binding = v5Utils.getBindingFromScope(path, node.name, filePath) + + const isVariableDeclaration = jscodeshift.match(binding, { + type: jscodeshift.VariableDeclarator.name, + }) + + if (!isVariableDeclaration) { + return undefined + } + + const isTSAsExpression = jscodeshift.match(binding.init, { + type: jscodeshift.TSAsExpression.name, + }) + const initializer = isTSAsExpression + ? binding.init.expression + : binding.init + + if (utils.isObjectExpression(initializer)) { + return jscodeshift.spreadElement(binding.id) + } + } + + return undefined + } + const replacer = (path) => { const node = path.node @@ -133,7 +160,10 @@ const transformQueryFnAwareUsages = ({ const secondParameter = node.arguments[1] if (secondParameter) { - const queryFnProperty = myFnQueryFn(path, secondParameter) + const queryFnProperty = transformArgumentToQueryFunction( + path, + secondParameter, + ) if (queryFnProperty) { targetObject.properties.push(queryFnProperty) @@ -155,6 +185,17 @@ const transformQueryFnAwareUsages = ({ return jscodeshift.callExpression(node.original.callee, parameters) } + const optionsProperty = transformArgumentToOptionsObject( + path, + secondParameter, + ) + + if (optionsProperty) { + targetObject.properties.push(optionsProperty) + + return jscodeshift.callExpression(node.original.callee, parameters) + } + if (utils.isObjectExpression(secondParameter)) { v5Utils.copyPropertiesFromSource( secondParameter, diff --git a/packages/codemods/src/v5/remove-overloads/utils/index.js b/packages/codemods/src/v5/remove-overloads/utils/index.js index 24d21ae89f..6f6257cb90 100644 --- a/packages/codemods/src/v5/remove-overloads/utils/index.js +++ b/packages/codemods/src/v5/remove-overloads/utils/index.js @@ -19,9 +19,10 @@ module.exports = ({ jscodeshift, utils }) => { /** * @param {import('jscodeshift').NodePath} path * @param {string} argumentName + * @param {string} filePath * @returns {*} */ - const getBindingFromScope = (path, argumentName) => { + const getBindingFromScope = (path, argumentName, filePath) => { /** * If the current scope contains the declaration then we can use the actual one else we attempt to find the * binding from above. @@ -38,10 +39,16 @@ module.exports = ({ jscodeshift, utils }) => { return undefined } - return scope.bindings[argumentName] + const binding = scope.bindings[argumentName] .filter((item) => utils.isIdentifier(item.value)) .map((item) => item.parentPath.value) .at(0) + + if (!binding) { + throw new UnknownUsageError(path.node, filePath) + } + + return binding } /** @@ -66,11 +73,7 @@ module.exports = ({ jscodeshift, utils }) => { const transformArgumentToKey = (path, node, keyName, filePath) => { // If the first argument is an identifier we have to infer its type if possible. if (utils.isIdentifier(node)) { - const binding = getBindingFromScope(path, node.name) - - if (!binding) { - throw new UnknownUsageError(path.node, filePath) - } + const binding = getBindingFromScope(path, node.name, filePath) if (isArrayExpressionVariable(binding)) { return jscodeshift.property( From 4b1d95386fb6eddf0f59659574937d64723e4905 Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Fri, 3 Mar 2023 16:54:51 +0100 Subject: [PATCH 6/8] refactor(codemod): introduce `isSpreadElement` and `isFunctionDefinition` utility functions --- packages/codemods/src/utils/index.js | 20 ++++++++++++++++ .../filter-aware-usage-transformer.js | 4 +--- .../query-fn-aware-usage-transformer.js | 23 +++---------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/codemods/src/utils/index.js b/packages/codemods/src/utils/index.js index 0e5bb61c63..67d67d2f10 100644 --- a/packages/codemods/src/utils/index.js +++ b/packages/codemods/src/utils/index.js @@ -126,6 +126,24 @@ module.exports = ({ root, jscodeshift }) => { const isObjectProperty = (node) => jscodeshift.match(node, { type: jscodeshift.ObjectProperty.name }) + const isSpreadElement = (node) => + jscodeshift.match(node, { type: jscodeshift.SpreadElement.name }) + + /** + * @param {import('jscodeshift').Node} node + * @returns {boolean} + */ + const isFunctionDefinition = (node) => { + const isArrowFunctionExpression = jscodeshift.match(node, { + type: jscodeshift.ArrowFunctionExpression.name, + }) + const isFunctionExpression = jscodeshift.match(node, { + type: jscodeshift.FunctionExpression.name, + }) + + return isArrowFunctionExpression || isFunctionExpression + } + const warn = (message) => { if (process.env.NODE_ENV !== 'test') { console.warn(message) @@ -176,6 +194,8 @@ module.exports = ({ root, jscodeshift }) => { isArrayExpression, isObjectExpression, isObjectProperty, + isSpreadElement, + isFunctionDefinition, locateImports, warn, queryClient: { diff --git a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js index c5ac9ca2d6..92abad5427 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js @@ -78,9 +78,7 @@ const transformFilterAwareUsages = ({ // first argument, otherwise we use an empty array, because we can spread it during the objectExpression creation. if (utils.isObjectExpression(secondParameter)) { secondParameter.properties.forEach((property) => { - const isSpreadElement = jscodeshift.match(property, { - type: jscodeshift.SpreadElement.name, - }) + const isSpreadElement = utils.isSpreadElement(property) const isObjectProperty = utils.isObjectProperty(property) if ( diff --git a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js index 28aca2861c..b345eaa362 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js @@ -42,25 +42,8 @@ const transformQueryFnAwareUsages = ({ ) } - /** - * @param {import('jscodeshift').Node} node - * @returns {boolean} - */ - const isFunctionDefinition = (node) => { - const isArrowFunctionExpression = jscodeshift.match(node, { - type: jscodeshift.ArrowFunctionExpression.name, - }) - const isFunctionExpression = jscodeshift.match(node, { - type: jscodeshift.FunctionExpression.name, - }) - - return isArrowFunctionExpression || isFunctionExpression - } - const predicate = (property) => { - const isSpreadElement = jscodeshift.match(property, { - type: jscodeshift.SpreadElement.name, - }) + const isSpreadElement = utils.isSpreadElement(property) const isObjectProperty = utils.isObjectProperty(property) return ( @@ -70,7 +53,7 @@ const transformQueryFnAwareUsages = ({ } const transformArgumentToQueryFunction = (path, node) => { - if (isFunctionDefinition(node)) { + if (utils.isFunctionDefinition(node)) { return jscodeshift.property( 'init', jscodeshift.identifier('queryFn'), @@ -96,7 +79,7 @@ const transformQueryFnAwareUsages = ({ ? binding.init.expression : binding.init - if (isFunctionDefinition(initializer)) { + if (utils.isFunctionDefinition(initializer)) { return jscodeshift.property( 'init', jscodeshift.identifier('queryFn'), From 692a1e294df4f0f33ae32ba1a4af8e8e4acec923 Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Fri, 3 Mar 2023 17:20:47 +0100 Subject: [PATCH 7/8] refactor(codemod): re-use previously written utility functions in transformer functions --- .../filter-aware-usage-transformer.js | 34 +++++--- .../query-fn-aware-usage-transformer.js | 83 +++++++------------ .../src/v5/remove-overloads/utils/index.js | 21 +++++ 3 files changed, 73 insertions(+), 65 deletions(-) diff --git a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js index 92abad5427..09e1db4924 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js @@ -50,6 +50,20 @@ const transformFilterAwareUsages = ({ ) } + /** + * @param {import('jscodeshift').ObjectProperty} property + * @returns {boolean} + */ + const predicate = (property) => { + const isSpreadElement = utils.isSpreadElement(property) + const isObjectProperty = utils.isObjectProperty(property) + + return ( + isSpreadElement || + (isObjectProperty && property.key.name !== config.keyName) + ) + } + const replacer = (path) => { const node = path.node @@ -74,22 +88,18 @@ const transformFilterAwareUsages = ({ const secondParameter = node.arguments[1] if (secondParameter) { + const createdObjectExpression = parameters[0] + // If it has a second argument, and it's an object, then we get the properties of it, because it will be part of the // first argument, otherwise we use an empty array, because we can spread it during the objectExpression creation. if (utils.isObjectExpression(secondParameter)) { - secondParameter.properties.forEach((property) => { - const isSpreadElement = utils.isSpreadElement(property) - const isObjectProperty = utils.isObjectProperty(property) - - if ( - isSpreadElement || - (isObjectProperty && property.key.name !== config.keyName) - ) { - parameters[0].properties.push(property) - } - }) + v5Utils.copyPropertiesFromSource( + secondParameter, + createdObjectExpression, + predicate, + ) } else { - parameters[0].properties.push( + createdObjectExpression.properties.push( jscodeshift.spreadElement(secondParameter), ) } diff --git a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js index b345eaa362..77238d2caa 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js @@ -53,7 +53,14 @@ const transformQueryFnAwareUsages = ({ } const transformArgumentToQueryFunction = (path, node) => { - if (utils.isFunctionDefinition(node)) { + const isIdentifier = utils.isIdentifier(node) + const isFunctionDefinition = utils.isFunctionDefinition(node) + + if (!isIdentifier && !isFunctionDefinition) { + return undefined + } + + if (isFunctionDefinition) { return jscodeshift.property( 'init', jscodeshift.identifier('queryFn'), @@ -61,61 +68,31 @@ const transformQueryFnAwareUsages = ({ ) } - if (utils.isIdentifier(node)) { - const binding = v5Utils.getBindingFromScope(path, node.name, filePath) - - const isVariableDeclaration = jscodeshift.match(binding, { - type: jscodeshift.VariableDeclarator.name, - }) + const binding = v5Utils.getBindingFromScope(path, node.name, filePath) + const initializer = v5Utils.getInitializerByDeclarator(binding) - if (!isVariableDeclaration) { - return undefined - } - - const isTSAsExpression = jscodeshift.match(binding.init, { - type: jscodeshift.TSAsExpression.name, - }) - const initializer = isTSAsExpression - ? binding.init.expression - : binding.init - - if (utils.isFunctionDefinition(initializer)) { - return jscodeshift.property( - 'init', - jscodeshift.identifier('queryFn'), - binding.id, - ) - } + if (!utils.isFunctionDefinition(initializer)) { + return undefined } - return undefined + return jscodeshift.property( + 'init', + jscodeshift.identifier('queryFn'), + binding.id, + ) } const transformArgumentToOptionsObject = (path, node) => { - if (utils.isIdentifier(node)) { - const binding = v5Utils.getBindingFromScope(path, node.name, filePath) - - const isVariableDeclaration = jscodeshift.match(binding, { - type: jscodeshift.VariableDeclarator.name, - }) - - if (!isVariableDeclaration) { - return undefined - } + if (!utils.isIdentifier(node)) { + return undefined + } - const isTSAsExpression = jscodeshift.match(binding.init, { - type: jscodeshift.TSAsExpression.name, - }) - const initializer = isTSAsExpression - ? binding.init.expression - : binding.init + const binding = v5Utils.getBindingFromScope(path, node.name, filePath) + const initializer = v5Utils.getInitializerByDeclarator(binding) - if (utils.isObjectExpression(initializer)) { - return jscodeshift.spreadElement(binding.id) - } + if (utils.isObjectExpression(initializer)) { + return jscodeshift.spreadElement(binding.id) } - - return undefined } const replacer = (path) => { @@ -139,7 +116,7 @@ const transformQueryFnAwareUsages = ({ } const parameters = [jscodeshift.objectExpression([keyProperty])] - const targetObject = parameters[0] + const createdObjectExpression = parameters[0] const secondParameter = node.arguments[1] if (secondParameter) { @@ -149,18 +126,18 @@ const transformQueryFnAwareUsages = ({ ) if (queryFnProperty) { - targetObject.properties.push(queryFnProperty) + createdObjectExpression.properties.push(queryFnProperty) const thirdParameter = node.arguments[2] if (utils.isObjectExpression(thirdParameter)) { v5Utils.copyPropertiesFromSource( thirdParameter, - targetObject, + createdObjectExpression, predicate, ) } else { - targetObject.properties.push( + createdObjectExpression.properties.push( jscodeshift.spreadElement(thirdParameter), ) } @@ -174,7 +151,7 @@ const transformQueryFnAwareUsages = ({ ) if (optionsProperty) { - targetObject.properties.push(optionsProperty) + createdObjectExpression.properties.push(optionsProperty) return jscodeshift.callExpression(node.original.callee, parameters) } @@ -182,7 +159,7 @@ const transformQueryFnAwareUsages = ({ if (utils.isObjectExpression(secondParameter)) { v5Utils.copyPropertiesFromSource( secondParameter, - targetObject, + createdObjectExpression, predicate, ) } diff --git a/packages/codemods/src/v5/remove-overloads/utils/index.js b/packages/codemods/src/v5/remove-overloads/utils/index.js index 6f6257cb90..88512fd61e 100644 --- a/packages/codemods/src/v5/remove-overloads/utils/index.js +++ b/packages/codemods/src/v5/remove-overloads/utils/index.js @@ -51,6 +51,26 @@ module.exports = ({ jscodeshift, utils }) => { return binding } + /** + * @param {import('jscodeshift').VariableDeclarator} binding + * @returns {import('jscodeshift').Node|undefined} + */ + const getInitializerByDeclarator = (binding) => { + const isVariableDeclaration = jscodeshift.match(binding, { + type: jscodeshift.VariableDeclarator.name, + }) + + if (!isVariableDeclaration) { + return undefined + } + + const isTSAsExpression = jscodeshift.match(binding.init, { + type: jscodeshift.TSAsExpression.name, + }) + + return isTSAsExpression ? binding.init.expression : binding.init + } + /** * @param {import('jscodeshift').Node} node * @returns {boolean} @@ -97,6 +117,7 @@ module.exports = ({ jscodeshift, utils }) => { return { copyPropertiesFromSource, + getInitializerByDeclarator, getBindingFromScope, transformArgumentToKey, } From 5af2a2e83386af61acdac919b65e42e2ecc0b591 Mon Sep 17 00:00:00 2001 From: balazsmatepetro Date: Tue, 7 Mar 2023 17:30:45 +0100 Subject: [PATCH 8/8] chore(codemod): add codemod usage instructions --- docs/react/guides/migrating-to-v5.md | 35 +++++++++++++++++++ packages/codemods/src/v5/README.md | 1 - .../filter-aware-usage-transformer.js | 25 ++++++++++--- 3 files changed, 56 insertions(+), 5 deletions(-) delete mode 100644 packages/codemods/src/v5/README.md diff --git a/docs/react/guides/migrating-to-v5.md b/docs/react/guides/migrating-to-v5.md index b697284f9b..236dab2c8a 100644 --- a/docs/react/guides/migrating-to-v5.md +++ b/docs/react/guides/migrating-to-v5.md @@ -97,6 +97,41 @@ now we only support the object format. + queryClient.getQueryState(queryKey) ``` +#### Codemod + +To make the remove overloads migration easier, v5 comes with a codemod. + +> The codemod is a best efforts attempt to help you migrate the breaking change. Please review the generated code thoroughly! Also, there are edge cases that cannot be found by the code mod, so please keep an eye on the log output. + +If you want to run it against `.js` or `.jsx` files, please use the command below: + +``` +npx jscodeshift ./path/to/src/ \ + --extensions=js,jsx \ + --transform=./node_modules/@tanstack/react-query/codemods/v5/remove-overloads/remove-overloads.js +``` + +If you want to run it against `.ts` or `.tsx` files, please use the command below: + +``` +npx jscodeshift ./path/to/src/ \ + --extensions=ts,tsx \ + --parser=tsx \ + --transform=./node_modules/@tanstack/react-query/codemods/v5/remove-overloads/remove-overloads.js +``` + +Please note in the case of `TypeScript` you need to use `tsx` as the parser; otherwise, the codemod won't be applied properly! + +**Note:** Applying the codemod might break your code formatting, so please don't forget to run `prettier` and/or `eslint` after you've applied the codemod! + +A few notes about how codemod works: + +- Generally, we're looking for the lucky case, when the first parameter is an object expression and contains the "queryKey" or "mutationKey" property (depending on which hook/method call is being transformed). If this is the case, your code already matches the new signature, so the codemod won't touch it. 🎉 +- If the condition above is not fulfilled, then the codemod will check whether the first parameter is an array expression or an identifier that references an array expression. If this is the case, the codemod will put it into an object expression, then it will be the first parameter. +- If object parameters can be inferred, the codemod will attempt to copy the already existing properties to the newly created one. +- If the codemod cannot infer the usage, then it will leave a message on the console. The message contains the file name and the line number of the usage. In this case, you need to do the migration manually. +- If the transformation results in an error, you will also see a message on the console. This message will notify you something unexpected happened, please do the migration manually. + ### The `remove` method has been removed from useQuery Previously, remove method used to remove the query from the queryCache without informing observers about it. It was best used to remove data imperatively that is no longer needed, e.g. when logging a user out. diff --git a/packages/codemods/src/v5/README.md b/packages/codemods/src/v5/README.md deleted file mode 100644 index 5941d81a32..0000000000 --- a/packages/codemods/src/v5/README.md +++ /dev/null @@ -1 +0,0 @@ -# Codemods for v5 diff --git a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js index 09e1db4924..94246a7965 100644 --- a/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js +++ b/packages/codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.js @@ -40,8 +40,8 @@ const transformFilterAwareUsages = ({ ) /** - * This call has only one argument, which is an object expression. According to the new signature, this is a - * valid use case, so code changes are not needed. + * This call has at least one argument. If it's an object expression and contains the "queryKey" or "mutationKey" + * field, the transformation can be skipped, because it's already matching the expected signature. */ return ( callArguments.length > 0 && @@ -51,6 +51,9 @@ const transformFilterAwareUsages = ({ } /** + * This function checks whether the given object property is a spread element or a property that's not named + * "queryKey" or "mutationKey". + * * @param {import('jscodeshift').ObjectProperty} property * @returns {boolean} */ @@ -73,6 +76,12 @@ const transformFilterAwareUsages = ({ return node } + /** + * Here we attempt to determine the first parameter of the function call. If it's an array expression or an + * identifier that references an array expression then we create an object property from it. + * + * @type {import('jscodeshift').Property|undefined} + */ const keyProperty = v5Utils.transformArgumentToKey( path, node.arguments[0], @@ -80,6 +89,10 @@ const transformFilterAwareUsages = ({ filePath, ) + /** + * The first parameter couldn't be transformed into an object property, so it's time to throw an exception, + * it will notify the consumers that they need to rewrite this usage manually. + */ if (!keyProperty) { throw new UnknownUsageError(node, filePath) } @@ -90,8 +103,11 @@ const transformFilterAwareUsages = ({ if (secondParameter) { const createdObjectExpression = parameters[0] - // If it has a second argument, and it's an object, then we get the properties of it, because it will be part of the - // first argument, otherwise we use an empty array, because we can spread it during the objectExpression creation. + /** + * If it has a second argument, and it's an object expression, then we get the properties from it + * (except the "queryKey" or "mutationKey" properties), because these arguments will also be moved to the + * newly created object expression. + */ if (utils.isObjectExpression(secondParameter)) { v5Utils.copyPropertiesFromSource( secondParameter, @@ -99,6 +115,7 @@ const transformFilterAwareUsages = ({ predicate, ) } else { + // Otherwise, we simply spread the second parameter in the newly created object expression. createdObjectExpression.properties.push( jscodeshift.spreadElement(secondParameter), )