diff --git a/.changeset/cold-falcons-warn.md b/.changeset/cold-falcons-warn.md new file mode 100644 index 0000000000..fa005725e1 --- /dev/null +++ b/.changeset/cold-falcons-warn.md @@ -0,0 +1,5 @@ +--- +'@tanstack/eslint-plugin-query': patch +--- + +fix: exhaustive-deps with variables and type assertions 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 e3ea5790dd..f27b058ec5 100644 --- a/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts @@ -226,6 +226,39 @@ ruleTester.run('exhaustive-deps', rule, { }) `, }, + { + name: 'should pass with queryKeyFactory result assigned to a variable', + code: ` + function fooQueryKeyFactory(dep: string) { + return ["foo", dep]; + } + + const useFoo = (dep: string) => { + const queryKey = fooQueryKeyFactory(dep); + return useQuery({ + queryKey, + queryFn: () => Promise.resolve(dep), + }) + } + `, + }, + { + name: 'should pass with queryKeyFactory result assigned to a variable 2', + code: ` + function fooQueryKeyFactory(dep: string) { + const x = ["foo", dep] as const; + return x as const; + } + + const useFoo = (dep: string) => { + const queryKey = fooQueryKeyFactory(dep); + return useQuery({ + queryKey, + queryFn: () => Promise.resolve(dep), + }) + } + `, + }, { name: 'should not treat new Error as missing dependency', code: normalizeIndent` @@ -246,6 +279,30 @@ ruleTester.run('exhaustive-deps', rule, { } `, }, + { + name: 'should see id when there is a const assertion of a variable dereference', + code: normalizeIndent` + const useX = (id: number) => { + const queryKey = ['foo', id] + return useQuery({ + queryKey: queryKey as const, + queryFn: async () => id, + }) + } + `, + }, + { + name: 'should see id when there is a const assertion assigned to a variable', + code: normalizeIndent` + const useX = (id: number) => { + const queryKey = ['foo', id] as const + return useQuery({ + queryKey, + queryFn: async () => id, + }) + } + `, + }, { name: 'should not fail if queryKey is having the whole object while queryFn uses some props of it', code: normalizeIndent` 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 bb87b04552..15c0918e97 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 @@ -65,25 +65,10 @@ export const rule = createRule({ return } - let queryKeyNode = queryKey.value - - if ( - queryKeyNode.type === AST_NODE_TYPES.TSAsExpression && - queryKeyNode.expression.type === AST_NODE_TYPES.ArrayExpression - ) { - queryKeyNode = queryKeyNode.expression - } - - if (queryKeyNode.type === AST_NODE_TYPES.Identifier) { - const expression = ASTUtils.getReferencedExpressionByIdentifier({ - context, - node: queryKeyNode, - }) - - if (expression?.type === AST_NODE_TYPES.ArrayExpression) { - queryKeyNode = expression - } - } + const queryKeyNode = dereferenceVariablesAndTypeAssertions( + queryKey.value, + context, + ) const externalRefs = ASTUtils.getExternalRefs({ scopeManager, @@ -182,3 +167,38 @@ function getQueryFnRelevantNode(queryFn: TSESTree.Property) { return queryFn.value.consequent } + +function dereferenceVariablesAndTypeAssertions( + queryKeyNode: TSESTree.Node, + context: Readonly>>, +) { + const visitedNodes = new Set() + + for (let i = 0; i < 1 << 8; ++i) { + if (visitedNodes.has(queryKeyNode)) { + return queryKeyNode + } + visitedNodes.add(queryKeyNode) + + switch (queryKeyNode.type) { + case AST_NODE_TYPES.TSAsExpression: + queryKeyNode = queryKeyNode.expression + break + case AST_NODE_TYPES.Identifier: { + const expression = ASTUtils.getReferencedExpressionByIdentifier({ + context, + node: queryKeyNode, + }) + + if (expression == null) { + return queryKeyNode + } + queryKeyNode = expression + break + } + default: + return queryKeyNode + } + } + return queryKeyNode +}