diff --git a/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts index 8ec6b7d67b..e33bdb30b1 100644 --- a/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts @@ -402,6 +402,20 @@ ruleTester.run('exhaustive-deps', rule, { } `, }, + { + name: 'queryFn as a ternary expression with dep and a skipToken', + code: normalizeIndent` + import { useQuery, skipToken } from "@tanstack/react-query"; + const fetch = true + + function Component({ id }) { + useQuery({ + queryKey: [id], + queryFn: fetch ? () => Promise.resolve(id) : skipToken + }) + } + `, + }, ], invalid: [ { @@ -733,5 +747,25 @@ ruleTester.run('exhaustive-deps', rule, { }, ], }, + { + name: 'should fail if queryFn is a ternary expression with missing dep and a skipToken', + code: normalizeIndent` + import { useQuery, skipToken } from "@tanstack/react-query"; + const fetch = true + + function Component({ id }) { + useQuery({ + queryKey: [], + queryFn: fetch ? () => Promise.resolve(id) : skipToken + }) + } + `, + errors: [ + { + messageId: 'missingDeps', + data: { deps: 'id' }, + }, + ], + }, ], }) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts index cd641b1990..472a341bdd 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts @@ -4,7 +4,7 @@ import { getDocsUrl } from '../../utils/get-docs-url' import { uniqueBy } from '../../utils/unique-by' import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports' import { ExhaustiveDepsUtils } from './exhaustive-deps.utils' -import type { TSESLint } from '@typescript-eslint/utils' +import type { TSESLint, TSESTree } from '@typescript-eslint/utils' import type { ExtraRuleDocs } from '../../types' const QUERY_KEY = 'queryKey' @@ -59,6 +59,7 @@ export const rule = createRule({ !ASTUtils.isNodeOfOneOf(queryFn.value, [ AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.ConditionalExpression, ]) ) { return @@ -88,7 +89,7 @@ export const rule = createRule({ const externalRefs = ASTUtils.getExternalRefs({ scopeManager, sourceCode: context.sourceCode, - node: queryFn.value, + node: getQueryFnRelevantNode(queryFn), }) const relevantRefs = externalRefs.filter((reference) => @@ -96,7 +97,7 @@ export const rule = createRule({ sourceCode: context.sourceCode, reference, scopeManager, - node: queryFn.value, + node: getQueryFnRelevantNode(queryFn), }), ) @@ -161,3 +162,18 @@ export const rule = createRule({ } }), }) + +function getQueryFnRelevantNode(queryFn: TSESTree.Property) { + if (queryFn.value.type !== AST_NODE_TYPES.ConditionalExpression) { + return queryFn.value + } + + if ( + queryFn.value.consequent.type === AST_NODE_TYPES.Identifier && + queryFn.value.consequent.name === 'skipToken' + ) { + return queryFn.value.alternate + } + + return queryFn.value.consequent +}