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 ab675965e9..9cdbcd91b4 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 @@ -79,10 +79,20 @@ export const rule = createRule({ const sourceCode = context.getSourceCode() const queryKeyValue = queryKeyNode + const reactComponent = ASTUtils.getReactComponentOrHookAncestor(context) const refs = ASTUtils.getExternalRefs({ scopeManager, sourceCode, node: queryFn.value, + }).filter((ref) => { + return ( + reactComponent === undefined || + ASTUtils.isDeclaredInNode({ + scopeManager, + functionNode: reactComponent, + reference: ref, + }) + ) }) const relevantRefs = refs.filter((ref) => { diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts index 642e096313..457a8f3223 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts @@ -260,6 +260,30 @@ ruleTester.run('exhaustive-deps', rule, { }); `, }, + { + name: 'should ignore constants defined out of scope (react component)', + code: ` + const CONST_VAL = 1 + function MyComponent() { + useQuery({ + queryKey: ["foo"], + queryFn: () => CONST_VAL + }); + } + `, + }, + { + name: 'should ignore constants defined out of scope (react hook)', + code: ` + const CONST_VAL = 1 + function useHook() { + useQuery({ + queryKey: ["foo"], + queryFn: () => CONST_VAL + }); + } + `, + }, ], invalid: [ { diff --git a/packages/eslint-plugin-query/src/utils/ast-utils.ts b/packages/eslint-plugin-query/src/utils/ast-utils.ts index 95e5577e9e..6b30097da7 100644 --- a/packages/eslint-plugin-query/src/utils/ast-utils.ts +++ b/packages/eslint-plugin-query/src/utils/ast-utils.ts @@ -1,4 +1,5 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils' +import type TSESLintScopeManager from '@typescript-eslint/scope-manager' import { AST_NODE_TYPES } from '@typescript-eslint/utils' import type { RuleContext } from '@typescript-eslint/utils/dist/ts-eslint' import { uniqueBy } from './unique-by' @@ -140,6 +141,20 @@ export const ASTUtils = { return identifier }, + isDeclaredInNode(params: { + functionNode: TSESTree.Node + reference: TSESLintScopeManager.Reference + scopeManager: TSESLint.Scope.ScopeManager + }) { + const { functionNode, reference, scopeManager } = params + const scope = scopeManager.acquire(functionNode) + + if (scope === null) { + return false + } + + return scope.set.has(reference.identifier.name) + }, getExternalRefs(params: { scopeManager: TSESLint.Scope.ScopeManager sourceCode: Readonly @@ -188,6 +203,25 @@ export const ASTUtils = { ]), ) }, + getReactComponentOrHookAncestor( + context: Readonly>, + ) { + return context.getAncestors().find((x) => { + return ( + ASTUtils.isNodeOfOneOf(x, [ + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.ArrowFunctionExpression, + ]) && + x.id !== null && + /^(use|[A-Z])/.test(x.id.name) + ) + }) as + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression + | undefined + }, getReferencedExpressionByIdentifier(params: { node: TSESTree.Node context: Readonly>