Skip to content

Commit f643d72

Browse files
committed
fix: detect callee objects as dependencies in exhaustive-deps rule
1 parent af0933a commit f643d72

3 files changed

Lines changed: 60 additions & 28 deletions

File tree

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

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,6 @@ ruleTester.run('exhaustive-deps', rule, {
3434
name: 'should not pass api.entity.get',
3535
code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.entity.get(id) });',
3636
},
37-
{
38-
name: 'should not pass api when is being used for calling a function',
39-
code: `
40-
import useApi from './useApi'
41-
42-
const useFoo = () => {
43-
const api = useApi();
44-
return useQuery({
45-
queryKey: ['foo'],
46-
queryFn: () => api.fetchFoo(),
47-
})
48-
}
49-
`,
50-
},
5137
{
5238
name: 'should pass props.src',
5339
code: `
@@ -698,6 +684,43 @@ ruleTester.run('exhaustive-deps', rule, {
698684
},
699685
],
700686
invalid: [
687+
{
688+
name: 'should fail when api from hook is used for calling a function',
689+
code: normalizeIndent`
690+
import useApi from './useApi'
691+
692+
const useFoo = () => {
693+
const api = useApi();
694+
return useQuery({
695+
queryKey: ['foo'],
696+
queryFn: () => api.fetchFoo(),
697+
})
698+
}
699+
`,
700+
errors: [
701+
{
702+
messageId: 'missingDeps',
703+
data: { deps: 'api' },
704+
suggestions: [
705+
{
706+
messageId: 'fixTo',
707+
data: { result: "['foo', api]" },
708+
output: normalizeIndent`
709+
import useApi from './useApi'
710+
711+
const useFoo = () => {
712+
const api = useApi();
713+
return useQuery({
714+
queryKey: ['foo', api],
715+
queryFn: () => api.fetchFoo(),
716+
})
717+
}
718+
`,
719+
},
720+
],
721+
},
722+
],
723+
},
701724
{
702725
name: 'should fail when deps are missing in query factory',
703726
code: normalizeIndent`

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,16 @@ export const rule = createRule({
9595
const missingRefs = relevantRefs
9696
.map((ref) => ({
9797
ref: ref,
98-
text: ASTUtils.mapKeyNodeToBaseText(
99-
ref.identifier,
100-
context.sourceCode,
101-
),
98+
text: ASTUtils.isAncestorIsCallee(ref.identifier)
99+
? ref.identifier.name
100+
: ASTUtils.mapKeyNodeToBaseText(
101+
ref.identifier,
102+
context.sourceCode,
103+
),
102104
}))
103105
.filter(({ ref, text }) => {
104106
return (
105107
!ref.isTypeReference &&
106-
!ASTUtils.isAncestorIsCallee(ref.identifier) &&
107108
!queryKeyDeps.has(text) &&
108109
!queryKeyDeps.has(text.split(/[?.]/)[0] ?? '')
109110
)
@@ -117,9 +118,7 @@ export const rule = createRule({
117118

118119
if (uniqueMissingRefs.length > 0) {
119120
const missingAsText = uniqueMissingRefs
120-
.map((ref) =>
121-
ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),
122-
)
121+
.map((ref) => ref.text)
123122
.join(', ')
124123

125124
const queryKeyValue = context.sourceCode.getText(queryKeyNode)

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,12 @@ export const ExhaustiveDepsUtils = {
8080
}
8181

8282
function visitChildren(node: TSESTree.Node): void {
83-
for (const key of visitorKeys[node.type] ?? []) {
84-
const value = (node as Record<string, unknown>)[key]
83+
const keys = (visitorKeys[node.type] ?? []) as ReadonlyArray<
84+
keyof TSESTree.Node
85+
>
86+
87+
for (const key of keys) {
88+
const value = node[key]
8589

8690
if (Array.isArray(value)) {
8791
for (const item of value) {
@@ -122,7 +126,15 @@ export const ExhaustiveDepsUtils = {
122126
visit(node.value)
123127
return
124128
case AST_NODE_TYPES.MemberExpression:
125-
visit(node.object)
129+
if (
130+
node.parent.type === AST_NODE_TYPES.CallExpression &&
131+
node.parent.callee === node &&
132+
node.object.type === AST_NODE_TYPES.Identifier
133+
) {
134+
deps.add(node.object.name)
135+
} else {
136+
visit(node.object)
137+
}
126138
return
127139
case AST_NODE_TYPES.CallExpression:
128140
node.arguments.forEach((argument) => visit(argument))
@@ -154,9 +166,7 @@ export const ExhaustiveDepsUtils = {
154166
},
155167

156168
collectExternalRefsInFunction(params: {
157-
functionNode:
158-
| TSESTree.ArrowFunctionExpression
159-
| TSESTree.FunctionExpression
169+
functionNode: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression
160170
scopeManager: TSESLint.Scope.ScopeManager
161171
}): Array<TSESLint.Scope.Reference> {
162172
const { functionNode, scopeManager } = params

0 commit comments

Comments
 (0)