@tanstack/eslint-plugin-query 5.0.0-rc.1 → 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.
Files changed (41) hide show
  1. package/build/legacy/__tests__/configs.test.cjs +1 -0
  2. package/build/legacy/__tests__/configs.test.cjs.map +1 -1
  3. package/build/legacy/__tests__/configs.test.js +1 -0
  4. package/build/legacy/__tests__/configs.test.js.map +1 -1
  5. package/build/legacy/rules/stable-query-client/stable-query-client.rule.cjs +93 -0
  6. package/build/legacy/rules/stable-query-client/stable-query-client.rule.cjs.map +1 -0
  7. package/build/legacy/rules/stable-query-client/stable-query-client.rule.d.cts +6 -0
  8. package/build/legacy/rules/stable-query-client/stable-query-client.rule.d.ts +6 -0
  9. package/build/legacy/rules/stable-query-client/stable-query-client.rule.js +67 -0
  10. package/build/legacy/rules/stable-query-client/stable-query-client.rule.js.map +1 -0
  11. package/build/legacy/rules/stable-query-client/stable-query-client.test.cjs +197 -0
  12. package/build/legacy/rules/stable-query-client/stable-query-client.test.cjs.map +1 -0
  13. package/build/legacy/rules/stable-query-client/stable-query-client.test.d.cts +2 -0
  14. package/build/legacy/rules/stable-query-client/stable-query-client.test.d.ts +2 -0
  15. package/build/legacy/rules/stable-query-client/stable-query-client.test.js +195 -0
  16. package/build/legacy/rules/stable-query-client/stable-query-client.test.js.map +1 -0
  17. package/build/legacy/rules.cjs +5 -3
  18. package/build/legacy/rules.cjs.map +1 -1
  19. package/build/legacy/rules.d.cts +1 -0
  20. package/build/legacy/rules.d.ts +1 -0
  21. package/build/legacy/rules.js +3 -1
  22. package/build/legacy/rules.js.map +1 -1
  23. package/build/legacy/utils/ast-utils.cjs +21 -8
  24. package/build/legacy/utils/ast-utils.cjs.map +1 -1
  25. package/build/legacy/utils/ast-utils.d.cts +3 -2
  26. package/build/legacy/utils/ast-utils.d.ts +3 -2
  27. package/build/legacy/utils/ast-utils.js +21 -8
  28. package/build/legacy/utils/ast-utils.js.map +1 -1
  29. package/build/legacy/utils/detect-react-query-imports.cjs +11 -1
  30. package/build/legacy/utils/detect-react-query-imports.cjs.map +1 -1
  31. package/build/legacy/utils/detect-react-query-imports.d.cts +1 -0
  32. package/build/legacy/utils/detect-react-query-imports.d.ts +1 -0
  33. package/build/legacy/utils/detect-react-query-imports.js +11 -1
  34. package/build/legacy/utils/detect-react-query-imports.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/__tests__/configs.test.ts +1 -0
  37. package/src/rules/stable-query-client/stable-query-client.rule.ts +78 -0
  38. package/src/rules/stable-query-client/stable-query-client.test.ts +195 -0
  39. package/src/rules.ts +2 -0
  40. package/src/utils/ast-utils.ts +37 -11
  41. 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
  }
@@ -203,27 +203,37 @@ export const ASTUtils = {
203
203
  ]),
204
204
  )
205
205
  },
206
- isValidReactComponentOrHookName(identifier: TSESTree.Identifier | null) {
207
- return identifier !== null && /^(use|[A-Z])/.test(identifier.name)
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
- return context.getAncestors().find((x) => {
213
- if (x.type === AST_NODE_TYPES.FunctionDeclaration) {
214
- return true
218
+ for (const ancestor of context.getAncestors()) {
219
+ if (ancestor.type === AST_NODE_TYPES.FunctionDeclaration) {
220
+ return ancestor
215
221
  }
216
222
 
217
- return (
218
- x.parent?.type === AST_NODE_TYPES.VariableDeclarator &&
219
- x.parent.id.type === AST_NODE_TYPES.Identifier &&
220
- ASTUtils.isNodeOfOneOf(x, [
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 type { ESLintUtils, TSESLint, TSESTree } from '@typescript-eslint/utils'
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 === 'ImportSpecifier') {
45
+ if (specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier) {
27
46
  return node.name === specifier.local.name
28
47
  }
29
48