@tanstack/eslint-plugin-query 5.71.5 → 5.72.1

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 (119) hide show
  1. package/dist/index.cjs +1002 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +24 -0
  4. package/dist/index.d.ts +24 -0
  5. package/dist/index.js +968 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/rules.cjs +967 -0
  8. package/dist/rules.cjs.map +1 -0
  9. package/dist/rules.js +929 -0
  10. package/dist/rules.js.map +1 -0
  11. package/package.json +12 -12
  12. package/dist/cjs/index.cjs +0 -38
  13. package/dist/cjs/index.cjs.map +0 -1
  14. package/dist/cjs/index.d.cts +0 -13
  15. package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.cjs +0 -134
  16. package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.cjs.map +0 -1
  17. package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.d.cts +0 -4
  18. package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.cjs +0 -26
  19. package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.cjs.map +0 -1
  20. package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.d.cts +0 -10
  21. package/dist/cjs/rules/infinite-query-property-order/constants.cjs +0 -13
  22. package/dist/cjs/rules/infinite-query-property-order/constants.cjs.map +0 -1
  23. package/dist/cjs/rules/infinite-query-property-order/constants.d.cts +0 -4
  24. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs +0 -88
  25. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs.map +0 -1
  26. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.d.cts +0 -4
  27. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs +0 -47
  28. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs.map +0 -1
  29. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.d.cts +0 -1
  30. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.cjs +0 -92
  31. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.cjs.map +0 -1
  32. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.d.cts +0 -4
  33. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.cjs +0 -13
  34. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.cjs.map +0 -1
  35. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.d.cts +0 -4
  36. package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs +0 -93
  37. package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs.map +0 -1
  38. package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.d.cts +0 -6
  39. package/dist/cjs/rules/stable-query-client/stable-query-client.rule.cjs +0 -71
  40. package/dist/cjs/rules/stable-query-client/stable-query-client.rule.cjs.map +0 -1
  41. package/dist/cjs/rules/stable-query-client/stable-query-client.rule.d.cts +0 -4
  42. package/dist/cjs/rules.cjs +0 -16
  43. package/dist/cjs/rules.cjs.map +0 -1
  44. package/dist/cjs/rules.d.cts +0 -3
  45. package/dist/cjs/types.d.cts +0 -3
  46. package/dist/cjs/utils/ast-utils.cjs +0 -267
  47. package/dist/cjs/utils/ast-utils.cjs.map +0 -1
  48. package/dist/cjs/utils/ast-utils.d.cts +0 -38
  49. package/dist/cjs/utils/detect-react-query-imports.cjs +0 -55
  50. package/dist/cjs/utils/detect-react-query-imports.cjs.map +0 -1
  51. package/dist/cjs/utils/detect-react-query-imports.d.cts +0 -11
  52. package/dist/cjs/utils/get-docs-url.cjs +0 -5
  53. package/dist/cjs/utils/get-docs-url.cjs.map +0 -1
  54. package/dist/cjs/utils/get-docs-url.d.cts +0 -1
  55. package/dist/cjs/utils/unique-by.cjs +0 -7
  56. package/dist/cjs/utils/unique-by.cjs.map +0 -1
  57. package/dist/cjs/utils/unique-by.d.cts +0 -1
  58. package/dist/esm/index.d.ts +0 -13
  59. package/dist/esm/index.js +0 -39
  60. package/dist/esm/index.js.map +0 -1
  61. package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.d.ts +0 -4
  62. package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js +0 -134
  63. package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js.map +0 -1
  64. package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.d.ts +0 -10
  65. package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.js +0 -26
  66. package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.js.map +0 -1
  67. package/dist/esm/rules/infinite-query-property-order/constants.d.ts +0 -4
  68. package/dist/esm/rules/infinite-query-property-order/constants.js +0 -13
  69. package/dist/esm/rules/infinite-query-property-order/constants.js.map +0 -1
  70. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.d.ts +0 -4
  71. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js +0 -88
  72. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js.map +0 -1
  73. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.d.ts +0 -1
  74. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js +0 -47
  75. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js.map +0 -1
  76. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.d.ts +0 -4
  77. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.js +0 -92
  78. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.js.map +0 -1
  79. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.d.ts +0 -4
  80. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.js +0 -13
  81. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.js.map +0 -1
  82. package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.d.ts +0 -6
  83. package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js +0 -93
  84. package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js.map +0 -1
  85. package/dist/esm/rules/stable-query-client/stable-query-client.rule.d.ts +0 -4
  86. package/dist/esm/rules/stable-query-client/stable-query-client.rule.js +0 -71
  87. package/dist/esm/rules/stable-query-client/stable-query-client.rule.js.map +0 -1
  88. package/dist/esm/rules.d.ts +0 -3
  89. package/dist/esm/rules.js +0 -16
  90. package/dist/esm/rules.js.map +0 -1
  91. package/dist/esm/types.d.ts +0 -3
  92. package/dist/esm/utils/ast-utils.d.ts +0 -38
  93. package/dist/esm/utils/ast-utils.js +0 -267
  94. package/dist/esm/utils/ast-utils.js.map +0 -1
  95. package/dist/esm/utils/detect-react-query-imports.d.ts +0 -11
  96. package/dist/esm/utils/detect-react-query-imports.js +0 -55
  97. package/dist/esm/utils/detect-react-query-imports.js.map +0 -1
  98. package/dist/esm/utils/get-docs-url.d.ts +0 -1
  99. package/dist/esm/utils/get-docs-url.js +0 -5
  100. package/dist/esm/utils/get-docs-url.js.map +0 -1
  101. package/dist/esm/utils/unique-by.d.ts +0 -1
  102. package/dist/esm/utils/unique-by.js +0 -7
  103. package/dist/esm/utils/unique-by.js.map +0 -1
  104. package/src/index.ts +0 -52
  105. package/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +0 -184
  106. package/src/rules/exhaustive-deps/exhaustive-deps.utils.ts +0 -41
  107. package/src/rules/infinite-query-property-order/constants.ts +0 -17
  108. package/src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts +0 -107
  109. package/src/rules/infinite-query-property-order/infinite-query-property-order.utils.ts +0 -67
  110. package/src/rules/no-rest-destructuring/no-rest-destructuring.rule.ts +0 -115
  111. package/src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts +0 -11
  112. package/src/rules/no-unstable-deps/no-unstable-deps.rule.ts +0 -129
  113. package/src/rules/stable-query-client/stable-query-client.rule.ts +0 -88
  114. package/src/rules.ts +0 -23
  115. package/src/types.ts +0 -3
  116. package/src/utils/ast-utils.ts +0 -391
  117. package/src/utils/detect-react-query-imports.ts +0 -97
  118. package/src/utils/get-docs-url.ts +0 -2
  119. 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
- })