Skip to content

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

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

feat(eslint-plugin): add allowlist option to exhaustive-deps rule#10295
Newbie012 wants to merge 5 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 an allowlist option to the exhaustive-deps rule to ignore specific variables and types.
    • Improved detection/reporting of member-expression dependencies in query functions.
  • Documentation

    • Expanded exhaustive-deps docs with options, examples, and configuration snippets showing allowlist usage.
  • Examples

    • Added a React demo project showcasing rule configuration and allowlist scenarios.
  • Tests

    • Significantly expanded test coverage for exhaustive-deps, including allowlist behavior and many member-expression cases.

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

Adds an allowlist option (variables and types) to the exhaustive-deps ESLint rule, updates rule logic and utilities to compute and filter missing dependency paths with allowlist/type checks, expands tests, and includes docs plus an example demo project showing allowlist usage.

Changes

Cohort / File(s) Summary
Changeset & Docs
\.changeset/exhaustive-deps-allowlist.md, docs/eslint/exhaustive-deps.md
Added changeset and documentation describing the new allowlist options (variables, types), updated correct-code examples and options snippet.
Example demo
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
New ESLint flat config, package manifest, tsconfig, and demo TSX showing allowlist usage and intentional rule cases.
Rule implementation
packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts
Added RuleOption schema for allowlist, switched visitor to ObjectExpression, integrated new utility-driven flow to compute required refs, mark allowlisted-by-type, compute missing paths, and build suggestions/fixes.
Rule utilities
packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts
Introduced computeFilteredMissingPaths, variableIsAllowlistedByType, normalizeChain, computeRefPath, collectTypeIdentifiers, getQueryFnNodes, changed collectQueryKeyDeps to return { roots, paths }, and added helpers for member-chain and type analysis.
AST utilities
packages/eslint-plugin-query/src/utils/ast-utils.ts
Removed isAncestorIsCallee; refined findPropertyWithIdentifierKey to use a typed predicate instead of assertion.
Tests
packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts
Large expansion of valid/invalid cases covering member expressions, optional chaining, TS as const, identifier-referenced queryKey arrays, skipToken, and dedicated suites for allowlist.types and allowlist.variables.

Sequence Diagram

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

    ESLint->>Rule: Visit queryOptions object
    Rule->>Utils: collectQueryKeyDeps(queryKeyNode)
    Utils-->>Rule: {roots, paths}

    Rule->>Utils: getQueryFnNodes(queryFn)
    Utils-->>Rule: queryFnNodes[]

    Rule->>Utils: For each fnNode, find relevant references
    Utils-->>Rule: requiredRefs[] (path, root, allowlistedByType?)

    loop For each requiredRef
        Rule->>TypeAnalyzer: Check variable type annotations
        TypeAnalyzer->>Utils: variableIsAllowlistedByType(variable)
        Utils-->>Rule: allowlistedByType boolean
    end

    Rule->>Utils: computeFilteredMissingPaths(requiredRefs, allowlistedVariables, roots, paths)
    Utils-->>Rule: missingPaths[]

    alt missingPaths non-empty
        Rule->>ESLint: Report violations + suggestions/fixes
    else
        Rule->>ESLint: No violations
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped through ASTs with nimble paws,
Found roots and paths and marked their cause.
With a twitch of whiskers I pruned the noise,
Types and names now dance—quiet joys.
Hooray for allowlists and cleaner deploys! 🎉

🚥 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 directly and clearly describes the main change: adding an allowlist option to the exhaustive-deps ESLint rule.
Description check ✅ Passed The description comprehensively documents the changes (allowlist option for stable variables/types, finer-grained member expression reporting) and follows the required template with completed checklist items and changeset confirmation.

✏️ 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 98868ec

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

☁️ Nx Cloud last updated this comment at 2026-03-19 12:15:21 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: 98868ec

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

size-limit report 📦

Path Size
react full 11.98 KB (0%)
react minimal 9.01 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

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.

🧹 Nitpick comments (1)
packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts (1)

385-411: Consider handling TSParenthesizedType for completeness.

The current implementation handles common type patterns (references, unions, intersections, arrays, tuples). For full coverage, you might also want to handle TSParenthesizedType which wraps a type in parentheses.

♻️ Optional enhancement
       case AST_NODE_TYPES.TSTupleType: {
         typeNode.elementTypes.forEach((et) =>
           ExhaustiveDepsUtils.collectTypeIdentifiers(et, out),
         )
         break
       }
+      case AST_NODE_TYPES.TSParenthesizedType: {
+        ExhaustiveDepsUtils.collectTypeIdentifiers(typeNode.typeAnnotation, out)
+        break
+      }
     }
🤖 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 385 - 411, The collectTypeIdentifiers method in ExhaustiveDepsUtils
misses TSParenthesizedType nodes; add a case for
AST_NODE_TYPES.TSParenthesizedType and recursively call
ExhaustiveDepsUtils.collectTypeIdentifiers on the wrapped inner type (use the
node's inner property, e.g., typeAnnotation or type, whichever exists) so
parenthesized types are traversed like unions/arrays/tuples.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts`:
- Around line 385-411: The collectTypeIdentifiers method in ExhaustiveDepsUtils
misses TSParenthesizedType nodes; add a case for
AST_NODE_TYPES.TSParenthesizedType and recursively call
ExhaustiveDepsUtils.collectTypeIdentifiers on the wrapped inner type (use the
node's inner property, e.g., typeAnnotation or type, whichever exists) so
parenthesized types are traversed like unions/arrays/tuples.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dfea3476-2ba0-4e62-96a8-5da983c61dc0

📥 Commits

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

📒 Files selected for processing (4)
  • docs/eslint/exhaustive-deps.md
  • 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
✅ Files skipped from review due to trivial changes (1)
  • packages/eslint-plugin-query/src/tests/exhaustive-deps.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/eslint/exhaustive-deps.md

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