Skip to content

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

Open
Newbie012 wants to merge 2 commits into
TanStack:mainfrom
Newbie012:fix-no-rest-destructuring-custom-hooks
Open

fix(eslint-plugin-query): detect rest destructuring on custom query hooks#10775
Newbie012 wants to merge 2 commits into
TanStack:mainfrom
Newbie012:fix-no-rest-destructuring-custom-hooks

Conversation

@Newbie012
Copy link
Copy Markdown
Contributor

@Newbie012 Newbie012 commented May 24, 2026

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.

const useTodos = () =>
  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.
const todosQuery = useTodos()
return { ...todosQuery, data: todosQuery.data?.[0] }

Matched return types: UseQueryResult, UseSuspenseQueryResult, UseInfiniteQueryResult, UseSuspenseInfiniteQueryResult, their Defined* variants, and the underlying QueryObserverResult / InfiniteQueryObserverResult names.

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

A note on the recommendedTypeChecked preset from #8966: I would still like to land that preset and graduate type-aware rules into it, but the conversation has been stalled and I did not want to block this user-facing bug. Happy to follow up by moving this rule (and other type-aware rules) behind a dedicated preset whenever the maintainers want to take that direction.

✅ 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 rule now flags rest destructuring on custom hooks that return TanStack Query–like results when type-aware linting is enabled (untyped projects unaffected).
  • Documentation

    • Rule docs updated to explain the enhanced, type-based detection behavior.
  • Tests

    • Test coverage extended to include type-checked cases for custom hooks and query-like return values.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 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: 1c8ef9e7-074e-41ed-aac7-cb7d43484c47

📥 Commits

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

📒 Files selected for processing (3)
  • 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
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/eslint-plugin-query/src/tests/ts-fixture/react-query.d.ts
  • packages/eslint-plugin-query/src/tests/no-rest-destructuring.test.ts
  • 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 patterns on custom hooks that return TanStack Query results. Type-based detection is added via TypeScript type checking, active only when typed linting is enabled. Untyped projects remain unaffected.

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 feature, minor version bump, and clarifies that typed linting enables detection of rest destructuring on custom hooks.

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,
Calling checker paths down a winding road.
Custom hooks revealed beneath my nose,
Rest spreads caught where the query goes.
Hooray — linting wins, and the rabbit pose!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: extending the no-rest-destructuring rule to detect rest destructuring on custom query hooks.
Description check ✅ Passed The description comprehensively addresses all template sections: explains the changes, includes examples, covers release impact with a changeset, and completes the checklist items.
Linked Issues check ✅ Passed The PR directly addresses issue #8951 by implementing type-aware detection for rest destructuring on custom query hooks, matching the exact scenario and expected behavior described in the issue.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the custom hook detection feature: tests, documentation, utilities, rule implementation, and changeset—no unrelated modifications.

✏️ 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 Newbie012 force-pushed the fix-no-rest-destructuring-custom-hooks branch 2 times, most recently from ae64db4 to bded2a5 Compare May 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.

Closes TanStack#8951
@Newbie012 Newbie012 force-pushed the fix-no-rest-destructuring-custom-hooks branch from bded2a5 to fc06643 Compare May 24, 2026 15:16
>
type Type = ReturnType<TypeChecker['getTypeAtLocation']>

const QUERY_RESULT_TYPE_NAMES = new Set([
Copy link
Copy Markdown
Contributor Author

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
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: 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

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

Sign up for free to 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

1 participant