@tanstack/eslint-plugin-query 5.71.5 → 5.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1001 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +968 -0
- package/dist/index.js.map +1 -0
- package/dist/rules.cjs +966 -0
- package/dist/rules.cjs.map +1 -0
- package/dist/rules.js +929 -0
- package/dist/rules.js.map +1 -0
- package/package.json +12 -12
- package/dist/cjs/index.cjs +0 -38
- package/dist/cjs/index.cjs.map +0 -1
- package/dist/cjs/index.d.cts +0 -13
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.cjs +0 -134
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.cjs.map +0 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.d.cts +0 -4
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.cjs +0 -26
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.cjs.map +0 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.d.cts +0 -10
- package/dist/cjs/rules/infinite-query-property-order/constants.cjs +0 -13
- package/dist/cjs/rules/infinite-query-property-order/constants.cjs.map +0 -1
- package/dist/cjs/rules/infinite-query-property-order/constants.d.cts +0 -4
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs +0 -88
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs.map +0 -1
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.d.cts +0 -4
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs +0 -47
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs.map +0 -1
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.d.cts +0 -1
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.cjs +0 -92
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.cjs.map +0 -1
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.d.cts +0 -4
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.cjs +0 -13
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.cjs.map +0 -1
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.d.cts +0 -4
- package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs +0 -93
- package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs.map +0 -1
- package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.d.cts +0 -6
- package/dist/cjs/rules/stable-query-client/stable-query-client.rule.cjs +0 -71
- package/dist/cjs/rules/stable-query-client/stable-query-client.rule.cjs.map +0 -1
- package/dist/cjs/rules/stable-query-client/stable-query-client.rule.d.cts +0 -4
- package/dist/cjs/rules.cjs +0 -16
- package/dist/cjs/rules.cjs.map +0 -1
- package/dist/cjs/rules.d.cts +0 -3
- package/dist/cjs/types.d.cts +0 -3
- package/dist/cjs/utils/ast-utils.cjs +0 -267
- package/dist/cjs/utils/ast-utils.cjs.map +0 -1
- package/dist/cjs/utils/ast-utils.d.cts +0 -38
- package/dist/cjs/utils/detect-react-query-imports.cjs +0 -55
- package/dist/cjs/utils/detect-react-query-imports.cjs.map +0 -1
- package/dist/cjs/utils/detect-react-query-imports.d.cts +0 -11
- package/dist/cjs/utils/get-docs-url.cjs +0 -5
- package/dist/cjs/utils/get-docs-url.cjs.map +0 -1
- package/dist/cjs/utils/get-docs-url.d.cts +0 -1
- package/dist/cjs/utils/unique-by.cjs +0 -7
- package/dist/cjs/utils/unique-by.cjs.map +0 -1
- package/dist/cjs/utils/unique-by.d.cts +0 -1
- package/dist/esm/index.d.ts +0 -13
- package/dist/esm/index.js +0 -39
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.d.ts +0 -4
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js +0 -134
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js.map +0 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.d.ts +0 -10
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.js +0 -26
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.js.map +0 -1
- package/dist/esm/rules/infinite-query-property-order/constants.d.ts +0 -4
- package/dist/esm/rules/infinite-query-property-order/constants.js +0 -13
- package/dist/esm/rules/infinite-query-property-order/constants.js.map +0 -1
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.d.ts +0 -4
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js +0 -88
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js.map +0 -1
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.d.ts +0 -1
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js +0 -47
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js.map +0 -1
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.d.ts +0 -4
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.js +0 -92
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.js.map +0 -1
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.d.ts +0 -4
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.js +0 -13
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.js.map +0 -1
- package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.d.ts +0 -6
- package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js +0 -93
- package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js.map +0 -1
- package/dist/esm/rules/stable-query-client/stable-query-client.rule.d.ts +0 -4
- package/dist/esm/rules/stable-query-client/stable-query-client.rule.js +0 -71
- package/dist/esm/rules/stable-query-client/stable-query-client.rule.js.map +0 -1
- package/dist/esm/rules.d.ts +0 -3
- package/dist/esm/rules.js +0 -16
- package/dist/esm/rules.js.map +0 -1
- package/dist/esm/types.d.ts +0 -3
- package/dist/esm/utils/ast-utils.d.ts +0 -38
- package/dist/esm/utils/ast-utils.js +0 -267
- package/dist/esm/utils/ast-utils.js.map +0 -1
- package/dist/esm/utils/detect-react-query-imports.d.ts +0 -11
- package/dist/esm/utils/detect-react-query-imports.js +0 -55
- package/dist/esm/utils/detect-react-query-imports.js.map +0 -1
- package/dist/esm/utils/get-docs-url.d.ts +0 -1
- package/dist/esm/utils/get-docs-url.js +0 -5
- package/dist/esm/utils/get-docs-url.js.map +0 -1
- package/dist/esm/utils/unique-by.d.ts +0 -1
- package/dist/esm/utils/unique-by.js +0 -7
- package/dist/esm/utils/unique-by.js.map +0 -1
- package/src/index.ts +0 -52
- package/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +0 -184
- package/src/rules/exhaustive-deps/exhaustive-deps.utils.ts +0 -41
- package/src/rules/infinite-query-property-order/constants.ts +0 -17
- package/src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts +0 -107
- package/src/rules/infinite-query-property-order/infinite-query-property-order.utils.ts +0 -67
- package/src/rules/no-rest-destructuring/no-rest-destructuring.rule.ts +0 -115
- package/src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts +0 -11
- package/src/rules/no-unstable-deps/no-unstable-deps.rule.ts +0 -129
- package/src/rules/stable-query-client/stable-query-client.rule.ts +0 -88
- package/src/rules.ts +0 -23
- package/src/types.ts +0 -3
- package/src/utils/ast-utils.ts +0 -391
- package/src/utils/detect-react-query-imports.ts +0 -97
- package/src/utils/get-docs-url.ts +0 -2
- package/src/utils/unique-by.ts +0 -3
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
|
|
2
|
-
import { ASTUtils } from '../../utils/ast-utils'
|
|
3
|
-
import { getDocsUrl } from '../../utils/get-docs-url'
|
|
4
|
-
import { uniqueBy } from '../../utils/unique-by'
|
|
5
|
-
import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
|
|
6
|
-
import { ExhaustiveDepsUtils } from './exhaustive-deps.utils'
|
|
7
|
-
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
|
|
8
|
-
import type { ExtraRuleDocs } from '../../types'
|
|
9
|
-
|
|
10
|
-
const QUERY_KEY = 'queryKey'
|
|
11
|
-
const QUERY_FN = 'queryFn'
|
|
12
|
-
|
|
13
|
-
export const name = 'exhaustive-deps'
|
|
14
|
-
|
|
15
|
-
const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
|
|
16
|
-
|
|
17
|
-
export const rule = createRule({
|
|
18
|
-
name,
|
|
19
|
-
meta: {
|
|
20
|
-
type: 'problem',
|
|
21
|
-
docs: {
|
|
22
|
-
description: 'Exhaustive deps rule for useQuery',
|
|
23
|
-
recommended: 'error',
|
|
24
|
-
},
|
|
25
|
-
messages: {
|
|
26
|
-
missingDeps: `The following dependencies are missing in your queryKey: {{deps}}`,
|
|
27
|
-
fixTo: 'Fix to {{result}}',
|
|
28
|
-
},
|
|
29
|
-
hasSuggestions: true,
|
|
30
|
-
fixable: 'code',
|
|
31
|
-
schema: [],
|
|
32
|
-
},
|
|
33
|
-
defaultOptions: [],
|
|
34
|
-
|
|
35
|
-
create: detectTanstackQueryImports((context) => {
|
|
36
|
-
return {
|
|
37
|
-
Property: (node) => {
|
|
38
|
-
if (
|
|
39
|
-
!ASTUtils.isObjectExpression(node.parent) ||
|
|
40
|
-
!ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)
|
|
41
|
-
) {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const scopeManager = context.sourceCode.scopeManager
|
|
46
|
-
const queryKey = ASTUtils.findPropertyWithIdentifierKey(
|
|
47
|
-
node.parent.properties,
|
|
48
|
-
QUERY_KEY,
|
|
49
|
-
)
|
|
50
|
-
const queryFn = ASTUtils.findPropertyWithIdentifierKey(
|
|
51
|
-
node.parent.properties,
|
|
52
|
-
QUERY_FN,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
scopeManager === null ||
|
|
57
|
-
queryKey === undefined ||
|
|
58
|
-
queryFn === undefined ||
|
|
59
|
-
!ASTUtils.isNodeOfOneOf(queryFn.value, [
|
|
60
|
-
AST_NODE_TYPES.ArrowFunctionExpression,
|
|
61
|
-
AST_NODE_TYPES.FunctionExpression,
|
|
62
|
-
AST_NODE_TYPES.ConditionalExpression,
|
|
63
|
-
])
|
|
64
|
-
) {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let queryKeyNode = queryKey.value
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
queryKeyNode.type === AST_NODE_TYPES.TSAsExpression &&
|
|
72
|
-
queryKeyNode.expression.type === AST_NODE_TYPES.ArrayExpression
|
|
73
|
-
) {
|
|
74
|
-
queryKeyNode = queryKeyNode.expression
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (queryKeyNode.type === AST_NODE_TYPES.Identifier) {
|
|
78
|
-
const expression = ASTUtils.getReferencedExpressionByIdentifier({
|
|
79
|
-
context,
|
|
80
|
-
node: queryKeyNode,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
if (expression?.type === AST_NODE_TYPES.ArrayExpression) {
|
|
84
|
-
queryKeyNode = expression
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const externalRefs = ASTUtils.getExternalRefs({
|
|
89
|
-
scopeManager,
|
|
90
|
-
sourceCode: context.sourceCode,
|
|
91
|
-
node: getQueryFnRelevantNode(queryFn),
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const relevantRefs = externalRefs.filter((reference) =>
|
|
95
|
-
ExhaustiveDepsUtils.isRelevantReference({
|
|
96
|
-
sourceCode: context.sourceCode,
|
|
97
|
-
reference,
|
|
98
|
-
scopeManager,
|
|
99
|
-
node: getQueryFnRelevantNode(queryFn),
|
|
100
|
-
}),
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyNode).map(
|
|
104
|
-
(identifier) =>
|
|
105
|
-
ASTUtils.mapKeyNodeToBaseText(identifier, context.sourceCode),
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
const missingRefs = relevantRefs
|
|
109
|
-
.map((ref) => ({
|
|
110
|
-
ref: ref,
|
|
111
|
-
text: ASTUtils.mapKeyNodeToBaseText(
|
|
112
|
-
ref.identifier,
|
|
113
|
-
context.sourceCode,
|
|
114
|
-
),
|
|
115
|
-
}))
|
|
116
|
-
.filter(({ ref, text }) => {
|
|
117
|
-
return (
|
|
118
|
-
!ref.isTypeReference &&
|
|
119
|
-
!ASTUtils.isAncestorIsCallee(ref.identifier) &&
|
|
120
|
-
!existingKeys.some((existingKey) => existingKey === text) &&
|
|
121
|
-
!existingKeys.includes(text.split(/[?.]/)[0] ?? '')
|
|
122
|
-
)
|
|
123
|
-
})
|
|
124
|
-
.map(({ ref, text }) => ({
|
|
125
|
-
identifier: ref.identifier,
|
|
126
|
-
text: text,
|
|
127
|
-
}))
|
|
128
|
-
|
|
129
|
-
const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text)
|
|
130
|
-
|
|
131
|
-
if (uniqueMissingRefs.length > 0) {
|
|
132
|
-
const missingAsText = uniqueMissingRefs
|
|
133
|
-
.map((ref) =>
|
|
134
|
-
ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),
|
|
135
|
-
)
|
|
136
|
-
.join(', ')
|
|
137
|
-
|
|
138
|
-
const queryKeyValue = context.sourceCode.getText(queryKeyNode)
|
|
139
|
-
|
|
140
|
-
const existingWithMissing =
|
|
141
|
-
queryKeyValue === '[]'
|
|
142
|
-
? `[${missingAsText}]`
|
|
143
|
-
: queryKeyValue.replace(/\]$/, `, ${missingAsText}]`)
|
|
144
|
-
|
|
145
|
-
const suggestions: TSESLint.ReportSuggestionArray<string> = []
|
|
146
|
-
|
|
147
|
-
if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {
|
|
148
|
-
suggestions.push({
|
|
149
|
-
messageId: 'fixTo',
|
|
150
|
-
data: { result: existingWithMissing },
|
|
151
|
-
fix(fixer) {
|
|
152
|
-
return fixer.replaceText(queryKeyNode, existingWithMissing)
|
|
153
|
-
},
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
context.report({
|
|
158
|
-
node: node,
|
|
159
|
-
messageId: 'missingDeps',
|
|
160
|
-
data: {
|
|
161
|
-
deps: uniqueMissingRefs.map((ref) => ref.text).join(', '),
|
|
162
|
-
},
|
|
163
|
-
suggest: suggestions,
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
}
|
|
168
|
-
}),
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
function getQueryFnRelevantNode(queryFn: TSESTree.Property) {
|
|
172
|
-
if (queryFn.value.type !== AST_NODE_TYPES.ConditionalExpression) {
|
|
173
|
-
return queryFn.value
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
queryFn.value.consequent.type === AST_NODE_TYPES.Identifier &&
|
|
178
|
-
queryFn.value.consequent.name === 'skipToken'
|
|
179
|
-
) {
|
|
180
|
-
return queryFn.value.alternate
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return queryFn.value.consequent
|
|
184
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
|
|
2
|
-
import { ASTUtils } from '../../utils/ast-utils'
|
|
3
|
-
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
|
|
4
|
-
|
|
5
|
-
export const ExhaustiveDepsUtils = {
|
|
6
|
-
isRelevantReference(params: {
|
|
7
|
-
sourceCode: Readonly<TSESLint.SourceCode>
|
|
8
|
-
reference: TSESLint.Scope.Reference
|
|
9
|
-
scopeManager: TSESLint.Scope.ScopeManager
|
|
10
|
-
node: TSESTree.Node
|
|
11
|
-
}) {
|
|
12
|
-
const { sourceCode, reference, scopeManager, node } = params
|
|
13
|
-
const component = ASTUtils.getFunctionAncestor(sourceCode, node)
|
|
14
|
-
|
|
15
|
-
if (component === undefined) {
|
|
16
|
-
return false
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
!ASTUtils.isDeclaredInNode({
|
|
21
|
-
scopeManager,
|
|
22
|
-
reference,
|
|
23
|
-
functionNode: component,
|
|
24
|
-
})
|
|
25
|
-
) {
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
reference.identifier.name !== 'undefined' &&
|
|
31
|
-
reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression &&
|
|
32
|
-
!ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent)
|
|
33
|
-
)
|
|
34
|
-
},
|
|
35
|
-
isInstanceOfKind(node: TSESTree.Node) {
|
|
36
|
-
return (
|
|
37
|
-
node.type === AST_NODE_TYPES.BinaryExpression &&
|
|
38
|
-
node.operator === 'instanceof'
|
|
39
|
-
)
|
|
40
|
-
},
|
|
41
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const infiniteQueryFunctions = [
|
|
2
|
-
'infiniteQueryOptions',
|
|
3
|
-
'useInfiniteQuery',
|
|
4
|
-
'useSuspenseInfiniteQuery',
|
|
5
|
-
] as const
|
|
6
|
-
|
|
7
|
-
export type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number]
|
|
8
|
-
|
|
9
|
-
export const checkedProperties = [
|
|
10
|
-
'queryFn',
|
|
11
|
-
'getPreviousPageParam',
|
|
12
|
-
'getNextPageParam',
|
|
13
|
-
] as const
|
|
14
|
-
|
|
15
|
-
export const sortRules = [
|
|
16
|
-
[['queryFn'], ['getPreviousPageParam', 'getNextPageParam']],
|
|
17
|
-
] as const
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
|
|
2
|
-
|
|
3
|
-
import { getDocsUrl } from '../../utils/get-docs-url'
|
|
4
|
-
import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
|
|
5
|
-
import { sortDataByOrder } from './infinite-query-property-order.utils'
|
|
6
|
-
import { infiniteQueryFunctions, sortRules } from './constants'
|
|
7
|
-
import type { InfiniteQueryFunctions } from './constants'
|
|
8
|
-
import type { ExtraRuleDocs } from '../../types'
|
|
9
|
-
|
|
10
|
-
const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
|
|
11
|
-
|
|
12
|
-
const infiniteQueryFunctionsSet = new Set(infiniteQueryFunctions)
|
|
13
|
-
function isInfiniteQueryFunction(node: any): node is InfiniteQueryFunctions {
|
|
14
|
-
return infiniteQueryFunctionsSet.has(node)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const name = 'infinite-query-property-order'
|
|
18
|
-
|
|
19
|
-
export const rule = createRule({
|
|
20
|
-
name,
|
|
21
|
-
meta: {
|
|
22
|
-
type: 'problem',
|
|
23
|
-
docs: {
|
|
24
|
-
description:
|
|
25
|
-
'Ensure correct order of inference sensitive properties for infinite queries',
|
|
26
|
-
recommended: 'error',
|
|
27
|
-
},
|
|
28
|
-
messages: {
|
|
29
|
-
invalidOrder: 'Invalid order of properties for `{{function}}`.',
|
|
30
|
-
},
|
|
31
|
-
schema: [],
|
|
32
|
-
hasSuggestions: true,
|
|
33
|
-
fixable: 'code',
|
|
34
|
-
},
|
|
35
|
-
defaultOptions: [],
|
|
36
|
-
|
|
37
|
-
create: detectTanstackQueryImports((context) => {
|
|
38
|
-
return {
|
|
39
|
-
CallExpression(node) {
|
|
40
|
-
if (node.callee.type !== AST_NODE_TYPES.Identifier) {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
const infiniteQueryFunction = node.callee.name
|
|
44
|
-
if (!isInfiniteQueryFunction(infiniteQueryFunction)) {
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
const argument = node.arguments[0]
|
|
48
|
-
if (argument === undefined || argument.type !== 'ObjectExpression') {
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const allProperties = argument.properties
|
|
53
|
-
|
|
54
|
-
// no need to sort if there is at max 1 property
|
|
55
|
-
if (allProperties.length < 2) {
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const properties = allProperties.flatMap((p, index) => {
|
|
60
|
-
if (
|
|
61
|
-
p.type === AST_NODE_TYPES.Property &&
|
|
62
|
-
p.key.type === AST_NODE_TYPES.Identifier
|
|
63
|
-
) {
|
|
64
|
-
return { name: p.key.name, property: p }
|
|
65
|
-
} else return { name: `_property_${index}`, property: p }
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const sortedProperties = sortDataByOrder(properties, sortRules, 'name')
|
|
69
|
-
if (sortedProperties === null) {
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
context.report({
|
|
73
|
-
node: argument,
|
|
74
|
-
data: { function: node.callee.name },
|
|
75
|
-
messageId: 'invalidOrder',
|
|
76
|
-
fix(fixer) {
|
|
77
|
-
const sourceCode = context.sourceCode
|
|
78
|
-
|
|
79
|
-
const reorderedText = sortedProperties.reduce(
|
|
80
|
-
(sourceText, specifier, index) => {
|
|
81
|
-
let textBetweenProperties = ''
|
|
82
|
-
if (index < allProperties.length - 1) {
|
|
83
|
-
textBetweenProperties = sourceCode
|
|
84
|
-
.getText()
|
|
85
|
-
.slice(
|
|
86
|
-
allProperties[index]!.range[1],
|
|
87
|
-
allProperties[index + 1]!.range[0],
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
return (
|
|
91
|
-
sourceText +
|
|
92
|
-
sourceCode.getText(specifier.property) +
|
|
93
|
-
textBetweenProperties
|
|
94
|
-
)
|
|
95
|
-
},
|
|
96
|
-
'',
|
|
97
|
-
)
|
|
98
|
-
return fixer.replaceTextRange(
|
|
99
|
-
[allProperties[0]!.range[0], allProperties.at(-1)!.range[1]],
|
|
100
|
-
reorderedText,
|
|
101
|
-
)
|
|
102
|
-
},
|
|
103
|
-
})
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
}),
|
|
107
|
-
})
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export function sortDataByOrder<T, TKey extends keyof T>(
|
|
2
|
-
data: Array<T> | ReadonlyArray<T>,
|
|
3
|
-
orderRules: ReadonlyArray<
|
|
4
|
-
Readonly<[ReadonlyArray<T[TKey]>, ReadonlyArray<T[TKey]>]>
|
|
5
|
-
>,
|
|
6
|
-
key: TKey,
|
|
7
|
-
): Array<T> | null {
|
|
8
|
-
const getSubsetIndex = (
|
|
9
|
-
item: T[TKey],
|
|
10
|
-
subsets: ReadonlyArray<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,
|
|
11
|
-
): number | null => {
|
|
12
|
-
for (let i = 0; i < subsets.length; i++) {
|
|
13
|
-
if (subsets[i]?.includes(item)) {
|
|
14
|
-
return i
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return null
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const orderSets = orderRules.reduce(
|
|
21
|
-
(sets, [A, B]) => [...sets, A, B],
|
|
22
|
-
[] as Array<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
const inOrderArray = data.filter(
|
|
26
|
-
(item) => getSubsetIndex(item[key], orderSets) !== null,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
let wasResorted = false as boolean
|
|
30
|
-
|
|
31
|
-
// Sort by the relative order defined by the rules
|
|
32
|
-
const sortedArray = inOrderArray.sort((a, b) => {
|
|
33
|
-
const aKey = a[key],
|
|
34
|
-
bKey = b[key]
|
|
35
|
-
const aSubsetIndex = getSubsetIndex(aKey, orderSets)
|
|
36
|
-
const bSubsetIndex = getSubsetIndex(bKey, orderSets)
|
|
37
|
-
|
|
38
|
-
// If both items belong to different subsets, sort by their subset order
|
|
39
|
-
if (
|
|
40
|
-
aSubsetIndex !== null &&
|
|
41
|
-
bSubsetIndex !== null &&
|
|
42
|
-
aSubsetIndex !== bSubsetIndex
|
|
43
|
-
) {
|
|
44
|
-
return aSubsetIndex - bSubsetIndex
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// If both items belong to the same subset or neither is in the subset, keep their relative order
|
|
48
|
-
return 0
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const inOrderIterator = sortedArray.values()
|
|
52
|
-
const result = data.map((item) => {
|
|
53
|
-
if (getSubsetIndex(item[key], orderSets) !== null) {
|
|
54
|
-
const sortedItem = inOrderIterator.next().value!
|
|
55
|
-
if (sortedItem[key] !== item[key]) {
|
|
56
|
-
wasResorted = true
|
|
57
|
-
}
|
|
58
|
-
return sortedItem
|
|
59
|
-
}
|
|
60
|
-
return item
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
if (!wasResorted) {
|
|
64
|
-
return null
|
|
65
|
-
}
|
|
66
|
-
return result
|
|
67
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
|
|
2
|
-
import { getDocsUrl } from '../../utils/get-docs-url'
|
|
3
|
-
import { ASTUtils } from '../../utils/ast-utils'
|
|
4
|
-
import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
|
|
5
|
-
import { NoRestDestructuringUtils } from './no-rest-destructuring.utils'
|
|
6
|
-
import type { ExtraRuleDocs } from '../../types'
|
|
7
|
-
|
|
8
|
-
export const name = 'no-rest-destructuring'
|
|
9
|
-
|
|
10
|
-
const queryHooks = [
|
|
11
|
-
'useQuery',
|
|
12
|
-
'useQueries',
|
|
13
|
-
'useInfiniteQuery',
|
|
14
|
-
'useSuspenseQuery',
|
|
15
|
-
'useSuspenseQueries',
|
|
16
|
-
'useSuspenseInfiniteQuery',
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
|
|
20
|
-
|
|
21
|
-
export const rule = createRule({
|
|
22
|
-
name,
|
|
23
|
-
meta: {
|
|
24
|
-
type: 'problem',
|
|
25
|
-
docs: {
|
|
26
|
-
description: 'Disallows rest destructuring in queries',
|
|
27
|
-
recommended: 'warn',
|
|
28
|
-
},
|
|
29
|
-
messages: {
|
|
30
|
-
objectRestDestructure: `Object rest destructuring on a query will observe all changes to the query, leading to excessive re-renders.`,
|
|
31
|
-
},
|
|
32
|
-
schema: [],
|
|
33
|
-
},
|
|
34
|
-
defaultOptions: [],
|
|
35
|
-
|
|
36
|
-
create: detectTanstackQueryImports((context, _, helpers) => {
|
|
37
|
-
const queryResultVariables = new Set<string>()
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
CallExpression: (node) => {
|
|
41
|
-
if (
|
|
42
|
-
!ASTUtils.isIdentifierWithOneOfNames(node.callee, queryHooks) ||
|
|
43
|
-
node.parent.type !== AST_NODE_TYPES.VariableDeclarator ||
|
|
44
|
-
!helpers.isTanstackQueryImport(node.callee)
|
|
45
|
-
) {
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const returnValue = node.parent.id
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
node.callee.name !== 'useQueries' &&
|
|
53
|
-
node.callee.name !== 'useSuspenseQueries'
|
|
54
|
-
) {
|
|
55
|
-
if (NoRestDestructuringUtils.isObjectRestDestructuring(returnValue)) {
|
|
56
|
-
return context.report({
|
|
57
|
-
node: node.parent,
|
|
58
|
-
messageId: 'objectRestDestructure',
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (returnValue.type === AST_NODE_TYPES.Identifier) {
|
|
63
|
-
queryResultVariables.add(returnValue.name)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (returnValue.type !== AST_NODE_TYPES.ArrayPattern) {
|
|
70
|
-
if (returnValue.type === AST_NODE_TYPES.Identifier) {
|
|
71
|
-
queryResultVariables.add(returnValue.name)
|
|
72
|
-
}
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
returnValue.elements.forEach((queryResult) => {
|
|
77
|
-
if (queryResult === null) {
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
if (NoRestDestructuringUtils.isObjectRestDestructuring(queryResult)) {
|
|
81
|
-
context.report({
|
|
82
|
-
node: queryResult,
|
|
83
|
-
messageId: 'objectRestDestructure',
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
VariableDeclarator: (node) => {
|
|
90
|
-
if (
|
|
91
|
-
node.init?.type === AST_NODE_TYPES.Identifier &&
|
|
92
|
-
queryResultVariables.has(node.init.name) &&
|
|
93
|
-
NoRestDestructuringUtils.isObjectRestDestructuring(node.id)
|
|
94
|
-
) {
|
|
95
|
-
context.report({
|
|
96
|
-
node,
|
|
97
|
-
messageId: 'objectRestDestructure',
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
SpreadElement: (node) => {
|
|
103
|
-
if (
|
|
104
|
-
node.argument.type === AST_NODE_TYPES.Identifier &&
|
|
105
|
-
queryResultVariables.has(node.argument.name)
|
|
106
|
-
) {
|
|
107
|
-
context.report({
|
|
108
|
-
node,
|
|
109
|
-
messageId: 'objectRestDestructure',
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
}
|
|
114
|
-
}),
|
|
115
|
-
})
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
|
|
2
|
-
import type { TSESTree } from '@typescript-eslint/utils'
|
|
3
|
-
|
|
4
|
-
export const NoRestDestructuringUtils = {
|
|
5
|
-
isObjectRestDestructuring(node: TSESTree.Node): boolean {
|
|
6
|
-
if (node.type !== AST_NODE_TYPES.ObjectPattern) {
|
|
7
|
-
return false
|
|
8
|
-
}
|
|
9
|
-
return node.properties.some((p) => p.type === AST_NODE_TYPES.RestElement)
|
|
10
|
-
},
|
|
11
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
|
|
2
|
-
import { getDocsUrl } from '../../utils/get-docs-url'
|
|
3
|
-
import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
|
|
4
|
-
import type { TSESTree } from '@typescript-eslint/utils'
|
|
5
|
-
import type { ExtraRuleDocs } from '../../types'
|
|
6
|
-
|
|
7
|
-
export const name = 'no-unstable-deps'
|
|
8
|
-
|
|
9
|
-
export const reactHookNames = ['useEffect', 'useCallback', 'useMemo']
|
|
10
|
-
export const useQueryHookNames = [
|
|
11
|
-
'useQuery',
|
|
12
|
-
'useSuspenseQuery',
|
|
13
|
-
'useQueries',
|
|
14
|
-
'useSuspenseQueries',
|
|
15
|
-
'useInfiniteQuery',
|
|
16
|
-
'useSuspenseInfiniteQuery',
|
|
17
|
-
]
|
|
18
|
-
const allHookNames = ['useMutation', ...useQueryHookNames]
|
|
19
|
-
const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
|
|
20
|
-
|
|
21
|
-
export const rule = createRule({
|
|
22
|
-
name,
|
|
23
|
-
meta: {
|
|
24
|
-
type: 'problem',
|
|
25
|
-
docs: {
|
|
26
|
-
description:
|
|
27
|
-
'Disallow putting the result of query hooks directly in a React hook dependency array',
|
|
28
|
-
recommended: 'error',
|
|
29
|
-
},
|
|
30
|
-
messages: {
|
|
31
|
-
noUnstableDeps: `The result of {{queryHook}} is not referentially stable, so don't pass it directly into the dependencies array of {{reactHook}}. Instead, destructure the return value of {{queryHook}} and pass the destructured values into the dependency array of {{reactHook}}.`,
|
|
32
|
-
},
|
|
33
|
-
schema: [],
|
|
34
|
-
},
|
|
35
|
-
defaultOptions: [],
|
|
36
|
-
|
|
37
|
-
create: detectTanstackQueryImports((context) => {
|
|
38
|
-
const trackedVariables: Record<string, string> = {}
|
|
39
|
-
const hookAliasMap: Record<string, string> = {}
|
|
40
|
-
|
|
41
|
-
function getReactHook(node: TSESTree.CallExpression): string | undefined {
|
|
42
|
-
if (node.callee.type === 'Identifier') {
|
|
43
|
-
const calleeName = node.callee.name
|
|
44
|
-
// Check if the identifier is a known React hook or an alias
|
|
45
|
-
if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) {
|
|
46
|
-
return calleeName
|
|
47
|
-
}
|
|
48
|
-
} else if (
|
|
49
|
-
node.callee.type === 'MemberExpression' &&
|
|
50
|
-
node.callee.object.type === 'Identifier' &&
|
|
51
|
-
node.callee.object.name === 'React' &&
|
|
52
|
-
node.callee.property.type === 'Identifier' &&
|
|
53
|
-
reactHookNames.includes(node.callee.property.name)
|
|
54
|
-
) {
|
|
55
|
-
// Member expression case: `React.useCallback`
|
|
56
|
-
return node.callee.property.name
|
|
57
|
-
}
|
|
58
|
-
return undefined
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function collectVariableNames(
|
|
62
|
-
pattern: TSESTree.BindingName,
|
|
63
|
-
queryHook: string,
|
|
64
|
-
) {
|
|
65
|
-
if (pattern.type === AST_NODE_TYPES.Identifier) {
|
|
66
|
-
trackedVariables[pattern.name] = queryHook
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
ImportDeclaration(node: TSESTree.ImportDeclaration) {
|
|
72
|
-
if (
|
|
73
|
-
node.specifiers.length > 0 &&
|
|
74
|
-
node.importKind === 'value' &&
|
|
75
|
-
node.source.value === 'React'
|
|
76
|
-
) {
|
|
77
|
-
node.specifiers.forEach((specifier) => {
|
|
78
|
-
if (
|
|
79
|
-
specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
80
|
-
specifier.imported.type === AST_NODE_TYPES.Identifier &&
|
|
81
|
-
reactHookNames.includes(specifier.imported.name)
|
|
82
|
-
) {
|
|
83
|
-
// Track alias or direct import
|
|
84
|
-
hookAliasMap[specifier.local.name] = specifier.imported.name
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
VariableDeclarator(node) {
|
|
91
|
-
if (
|
|
92
|
-
node.init !== null &&
|
|
93
|
-
node.init.type === AST_NODE_TYPES.CallExpression &&
|
|
94
|
-
node.init.callee.type === AST_NODE_TYPES.Identifier &&
|
|
95
|
-
allHookNames.includes(node.init.callee.name)
|
|
96
|
-
) {
|
|
97
|
-
collectVariableNames(node.id, node.init.callee.name)
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
CallExpression: (node) => {
|
|
101
|
-
const reactHook = getReactHook(node)
|
|
102
|
-
if (
|
|
103
|
-
reactHook !== undefined &&
|
|
104
|
-
node.arguments.length > 1 &&
|
|
105
|
-
node.arguments[1]?.type === AST_NODE_TYPES.ArrayExpression
|
|
106
|
-
) {
|
|
107
|
-
const depsArray = node.arguments[1].elements
|
|
108
|
-
depsArray.forEach((dep) => {
|
|
109
|
-
if (
|
|
110
|
-
dep !== null &&
|
|
111
|
-
dep.type === AST_NODE_TYPES.Identifier &&
|
|
112
|
-
trackedVariables[dep.name] !== undefined
|
|
113
|
-
) {
|
|
114
|
-
const queryHook = trackedVariables[dep.name]
|
|
115
|
-
context.report({
|
|
116
|
-
node: dep,
|
|
117
|
-
messageId: 'noUnstableDeps',
|
|
118
|
-
data: {
|
|
119
|
-
queryHook,
|
|
120
|
-
reactHook,
|
|
121
|
-
},
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
}
|
|
128
|
-
}),
|
|
129
|
-
})
|