Skip to content

fix(eslint-plugin-query): detect rest destructuring on custom query hooks - #10775

Merged
TkDodo merged 4 commits into
TanStack:mainfrom
Newbie012:fix-no-rest-destructuring-custom-hooks
Jun 2, 2026
Merged

fix(eslint-plugin-query): detect rest destructuring on custom query hooks#10775
TkDodo merged 4 commits into
TanStack:mainfrom
Newbie012:fix-no-rest-destructuring-custom-hooks

Conversation

@Newbie012

@Newbie012Newbie012 commented May 24, 2026

Copy link
Copy Markdown
Contributor

Closes#8951

🎯 Changes

The lint rule no-rest-destructuring now also flags rest destructuring on custom hooks that return a TanStack Query result. Detection uses the TypeScript type checker and runs opportunistically, only when parser services are available, so untyped projects see no change.

constuseTodos=()=>useQuery({queryKey: ['todos'],queryFn: ()=>api.getTodos()})// 🔴 Before: not reported.// 🟢 After: reports "Object rest destructuring on a query will observe all// changes to the query, leading to excessive re-renders."const{ data, ...rest}=useTodos()

Direct calls to useQuery / useInfiniteQuery / useSuspenseQuery / useSuspenseInfiniteQuery keep reporting via the existing AST path. The type-aware path handles wrappers by checking whether the call result resolves to known TanStack Query result type names.

// 🔴 Before: not reported.// 🟢 After: reports on the spread.consttodosQuery=useTodos()return{ ...todosQuery,data: todosQuery.data?.[0]}

Note: wrappers around useQueries / useSuspenseQueries aren't detected yet.

A note on the recommendedTypeChecked preset from #8966: While some rules will probably rely on typechecker, this one is gradual. In case of typechecker absence, the rule won't go deeper. The only part here that bugs me is that it's quite implicit behavior. Wdyt?

✅ 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

    • The no-rest-destructuring ESLint rule now detects rest destructuring in custom hooks returning TanStack Query results when typed linting is enabled.
  • Documentation

    • Updated rule documentation to clarify behavior with type-aware linting.
  • Tests

    • Added test coverage for type-checked detection of query-like return types in custom hooks.

@coderabbitai

coderabbitaiBot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 222733cb-43c4-4767-ae27-3c0e30967b5a

📥 Commits

Reviewing files that changed from the base of the PR and between 134444c and a93b88b.

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

📝 Walkthrough

Walkthrough

The PR extends the no-rest-destructuring ESLint rule to detect rest destructuring on custom hooks that return TanStack Query results using TypeScript type checking when typed linting is enabled; untyped projects are not affected.

Changes

Custom Hook Detection in no-rest-destructuring Rule

Layer / File(s)Summary
Query result type recognition
packages/eslint-plugin-query/src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts
Adds TypeScript type helpers and QUERY_RESULT_TYPE_NAMES set; implements isQueryResultType to recognize query result types via symbol names, aliases, or union members.
Custom hook type detection
packages/eslint-plugin-query/src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts
Extends NoRestDestructuringUtils with isQueryResultCall, which uses parserServices and the type checker to determine whether a call expression returns a query result type.
Rule integration with call expression handling
packages/eslint-plugin-query/src/rules/no-rest-destructuring/no-rest-destructuring.rule.ts
Refactors CallExpression visitor: introduces isDirectHook flag for direct TanStack hooks, falls back to isQueryResultCall for custom hooks, and defensively derives calleeName via ASTUtils.isIdentifier.
Type-aware test setup and coverage
packages/eslint-plugin-query/src/__tests__/no-rest-destructuring.test.ts, packages/eslint-plugin-query/src/__tests__/ts-fixture/react-query.d.ts
Wires RuleTester to Vitest lifecycle helpers; adds second type-aware RuleTester with TypeScript parser and fixture tsconfigRootDir; creates ambient @tanstack/react-query stub; adds test cases for custom hook destructuring patterns.
Documentation
.changeset/no-rest-destructuring-custom-hooks.md, docs/eslint/no-rest-destructuring.md
Documents the new behavior and notes typed linting enables detection of rest destructuring on custom hooks; bumps plugin version and references issue closure.

Sequence Diagram(s)

sequenceDiagram
participant ESLintRule as no-rest-destructuring rule
participant ParserServices
participant TypeChecker
ESLintRule->>ParserServices: esTreeNodeToTSNodeMap(callee)
ParserServices->>TypeChecker: getCallSignatures()
TypeChecker->>TypeChecker: getReturnType(signature)
TypeChecker-->>ESLintRule: returnType matches QUERY_RESULT_TYPE_NAMES?
ESLintRule->>ESLintRule: report if rest-destructuring on query result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

package: eslint-plugin-query

Suggested reviewers

  • TkDodo

Poem

🐰 I nibble types and hop through code,
I map AST nodes and chase each load.
Custom hooks unmasked where return types hide,
Rest spreads uncovered — no place to slide.
Hooray — linting triumphs, carrot held with pride!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely describes the main change: extending no-rest-destructuring to detect rest destructuring on custom query hooks.
Description check✅ PassedThe description comprehensively covers changes, includes code examples, addresses checklist items, and documents release impact with a changeset.
Linked Issues check✅ PassedThe PR fully addresses #8951 by implementing type-aware detection for rest destructuring on custom hooks returning TanStack Query results, matching the expected behavior described in the issue.
Out of Scope Changes check✅ PassedAll changes are directly related to extending the no-rest-destructuring rule for custom hooks; no unrelated modifications are present.

✏️ 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.

@Newbie012
Newbie012force-pushed the fix-no-rest-destructuring-custom-hooks branch 2 times, most recently from ae64db4 to bded2a5CompareMay 24, 2026 15:06
…ooks
Adds an opportunistic type-aware path to no-rest-destructuring. When
TypeScript parser services are available, the rule resolves the call
expression's return type and reports rest destructuring on custom hooks
that return a TanStack Query result. Untyped projects keep the existing
AST-only behavior unchanged.
ClosesTanStack#8951
@Newbie012
Newbie012force-pushed the fix-no-rest-destructuring-custom-hooks branch from bded2a5 to fc06643CompareMay 24, 2026 15:16
>
type Type = ReturnType<TypeChecker['getTypeAtLocation']>

const QUERY_RESULT_TYPE_NAMES = new Set([

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Not a fan of this arbitrary list. I'm open to suggestions

@Newbie012
Newbie012 marked this pull request as ready for review May 28, 2026 14:47

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/eslint-plugin-query/src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts`:
- Around line 25-33: isQueryResultType currently returns true solely by matching
type.aliasSymbol.name / type.getSymbol()?.name in QUERY_RESULT_TYPE_NAMES,
causing false positives for same-named local types; update isQueryResultType to
also verify the symbol comes from TanStack query packages by inspecting
aliasSymbol.declarations / symbol.declarations and ensuring their source
module/file indicates `@tanstack/`*-query or query-core (e.g., via
declaration.getSourceFile() or module specifier checks) before accepting the
match, and preserve the existing union recursion (type.isUnion() &&
type.types.some(isQueryResultType)); you can extract a small helper like
isFromTanstackModule(declarations) to encapsulate the module-origin check and
use it in the aliasSymbol and symbol branches.
- Around line 51-53: The current code queries all overloads via
checker.getTypeAtLocation(tsNode).getCallSignatures(), which can misreport for
overloaded functions; instead obtain the TypeScript CallExpression node from
parserServices.esTreeNodeToTSNodeMap using the entire ESLint CallExpression node
(not node.callee), call checker.getResolvedSignature(...) to get the selected
Signature, and then check that signature's return type with
isQueryResultType(sig.getReturnType()); if getResolvedSignature returns
undefined, optionally fall back to the existing signatures scan to preserve
behavior.
🪄 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: e478a6dd-8ee1-4973-b061-4228b2c3889c

📥 Commits

Reviewing files that changed from the base of the PR and between 3042860 and fc06643.

📒 Files selected for processing (6)
  • .changeset/no-rest-destructuring-custom-hooks.md
  • docs/eslint/no-rest-destructuring.md
  • packages/eslint-plugin-query/src/__tests__/no-rest-destructuring.test.ts
  • packages/eslint-plugin-query/src/__tests__/ts-fixture/react-query.d.ts
  • packages/eslint-plugin-query/src/rules/no-rest-destructuring/no-rest-destructuring.rule.ts
  • packages/eslint-plugin-query/src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts

Only run the type checker on non-direct hook calls when the binding can
actually report (rest destructure or identifier), avoiding type lookups on
every variable declarator. Add tests for cross-statement rest destructuring
and interface-typed (non-alias) query results.
@coderabbitai

Copy link
Copy Markdown
Contributor

Actionable comments posted: 0

@nx-cloud

nx-cloudBot commented Jun 2, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 25b1be7

CommandStatusDurationResult
nx run-many --target=build --exclude=examples/*...✅ Succeeded9sView ↗
nx affected --targets=test:sherif,test:knip,tes...✅ Succeeded1m 54sView ↗

☁️ Nx Cloud last updated this comment at 2026-06-02 12:00:54 UTC

@pkg-pr-new

pkg-pr-newBot commented Jun 2, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-query-experimental

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

@tanstack/eslint-plugin-query

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

@tanstack/lit-query

npm i https://pkg.pr.new/@tanstack/lit-query@10775

@tanstack/preact-query

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

@tanstack/preact-query-devtools

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

@tanstack/preact-query-persist-client

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

@tanstack/query-async-storage-persister

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

@tanstack/query-broadcast-client-experimental

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

@tanstack/query-core

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

@tanstack/query-devtools

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

@tanstack/query-persist-client-core

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

@tanstack/query-sync-storage-persister

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

@tanstack/react-query

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

@tanstack/react-query-devtools

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

@tanstack/react-query-next-experimental

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

@tanstack/react-query-persist-client

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

@tanstack/solid-query

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

@tanstack/solid-query-devtools

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

@tanstack/solid-query-persist-client

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

@tanstack/svelte-query

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

@tanstack/svelte-query-devtools

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

@tanstack/svelte-query-persist-client

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

@tanstack/vue-query

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

@tanstack/vue-query-devtools

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

commit: a93b88b

@TkDodo
TkDodo merged commit dc54932 into TanStack:mainJun 2, 2026
9 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Jun 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

@Newbie012@TkDodo