-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat(eslint-plugin): add no-rest-destructuring rule (#6265) * chore: format test case code * test: add null array element testcase * test: add non-tanstack-query hooks testcase --------- Co-authored-by: Dominik Dorfmeister <office@dorfmeister.cc>
- Loading branch information
1 parent
47449b7
commit d77d9d8
Showing
8 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
--- | ||
id: no-rest-destructuring | ||
title: Disallow object rest destructuring on query results | ||
--- | ||
|
||
Use object rest destructuring on query results automatically subscribes to every field of the query result, which may cause unnecessary re-renders. | ||
This makes sure that you only subscribe to the fields that you actually need. | ||
|
||
## Rule Details | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```tsx | ||
/* eslint "@tanstack/query/no-rest-destructuring": "warn" */ | ||
|
||
const useTodos = () => { | ||
const { data: todos, ...rest } = useQuery({ | ||
queryKey: ['todos'], | ||
queryFn: () => api.getTodos(), | ||
}) | ||
return { todos, ...rest } | ||
} | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```tsx | ||
const todosQuery = useQuery({ | ||
queryKey: ['todos'], | ||
queryFn: () => api.getTodos(), | ||
}) | ||
|
||
// normal object destructuring is fine | ||
const { data: todos } = todosQuery | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
If you set the `notifyOnChangeProps` options manually, you can disable this rule. | ||
Since you are not using tracked queries, you are responsible for specifying which props should trigger a re-render. | ||
|
||
## Attributes | ||
|
||
- [x] ✅ Recommended | ||
- [ ] 🔧 Fixable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
import * as exhaustiveDeps from './rules/exhaustive-deps.rule' | ||
import * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule' | ||
import * as noRestDestructuring from './rules/no-rest-desctructuring/no-rest-destructuring.rule' | ||
|
||
export const rules = { | ||
[exhaustiveDeps.name]: exhaustiveDeps.rule, | ||
[stableQueryClient.name]: stableQueryClient.rule, | ||
[noRestDestructuring.name]: noRestDestructuring.rule, | ||
} |
64 changes: 64 additions & 0 deletions
64
packages/eslint-plugin-query/src/rules/no-rest-desctructuring/no-rest-destructuring.rule.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { AST_NODE_TYPES } from '@typescript-eslint/utils' | ||
import { createRule } from '../../utils/create-rule' | ||
import { ASTUtils } from '../../utils/ast-utils' | ||
import { NoRestDestructuringUtils } from './no-rest-destructuring.utils' | ||
|
||
export const name = 'no-rest-destructuring' | ||
|
||
const queryHooks = ['useQuery', 'useQueries', 'useInfiniteQuery'] | ||
|
||
export const rule = createRule({ | ||
name, | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Disallows rest destructuring in queries', | ||
recommended: 'warn', | ||
}, | ||
messages: { | ||
objectRestDestructure: `Object rest destructuring on a query will observe all changes to the query, leading to excessive re-renders.`, | ||
}, | ||
schema: [], | ||
}, | ||
defaultOptions: [], | ||
|
||
create(context, _, helpers) { | ||
return { | ||
CallExpression(node) { | ||
if ( | ||
!ASTUtils.isIdentifierWithOneOfNames(node.callee, queryHooks) || | ||
!helpers.isTanstackQueryImport(node.callee) || | ||
node.parent?.type !== AST_NODE_TYPES.VariableDeclarator | ||
) { | ||
return | ||
} | ||
|
||
const returnValue = node.parent.id | ||
if (node.callee.name !== 'useQueries') { | ||
if (NoRestDestructuringUtils.isObjectRestDestructuring(returnValue)) { | ||
context.report({ | ||
node: node.parent, | ||
messageId: 'objectRestDestructure', | ||
}) | ||
} | ||
return | ||
} | ||
|
||
if (returnValue.type !== AST_NODE_TYPES.ArrayPattern) { | ||
return | ||
} | ||
returnValue.elements.forEach((queryResult) => { | ||
if (queryResult === null) { | ||
return | ||
} | ||
if (NoRestDestructuringUtils.isObjectRestDestructuring(queryResult)) { | ||
context.report({ | ||
node: queryResult, | ||
messageId: 'objectRestDestructure', | ||
}) | ||
} | ||
}) | ||
}, | ||
} | ||
}, | ||
}) |
195 changes: 195 additions & 0 deletions
195
packages/eslint-plugin-query/src/rules/no-rest-desctructuring/no-rest-destructuring.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import { ESLintUtils } from '@typescript-eslint/utils' | ||
import { normalizeIndent } from '../../utils/test-utils' | ||
import { rule } from './no-rest-destructuring.rule' | ||
|
||
const ruleTester = new ESLintUtils.RuleTester({ | ||
parser: '@typescript-eslint/parser', | ||
settings: {}, | ||
}) | ||
|
||
ruleTester.run('no-rest-desctructuring', rule, { | ||
valid: [ | ||
{ | ||
name: 'useQuery is not captured', | ||
code: normalizeIndent` | ||
import { useQuery } from '@tanstack/react-query' | ||
function Component() { | ||
useQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useQuery is not destructured', | ||
code: normalizeIndent` | ||
import { useQuery } from '@tanstack/react-query' | ||
function Component() { | ||
const query = useQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useQuery is destructured without rest', | ||
code: normalizeIndent` | ||
import { useQuery } from '@tanstack/react-query' | ||
function Component() { | ||
const { data, isLoading, isError } = useQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useInfiniteQuery is not captured', | ||
code: normalizeIndent` | ||
import { useInfiniteQuery } from '@tanstack/react-query' | ||
function Component() { | ||
useInfiniteQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useInfiniteQuery is not destructured', | ||
code: normalizeIndent` | ||
import { useInfiniteQuery } from '@tanstack/react-query' | ||
function Component() { | ||
const query = useInfiniteQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useInfiniteQuery is destructured without rest', | ||
code: normalizeIndent` | ||
import { useInfiniteQuery } from '@tanstack/react-query' | ||
function Component() { | ||
const { data, isLoading, isError } = useInfiniteQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useQueries is not captured', | ||
code: normalizeIndent` | ||
import { useQueries } from '@tanstack/react-query' | ||
function Component() { | ||
useQueries([]) | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useQueries is not destructured', | ||
code: normalizeIndent` | ||
import { useQueries } from '@tanstack/react-query' | ||
function Component() { | ||
const queries = useQueries([]) | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useQueries array has no rest destructured element', | ||
code: normalizeIndent` | ||
import { useQueries } from '@tanstack/react-query' | ||
function Component() { | ||
const [query1, { data, isLoading },, ...others] = useQueries([ | ||
{ queryKey: ['key1'], queryFn: () => {} }, | ||
{ queryKey: ['key2'], queryFn: () => {} }, | ||
{ queryKey: ['key3'], queryFn: () => {} }, | ||
{ queryKey: ['key4'], queryFn: () => {} }, | ||
{ queryKey: ['key5'], queryFn: () => {} }, | ||
]) | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useQuery is destructured with rest but not from tanstack query', | ||
code: normalizeIndent` | ||
import { useQuery } from 'other-package' | ||
function Component() { | ||
const { data, ...rest } = useQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useInfiniteQuery is destructured with rest but not from tanstack query', | ||
code: normalizeIndent` | ||
import { useInfiniteQuery } from 'other-package' | ||
function Component() { | ||
const { data, ...rest } = useInfiniteQuery() | ||
return | ||
} | ||
`, | ||
}, | ||
{ | ||
name: 'useQueries array has rest destructured element but not from tanstack query', | ||
code: normalizeIndent` | ||
import { useQueries } from 'other-package' | ||
function Component() { | ||
const [query1, { data, ...rest }] = useQueries([ | ||
{ queryKey: ['key1'], queryFn: () => {} }, | ||
{ queryKey: ['key2'], queryFn: () => {} }, | ||
]) | ||
return | ||
} | ||
`, | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
name: 'useQuery is destructured with rest', | ||
code: normalizeIndent` | ||
import { useQuery } from '@tanstack/react-query' | ||
function Component() { | ||
const { data, ...rest } = useQuery() | ||
return | ||
} | ||
`, | ||
errors: [{ messageId: 'objectRestDestructure' }], | ||
}, | ||
{ | ||
name: 'useInfiniteQuery is destructured with rest', | ||
code: normalizeIndent` | ||
import { useInfiniteQuery } from '@tanstack/react-query' | ||
function Component() { | ||
const { data, ...rest } = useInfiniteQuery() | ||
return | ||
} | ||
`, | ||
errors: [{ messageId: 'objectRestDestructure' }], | ||
}, | ||
{ | ||
name: 'useQueries array has rest destructured element', | ||
code: normalizeIndent` | ||
import { useQueries } from '@tanstack/react-query' | ||
function Component() { | ||
const [query1, { data, ...rest }] = useQueries([ | ||
{ queryKey: ['key1'], queryFn: () => {} }, | ||
{ queryKey: ['key2'], queryFn: () => {} }, | ||
]) | ||
return | ||
} | ||
`, | ||
errors: [{ messageId: 'objectRestDestructure' }], | ||
}, | ||
], | ||
}) |
11 changes: 11 additions & 0 deletions
11
packages/eslint-plugin-query/src/rules/no-rest-desctructuring/no-rest-destructuring.utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { AST_NODE_TYPES } from '@typescript-eslint/utils' | ||
import type { TSESTree } from '@typescript-eslint/utils' | ||
|
||
export const NoRestDestructuringUtils = { | ||
isObjectRestDestructuring(node: TSESTree.Node): boolean { | ||
if (node.type !== AST_NODE_TYPES.ObjectPattern) { | ||
return false | ||
} | ||
return node.properties.some((p) => p.type === AST_NODE_TYPES.RestElement) | ||
}, | ||
} |