@tanstack/eslint-plugin-query 5.0.0-rc.0 → 5.0.0-rc.4
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/build/legacy/__tests__/configs.test.cjs +1 -0
- package/build/legacy/__tests__/configs.test.cjs.map +1 -1
- package/build/legacy/__tests__/configs.test.js +1 -0
- package/build/legacy/__tests__/configs.test.js.map +1 -1
- package/build/legacy/rules/stable-query-client/stable-query-client.rule.cjs +93 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.rule.cjs.map +1 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.rule.d.cts +6 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.rule.d.ts +6 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.rule.js +67 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.rule.js.map +1 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.test.cjs +197 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.test.cjs.map +1 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.test.d.cts +2 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.test.d.ts +2 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.test.js +195 -0
- package/build/legacy/rules/stable-query-client/stable-query-client.test.js.map +1 -0
- package/build/legacy/rules.cjs +5 -3
- package/build/legacy/rules.cjs.map +1 -1
- package/build/legacy/rules.d.cts +1 -0
- package/build/legacy/rules.d.ts +1 -0
- package/build/legacy/rules.js +3 -1
- package/build/legacy/rules.js.map +1 -1
- package/build/legacy/utils/ast-utils.cjs +21 -8
- package/build/legacy/utils/ast-utils.cjs.map +1 -1
- package/build/legacy/utils/ast-utils.d.cts +3 -2
- package/build/legacy/utils/ast-utils.d.ts +3 -2
- package/build/legacy/utils/ast-utils.js +21 -8
- package/build/legacy/utils/ast-utils.js.map +1 -1
- package/build/legacy/utils/detect-react-query-imports.cjs +11 -1
- package/build/legacy/utils/detect-react-query-imports.cjs.map +1 -1
- package/build/legacy/utils/detect-react-query-imports.d.cts +1 -0
- package/build/legacy/utils/detect-react-query-imports.d.ts +1 -0
- package/build/legacy/utils/detect-react-query-imports.js +11 -1
- package/build/legacy/utils/detect-react-query-imports.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/configs.test.ts +1 -0
- package/src/rules/stable-query-client/stable-query-client.rule.ts +78 -0
- package/src/rules/stable-query-client/stable-query-client.test.ts +195 -0
- package/src/rules.ts +2 -0
- package/src/utils/ast-utils.ts +37 -11
- package/src/utils/detect-react-query-imports.ts +21 -2
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils'
|
|
2
|
+
import { normalizeIndent } from '../../utils/test-utils'
|
|
3
|
+
import { rule } from './stable-query-client.rule'
|
|
4
|
+
|
|
5
|
+
const ruleTester = new ESLintUtils.RuleTester({
|
|
6
|
+
parser: '@typescript-eslint/parser',
|
|
7
|
+
settings: {},
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
ruleTester.run('stable-query-client', rule, {
|
|
11
|
+
valid: [
|
|
12
|
+
{
|
|
13
|
+
name: 'QueryClient is stable when wrapped in React.useState',
|
|
14
|
+
code: normalizeIndent`
|
|
15
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
16
|
+
|
|
17
|
+
function Component() {
|
|
18
|
+
const [queryClient] = React.useState(() => new QueryClient());
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
`,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'QueryClient is stable when wrapped in useState',
|
|
25
|
+
code: normalizeIndent`
|
|
26
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
27
|
+
|
|
28
|
+
function Component() {
|
|
29
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'QueryClient is stable when wrapped in React.useMemo',
|
|
36
|
+
code: normalizeIndent`
|
|
37
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
38
|
+
|
|
39
|
+
function Component() {
|
|
40
|
+
const [queryClient] = React.useMemo(() => new QueryClient(), []);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'QueryClient is stable when wrapped in useAnything',
|
|
47
|
+
code: normalizeIndent`
|
|
48
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
49
|
+
|
|
50
|
+
function Component() {
|
|
51
|
+
const [queryClient] = useAnything(() => new QueryClient());
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
`,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'QueryClient is imported from a non-tanstack package',
|
|
58
|
+
code: normalizeIndent`
|
|
59
|
+
import { QueryClient } from "other-library";
|
|
60
|
+
|
|
61
|
+
function Component() {
|
|
62
|
+
const queryClient = new QueryClient();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'QueryClient is not imported from @tanstack/react-query',
|
|
69
|
+
code: normalizeIndent`
|
|
70
|
+
import { QueryClient } from "@tanstack/solid-query";
|
|
71
|
+
|
|
72
|
+
function Component() {
|
|
73
|
+
const queryClient = new QueryClient();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
`,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'QueryClient is invoked outside of a function',
|
|
80
|
+
code: normalizeIndent`
|
|
81
|
+
import { QueryClient } from "@tanstack/solid-query";
|
|
82
|
+
|
|
83
|
+
const queryClient = new QueryClient();
|
|
84
|
+
|
|
85
|
+
function Component() {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'QueryClient is invoked in a non-component function',
|
|
92
|
+
code: normalizeIndent`
|
|
93
|
+
import { QueryClient } from "@tanstack/solid-query";
|
|
94
|
+
|
|
95
|
+
function someFn() {
|
|
96
|
+
const queryClient = new QueryClient();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'QueryClient is invoked in an async (react server) component',
|
|
103
|
+
code: normalizeIndent`
|
|
104
|
+
import { QueryClient } from "@tanstack/solid-query";
|
|
105
|
+
|
|
106
|
+
async function AsyncComponent() {
|
|
107
|
+
const queryClient = new QueryClient();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
invalid: [
|
|
114
|
+
{
|
|
115
|
+
name: 'QueryClient is not stable when it is not wrapped in React.useState in component',
|
|
116
|
+
code: normalizeIndent`
|
|
117
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
118
|
+
|
|
119
|
+
function Component() {
|
|
120
|
+
const queryClient = new QueryClient();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
`,
|
|
124
|
+
output: normalizeIndent`
|
|
125
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
126
|
+
|
|
127
|
+
function Component() {
|
|
128
|
+
const [queryClient] = React.useState(() => new QueryClient());
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
`,
|
|
132
|
+
errors: [{ messageId: 'unstable' }],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'QueryClient is not stable when it is not wrapped in React.useState in custom hook',
|
|
136
|
+
code: normalizeIndent`
|
|
137
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
138
|
+
|
|
139
|
+
function useHook() {
|
|
140
|
+
const queryClient = new QueryClient();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
`,
|
|
144
|
+
output: normalizeIndent`
|
|
145
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
146
|
+
|
|
147
|
+
function useHook() {
|
|
148
|
+
const [queryClient] = React.useState(() => new QueryClient());
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
`,
|
|
152
|
+
errors: [{ messageId: 'unstable' }],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'preserve QueryClient options',
|
|
156
|
+
code: normalizeIndent`
|
|
157
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
158
|
+
|
|
159
|
+
function Component() {
|
|
160
|
+
const queryClient = new QueryClient({ defaultOptions: { /* */ } });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
`,
|
|
164
|
+
output: normalizeIndent`
|
|
165
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
166
|
+
|
|
167
|
+
function Component() {
|
|
168
|
+
const [queryClient] = React.useState(() => new QueryClient({ defaultOptions: { /* */ } }));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
`,
|
|
172
|
+
errors: [{ messageId: 'unstable' }],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'preserve QueryClient variable declarator name',
|
|
176
|
+
code: normalizeIndent`
|
|
177
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
178
|
+
|
|
179
|
+
function Component() {
|
|
180
|
+
const customName = new QueryClient();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
`,
|
|
184
|
+
output: normalizeIndent`
|
|
185
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
186
|
+
|
|
187
|
+
function Component() {
|
|
188
|
+
const [customName] = React.useState(() => new QueryClient());
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
`,
|
|
192
|
+
errors: [{ messageId: 'unstable' }],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
})
|
package/src/rules.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as exhaustiveDeps from './rules/exhaustive-deps.rule'
|
|
2
|
+
import * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'
|
|
2
3
|
|
|
3
4
|
export const rules = {
|
|
4
5
|
[exhaustiveDeps.name]: exhaustiveDeps.rule,
|
|
6
|
+
[stableQueryClient.name]: stableQueryClient.rule,
|
|
5
7
|
}
|
package/src/utils/ast-utils.ts
CHANGED
|
@@ -203,27 +203,37 @@ export const ASTUtils = {
|
|
|
203
203
|
]),
|
|
204
204
|
)
|
|
205
205
|
},
|
|
206
|
-
isValidReactComponentOrHookName(
|
|
207
|
-
|
|
206
|
+
isValidReactComponentOrHookName(
|
|
207
|
+
identifier: TSESTree.Identifier | null | undefined,
|
|
208
|
+
) {
|
|
209
|
+
return (
|
|
210
|
+
identifier !== null &&
|
|
211
|
+
identifier !== undefined &&
|
|
212
|
+
/^(use|[A-Z])/.test(identifier.name)
|
|
213
|
+
)
|
|
208
214
|
},
|
|
209
215
|
getFunctionAncestor(
|
|
210
216
|
context: Readonly<RuleContext<string, ReadonlyArray<unknown>>>,
|
|
211
217
|
) {
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
return
|
|
218
|
+
for (const ancestor of context.getAncestors()) {
|
|
219
|
+
if (ancestor.type === AST_NODE_TYPES.FunctionDeclaration) {
|
|
220
|
+
return ancestor
|
|
215
221
|
}
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
ASTUtils.isNodeOfOneOf(
|
|
223
|
+
if (
|
|
224
|
+
ancestor.parent?.type === AST_NODE_TYPES.VariableDeclarator &&
|
|
225
|
+
ancestor.parent.id.type === AST_NODE_TYPES.Identifier &&
|
|
226
|
+
ASTUtils.isNodeOfOneOf(ancestor, [
|
|
221
227
|
AST_NODE_TYPES.FunctionDeclaration,
|
|
222
228
|
AST_NODE_TYPES.FunctionExpression,
|
|
223
229
|
AST_NODE_TYPES.ArrowFunctionExpression,
|
|
224
230
|
])
|
|
225
|
-
)
|
|
226
|
-
|
|
231
|
+
) {
|
|
232
|
+
return ancestor
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return undefined
|
|
227
237
|
},
|
|
228
238
|
getReferencedExpressionByIdentifier(params: {
|
|
229
239
|
node: TSESTree.Node
|
|
@@ -242,6 +252,22 @@ export const ASTUtils = {
|
|
|
242
252
|
|
|
243
253
|
return resolvedNode.init
|
|
244
254
|
},
|
|
255
|
+
getClosestVariableDeclarator(node: TSESTree.Node) {
|
|
256
|
+
let currentNode: TSESTree.Node | undefined = node
|
|
257
|
+
|
|
258
|
+
while (
|
|
259
|
+
currentNode !== undefined &&
|
|
260
|
+
currentNode.type !== AST_NODE_TYPES.Program
|
|
261
|
+
) {
|
|
262
|
+
if (currentNode.type === AST_NODE_TYPES.VariableDeclarator) {
|
|
263
|
+
return currentNode
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
currentNode = currentNode.parent
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return undefined
|
|
270
|
+
},
|
|
245
271
|
getNestedReturnStatements(
|
|
246
272
|
node: TSESTree.Node,
|
|
247
273
|
): Array<TSESTree.ReturnStatement> {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils'
|
|
2
|
+
import type { ESLintUtils, TSESLint } from '@typescript-eslint/utils'
|
|
2
3
|
|
|
3
4
|
type Create = Parameters<
|
|
4
5
|
ReturnType<typeof ESLintUtils.RuleCreator>
|
|
@@ -7,6 +8,10 @@ type Create = Parameters<
|
|
|
7
8
|
type Context = Parameters<Create>[0]
|
|
8
9
|
type Options = Parameters<Create>[1]
|
|
9
10
|
type Helpers = {
|
|
11
|
+
isSpecificTanstackQueryImport: (
|
|
12
|
+
node: TSESTree.Identifier,
|
|
13
|
+
source: string,
|
|
14
|
+
) => boolean
|
|
10
15
|
isTanstackQueryImport: (node: TSESTree.Identifier) => boolean
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -21,9 +26,23 @@ export function detectTanstackQueryImports(create: EnhancedCreate): Create {
|
|
|
21
26
|
const tanstackQueryImportSpecifiers: Array<TSESTree.ImportClause> = []
|
|
22
27
|
|
|
23
28
|
const helpers: Helpers = {
|
|
29
|
+
isSpecificTanstackQueryImport(node, source) {
|
|
30
|
+
return !!tanstackQueryImportSpecifiers.find((specifier) => {
|
|
31
|
+
if (
|
|
32
|
+
specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier &&
|
|
33
|
+
specifier.parent?.type ===
|
|
34
|
+
TSESTree.AST_NODE_TYPES.ImportDeclaration &&
|
|
35
|
+
specifier.parent.source.value === source
|
|
36
|
+
) {
|
|
37
|
+
return node.name === specifier.local.name
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false
|
|
41
|
+
})
|
|
42
|
+
},
|
|
24
43
|
isTanstackQueryImport(node) {
|
|
25
44
|
return !!tanstackQueryImportSpecifiers.find((specifier) => {
|
|
26
|
-
if (specifier.type ===
|
|
45
|
+
if (specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier) {
|
|
27
46
|
return node.name === specifier.local.name
|
|
28
47
|
}
|
|
29
48
|
|