From 97c8dc8baf0dfca0a420f2af8d67477424ba3d3c Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 30 Mar 2025 14:47:50 +0200 Subject: [PATCH] Revert "feat(eslint-plugin): no void `queryFn` (#8878)" This reverts commit c2e5272346b9ab594499e2d579eb51680d6b75c5. --- docs/eslint/eslint-plugin-query.md | 1 - docs/eslint/no-void-query-fn.md | 39 --- .../src/__tests__/no-void-query-fn.test.ts | 325 ------------------ .../src/__tests__/ts-fixture/file.ts | 1 - .../src/__tests__/ts-fixture/tsconfig.json | 10 - packages/eslint-plugin-query/src/index.ts | 2 - packages/eslint-plugin-query/src/rules.ts | 2 - .../no-void-query-fn/no-void-query-fn.rule.ts | 84 ----- 8 files changed, 464 deletions(-) delete mode 100644 docs/eslint/no-void-query-fn.md delete mode 100644 packages/eslint-plugin-query/src/__tests__/no-void-query-fn.test.ts delete mode 100644 packages/eslint-plugin-query/src/__tests__/ts-fixture/file.ts delete mode 100644 packages/eslint-plugin-query/src/__tests__/ts-fixture/tsconfig.json delete mode 100644 packages/eslint-plugin-query/src/rules/no-void-query-fn/no-void-query-fn.rule.ts diff --git a/docs/eslint/eslint-plugin-query.md b/docs/eslint/eslint-plugin-query.md index bc092f3b77..359fb1274f 100644 --- a/docs/eslint/eslint-plugin-query.md +++ b/docs/eslint/eslint-plugin-query.md @@ -98,4 +98,3 @@ Alternatively, add `@tanstack/query` to the plugins section, and configure the r - [@tanstack/query/stable-query-client](./stable-query-client.md) - [@tanstack/query/no-unstable-deps](./no-unstable-deps.md) - [@tanstack/query/infinite-query-property-order](./infinite-query-property-order.md) -- [@tanstack/query/no-void-query-fn](./no-void-query-fn.md) diff --git a/docs/eslint/no-void-query-fn.md b/docs/eslint/no-void-query-fn.md deleted file mode 100644 index 445c437751..0000000000 --- a/docs/eslint/no-void-query-fn.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -id: no-void-query-fn -title: Disallow returning void from query functions ---- - -Query functions must return a value that will be cached by TanStack Query. Functions that don't return a value (void functions) can lead to unexpected behavior and might indicate a mistake in the implementation. - -## Rule Details - -Example of **incorrect** code for this rule: - -```tsx -/* eslint "@tanstack/query/no-void-query-fn": "error" */ - -useQuery({ - queryKey: ['todos'], - queryFn: async () => { - await api.todos.fetch() // Function doesn't return the fetched data - }, -}) -``` - -Example of **correct** code for this rule: - -```tsx -/* eslint "@tanstack/query/no-void-query-fn": "error" */ -useQuery({ - queryKey: ['todos'], - queryFn: async () => { - const todos = await api.todos.fetch() - return todos - }, -}) -``` - -## Attributes - -- [x] ✅ Recommended -- [ ] 🔧 Fixable 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 deleted file mode 100644 index c136a2582c..0000000000 --- a/packages/eslint-plugin-query/src/__tests__/no-void-query-fn.test.ts +++ /dev/null @@ -1,325 +0,0 @@ -import path from 'node:path' -import { RuleTester } from '@typescript-eslint/rule-tester' -import { afterAll, describe, it } from 'vitest' -import { rule } from '../rules/no-void-query-fn/no-void-query-fn.rule' -import { normalizeIndent } from './test-utils' - -RuleTester.afterAll = afterAll -RuleTester.describe = describe -RuleTester.it = it - -const ruleTester = new RuleTester({ - languageOptions: { - parser: await import('@typescript-eslint/parser'), - parserOptions: { - project: true, - tsconfigRootDir: path.resolve(__dirname, './ts-fixture'), - }, - }, -}) - -ruleTester.run('no-void-query-fn', rule, { - valid: [ - { - name: 'queryFn returns a value', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => ({ data: 'test' }), - }) - return null - } - `, - }, - { - name: 'queryFn returns a Promise', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: async () => ({ data: 'test' }), - }) - return null - } - `, - }, - { - name: 'queryFn returns Promise.resolve', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => Promise.resolve({ data: 'test' }), - }) - return null - } - `, - }, - { - name: 'queryFn with explicit Promise type', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - interface Data { - value: string - } - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: async (): Promise => { - return { value: 'test' } - }, - }) - return null - } - `, - }, - { - name: 'queryFn with generic Promise type', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - interface Response { - data: T - } - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: async (): Promise> => { - return { data: 'test' } - }, - }) - return null - } - `, - }, - { - name: 'queryFn with external async function', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - async function fetchData(): Promise<{ data: string }> { - return { data: 'test' } - } - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: fetchData, - }) - return null - } - `, - }, - { - name: 'queryFn returns null', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => null, - }) - return null - } - `, - }, - { - name: 'queryFn returns 0', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => 0, - }) - return null - } - `, - }, - { - name: 'queryFn returns false', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => false, - }) - return null - } - `, - }, - ], - invalid: [ - { - name: 'queryFn returns void', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => { - console.log('test') - }, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'queryFn returns undefined', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => undefined, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'async queryFn returns void', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: async () => { - await someOperation() - }, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'queryFn with explicit void Promise', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: async (): Promise => { - await someOperation() - }, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'queryFn with Promise.resolve(undefined)', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => Promise.resolve(undefined), - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'queryFn with external void async function', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - async function voidOperation(): Promise { - await someOperation() - } - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: voidOperation, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'queryFn with conditional return (one branch missing)', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => { - if (Math.random() > 0.5) { - return { data: 'test' } - } - // Missing return in the else case - }, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'queryFn with ternary operator returning undefined', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: () => Math.random() > 0.5 ? { data: 'test' } : undefined, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - { - name: 'async queryFn with try/catch missing return in catch', - code: normalizeIndent` - import { useQuery } from '@tanstack/react-query' - - function Component() { - const query = useQuery({ - queryKey: ['test'], - queryFn: async () => { - try { - return { data: 'test' } - } catch (error) { - console.error(error) - // No return here results in an implicit undefined - } - }, - }) - return null - } - `, - errors: [{ messageId: 'noVoidReturn' }], - }, - ], -}) diff --git a/packages/eslint-plugin-query/src/__tests__/ts-fixture/file.ts b/packages/eslint-plugin-query/src/__tests__/ts-fixture/file.ts deleted file mode 100644 index 182a85033f..0000000000 --- a/packages/eslint-plugin-query/src/__tests__/ts-fixture/file.ts +++ /dev/null @@ -1 +0,0 @@ -// File needs to exists diff --git a/packages/eslint-plugin-query/src/__tests__/ts-fixture/tsconfig.json b/packages/eslint-plugin-query/src/__tests__/ts-fixture/tsconfig.json deleted file mode 100644 index 27fd308e22..0000000000 --- a/packages/eslint-plugin-query/src/__tests__/ts-fixture/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "Bundler", - "strict": true, - "skipLibCheck": true - }, - "include": ["**/*"] -} diff --git a/packages/eslint-plugin-query/src/index.ts b/packages/eslint-plugin-query/src/index.ts index f16cb985b8..19c04064d4 100644 --- a/packages/eslint-plugin-query/src/index.ts +++ b/packages/eslint-plugin-query/src/index.ts @@ -30,7 +30,6 @@ Object.assign(plugin.configs, { '@tanstack/query/stable-query-client': 'error', '@tanstack/query/no-unstable-deps': 'error', '@tanstack/query/infinite-query-property-order': 'error', - '@tanstack/query/no-void-query-fn': 'error', }, }, 'flat/recommended': [ @@ -45,7 +44,6 @@ Object.assign(plugin.configs, { '@tanstack/query/stable-query-client': 'error', '@tanstack/query/no-unstable-deps': 'error', '@tanstack/query/infinite-query-property-order': 'error', - '@tanstack/query/no-void-query-fn': 'error', }, }, ], diff --git a/packages/eslint-plugin-query/src/rules.ts b/packages/eslint-plugin-query/src/rules.ts index 5163deb3de..73a6efdd51 100644 --- a/packages/eslint-plugin-query/src/rules.ts +++ b/packages/eslint-plugin-query/src/rules.ts @@ -3,7 +3,6 @@ import * as stableQueryClient from './rules/stable-query-client/stable-query-cli import * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule' import * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.rule' import * as infiniteQueryPropertyOrder from './rules/infinite-query-property-order/infinite-query-property-order.rule' -import * as noVoidQueryFn from './rules/no-void-query-fn/no-void-query-fn.rule' import type { ESLintUtils } from '@typescript-eslint/utils' import type { ExtraRuleDocs } from './types' @@ -21,5 +20,4 @@ export const rules: Record< [noRestDestructuring.name]: noRestDestructuring.rule, [noUnstableDeps.name]: noUnstableDeps.rule, [infiniteQueryPropertyOrder.name]: infiniteQueryPropertyOrder.rule, - [noVoidQueryFn.name]: noVoidQueryFn.rule, } diff --git a/packages/eslint-plugin-query/src/rules/no-void-query-fn/no-void-query-fn.rule.ts b/packages/eslint-plugin-query/src/rules/no-void-query-fn/no-void-query-fn.rule.ts deleted file mode 100644 index 9c202e621a..0000000000 --- a/packages/eslint-plugin-query/src/rules/no-void-query-fn/no-void-query-fn.rule.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ESLintUtils } from '@typescript-eslint/utils' -import ts from 'typescript' -import { ASTUtils } from '../../utils/ast-utils' -import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports' -import { getDocsUrl } from '../../utils/get-docs-url' -import type { ExtraRuleDocs } from '../../types' - -export const name = 'no-void-query-fn' - -const createRule = ESLintUtils.RuleCreator(getDocsUrl) - -export const rule = createRule({ - name, - meta: { - type: 'problem', - docs: { - description: 'Ensures queryFn returns a non-undefined value', - recommended: 'error', - }, - messages: { - noVoidReturn: 'queryFn must return a non-undefined value', - }, - schema: [], - }, - defaultOptions: [], - - create: detectTanstackQueryImports((context) => { - return { - Property(node) { - if ( - !ASTUtils.isObjectExpression(node.parent) || - !ASTUtils.isIdentifierWithName(node.key, 'queryFn') - ) { - return - } - - const parserServices = context.sourceCode.parserServices - - if ( - !parserServices || - !parserServices.esTreeNodeToTSNodeMap || - !parserServices.program - ) { - return - } - - const checker = parserServices.program.getTypeChecker() - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.value) - const type = checker.getTypeAtLocation(tsNode) - - // Get the return type of the function - if (type.getCallSignatures().length > 0) { - const returnType = type.getCallSignatures()[0]?.getReturnType() - - if (!returnType) { - return - } - - // Check if return type is void or undefined - if (isIllegalReturn(checker, returnType)) { - context.report({ - node: node.value, - messageId: 'noVoidReturn', - }) - } - } - }, - } - }), -}) - -function isIllegalReturn(checker: ts.TypeChecker, type: ts.Type): boolean { - const awaited = checker.getAwaitedType(type) - - if (!awaited) return false - - if (awaited.isUnion()) { - return awaited.types.some((t) => isIllegalReturn(checker, t)) - } - - return awaited.flags & (ts.TypeFlags.Void | ts.TypeFlags.Undefined) - ? true - : false -}