From 3565672894799c850fecf89a93d1f12cd66e1b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Curley?= Date: Tue, 15 Sep 2026 18:40:33 +0100 Subject: [PATCH 1/3] fix(eslint-plugin-query): recognize query client methods - Update prefer-query-options for `query` and `infiniteQuery` - Refresh infinite-query type tests and lint rule coverage --- .changeset/tough-queries-listen.md | 5 + .../infinite-query-options.test-d.ts | 17 --- .../src/__tests__/no-void-query-fn.test.ts | 102 ++---------------- .../__tests__/prefer-query-options.test.ts | 92 +++------------- .../prefer-query-options.rule.ts | 9 +- .../__tests__/infiniteQueryOptions.test-d.tsx | 17 --- .../__tests__/infiniteQueryOptions.test-d.tsx | 17 --- .../src/__tests__/useInfiniteQuery.test-d.tsx | 18 +--- .../createInfiniteQuery.test-d.ts | 18 +--- 9 files changed, 34 insertions(+), 261 deletions(-) create mode 100644 .changeset/tough-queries-listen.md diff --git a/.changeset/tough-queries-listen.md b/.changeset/tough-queries-listen.md new file mode 100644 index 00000000000..d5aafca42ad --- /dev/null +++ b/.changeset/tough-queries-listen.md @@ -0,0 +1,5 @@ +--- +'@tanstack/eslint-plugin-query': patch +--- + +Recognize `query` and `infiniteQuery` client calls in the `prefer-query-options` rule. diff --git a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts index 91a9d7e2fdf..babbf3c8da9 100644 --- a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts +++ b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts @@ -221,23 +221,6 @@ describe('infiniteQueryOptions', () => { // @ts-expect-error cannot pass infinite options to non-infinite query functions queryClient.query(options), ) - - // deprecated methods below to be removed next major version - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.ensureQueryData(options), - ) - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.fetchQuery(options), - ) - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.prefetchQuery(options), - ) }) it('should allow optional initialData function', () => { diff --git a/packages/eslint-plugin-query/src/__tests__/no-void-query-fn.test.ts b/packages/eslint-plugin-query/src/__tests__/no-void-query-fn.test.ts index 700a4f95e68..4cdd0ec8621 100644 --- a/packages/eslint-plugin-query/src/__tests__/no-void-query-fn.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/no-void-query-fn.test.ts @@ -204,66 +204,29 @@ ruleTester.run('no-void-query-fn', rule, { `, }, { - name: 'fetchQuery queryFn returns a value', + name: 'query queryFn returns a value', code: normalizeIndent` import { QueryClient } from '@tanstack/react-query' const queryClient = new QueryClient() - queryClient.fetchQuery({ + queryClient.query({ queryKey: ['test'], queryFn: () => fetch('/api/test').then((r) => r.json()), }) `, }, { - name: 'prefetchQuery queryFn returns a value', + name: 'infiniteQuery queryFn returns a value', code: normalizeIndent` import { QueryClient } from '@tanstack/react-query' const queryClient = new QueryClient() - queryClient.prefetchQuery({ - queryKey: ['test'], - queryFn: () => fetch('/api/test').then((r) => r.json()), - }) - `, - }, - { - name: 'prefetchInfiniteQuery queryFn returns a value', - code: normalizeIndent` - import { QueryClient } from '@tanstack/react-query' - - const queryClient = new QueryClient() - queryClient.prefetchInfiniteQuery({ - queryKey: ['test'], - queryFn: ({ pageParam }: { pageParam: number }) => - fetch(\`/api/test?page=\${pageParam}\`).then((r) => r.json()), - initialPageParam: 0, - }) - `, - }, - { - name: 'ensureQueryData queryFn returns a value', - code: normalizeIndent` - import { QueryClient } from '@tanstack/react-query' - - const queryClient = new QueryClient() - queryClient.ensureQueryData({ - queryKey: ['test'], - queryFn: () => fetch('/api/test').then((r) => r.json()), - }) - `, - }, - { - name: 'ensureInfiniteQueryData queryFn returns a value', - code: normalizeIndent` - import { QueryClient } from '@tanstack/react-query' - - const queryClient = new QueryClient() - queryClient.ensureInfiniteQueryData({ + queryClient.infiniteQuery({ queryKey: ['test'], queryFn: ({ pageParam }: { pageParam: number }) => fetch(\`/api/test?page=\${pageParam}\`).then((r) => r.json()), initialPageParam: 0, + getNextPageParam: () => undefined, }) `, }, @@ -555,58 +518,12 @@ ruleTester.run('no-void-query-fn', rule, { errors: [{ messageId: 'noVoidReturn' }], }, { - name: 'fetchQuery queryFn returns void', - code: normalizeIndent` - import { QueryClient } from '@tanstack/react-query' - - const queryClient = new QueryClient() - queryClient.fetchQuery({ - queryKey: ['test'], - queryFn: async () => { - await fetch('/api/test') - }, - }) - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'prefetchQuery queryFn returns void', - code: normalizeIndent` - import { QueryClient } from '@tanstack/react-query' - - const queryClient = new QueryClient() - queryClient.prefetchQuery({ - queryKey: ['test'], - queryFn: async () => { - await fetch('/api/test') - }, - }) - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'prefetchInfiniteQuery queryFn returns void', - code: normalizeIndent` - import { QueryClient } from '@tanstack/react-query' - - const queryClient = new QueryClient() - queryClient.prefetchInfiniteQuery({ - queryKey: ['test'], - queryFn: async ({ pageParam }: { pageParam: number }) => { - await fetch(\`/api/test?page=\${pageParam}\`) - }, - initialPageParam: 0, - }) - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'ensureQueryData queryFn returns void', + name: 'query queryFn returns void', code: normalizeIndent` import { QueryClient } from '@tanstack/react-query' const queryClient = new QueryClient() - queryClient.ensureQueryData({ + queryClient.query({ queryKey: ['test'], queryFn: async () => { await fetch('/api/test') @@ -616,17 +533,18 @@ ruleTester.run('no-void-query-fn', rule, { errors: [{ messageId: 'noVoidReturn' }], }, { - name: 'ensureInfiniteQueryData queryFn returns void', + name: 'infiniteQuery queryFn returns void', code: normalizeIndent` import { QueryClient } from '@tanstack/react-query' const queryClient = new QueryClient() - queryClient.ensureInfiniteQueryData({ + queryClient.infiniteQuery({ queryKey: ['test'], queryFn: async ({ pageParam }: { pageParam: number }) => { await fetch(\`/api/test?page=\${pageParam}\`) }, initialPageParam: 0, + getNextPageParam: () => undefined, }) `, errors: [{ messageId: 'noVoidReturn' }], diff --git a/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts index ff5962623ba..2442b56c794 100644 --- a/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/prefer-query-options.test.ts @@ -221,7 +221,7 @@ describe('prefer-query-options', () => { const queryClient = useQueryClient() function run(queryClient) { - queryClient.fetchQuery({ + queryClient.query({ queryKey: ['todos'], queryFn: () => fetchTodos(), }) @@ -232,12 +232,12 @@ describe('prefer-query-options', () => { `, }, { - name: 'non-queryClient fetchQuery call is ignored', + name: 'non-queryClient query call is ignored', code: normalizeIndent` import { useQuery } from '@tanstack/react-query' const analytics = { - fetchQuery(options) { + query(options) { return options }, } @@ -245,7 +245,7 @@ describe('prefer-query-options', () => { function Component() { useQuery(todosOptions) - analytics.fetchQuery({ + analytics.query({ queryKey: ['todos'], queryFn: () => fetchTodos(), }) @@ -581,13 +581,13 @@ describe('prefer-query-options', () => { valid: [], invalid: [ { - name: 'client.fetchQuery with inline queryKey + queryFn', + name: 'client.query with inline queryKey + queryFn', code: normalizeIndent` import { useQueryClient } from '@tanstack/react-query' function Component() { const client = useQueryClient() - client.fetchQuery({ + client.query({ queryKey: ['todos'], queryFn: () => fetchTodos(), }) @@ -603,7 +603,7 @@ describe('prefer-query-options', () => { function Component() { const client = getClient() - client.fetchQuery({ + client.query({ queryKey: ['todos'], queryFn: () => fetchTodos(), }) @@ -619,7 +619,7 @@ describe('prefer-query-options', () => { const queryClient = new Client() - queryClient.fetchQuery({ + queryClient.query({ queryKey: ['todos'], queryFn: () => fetchTodos(), }) @@ -661,13 +661,13 @@ describe('prefer-query-options', () => { valid: [], invalid: [ { - name: 'queryClient.fetchQuery with inline queryKey + queryFn', + name: 'queryClient.query with inline queryKey + queryFn', code: normalizeIndent` import { useQueryClient } from '@tanstack/react-query' function Component() { const queryClient = useQueryClient() - queryClient.fetchQuery({ + queryClient.query({ queryKey: ['todos'], queryFn: () => fetchTodos(), }) @@ -677,81 +677,13 @@ describe('prefer-query-options', () => { errors: [{ messageId: 'preferQueryOptions' }], }, { - name: 'queryClient.prefetchQuery with inline queryKey + queryFn', + name: 'queryClient.infiniteQuery with inline queryKey + queryFn', code: normalizeIndent` import { useQueryClient } from '@tanstack/react-query' function Component() { const queryClient = useQueryClient() - queryClient.prefetchQuery({ - queryKey: ['todos'], - queryFn: () => fetchTodos(), - }) - return null - } - `, - errors: [{ messageId: 'preferQueryOptions' }], - }, - { - name: 'queryClient.fetchInfiniteQuery with inline queryKey + queryFn', - code: normalizeIndent` - import { useQueryClient } from '@tanstack/react-query' - - function Component() { - const queryClient = useQueryClient() - queryClient.fetchInfiniteQuery({ - queryKey: ['todos'], - queryFn: ({ pageParam }) => fetchTodos(pageParam), - initialPageParam: 0, - getNextPageParam: (lastPage) => lastPage.nextCursor, - }) - return null - } - `, - errors: [{ messageId: 'preferQueryOptions' }], - }, - { - name: 'queryClient.prefetchInfiniteQuery with inline queryKey + queryFn', - code: normalizeIndent` - import { useQueryClient } from '@tanstack/react-query' - - function Component() { - const queryClient = useQueryClient() - queryClient.prefetchInfiniteQuery({ - queryKey: ['todos'], - queryFn: ({ pageParam }) => fetchTodos(pageParam), - initialPageParam: 0, - getNextPageParam: (lastPage) => lastPage.nextCursor, - }) - return null - } - `, - errors: [{ messageId: 'preferQueryOptions' }], - }, - { - name: 'queryClient.ensureQueryData with inline queryKey + queryFn', - code: normalizeIndent` - import { useQueryClient } from '@tanstack/react-query' - - function Component() { - const queryClient = useQueryClient() - queryClient.ensureQueryData({ - queryKey: ['todos'], - queryFn: () => fetchTodos(), - }) - return null - } - `, - errors: [{ messageId: 'preferQueryOptions' }], - }, - { - name: 'queryClient.ensureInfiniteQueryData with inline queryKey + queryFn', - code: normalizeIndent` - import { useQueryClient } from '@tanstack/react-query' - - function Component() { - const queryClient = useQueryClient() - queryClient.ensureInfiniteQueryData({ + queryClient.infiniteQuery({ queryKey: ['todos'], queryFn: ({ pageParam }) => fetchTodos(pageParam), initialPageParam: 0, diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts index b7e65e9907e..e4c858ba35a 100644 --- a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts +++ b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts @@ -20,14 +20,7 @@ const queriesHooks = ['useQueries', 'useSuspenseQueries'] const filterHooks = ['useIsFetching'] -const queryClientOptionMethods = [ - 'fetchQuery', - 'prefetchQuery', - 'fetchInfiniteQuery', - 'prefetchInfiniteQuery', - 'ensureQueryData', - 'ensureInfiniteQueryData', -] +const queryClientOptionMethods = ['query', 'infiniteQuery'] const queryClientQueryKeyMethods = [ 'getQueryData', diff --git a/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx index 84edddab4d3..fb764b2daa6 100644 --- a/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx +++ b/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -231,23 +231,6 @@ describe('infiniteQueryOptions', () => { // @ts-expect-error cannot pass infinite options to non-infinite query functions queryClient.query(options), ) - - // deprecated methods below to be removed next major version - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.ensureQueryData(options), - ) - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.fetchQuery(options), - ) - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.prefetchQuery(options), - ) }) it('allow optional initialData function', () => { diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx index 4e4fb937716..2aa1d61039e 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -271,23 +271,6 @@ describe('infiniteQueryOptions', () => { // @ts-expect-error cannot pass infinite options to non-infinite query functions queryClient.query(options), ) - - // deprecated methods to be removed in v6 - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.ensureQueryData(options), - ) - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.fetchQuery(options), - ) - assertType( - // @ts-expect-error cannot pass infinite options to non-infinite query functions - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.prefetchQuery(options), - ) }) it('allow optional initialData function', () => { diff --git a/packages/solid-query/src/__tests__/useInfiniteQuery.test-d.tsx b/packages/solid-query/src/__tests__/useInfiniteQuery.test-d.tsx index a29a11f507b..d8c0aef7483 100644 --- a/packages/solid-query/src/__tests__/useInfiniteQuery.test-d.tsx +++ b/packages/solid-query/src/__tests__/useInfiniteQuery.test-d.tsx @@ -28,25 +28,13 @@ describe('useInfiniteQuery', () => { })) }) - it('initialPageParam should define type of param passed to queryFunctionContext for fetchInfiniteQuery', () => { + it('initialPageParam should define type of param passed to queryFunctionContext for infiniteQuery', () => { const queryClient = new QueryClient() - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.fetchInfiniteQuery({ - queryKey: queryKey(), - queryFn: ({ pageParam }) => { - expectTypeOf(pageParam).toEqualTypeOf() - }, - initialPageParam: 1, - }) - }) - - it('initialPageParam should define type of param passed to queryFunctionContext for prefetchInfiniteQuery', () => { - const queryClient = new QueryClient() - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.prefetchInfiniteQuery({ + queryClient.infiniteQuery({ queryKey: queryKey(), queryFn: ({ pageParam }) => { expectTypeOf(pageParam).toEqualTypeOf() + return pageParam }, initialPageParam: 1, }) diff --git a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.test-d.ts b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.test-d.ts index 0dc76fbb5d6..e3f4bcf5301 100644 --- a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.test-d.ts +++ b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.test-d.ts @@ -28,25 +28,13 @@ describe('createInfiniteQuery', () => { })) }) - it('initialPageParam should define type of param passed to queryFunctionContext for fetchInfiniteQuery', () => { + it('initialPageParam should define type of param passed to queryFunctionContext for infiniteQuery', () => { const queryClient = new QueryClient() - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.fetchInfiniteQuery({ - queryKey: queryKey(), - queryFn: ({ pageParam }) => { - expectTypeOf(pageParam).toEqualTypeOf() - }, - initialPageParam: 1, - }) - }) - - it('initialPageParam should define type of param passed to queryFunctionContext for prefetchInfiniteQuery', () => { - const queryClient = new QueryClient() - // eslint-disable-next-line no-restricted-syntax -- grandfathered direct test - queryClient.prefetchInfiniteQuery({ + queryClient.infiniteQuery({ queryKey: queryKey(), queryFn: ({ pageParam }) => { expectTypeOf(pageParam).toEqualTypeOf() + return pageParam }, initialPageParam: 1, }) From 9827188ed5a248079fd777f8894197bf5491d523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Curley?= Date: Thu, 17 Sep 2026 13:02:10 +0100 Subject: [PATCH 2/3] fix(eslint-plugin-query): keep flagging deprecated query client methods --- .../prefer-query-options/prefer-query-options.rule.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts index e4c858ba35a..5e5342d2069 100644 --- a/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts +++ b/packages/eslint-plugin-query/src/rules/prefer-query-options/prefer-query-options.rule.ts @@ -20,7 +20,16 @@ const queriesHooks = ['useQueries', 'useSuspenseQueries'] const filterHooks = ['useIsFetching'] -const queryClientOptionMethods = ['query', 'infiniteQuery'] +const queryClientOptionMethods = [ + 'query', + 'infiniteQuery', + 'fetchQuery', + 'prefetchQuery', + 'fetchInfiniteQuery', + 'prefetchInfiniteQuery', + 'ensureQueryData', + 'ensureInfiniteQueryData', +] const queryClientQueryKeyMethods = [ 'getQueryData', From 2217395cbb8e0ccd213b2349d99aff04e579e146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Curley?= Date: Thu, 17 Sep 2026 14:30:16 +0100 Subject: [PATCH 3/3] test(query-core): assert prefetch caches data in garbage collection test --- packages/query-core/src/__tests__/queryClient.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/query-core/src/__tests__/queryClient.test.tsx b/packages/query-core/src/__tests__/queryClient.test.tsx index d76bd14da49..df4d0afdf60 100644 --- a/packages/query-core/src/__tests__/queryClient.test.tsx +++ b/packages/query-core/src/__tests__/queryClient.test.tsx @@ -1955,7 +1955,7 @@ describe('queryClient', () => { gcTime: 10, }) .catch(noop) - expect(queryCache.find({ queryKey: key })).toBeDefined() + expect(queryCache.find({ queryKey: key })?.state.data).toBe('data') await vi.advanceTimersByTimeAsync(15) expect(queryCache.find({ queryKey: key })).not.toBeDefined() })