Skip to content

build(eslint-plugin-query): add typescript-aware detection for custom query hooks#10376

Closed
chinhkrb113 wants to merge 3 commits intoTanStack:mainfrom
chinhkrb113:contribai/feat/add-typescript-aware-detection-for-custo
Closed

build(eslint-plugin-query): add typescript-aware detection for custom query hooks#10376
chinhkrb113 wants to merge 3 commits intoTanStack:mainfrom
chinhkrb113:contribai/feat/add-typescript-aware-detection-for-custo

Conversation

@chinhkrb113
Copy link
Copy Markdown

@chinhkrb113 chinhkrb113 commented Apr 1, 2026

New Feature

Problem

Extend the no-rest-destructuring rule to also report when rest destructuring is used on custom hooks (functions starting with "use") that return a TanStack Query result type. This requires the TypeScript type checker, so the logic is only executed when @typescript-eslint/parser provides type information.

Severity: high
File: packages/eslint-plugin-query/src/rules/noRestDestructuring.ts

Solution

Extend the no-rest-destructuring rule to also report when rest destructuring is used on custom hooks (functions starting with "use") that return a TanStack Query result type. This requires the TypeScript type checker, so the logic is only executed when @typescript-eslint/parser provides type information.

Changes

  • packages/eslint-plugin-query/src/rules/noRestDestructuring.ts (modified)

🎯 Changes

✅ 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).
    Contributed by Lê Thành Chỉnh
    Code is a tool. Mindset is the real value.

Closes #8951

Summary by CodeRabbit

  • New Features
    • Added a new ESLint rule, no-rest-destructuring, to the query plugin. The rule detects rest destructuring patterns involving TanStack Query hooks (e.g., useQuery, useInfiniteQuery) and uses TypeScript-aware analysis to recognize query result types across codebases. Documentation and a single standardized diagnostic message are included.

…query hooks

Extend the no-rest-destructuring rule to also report when rest destructuring is used on custom hooks (functions starting with "use") that return a TanStack Query result type. This requires the TypeScript type checker, so the logic is only executed when @typescript-eslint/parser provides type information.

Affected files: noRestDestructuring.ts

Signed-off-by: ChinhLee <76194645+chinhkrb113@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2839ccae-5f11-40b0-b8a3-2be83d69b59b

📥 Commits

Reviewing files that changed from the base of the PR and between 401bdd6 and 0839f5f.

📒 Files selected for processing (1)
  • packages/eslint-plugin-query/src/rules/noRestDestructuring.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/eslint-plugin-query/src/rules/noRestDestructuring.ts

📝 Walkthrough

Walkthrough

Adds a new ESLint rule no-rest-destructuring that flags rest (...rest) object destructuring when the initializer is a TanStack Query result call (including custom hooks), using AST visitors and optional TypeScript type-checking to identify query result types.

Changes

Cohort / File(s) Summary
New ESLint rule
packages/eslint-plugin-query/src/rules/noRestDestructuring.ts
Adds no-rest-destructuring rule: visits VariableDeclarator nodes, detects ObjectPattern with RestElement, requires initializer to be a CallExpression, heuristically recognizes TanStack Query calls by callee names (useQuery, useInfiniteQuery) or via TypeScript type resolution (symbol names like UseQueryResult/UseInfiniteQueryResult or declarations under @tanstack/*query*) and reports violations. Declares meta.type = 'problem', docs URL, one message id, and no options schema.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I sniffed the AST beneath the log-tree shade,
Found rest spreads where query results were laid,
I hopped with types and names to make a stand,
No more sneaky rest from a hook's gentle hand,
Hooray — linted gardens across the land! 🥕✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the problem and solution but the 'Changes' section appears incomplete, and release impact checkboxes are unchecked despite the changeset being required for published code. Complete the 'Changes' section in the description and mark the release impact checkbox if a changeset has been generated as required.
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 (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'build(eslint-plugin-query): add typescript-aware detection for custom query hooks' accurately describes the main change: extending the no-rest-destructuring rule with TypeScript-aware detection for custom hooks.
Linked Issues check ✅ Passed The code changes align with issue #8951 requirements: the rule now detects rest destructuring on custom hooks returning TanStack Query results using TypeScript type information.
Out of Scope Changes check ✅ Passed The single file modification is directly scoped to implementing the TypeScript-aware detection for custom query hooks as specified in issue #8951.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
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: 3

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

Inline comments:
In `@packages/eslint-plugin-query/src/rules/noRestDestructuring.ts`:
- Around line 46-47: The current check uses typeName.includes(...) which causes
false positives; change the condition in the noRestDestructuring rule so it only
matches exact type names (e.g., replace queryResultTypes.some((t) =>
typeName.includes(t)) with queryResultTypes.some((t) => t === typeName) or use
queryResultTypes.includes(typeName)) so only exact matches of typeName trigger
the return true in that if branch.
- Around line 79-85: The hook-detection currently accepts call expressions named
'useQuery' or 'useInfiniteQuery' by name even when type information is
available; change the logic so that when parserServices?.hasTypeInformation is
true you always set isQueryHook by calling isTanstackQueryResult(init) (using
the existing isTanstackQueryResult function), and only fall back to the
name-based check (checking callee.type === 'Identifier' and callee.name in
['useQuery','useInfiniteQuery'] or startsWith('use')) when
parserServices?.hasTypeInformation is falsy; update the branch around callee,
parserServices, isQueryHook and isTanstackQueryResult accordingly.
- Around line 4-5: The exported symbol noRestDestructuring (created via
createRule) in this file appears to be unused because the plugin registry
imports the rule from ./rules/no-rest-destructuring/no-rest-destructuring.rule;
inspect the rules.ts registry and the subdirectory implementation to determine
if this file is a duplicate or part of an incomplete refactor, then either (a)
remove this stale export/file if the canonical implementation lives in
no-rest-destructuring/no-rest-destructuring.rule, or (b) update the registry
(rules.ts) to import and expose this noRestDestructuring export instead of the
subfolder version so there is a single source of truth; ensure the unique symbol
noRestDestructuring and createRule usage are removed or unified accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d30a09fc-c387-47fc-bb0b-14604512e2a0

📥 Commits

Reviewing files that changed from the base of the PR and between 67b12ae and 401bdd6.

📒 Files selected for processing (1)
  • packages/eslint-plugin-query/src/rules/noRestDestructuring.ts

Comment on lines +4 to +5
export const noRestDestructuring = createRule<[], 'noRestDestructuring'>({
name: 'no-rest-destructuring',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only verification of rule wiring and export usage
rg -n -C2 "noRestDestructuring|no-rest-destructuring" packages/eslint-plugin-query/src
fd -i "noRestDestructuring.ts" packages/eslint-plugin-query/src
fd -i "no-rest-destructuring.rule.ts" packages/eslint-plugin-query/src

Repository: TanStack/query

Length of output: 5834


The rule export in this file is not wired into plugin registry; the plugin imports from a different file structure.

The plugin's rules.ts imports the rule from ./rules/no-rest-destructuring/no-rest-destructuring.rule, not from this file. This means the noRestDestructuring export here is unused and the rule's implementation must be provided by the separate no-rest-destructuring/ subdirectory file. Verify whether this file is a duplicate, stale, or represents an incomplete refactor.

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

In `@packages/eslint-plugin-query/src/rules/noRestDestructuring.ts` around lines 4
- 5, The exported symbol noRestDestructuring (created via createRule) in this
file appears to be unused because the plugin registry imports the rule from
./rules/no-rest-destructuring/no-rest-destructuring.rule; inspect the rules.ts
registry and the subdirectory implementation to determine if this file is a
duplicate or part of an incomplete refactor, then either (a) remove this stale
export/file if the canonical implementation lives in
no-rest-destructuring/no-rest-destructuring.rule, or (b) update the registry
(rules.ts) to import and expose this noRestDestructuring export instead of the
subfolder version so there is a single source of truth; ensure the unique symbol
noRestDestructuring and createRule usage are removed or unified accordingly.

Comment on lines +46 to +47
if (queryResultTypes.some((t) => typeName.includes(t))) {
return true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use exact type-name matching to reduce false positives.

typeName.includes(...) can match unrelated symbols (e.g., wrapper/mock types). Exact match is safer here.

Proposed fix
-      if (queryResultTypes.some((t) => typeName.includes(t))) {
+      if (queryResultTypes.includes(typeName)) {
         return true
       }
📝 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
if (queryResultTypes.some((t) => typeName.includes(t))) {
return true
if (queryResultTypes.includes(typeName)) {
return true
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eslint-plugin-query/src/rules/noRestDestructuring.ts` around lines
46 - 47, The current check uses typeName.includes(...) which causes false
positives; change the condition in the noRestDestructuring rule so it only
matches exact type names (e.g., replace queryResultTypes.some((t) =>
typeName.includes(t)) with queryResultTypes.some((t) => t === typeName) or use
queryResultTypes.includes(typeName)) so only exact matches of typeName trigger
the return true in that if branch.

Comment on lines +79 to +85
if (callee.type === 'Identifier' && callee.name.startsWith('use')) {
const name = callee.name
if (['useQuery', 'useInfiniteQuery'].includes(name)) {
isQueryHook = true
} else if (parserServices?.hasTypeInformation) {
isQueryHook = isTanstackQueryResult(init)
}
Copy link
Copy Markdown
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 short-circuit on hook name when type info is available.

On Line 81–83, useQuery/useInfiniteQuery are accepted by name only, which can report non-TanStack hooks with the same names. Prefer type-based gating whenever parser services are available.

Proposed fix
-        if (callee.type === 'Identifier' && callee.name.startsWith('use')) {
-          const name = callee.name
-          if (['useQuery', 'useInfiniteQuery'].includes(name)) {
-            isQueryHook = true
-          } else if (parserServices?.hasTypeInformation) {
-            isQueryHook = isTanstackQueryResult(init)
-          }
-        }
+        if (callee.type === 'Identifier' && callee.name.startsWith('use')) {
+          if (parserServices?.hasTypeInformation) {
+            isQueryHook = isTanstackQueryResult(init)
+          } else {
+            isQueryHook = ['useQuery', 'useInfiniteQuery'].includes(callee.name)
+          }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eslint-plugin-query/src/rules/noRestDestructuring.ts` around lines
79 - 85, The hook-detection currently accepts call expressions named 'useQuery'
or 'useInfiniteQuery' by name even when type information is available; change
the logic so that when parserServices?.hasTypeInformation is true you always set
isQueryHook by calling isTanstackQueryResult(init) (using the existing
isTanstackQueryResult function), and only fall back to the name-based check
(checking callee.type === 'Identifier' and callee.name in
['useQuery','useInfiniteQuery'] or startsWith('use')) when
parserServices?.hasTypeInformation is falsy; update the branch around callee,
parserServices, isQueryHook and isTanstackQueryResult accordingly.

@TkDodo TkDodo requested a review from Newbie012 April 9, 2026 07:45
@TkDodo
Copy link
Copy Markdown
Collaborator

TkDodo commented Apr 9, 2026

@Newbie012 can you have a look here please 🙏

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Apr 9, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 0839f5f

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ⛔ Cancelled 32s View ↗
nx run-many --target=build --exclude=examples/*... ❌ Failed 6s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-09 07:47:17 UTC

@TkDodo
Copy link
Copy Markdown
Collaborator

TkDodo commented Apr 9, 2026

I’m confused. We already have a rule called no-rest-destructuring. Also nothing in the repo uses camelCasing for file names ...

@TkDodo TkDodo closed this Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[lint]: no-rest-destructuring does not report on custom hooks

2 participants