Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.2k
fix(eslint-plugin-query): detect rest destructuring on custom query hooks#10775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
fc06643134444c25b1be7a93b88bFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@tanstack/eslint-plugin-query": minor | ||
| --- | ||
| `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 only when typed linting is enabled, so untyped projects are unaffected. Closes #8951. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Ambient stub so type-checked tests can resolve `@tanstack/react-query` | ||
| // without adding it as a devDependency of this plugin. | ||
| declare module '@tanstack/react-query' { | ||
| export type UseQueryResult<TData = unknown> = { | ||
| data: TData | undefined | ||
| isLoading: boolean | ||
| isError: boolean | ||
| } | ||
| // Declared as an interface so its type resolves via `getSymbol()` rather | ||
| // than `aliasSymbol`, exercising the non-alias detection path. | ||
| export interface QueryObserverResult<TData = unknown> { | ||
| data: TData | undefined | ||
| isLoading: boolean | ||
| isError: boolean | ||
| } | ||
| export function useQuery<TData>(options: { | ||
| queryKey: ReadonlyArray<unknown> | ||
| queryFn: () => Promise<TData> | ||
| }): UseQueryResult<TData> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,37 @@ | ||
| import { AST_NODE_TYPES } from '@typescript-eslint/utils' | ||
| import type { TSESTree } from '@typescript-eslint/utils' | ||
| import type { | ||
| ParserServices, | ||
| ParserServicesWithTypeInformation, | ||
| TSESTree, | ||
| } from '@typescript-eslint/utils' | ||
| type TypeChecker = ReturnType< | ||
| ParserServicesWithTypeInformation['program']['getTypeChecker'] | ||
| > | ||
| type Type = ReturnType<TypeChecker['getTypeAtLocation']> | ||
| const QUERY_RESULT_TYPE_NAMES = new Set([ | ||
| 'UseBaseQueryResult', | ||
| 'UseQueryResult', | ||
| 'UseSuspenseQueryResult', | ||
| 'DefinedUseQueryResult', | ||
| 'UseInfiniteQueryResult', | ||
| 'UseSuspenseInfiniteQueryResult', | ||
| 'DefinedUseInfiniteQueryResult', | ||
| 'QueryObserverResult', | ||
| 'InfiniteQueryObserverResult', | ||
| ]) | ||
| function isQueryResultType(type: Type): boolean { | ||
| if (type.aliasSymbol && QUERY_RESULT_TYPE_NAMES.has(type.aliasSymbol.name)) { | ||
| return true | ||
| } | ||
| const symbol = type.getSymbol() | ||
| if (symbol && QUERY_RESULT_TYPE_NAMES.has(symbol.name)) { | ||
| return true | ||
| } | ||
| return type.isUnion() && type.types.some(isQueryResultType) | ||
Newbie012 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| export const NoRestDestructuringUtils = { | ||
| isObjectRestDestructuring(node: TSESTree.Node): boolean { | ||
| @@ -8,4 +40,16 @@ export const NoRestDestructuringUtils = { | ||
| } | ||
| return node.properties.some((p) => p.type === AST_NODE_TYPES.RestElement) | ||
| }, | ||
| isQueryResultCall( | ||
| node: TSESTree.CallExpression, | ||
| parserServices: Partial<ParserServices> | null | undefined, | ||
| ): boolean { | ||
| if (!parserServices?.program || !parserServices.esTreeNodeToTSNodeMap) { | ||
| return false | ||
| } | ||
| const checker = parserServices.program.getTypeChecker() | ||
| const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.callee) | ||
| const signatures = checker.getTypeAtLocation(tsNode).getCallSignatures() | ||
| return signatures.some((sig) => isQueryResultType(sig.getReturnType())) | ||
Newbie012 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }, | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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