Skip to content

feat(eslint-plugin): add allowlist option to exhaustive-deps rule#10295

Open
Newbie012 wants to merge 3 commits intomainfrom
feat-eslint-exhaustive-deps-allowlist
Open

feat(eslint-plugin): add allowlist option to exhaustive-deps rule#10295
Newbie012 wants to merge 3 commits intomainfrom
feat-eslint-exhaustive-deps-allowlist

Conversation

@Newbie012
Copy link
Collaborator

@Newbie012 Newbie012 commented Mar 19, 2026

Prior PR - #10258
Relevant issue - #6853

🎯 Changes

The lint rule exhaustive-deps now has an allowlist option so stable variables and types can be excluded from dependency checks. It also reports member expression dependencies in a finer-grained way for call expressions.

// 🔴 Before: reports "The following dependencies are missing: api"
// 🟢 After: with allowlist: { variables: ["api"] }, no exhaustive-deps error
useQuery({
  queryKey: ['todo', todoId],
  queryFn: () => api.getTodo(todoId),
})

// 🔴 Before: a.b.foo() might suggest only 'a'
// 🟢 After: suggests 'a.b' more precisely for call expressions
useQuery({
  queryKey: ['todo', a],
  queryFn: () => a.b.getTodo(todoId),
})

The allowlist option supports:

  • allowlist.variables: variable names to ignore when checking dependencies
  • allowlist.types: TypeScript type names whose variables are ignored

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added allowlist option to exhaustive-deps rule, enabling developers to exclude specific variables and types from dependency checking enforcement.
    • Added example project demonstrating rule configuration and practical usage patterns.
    • Enhanced dependency detection for member-expression patterns in query functions.
  • Documentation

    • Updated exhaustive-deps documentation with configuration examples and detailed allowlist option specifications.

Introduce an `allowlist` option with `variables` and `types` arrays so
stable variables and types can be excluded from dependency enforcement.

Also report member expression dependencies more granularly for call
expressions (e.g. `a.b.foo()` suggests `a.b` instead of only `a`).

BREAKING CHANGE: exhaustive-deps now reports member expression deps more
granularly, so some previously passing code may now report missing deps.
Use the allowlist option to exclude stable variables/types as needed.
@github-actions github-actions bot added documentation Improvements or additions to documentation package: eslint-plugin-query labels Mar 19, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

The changes introduce an allowlist configuration option to the exhaustive-deps ESLint rule in @tanstack/eslint-plugin-query, allowing developers to exclude specific variables and types from dependency checking. The feature includes updated documentation, new demo example files, enhanced test coverage, and refactored rule and utility logic to support allowlist-based filtering.

Changes

Cohort / File(s) Summary
Documentation & Changeset
.changeset/exhaustive-deps-allowlist.md, docs/eslint/exhaustive-deps.md
Documented the new allowlist option with configuration examples for both allowlist.variables and allowlist.types; updated examples to show how captured scope values must be included in queryKey and how the allowlist suppresses specific dependencies.
Example Demo Project
examples/react/eslint-plugin-demo/eslint.config.js, examples/react/eslint-plugin-demo/package.json, examples/react/eslint-plugin-demo/tsconfig.json, examples/react/eslint-plugin-demo/src/allowlist-demo.tsx
Added a complete example project demonstrating exhaustive-deps rule usage and configuration with ESLint flat config; includes helper functions for queryOptions with intentional violations and allowlist exemptions.
Rule Implementation
packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts
Added allowlist schema with variables and types options; changed visitor from Property to ObjectExpression; refactored missing-dependency computation to use new utility functions for allowlist-based filtering and type annotation checking.
Rule Utilities
packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts
Added multiple utility functions (computeFilteredMissingPaths, variableIsAllowlistedByType, normalizeChain, computeRefPath, collectTypeIdentifiers, getQueryFnFunctionExpression); updated collectQueryKeyDeps return type to provide separate roots and paths sets; enhanced type and dependency analysis logic.
AST Utilities
packages/eslint-plugin-query/src/utils/ast-utils.ts
Removed isAncestorIsCallee function and refactored findPropertyWithIdentifierKey to use type-guard predicate instead of type assertion.
Test Suite
packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts
Expanded test coverage with new valid/invalid cases for dependency extraction, optional chaining, member expressions, and allowlist-based suppression; added dedicated test blocks for allowlist.types and allowlist.variables configurations.

Sequence Diagram

sequenceDiagram
    actor ESLint as ESLint Engine
    participant Rule as exhaustive-deps Rule
    participant Utils as ExhaustiveDepsUtils
    participant TypeCheck as Type Analysis

    ESLint->>Rule: Process queryOptions object
    Rule->>Utils: collectQueryKeyDeps(queryKeyNode)
    Utils-->>Rule: {roots, paths}
    
    Rule->>Rule: Extract queryFn from object
    Rule->>Utils: getQueryFnFunctionExpression(queryFn)
    Utils-->>Rule: Expression node
    
    Rule->>Utils: Find required references in queryFn
    Note over Rule,Utils: Analyze identifiers & member expressions
    Utils-->>Rule: requiredRefs[]
    
    loop For each required ref
        Rule->>TypeCheck: Check variable type annotations
        TypeCheck->>Utils: variableIsAllowlistedByType()
        Utils->>Utils: collectTypeIdentifiers(typeNode)
        Utils-->>Rule: allowlistedByType: boolean
    end
    
    Rule->>Utils: computeFilteredMissingPaths()
    Note over Utils: Filter by allowlist.variables<br/>and allowlistedByType
    Utils-->>Rule: missingPaths[]
    
    alt Missing paths found
        Rule->>ESLint: Report violations with fixes
    else All dependencies allowed
        Rule->>ESLint: No violations
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 With eager whiskers and a hop so grand,
We craft an allowlist, wonderfully planned!
Dependencies checked, yet some set free,
Types and variables now dance with glee,
The ESLint rule now bends to our will,
Hopping toward cleaner code, what a thrill! 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main feature being added: an allowlist option to the exhaustive-deps ESLint rule.
Description check ✅ Passed The description comprehensively covers the changes, includes clear before/after examples, documents the allowlist option features, and completes the required checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-eslint-exhaustive-deps-allowlist
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Mar 19, 2026

View your CI Pipeline Execution ↗ for commit 7b0f821

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 42s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-19 08:46:51 UTC

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

🚀 Changeset Version Preview

1 package(s) bumped directly, 0 bumped as dependents.

🟨 Minor bumps

Package Version Reason
@tanstack/eslint-plugin-query 5.91.5 → 5.92.0 Changeset

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 19, 2026

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@10295

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@10295

@tanstack/preact-query

npm i https://pkg.pr.new/@tanstack/preact-query@10295

@tanstack/preact-query-devtools

npm i https://pkg.pr.new/@tanstack/preact-query-devtools@10295

@tanstack/preact-query-persist-client

npm i https://pkg.pr.new/@tanstack/preact-query-persist-client@10295

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@10295

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@10295

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@10295

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@10295

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@10295

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@10295

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@10295

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@10295

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@10295

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@10295

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@10295

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@10295

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@10295

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@10295

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@10295

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@10295

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@10295

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@10295

commit: 7b0f821

@github-actions
Copy link
Contributor

size-limit report 📦

Path Size
react full 11.96 KB (0%)
react minimal 8.98 KB (0%)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/exhaustive-deps-allowlist.md:
- Around line 2-5: The changeset currently marks '@tanstack/eslint-plugin-query'
as a minor release but introduces a breaking behavioral change for the
`exhaustive-deps` rule; update the changeset to a major release by changing the
release type for '@tanstack/eslint-plugin-query' from "minor" to "major" and
keep the description about the `exhaustive-deps` rule and its new `allowlist`
option (`variables` and `types`) intact so the package is versioned
appropriately.

In `@docs/eslint/exhaustive-deps.md`:
- Around line 39-45: Move the note about { allowlist: { variables: ["todos"] } }
above the very first createTodos() example and explicitly state that the first
"correct" snippet (where todoQueries.detail closes over todos) requires that
allowlist to be enabled; alternatively, if you want the snippet to be valid
without the allowlist, update todoQueries.detail so its queryKey includes the
closed-over variable (e.g., include todos in the key) — reference createTodos,
todoQueries.detail, and Component when making the clarification or code change.

In `@examples/react/eslint-plugin-demo/package.json`:
- Around line 8-16: The package.json currently pins TanStack packages with
semver ranges, so tests may install published releases instead of the workspace
packages; update the dependency entries for "@tanstack/react-query" and
"@tanstack/eslint-plugin-query" in package.json to use workspace refs (e.g.,
"workspace:*" or an appropriate workspace:... spec) so the test:eslint script
runs against the in-repo builds from this PR branch rather than npm releases;
ensure the updated entries are in the "dependencies" and "devDependencies"
sections respectively and run pnpm install to lock the workspace resolution.

In
`@packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts`:
- Around line 152-168: The array-suggestion builder currently composes
fixedQueryKeyValue by string replacement (using fixedQueryKeyValue,
queryKeyValue, missingAsText and queryKeyNode) which breaks when arrays have
spaces, trailing commas or multiline formatting; replace this string-based
method with a token-based insertion: use context.sourceCode to locate the
array's closing bracket token (e.g. sourceCode.getLastToken(queryKeyNode) or
getTokenBefore/After) and then produce a suggestion that calls the fixer to
insert the dependency text right before that closing bracket (using
fixer.insertTextBefore/insertTextBeforeRange or similar) so you correctly handle
`[ ]`, trailing commas, and multiline arrays instead of using fixer.replaceText;
update the suggestions push logic for queryKeyNode.type ===
AST_NODE_TYPES.ArrayExpression to use this token-based fixer insertion.

In
`@packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts`:
- Around line 84-96: The loop currently adds every missing path (variable
`missingPaths`) including deeper members like "api.part" even when the root
"api" is missing; change it to collapse descendants per root by tracking the
chosen shortest missing path for each `root` (e.g., maintain a map or set keyed
by `root`) so that if the root itself is missing you skip deeper members, and if
you add a shorter path for a `root` you remove/replace any previously recorded
longer paths for that same `root`; use the existing identifiers (`requiredRefs`,
`root`, `path`, `existingRootIdentifiers`, `allowlistedVariables`,
`existingFullPaths`, `allowlistedByType`, `missingPaths`) to locate and
implement this behavior.
- Around line 412-425: The helper getQueryFnFunctionExpression currently returns
only the consequent for ConditionalExpression branches (unless the consequent is
skipToken), which misses scanning the alternate; change the logic so that when
queryFn.value is a ConditionalExpression and neither branch is the skipToken
identifier, you return the entire ConditionalExpression (queryFn.value) instead
of always returning the consequent; keep the existing special-case that returns
alternate when consequent is skipToken and symmetrically return consequent when
alternate is skipToken, but otherwise return queryFn.value so both branches get
scanned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99a8602d-e3e9-4243-948d-6d6bf5dc99f5

📥 Commits

Reviewing files that changed from the base of the PR and between b799510 and 7b0f821.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • .changeset/exhaustive-deps-allowlist.md
  • docs/eslint/exhaustive-deps.md
  • examples/react/eslint-plugin-demo/eslint.config.js
  • examples/react/eslint-plugin-demo/package.json
  • examples/react/eslint-plugin-demo/src/allowlist-demo.tsx
  • examples/react/eslint-plugin-demo/tsconfig.json
  • packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts
  • packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts
  • packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts
  • packages/eslint-plugin-query/src/utils/ast-utils.ts

Comment on lines +152 to 168
const missingAsText = missingPaths.join(', ')
const queryKeyValue = context.sourceCode.getText(queryKeyNode)

const fixedQueryKeyValue =
queryKeyValue === '[]'
? `[${missingAsText}]`
: queryKeyValue.replace(/\]$/, `, ${missingAsText}]`)

const suggestions: TSESLint.ReportSuggestionArray<string> = []

if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {
suggestions.push({
messageId: 'fixTo',
data: { result: fixedQueryKeyValue },
fix(fixer) {
return fixer.replaceText(queryKeyNode, fixedQueryKeyValue)
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
def current_fix(query_key, dep="missing"):
    return f"[{dep}]" if query_key == "[]" else query_key[:-1] + f", {dep}]"

for query_key in ["[]", "[ ]", "['todo']", "['todo',]", "[\n  'todo',\n]"]:
    print(f"{query_key!r} -> {current_fix(query_key)!r}")
PY

Repository: TanStack/query

Length of output: 219


🏁 Script executed:

fd "exhaustive-deps.rule.ts" --type f

Repository: TanStack/query

Length of output: 136


🏁 Script executed:

# Once located, read the file around lines 152-168
cat -n packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts | sed -n '140,175p'

Repository: TanStack/query

Length of output: 1478


🏁 Script executed:

# Check if there's any existing handling for whitespace or edge cases
rg "queryKeyValue.*replace\]" packages/eslint-plugin-query/src/rules/exhaustive-deps/

Repository: TanStack/query

Length of output: 40


🏁 Script executed:

# Check if getLastToken and getTokenBefore are commonly used in this codebase
rg "getLastToken|getTokenBefore" packages/eslint-plugin-query/src/

Repository: TanStack/query

Length of output: 40


🏁 Script executed:

# Check for fixer usage patterns in the codebase
rg "fixer\." packages/eslint-plugin-query/src/ -A 1 | head -40

Repository: TanStack/query

Length of output: 841


🏁 Script executed:

# Look for any array element access patterns
rg "queryKeyNode\.elements" packages/eslint-plugin-query/src/

Repository: TanStack/query

Length of output: 40


Fix the array suggestion builder to handle all array formats correctly.

The current string-based approach with replace(/\]$/) generates invalid syntax for arrays with whitespace or trailing commas:

  • [ ] becomes [ , missing] (space before comma)
  • ['todo',] becomes ['todo',, missing] (double comma)
  • Multiline arrays with trailing commas similarly break

Use the token-based approach below to safely insert dependencies before the closing bracket, which handles all formatting variations:

Suggested fix
-        const queryKeyValue = context.sourceCode.getText(queryKeyNode)
-
-        const fixedQueryKeyValue =
-          queryKeyValue === '[]'
-            ? `[${missingAsText}]`
-            : queryKeyValue.replace(/\]$/, `, ${missingAsText}]`)
-
         const suggestions: TSESLint.ReportSuggestionArray<string> = []

         if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {
+          const closingBracket = context.sourceCode.getLastToken(queryKeyNode)
+          const tokenBeforeClosingBracket =
+            closingBracket && context.sourceCode.getTokenBefore(closingBracket)
+          const separator =
+            queryKeyNode.elements.length === 0
+              ? ''
+              : tokenBeforeClosingBracket?.value === ','
+                ? ' '
+                : ', '
+
           suggestions.push({
             messageId: 'fixTo',
-            data: { result: fixedQueryKeyValue },
+            data: { result: `[${[
+              ...queryKeyNode.elements.flatMap((element) =>
+                element ? [context.sourceCode.getText(element)] : [],
+              ),
+              ...missingPaths,
+            ].join(', ')}]` },
             fix(fixer) {
-              return fixer.replaceText(queryKeyNode, fixedQueryKeyValue)
+              return closingBracket
+                ? fixer.insertTextBefore(
+                    closingBracket,
+                    `${separator}${missingAsText}`,
+                  )
+                : null
             },
           })
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const missingAsText = missingPaths.join(', ')
const queryKeyValue = context.sourceCode.getText(queryKeyNode)
const fixedQueryKeyValue =
queryKeyValue === '[]'
? `[${missingAsText}]`
: queryKeyValue.replace(/\]$/, `, ${missingAsText}]`)
const suggestions: TSESLint.ReportSuggestionArray<string> = []
if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {
suggestions.push({
messageId: 'fixTo',
data: { result: fixedQueryKeyValue },
fix(fixer) {
return fixer.replaceText(queryKeyNode, fixedQueryKeyValue)
},
const missingAsText = missingPaths.join(', ')
const suggestions: TSESLint.ReportSuggestionArray<string> = []
if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {
const closingBracket = context.sourceCode.getLastToken(queryKeyNode)
const tokenBeforeClosingBracket =
closingBracket && context.sourceCode.getTokenBefore(closingBracket)
const separator =
queryKeyNode.elements.length === 0
? ''
: tokenBeforeClosingBracket?.value === ','
? ' '
: ', '
suggestions.push({
messageId: 'fixTo',
data: { result: `[${[
...queryKeyNode.elements.flatMap((element) =>
element ? [context.sourceCode.getText(element)] : [],
),
...missingPaths,
].join(', ')}]` },
fix(fixer) {
return closingBracket
? fixer.insertTextBefore(
closingBracket,
`${separator}${missingAsText}`,
)
: null
},
})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts`
around lines 152 - 168, The array-suggestion builder currently composes
fixedQueryKeyValue by string replacement (using fixedQueryKeyValue,
queryKeyValue, missingAsText and queryKeyNode) which breaks when arrays have
spaces, trailing commas or multiline formatting; replace this string-based
method with a token-based insertion: use context.sourceCode to locate the
array's closing bracket token (e.g. sourceCode.getLastToken(queryKeyNode) or
getTokenBefore/After) and then produce a suggestion that calls the fixer to
insert the dependency text right before that closing bracket (using
fixer.insertTextBefore/insertTextBeforeRange or similar) so you correctly handle
`[ ]`, trailing commas, and multiline arrays instead of using fixer.replaceText;
update the suggestions push logic for queryKeyNode.type ===
AST_NODE_TYPES.ArrayExpression to use this token-based fixer insertion.

Comment on lines +84 to +96
const missingPaths = new Set<string>()

for (const { root, path, allowlistedByType } of requiredRefs) {
// If root itself is present in the key, it covers all members
if (existingRootIdentifiers.has(root)) continue
if (allowlistedVariables.has(root)) continue
if (existingFullPaths.has(path)) continue
if (allowlistedByType) continue

missingPaths.add(path)
}

return Array.from(missingPaths)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Collapse descendants once their root is already missing.

If a queryFn reads both api.fetch() and api.part.load() with an empty key, this loop reports api, api.part. Adding api already satisfies the deeper member access, so the diagnostic/fix becomes noisier than necessary. Prefer the shortest missing path per root.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts`
around lines 84 - 96, The loop currently adds every missing path (variable
`missingPaths`) including deeper members like "api.part" even when the root
"api" is missing; change it to collapse descendants per root by tracking the
chosen shortest missing path for each `root` (e.g., maintain a map or set keyed
by `root`) so that if the root itself is missing you skip deeper members, and if
you add a shorter path for a `root` you remove/replace any previously recorded
longer paths for that same `root`; use the existing identifiers (`requiredRefs`,
`root`, `path`, `existingRootIdentifiers`, `allowlistedVariables`,
`existingFullPaths`, `allowlistedByType`, `missingPaths`) to locate and
implement this behavior.

Comment on lines +412 to +425
getQueryFnFunctionExpression(queryFn: TSESTree.Property): TSESTree.Node {
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
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t ignore the alternate queryFn branch.

For queryFn: cond ? () => fetchA(a) : () => fetchB(b), this helper only returns the consequent unless that branch is literally skipToken. The alternate function never gets scanned, so b can be omitted from queryKey without a lint error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts`
around lines 412 - 425, The helper getQueryFnFunctionExpression currently
returns only the consequent for ConditionalExpression branches (unless the
consequent is skipToken), which misses scanning the alternate; change the logic
so that when queryFn.value is a ConditionalExpression and neither branch is the
skipToken identifier, you return the entire ConditionalExpression
(queryFn.value) instead of always returning the consequent; keep the existing
special-case that returns alternate when consequent is skipToken and
symmetrically return consequent when alternate is skipToken, but otherwise
return queryFn.value so both branches get scanned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: eslint-plugin-query

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant