@tanstack/eslint-plugin-query 5.91.4 → 5.94.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 (44) hide show
  1. package/package.json +5 -6
  2. package/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +14 -14
  3. package/src/rules/exhaustive-deps/exhaustive-deps.utils.ts +159 -0
  4. package/src/utils/ast-utils.ts +13 -1
  5. package/build/legacy/chunk-MUQBIZ2R.js +0 -1027
  6. package/build/legacy/chunk-MUQBIZ2R.js.map +0 -1
  7. package/build/legacy/index.cjs +0 -1096
  8. package/build/legacy/index.cjs.map +0 -1
  9. package/build/legacy/index.d.cts +0 -52
  10. package/build/legacy/index.d.ts +0 -52
  11. package/build/legacy/index.js +0 -50
  12. package/build/legacy/index.js.map +0 -1
  13. package/build/legacy/rules.cjs +0 -1053
  14. package/build/legacy/rules.cjs.map +0 -1
  15. package/build/legacy/rules.d.cts +0 -6
  16. package/build/legacy/rules.d.ts +0 -6
  17. package/build/legacy/rules.js +0 -7
  18. package/build/legacy/rules.js.map +0 -1
  19. package/build/legacy/types.cjs +0 -19
  20. package/build/legacy/types.cjs.map +0 -1
  21. package/build/legacy/types.d.cts +0 -5
  22. package/build/legacy/types.d.ts +0 -5
  23. package/build/legacy/types.js +0 -1
  24. package/build/legacy/types.js.map +0 -1
  25. package/build/modern/chunk-2FPOGBVA.js +0 -1019
  26. package/build/modern/chunk-2FPOGBVA.js.map +0 -1
  27. package/build/modern/index.cjs +0 -1089
  28. package/build/modern/index.cjs.map +0 -1
  29. package/build/modern/index.d.cts +0 -52
  30. package/build/modern/index.d.ts +0 -52
  31. package/build/modern/index.js +0 -50
  32. package/build/modern/index.js.map +0 -1
  33. package/build/modern/rules.cjs +0 -1046
  34. package/build/modern/rules.cjs.map +0 -1
  35. package/build/modern/rules.d.cts +0 -6
  36. package/build/modern/rules.d.ts +0 -6
  37. package/build/modern/rules.js +0 -7
  38. package/build/modern/rules.js.map +0 -1
  39. package/build/modern/types.cjs +0 -20
  40. package/build/modern/types.cjs.map +0 -1
  41. package/build/modern/types.d.cts +0 -5
  42. package/build/modern/types.d.ts +0 -5
  43. package/build/modern/types.js +0 -1
  44. package/build/modern/types.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/eslint-plugin-query",
3
- "version": "5.91.4",
3
+ "version": "5.94.4",
4
4
  "description": "ESLint plugin for TanStack Query",
5
5
  "author": "Eliya Cohen",
6
6
  "license": "MIT",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "peerDependencies": {
53
53
  "eslint": "^8.57.0 || ^9.0.0",
54
- "typescript": "^5.0.0"
54
+ "typescript": "^5.4.0"
55
55
  },
56
56
  "peerDependenciesMeta": {
57
57
  "typescript": {
@@ -63,15 +63,14 @@
63
63
  "compile": "tsc --build",
64
64
  "test:eslint": "eslint --concurrency=auto ./src",
65
65
  "test:types": "npm-run-all --serial test:types:*",
66
- "test:types:ts50": "node ../../node_modules/typescript50/lib/tsc.js --build",
67
- "test:types:ts51": "node ../../node_modules/typescript51/lib/tsc.js --build",
68
- "test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js --build",
69
- "test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build",
70
66
  "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build",
71
67
  "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build",
72
68
  "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build",
73
69
  "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build",
70
+ "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js --build",
71
+ "test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js --build",
74
72
  "test:types:tscurrent": "tsc --build",
73
+ "test:types:ts60": "node ../../node_modules/typescript60/lib/tsc.js --build",
75
74
  "test:lib": "vitest",
76
75
  "test:lib:dev": "pnpm run test:lib --watch",
77
76
  "test:build": "publint --strict && attw --pack",
@@ -86,25 +86,27 @@ export const rule = createRule({
86
86
  }),
87
87
  )
88
88
 
89
- const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyNode).map(
90
- (identifier) =>
91
- ASTUtils.mapKeyNodeToBaseText(identifier, context.sourceCode),
92
- )
89
+ const queryKeyDeps = ExhaustiveDepsUtils.collectQueryKeyDeps({
90
+ sourceCode: context.sourceCode,
91
+ scopeManager,
92
+ queryKeyNode,
93
+ })
93
94
 
94
95
  const missingRefs = relevantRefs
95
96
  .map((ref) => ({
96
97
  ref: ref,
97
- text: ASTUtils.mapKeyNodeToBaseText(
98
- ref.identifier,
99
- context.sourceCode,
100
- ),
98
+ text: ASTUtils.isAncestorIsCallee(ref.identifier)
99
+ ? ref.identifier.name
100
+ : ASTUtils.mapKeyNodeToBaseText(
101
+ ref.identifier,
102
+ context.sourceCode,
103
+ ),
101
104
  }))
102
105
  .filter(({ ref, text }) => {
103
106
  return (
104
107
  !ref.isTypeReference &&
105
- !ASTUtils.isAncestorIsCallee(ref.identifier) &&
106
- !existingKeys.some((existingKey) => existingKey === text) &&
107
- !existingKeys.includes(text.split(/[?.]/)[0] ?? '')
108
+ !queryKeyDeps.has(text) &&
109
+ !queryKeyDeps.has(text.split(/[?.]/)[0] ?? '')
108
110
  )
109
111
  })
110
112
  .map(({ ref, text }) => ({
@@ -116,9 +118,7 @@ export const rule = createRule({
116
118
 
117
119
  if (uniqueMissingRefs.length > 0) {
118
120
  const missingAsText = uniqueMissingRefs
119
- .map((ref) =>
120
- ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),
121
- )
121
+ .map((ref) => ref.text)
122
122
  .join(', ')
123
123
 
124
124
  const queryKeyValue = context.sourceCode.getText(queryKeyNode)
@@ -12,6 +12,20 @@ export const ExhaustiveDepsUtils = {
12
12
  }) {
13
13
  const { sourceCode, reference, scopeManager, node, filename } = params
14
14
  const component = ASTUtils.getFunctionAncestor(sourceCode, node)
15
+ const queryFnScope = scopeManager.acquire(node)
16
+
17
+ if (queryFnScope === null) {
18
+ return false
19
+ }
20
+
21
+ let currentScope = reference.resolved?.scope ?? null
22
+ while (currentScope !== null) {
23
+ if (currentScope === queryFnScope) {
24
+ return false
25
+ }
26
+
27
+ currentScope = currentScope.upper
28
+ }
15
29
 
16
30
  if (component !== undefined) {
17
31
  if (
@@ -51,4 +65,149 @@ export const ExhaustiveDepsUtils = {
51
65
  node.operator === 'instanceof'
52
66
  )
53
67
  },
68
+
69
+ collectQueryKeyDeps(params: {
70
+ sourceCode: Readonly<TSESLint.SourceCode>
71
+ scopeManager: TSESLint.Scope.ScopeManager
72
+ queryKeyNode: TSESTree.Node
73
+ }): Set<string> {
74
+ const { sourceCode, scopeManager, queryKeyNode } = params
75
+ const deps = new Set<string>()
76
+ const visitorKeys = sourceCode.visitorKeys
77
+
78
+ function add(identifier: TSESTree.Identifier) {
79
+ deps.add(ASTUtils.mapKeyNodeToBaseText(identifier, sourceCode))
80
+ }
81
+
82
+ function visitChildren(node: TSESTree.Node): void {
83
+ const keys = (visitorKeys[node.type] ?? []) as ReadonlyArray<
84
+ keyof TSESTree.Node
85
+ >
86
+
87
+ for (const key of keys) {
88
+ const value = node[key]
89
+
90
+ if (Array.isArray(value)) {
91
+ for (const item of value) {
92
+ if (ExhaustiveDepsUtils.isNode(item)) {
93
+ visit(item)
94
+ }
95
+ }
96
+ continue
97
+ }
98
+
99
+ if (ExhaustiveDepsUtils.isNode(value)) {
100
+ visit(value)
101
+ }
102
+ }
103
+ }
104
+
105
+ function visit(node: TSESTree.Node | null | undefined): void {
106
+ if (!node) return
107
+
108
+ switch (node.type) {
109
+ case AST_NODE_TYPES.Identifier:
110
+ add(node)
111
+ return
112
+ case AST_NODE_TYPES.ArrowFunctionExpression:
113
+ case AST_NODE_TYPES.FunctionExpression:
114
+ for (const reference of ExhaustiveDepsUtils.collectExternalRefsInFunction(
115
+ {
116
+ functionNode: node,
117
+ scopeManager: scopeManager,
118
+ },
119
+ )) {
120
+ if (reference.identifier.type === AST_NODE_TYPES.Identifier) {
121
+ add(reference.identifier)
122
+ }
123
+ }
124
+ return
125
+ case AST_NODE_TYPES.Property:
126
+ visit(node.value)
127
+ return
128
+ case AST_NODE_TYPES.MemberExpression:
129
+ if (
130
+ node.parent.type === AST_NODE_TYPES.CallExpression &&
131
+ node.parent.callee === node &&
132
+ node.object.type === AST_NODE_TYPES.Identifier
133
+ ) {
134
+ deps.add(node.object.name)
135
+ } else {
136
+ visit(node.object)
137
+ }
138
+ return
139
+ case AST_NODE_TYPES.CallExpression:
140
+ node.arguments.forEach((argument) => visit(argument))
141
+ if (
142
+ node.callee.type === AST_NODE_TYPES.MemberExpression ||
143
+ node.callee.type === AST_NODE_TYPES.ChainExpression ||
144
+ node.callee.type === AST_NODE_TYPES.TSNonNullExpression
145
+ ) {
146
+ visit(node.callee)
147
+ }
148
+ return
149
+ }
150
+
151
+ visitChildren(node)
152
+ }
153
+
154
+ visit(queryKeyNode)
155
+
156
+ return deps
157
+ },
158
+
159
+ isNode(value: unknown): value is TSESTree.Node {
160
+ return (
161
+ typeof value === 'object' &&
162
+ value !== null &&
163
+ 'type' in value &&
164
+ typeof value.type === 'string'
165
+ )
166
+ },
167
+
168
+ collectExternalRefsInFunction(params: {
169
+ functionNode: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression
170
+ scopeManager: TSESLint.Scope.ScopeManager
171
+ }): Array<TSESLint.Scope.Reference> {
172
+ const { functionNode, scopeManager } = params
173
+ const functionScope = scopeManager.acquire(functionNode)
174
+
175
+ if (functionScope === null) {
176
+ return []
177
+ }
178
+
179
+ const externalRefs: Array<TSESLint.Scope.Reference> = []
180
+
181
+ function collect(scope: TSESLint.Scope.Scope) {
182
+ for (const reference of scope.references) {
183
+ if (!reference.isRead() || reference.resolved === null) {
184
+ continue
185
+ }
186
+
187
+ let currentScope: TSESLint.Scope.Scope | null = reference.resolved.scope
188
+ let declaredInsideFunction = false
189
+
190
+ while (currentScope !== null) {
191
+ if (currentScope === functionScope) {
192
+ declaredInsideFunction = true
193
+ break
194
+ }
195
+
196
+ currentScope = currentScope.upper
197
+ }
198
+
199
+ if (!declaredInsideFunction) {
200
+ externalRefs.push(reference)
201
+ }
202
+ }
203
+
204
+ for (const childScope of scope.childScopes) {
205
+ collect(childScope)
206
+ }
207
+ }
208
+
209
+ collect(functionScope)
210
+
211
+ return externalRefs
212
+ },
54
213
  }
@@ -192,7 +192,19 @@ export const ASTUtils = {
192
192
  return []
193
193
  }
194
194
 
195
- const references = scope.references
195
+ const collectReferences = (
196
+ currentScope: TSESLint.Scope.Scope,
197
+ ): Array<TSESLint.Scope.Reference> => {
198
+ const references = [...currentScope.references]
199
+
200
+ for (const childScope of currentScope.childScopes) {
201
+ references.push(...collectReferences(childScope))
202
+ }
203
+
204
+ return references
205
+ }
206
+
207
+ const references = collectReferences(scope)
196
208
  .filter((x) => x.isRead() && !scope.set.has(x.identifier.name))
197
209
  .map((x) => {
198
210
  const referenceNode = ASTUtils.traverseUpOnly(x.identifier, [