Skip to content

Commit 24a2266

Browse files
fbielercoderabbitai[bot]lachlancollinsautofix-ci[bot]TkDodo
authored
fix(eslint-plugin): exhaustive-deps with variables and type assertions (#9643) (#9687)
* fix(eslint-plugin): exhaustive-deps with variables and type assertions (#9643) Recursively dereference variables and type assertions in query keys. Also dereference even if the referenced expression is not an array expression. * fixup! fix(eslint-plugin): exhaustive-deps with variables and type assertions (#9643) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fixup! fix(eslint-plugin): exhaustive-deps with variables and type assertions (#9643) * Add changeset * ci: apply automated fixes --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Lachlan Collins <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Dominik Dorfmeister <[email protected]>
1 parent b01be85 commit 24a2266

File tree

3 files changed

+101
-19
lines changed

3 files changed

+101
-19
lines changed

.changeset/cold-falcons-warn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/eslint-plugin-query': patch
3+
---
4+
5+
fix: exhaustive-deps with variables and type assertions

packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,39 @@ ruleTester.run('exhaustive-deps', rule, {
226226
})
227227
`,
228228
},
229+
{
230+
name: 'should pass with queryKeyFactory result assigned to a variable',
231+
code: `
232+
function fooQueryKeyFactory(dep: string) {
233+
return ["foo", dep];
234+
}
235+
236+
const useFoo = (dep: string) => {
237+
const queryKey = fooQueryKeyFactory(dep);
238+
return useQuery({
239+
queryKey,
240+
queryFn: () => Promise.resolve(dep),
241+
})
242+
}
243+
`,
244+
},
245+
{
246+
name: 'should pass with queryKeyFactory result assigned to a variable 2',
247+
code: `
248+
function fooQueryKeyFactory(dep: string) {
249+
const x = ["foo", dep] as const;
250+
return x as const;
251+
}
252+
253+
const useFoo = (dep: string) => {
254+
const queryKey = fooQueryKeyFactory(dep);
255+
return useQuery({
256+
queryKey,
257+
queryFn: () => Promise.resolve(dep),
258+
})
259+
}
260+
`,
261+
},
229262
{
230263
name: 'should not treat new Error as missing dependency',
231264
code: normalizeIndent`
@@ -246,6 +279,30 @@ ruleTester.run('exhaustive-deps', rule, {
246279
}
247280
`,
248281
},
282+
{
283+
name: 'should see id when there is a const assertion of a variable dereference',
284+
code: normalizeIndent`
285+
const useX = (id: number) => {
286+
const queryKey = ['foo', id]
287+
return useQuery({
288+
queryKey: queryKey as const,
289+
queryFn: async () => id,
290+
})
291+
}
292+
`,
293+
},
294+
{
295+
name: 'should see id when there is a const assertion assigned to a variable',
296+
code: normalizeIndent`
297+
const useX = (id: number) => {
298+
const queryKey = ['foo', id] as const
299+
return useQuery({
300+
queryKey,
301+
queryFn: async () => id,
302+
})
303+
}
304+
`,
305+
},
249306
{
250307
name: 'should not fail if queryKey is having the whole object while queryFn uses some props of it',
251308
code: normalizeIndent`

packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,10 @@ export const rule = createRule({
6565
return
6666
}
6767

68-
let queryKeyNode = queryKey.value
69-
70-
if (
71-
queryKeyNode.type === AST_NODE_TYPES.TSAsExpression &&
72-
queryKeyNode.expression.type === AST_NODE_TYPES.ArrayExpression
73-
) {
74-
queryKeyNode = queryKeyNode.expression
75-
}
76-
77-
if (queryKeyNode.type === AST_NODE_TYPES.Identifier) {
78-
const expression = ASTUtils.getReferencedExpressionByIdentifier({
79-
context,
80-
node: queryKeyNode,
81-
})
82-
83-
if (expression?.type === AST_NODE_TYPES.ArrayExpression) {
84-
queryKeyNode = expression
85-
}
86-
}
68+
const queryKeyNode = dereferenceVariablesAndTypeAssertions(
69+
queryKey.value,
70+
context,
71+
)
8772

8873
const externalRefs = ASTUtils.getExternalRefs({
8974
scopeManager,
@@ -182,3 +167,38 @@ function getQueryFnRelevantNode(queryFn: TSESTree.Property) {
182167

183168
return queryFn.value.consequent
184169
}
170+
171+
function dereferenceVariablesAndTypeAssertions(
172+
queryKeyNode: TSESTree.Node,
173+
context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>,
174+
) {
175+
const visitedNodes = new Set<TSESTree.Node>()
176+
177+
for (let i = 0; i < 1 << 8; ++i) {
178+
if (visitedNodes.has(queryKeyNode)) {
179+
return queryKeyNode
180+
}
181+
visitedNodes.add(queryKeyNode)
182+
183+
switch (queryKeyNode.type) {
184+
case AST_NODE_TYPES.TSAsExpression:
185+
queryKeyNode = queryKeyNode.expression
186+
break
187+
case AST_NODE_TYPES.Identifier: {
188+
const expression = ASTUtils.getReferencedExpressionByIdentifier({
189+
context,
190+
node: queryKeyNode,
191+
})
192+
193+
if (expression == null) {
194+
return queryKeyNode
195+
}
196+
queryKeyNode = expression
197+
break
198+
}
199+
default:
200+
return queryKeyNode
201+
}
202+
}
203+
return queryKeyNode
204+
}

0 commit comments

Comments
 (0)