@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.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.
Files changed (199) hide show
  1. package/README.md +169 -139
  2. package/lib/index.js +200 -9
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/avoid-utils-directory.js +0 -4
  5. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +43 -4
  7. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  8. package/lib/rules/dynamic-https-errors.js +134 -44
  9. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  10. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
  11. package/lib/rules/enforce-callback-memo.js +1 -1
  12. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  13. package/lib/rules/enforce-console-error.js +35 -7
  14. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  15. package/lib/rules/enforce-date-ttime.js +156 -0
  16. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  17. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  18. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  20. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  21. package/lib/rules/enforce-early-destructuring.js +980 -0
  22. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  23. package/lib/rules/enforce-empty-object-check.js +502 -0
  24. package/lib/rules/enforce-exported-function-types.js +1 -1
  25. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  26. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  27. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  28. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  29. package/lib/rules/enforce-firestore-facade.js +4 -2
  30. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  31. package/lib/rules/enforce-global-constants.js +4 -4
  32. package/lib/rules/enforce-id-capitalization.js +1 -1
  33. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  34. package/lib/rules/enforce-memoize-async.js +71 -21
  35. package/lib/rules/enforce-memoize-getters.js +39 -15
  36. package/lib/rules/enforce-microdiff.js +1 -1
  37. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  38. package/lib/rules/enforce-positive-naming.js +4 -0
  39. package/lib/rules/enforce-props-argument-name.js +44 -18
  40. package/lib/rules/enforce-safe-stringify.js +1 -1
  41. package/lib/rules/enforce-serializable-params.js +3 -3
  42. package/lib/rules/enforce-singular-type-names.js +1 -1
  43. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  44. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  45. package/lib/rules/enforce-storage-context.d.ts +9 -0
  46. package/lib/rules/enforce-storage-context.js +646 -0
  47. package/lib/rules/enforce-timestamp-now.js +23 -7
  48. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  49. package/lib/rules/enforce-transform-memoization.js +416 -0
  50. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  51. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  52. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  53. package/lib/rules/enforce-verb-noun-naming.js +3818 -4644
  54. package/lib/rules/ensure-pointer-events-none.js +28 -2
  55. package/lib/rules/export-if-in-doubt.js +27 -4
  56. package/lib/rules/extract-global-constants.js +40 -13
  57. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  58. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  59. package/lib/rules/flatten-push-calls.d.ts +2 -0
  60. package/lib/rules/flatten-push-calls.js +428 -0
  61. package/lib/rules/global-const-style.js +66 -23
  62. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  63. package/lib/rules/jsdoc-above-field.js +208 -0
  64. package/lib/rules/key-only-outermost-element.js +21 -3
  65. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  66. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  67. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  68. package/lib/rules/memo-compare-deeply-complex-props.js +801 -0
  69. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  70. package/lib/rules/memo-nested-react-components.js +415 -0
  71. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  72. package/lib/rules/no-always-true-false-conditions.js +68 -8
  73. package/lib/rules/no-array-length-in-deps.js +9 -3
  74. package/lib/rules/no-async-foreach.js +215 -12
  75. package/lib/rules/no-circular-references.d.ts +2 -1
  76. package/lib/rules/no-circular-references.js +31 -32
  77. package/lib/rules/no-class-instance-destructuring.js +55 -12
  78. package/lib/rules/no-complex-cloud-params.js +7 -3
  79. package/lib/rules/no-compositing-layer-props.js +2 -2
  80. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  81. package/lib/rules/no-console-error.d.ts +9 -0
  82. package/lib/rules/no-console-error.js +527 -0
  83. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  84. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  85. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  86. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  87. package/lib/rules/no-entire-object-hook-deps.js +52 -7
  88. package/lib/rules/no-excessive-parent-chain.js +4 -1
  89. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  90. package/lib/rules/no-explicit-return-type.js +215 -26
  91. package/lib/rules/no-filter-without-return.js +5 -1
  92. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  93. package/lib/rules/no-firestore-jest-mock.js +126 -8
  94. package/lib/rules/no-firestore-object-arrays.js +67 -12
  95. package/lib/rules/no-handler-suffix.d.ts +12 -0
  96. package/lib/rules/no-handler-suffix.js +305 -0
  97. package/lib/rules/no-hungarian.js +1 -1
  98. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  99. package/lib/rules/no-inline-component-prop.js +465 -0
  100. package/lib/rules/no-jsx-in-hooks.js +6 -1
  101. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  102. package/lib/rules/no-margin-properties.js +6 -6
  103. package/lib/rules/no-memoize-on-static.js +9 -1
  104. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  105. package/lib/rules/no-misused-switch-case.js +22 -1
  106. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  107. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  108. package/lib/rules/no-mock-firebase-admin.js +5 -2
  109. package/lib/rules/no-object-values-on-strings.js +10 -2
  110. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  111. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  112. package/lib/rules/no-passthrough-getters.js +120 -2
  113. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  114. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  115. package/lib/rules/no-redundant-param-types.js +16 -6
  116. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  117. package/lib/rules/no-redundant-this-params.js +508 -0
  118. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  119. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  120. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  121. package/lib/rules/no-restricted-properties-fix.js +11 -10
  122. package/lib/rules/no-separate-loading-state.js +7 -16
  123. package/lib/rules/no-stale-state-across-await.js +1 -1
  124. package/lib/rules/no-type-assertion-returns.js +43 -49
  125. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  126. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  127. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  128. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  129. package/lib/rules/no-unnecessary-destructuring-rename.js +344 -0
  130. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  131. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  132. package/lib/rules/no-unpinned-dependencies.js +36 -5
  133. package/lib/rules/no-unused-props.d.ts +2 -2
  134. package/lib/rules/no-unused-props.js +295 -91
  135. package/lib/rules/no-unused-usestate.js +6 -2
  136. package/lib/rules/no-useless-fragment.js +28 -2
  137. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  138. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  139. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  140. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  141. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  142. package/lib/rules/omit-index-html.d.ts +2 -1
  143. package/lib/rules/omit-index-html.js +62 -7
  144. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  145. package/lib/rules/parallelize-async-operations.js +143 -59
  146. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  147. package/lib/rules/prefer-batch-operations.js +32 -5
  148. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  149. package/lib/rules/prefer-clone-deep.js +3 -3
  150. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  151. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  152. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  153. package/lib/rules/prefer-docsetter-setall.js +243 -0
  154. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  155. package/lib/rules/prefer-fragment-component.js +1 -1
  156. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  157. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  158. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  159. package/lib/rules/prefer-global-router-state-key.js +88 -13
  160. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  161. package/lib/rules/prefer-memoized-props.js +445 -0
  162. package/lib/rules/prefer-next-dynamic.js +60 -69
  163. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +118 -8
  164. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  165. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  166. package/lib/rules/prefer-params-over-parent-id.js +221 -172
  167. package/lib/rules/prefer-settings-object.js +27 -10
  168. package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
  169. package/lib/rules/prefer-type-over-interface.js +7 -2
  170. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  171. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  172. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  173. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  174. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  175. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  176. package/lib/rules/prevent-children-clobber.js +540 -0
  177. package/lib/rules/react-memoize-literals.d.ts +4 -0
  178. package/lib/rules/react-memoize-literals.js +614 -0
  179. package/lib/rules/react-usememo-should-be-component.js +2 -2
  180. package/lib/rules/require-hooks-default-params.js +17 -16
  181. package/lib/rules/require-https-error-cause.d.ts +4 -0
  182. package/lib/rules/require-https-error-cause.js +155 -0
  183. package/lib/rules/require-https-error.js +43 -21
  184. package/lib/rules/require-image-optimized.js +1 -1
  185. package/lib/rules/require-memo.js +18 -10
  186. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  187. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  188. package/lib/rules/require-usememo-object-literals.js +2 -3
  189. package/lib/rules/test-file-location-enforcement.js +1 -1
  190. package/lib/rules/use-latest-callback.js +6 -5
  191. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  192. package/lib/rules/vertically-group-related-functions.js +480 -0
  193. package/lib/utils/ASTHelpers.d.ts +47 -2
  194. package/lib/utils/ASTHelpers.js +370 -112
  195. package/lib/utils/getMethodName.d.ts +27 -0
  196. package/lib/utils/getMethodName.js +35 -0
  197. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  198. package/lib/utils/tsTypeClassifier.js +149 -0
  199. package/package.json +3 -1
@@ -0,0 +1,576 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noEmptyDependencyUseCallbacks = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const minimatch_1 = require("minimatch");
6
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
7
+ const createRule_1 = require("../utils/createRule");
8
+ const DEFAULT_TEST_PATTERNS = ['**/__tests__/**', '**/*.test.*', '**/*.spec.*'];
9
+ const PREFER_UTILITY_FUNCTION_MESSAGE = [
10
+ 'What\'s wrong: "{{name}}" uses useCallback([]) but never reads component/hook state',
11
+ 'Why it matters: empty-deps callbacks are already stable, so hook bookkeeping adds overhead without benefit',
12
+ 'How to fix: move "{{name}}" to a module-level utility (or add an eslint-disable with a short justification if you intentionally keep it for memoized children).',
13
+ ].join(' -> ');
14
+ const PREFER_UTILITY_LATEST_MESSAGE = [
15
+ 'What\'s wrong: useLatestCallback wraps "{{name}}" even though it never reads component/hook state',
16
+ 'Why it matters: the hook wrapper adds indirection without preventing stale closures',
17
+ 'How to fix: extract "{{name}}" to a module-level utility (or disable with a brief note if you rely on HMR or a stale-closure pattern).',
18
+ ].join(' -> ');
19
+ function isHookCallee(callee, hookName) {
20
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
21
+ return callee.name === hookName;
22
+ }
23
+ return (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
24
+ !callee.computed &&
25
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
26
+ callee.property.name === hookName);
27
+ }
28
+ function isUseCallbackCallee(callee) {
29
+ return isHookCallee(callee, 'useCallback');
30
+ }
31
+ function isUseLatestCallbackCallee(callee) {
32
+ return isHookCallee(callee, 'useLatestCallback');
33
+ }
34
+ function isFunctionExpression(node) {
35
+ return (!!node &&
36
+ (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
37
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression));
38
+ }
39
+ function isPropertyKey(parent, identifier) {
40
+ if (!parent)
41
+ return false;
42
+ if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
43
+ parent.property === identifier &&
44
+ !parent.computed) {
45
+ return true;
46
+ }
47
+ if (parent.type === utils_1.AST_NODE_TYPES.Property &&
48
+ parent.key === identifier &&
49
+ !parent.computed &&
50
+ !parent.shorthand) {
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ function collectNearestBlockTypeBindings(node) {
56
+ const localTypes = new Set();
57
+ let current = node.parent ?? undefined;
58
+ const addTypeParameters = (maybeNode) => {
59
+ if (!maybeNode)
60
+ return;
61
+ const typeParameters = maybeNode.typeParameters;
62
+ if (typeParameters &&
63
+ typeParameters.type === utils_1.AST_NODE_TYPES.TSTypeParameterDeclaration) {
64
+ for (const param of typeParameters.params) {
65
+ const name = typeof param.name === 'string' ? param.name : param.name.name;
66
+ localTypes.add(name);
67
+ }
68
+ }
69
+ };
70
+ while (current) {
71
+ addTypeParameters(current);
72
+ if (current.type === utils_1.AST_NODE_TYPES.BlockStatement) {
73
+ for (const statement of current.body) {
74
+ if (statement.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
75
+ statement.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
76
+ statement.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
77
+ statement.type === utils_1.AST_NODE_TYPES.TSEnumDeclaration) {
78
+ localTypes.add(statement.id.name);
79
+ continue;
80
+ }
81
+ if (statement.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration &&
82
+ (statement.id.type === utils_1.AST_NODE_TYPES.Identifier ||
83
+ statement.id.type === utils_1.AST_NODE_TYPES.Literal)) {
84
+ const name = statement.id.type === utils_1.AST_NODE_TYPES.Identifier
85
+ ? statement.id.name
86
+ : String(statement.id.value);
87
+ localTypes.add(name);
88
+ }
89
+ }
90
+ }
91
+ if (current.type === utils_1.AST_NODE_TYPES.Program) {
92
+ break;
93
+ }
94
+ current = current.parent ?? undefined;
95
+ }
96
+ return localTypes;
97
+ }
98
+ function usesLocalTypeBindings(typeRoots, localTypes, sourceCode) {
99
+ if (!typeRoots.length || localTypes.size === 0) {
100
+ return false;
101
+ }
102
+ const visitorKeys = sourceCode.visitorKeys;
103
+ const stack = [...typeRoots];
104
+ while (stack.length) {
105
+ const current = stack.pop();
106
+ if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
107
+ localTypes.has(current.name)) {
108
+ return true;
109
+ }
110
+ if (current.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
111
+ let left = current.left;
112
+ while (left.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
113
+ left = left.left;
114
+ }
115
+ if (left.type === utils_1.AST_NODE_TYPES.Identifier &&
116
+ localTypes.has(left.name)) {
117
+ return true;
118
+ }
119
+ // Qualified names (e.g. External.LocalType) are scoped by their leftmost
120
+ // namespace. Traversing into the right side can falsely match an
121
+ // unrelated local type with the same name.
122
+ continue;
123
+ }
124
+ const keys = visitorKeys[current.type];
125
+ if (!keys)
126
+ continue;
127
+ for (const key of keys) {
128
+ const value = current[key];
129
+ if (Array.isArray(value)) {
130
+ for (const element of value) {
131
+ if (element && typeof element.type === 'string') {
132
+ stack.push(element);
133
+ }
134
+ }
135
+ }
136
+ else if (value && typeof value.type === 'string') {
137
+ stack.push(value);
138
+ }
139
+ }
140
+ }
141
+ return false;
142
+ }
143
+ function collectBodyTypeAnnotations(node, sourceCode) {
144
+ const typeNodes = [];
145
+ const visitorKeys = sourceCode.visitorKeys;
146
+ const stack = [node];
147
+ while (stack.length) {
148
+ const current = stack.pop();
149
+ if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
150
+ current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression) {
151
+ typeNodes.push(current.typeAnnotation);
152
+ }
153
+ else if (current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
154
+ typeNodes.push(current.typeAnnotation);
155
+ }
156
+ const typeAnnotation = current
157
+ .typeAnnotation;
158
+ if (typeAnnotation) {
159
+ if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
160
+ typeNodes.push(typeAnnotation.typeAnnotation);
161
+ }
162
+ else {
163
+ typeNodes.push(typeAnnotation);
164
+ }
165
+ }
166
+ const typeParameters = current.typeParameters;
167
+ if (typeParameters) {
168
+ typeNodes.push(typeParameters);
169
+ }
170
+ const returnType = current.returnType;
171
+ if (returnType) {
172
+ typeNodes.push(returnType);
173
+ }
174
+ const keys = visitorKeys[current.type];
175
+ if (!keys)
176
+ continue;
177
+ for (const key of keys) {
178
+ const value = current[key];
179
+ if (Array.isArray(value)) {
180
+ for (const element of value) {
181
+ if (element && typeof element.type === 'string') {
182
+ stack.push(element);
183
+ }
184
+ }
185
+ }
186
+ else if (value && typeof value.type === 'string') {
187
+ stack.push(value);
188
+ }
189
+ }
190
+ }
191
+ return typeNodes;
192
+ }
193
+ function findHoistTarget(node) {
194
+ let current = node;
195
+ while (current?.parent) {
196
+ if (current.parent.type === utils_1.AST_NODE_TYPES.Program) {
197
+ return current;
198
+ }
199
+ if (current.parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
200
+ current.parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
201
+ return current.parent;
202
+ }
203
+ current = current.parent;
204
+ }
205
+ return null;
206
+ }
207
+ function getCallbackArg(node) {
208
+ const [maybeFn] = node.arguments;
209
+ if (isFunctionExpression(maybeFn)) {
210
+ return maybeFn;
211
+ }
212
+ return null;
213
+ }
214
+ function hasEmptyDependencyArray(node) {
215
+ if (node.arguments.length < 2)
216
+ return false;
217
+ const deps = node.arguments[1];
218
+ return (deps.type === utils_1.AST_NODE_TYPES.ArrayExpression && deps.elements.length === 0);
219
+ }
220
+ function filenameMatchesPatterns(filename, patterns) {
221
+ const normalized = filename.replace(/\\/g, '/');
222
+ const basename = normalized.split('/').pop() ?? normalized;
223
+ return patterns.some((pattern) => (0, minimatch_1.minimatch)(normalized, pattern) || (0, minimatch_1.minimatch)(basename, pattern));
224
+ }
225
+ function usesThisOrSuper(node, sourceCode) {
226
+ const visitorKeys = sourceCode.visitorKeys;
227
+ const stack = [node];
228
+ while (stack.length) {
229
+ const current = stack.pop();
230
+ if (current.type === utils_1.AST_NODE_TYPES.ThisExpression ||
231
+ current.type === utils_1.AST_NODE_TYPES.Super) {
232
+ return true;
233
+ }
234
+ const keys = visitorKeys[current.type];
235
+ if (!keys)
236
+ continue;
237
+ for (const key of keys) {
238
+ const value = current[key];
239
+ if (Array.isArray(value)) {
240
+ for (const element of value) {
241
+ if (element && typeof element.type === 'string') {
242
+ stack.push(element);
243
+ }
244
+ }
245
+ }
246
+ else if (value && typeof value.type === 'string') {
247
+ stack.push(value);
248
+ }
249
+ }
250
+ }
251
+ return false;
252
+ }
253
+ function analyzeExternalReferences(context, fn, extraTypeRoots = []) {
254
+ const sourceCode = context.getSourceCode();
255
+ if (usesThisOrSuper(fn.body, sourceCode)) {
256
+ return { hasComponentScopeRef: true };
257
+ }
258
+ let hasComponentScopeRef = false;
259
+ const scopeManager = sourceCode.scopeManager;
260
+ if (!scopeManager) {
261
+ return { hasComponentScopeRef: true };
262
+ }
263
+ const scope = scopeManager.acquire(fn, true) ?? scopeManager.acquire(fn);
264
+ if (!scope) {
265
+ return { hasComponentScopeRef: true };
266
+ }
267
+ if (!hasComponentScopeRef) {
268
+ for (const ref of scope.through) {
269
+ const identifier = ref.identifier;
270
+ if (isPropertyKey(identifier.parent, identifier)) {
271
+ continue;
272
+ }
273
+ const resolved = ref.resolved;
274
+ if (!resolved) {
275
+ continue;
276
+ }
277
+ const def = resolved.defs[0];
278
+ const scopeType = resolved.scope.type;
279
+ const isImport = def?.type === 'ImportBinding';
280
+ const isModuleOrGlobal = scopeType === 'module' || scopeType === 'global';
281
+ if (!isImport && !isModuleOrGlobal) {
282
+ hasComponentScopeRef = true;
283
+ break;
284
+ }
285
+ }
286
+ }
287
+ if (!hasComponentScopeRef) {
288
+ const typeRoots = [];
289
+ if (fn.returnType) {
290
+ typeRoots.push(fn.returnType.typeAnnotation);
291
+ }
292
+ if (fn.typeParameters) {
293
+ typeRoots.push(fn.typeParameters);
294
+ }
295
+ for (const param of fn.params) {
296
+ if ('typeAnnotation' in param && param.typeAnnotation) {
297
+ typeRoots.push(param.typeAnnotation.typeAnnotation);
298
+ }
299
+ else if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
300
+ 'typeAnnotation' in param.left &&
301
+ param.left.typeAnnotation) {
302
+ typeRoots.push(param.left.typeAnnotation.typeAnnotation);
303
+ }
304
+ }
305
+ typeRoots.push(...collectBodyTypeAnnotations(fn.body, sourceCode));
306
+ typeRoots.push(...extraTypeRoots);
307
+ if (typeRoots.some((typeRoot) => usesThisOrSuper(typeRoot, sourceCode))) {
308
+ hasComponentScopeRef = true;
309
+ }
310
+ if (!hasComponentScopeRef) {
311
+ const localTypes = collectNearestBlockTypeBindings(fn);
312
+ if (localTypes.size > 0 &&
313
+ usesLocalTypeBindings(typeRoots, localTypes, sourceCode)) {
314
+ hasComponentScopeRef = true;
315
+ }
316
+ }
317
+ }
318
+ return { hasComponentScopeRef };
319
+ }
320
+ function getProgramNode(node) {
321
+ let current = node;
322
+ while (current && current.type !== utils_1.AST_NODE_TYPES.Program) {
323
+ current = current.parent ?? undefined;
324
+ }
325
+ return current?.type === utils_1.AST_NODE_TYPES.Program ? current : null;
326
+ }
327
+ function addPatternIdentifiersToSet(pattern, names) {
328
+ switch (pattern.type) {
329
+ case utils_1.AST_NODE_TYPES.Identifier:
330
+ names.add(pattern.name);
331
+ return;
332
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
333
+ for (const property of pattern.properties) {
334
+ if (property.type === utils_1.AST_NODE_TYPES.Property) {
335
+ addPatternIdentifiersToSet(property.value, names);
336
+ }
337
+ else if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
338
+ addPatternIdentifiersToSet(property.argument, names);
339
+ }
340
+ }
341
+ return;
342
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
343
+ for (const element of pattern.elements) {
344
+ if (!element)
345
+ continue;
346
+ if (element.type === utils_1.AST_NODE_TYPES.RestElement) {
347
+ addPatternIdentifiersToSet(element.argument, names);
348
+ }
349
+ else {
350
+ addPatternIdentifiersToSet(element, names);
351
+ }
352
+ }
353
+ return;
354
+ case utils_1.AST_NODE_TYPES.RestElement:
355
+ addPatternIdentifiersToSet(pattern.argument, names);
356
+ return;
357
+ case utils_1.AST_NODE_TYPES.AssignmentPattern:
358
+ addPatternIdentifiersToSet(pattern.left, names);
359
+ return;
360
+ }
361
+ }
362
+ function getModuleScopeValueBindings(program) {
363
+ const names = new Set();
364
+ for (const statement of program.body) {
365
+ const target = statement.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
366
+ statement.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration
367
+ ? statement.declaration
368
+ : statement;
369
+ if (!target)
370
+ continue;
371
+ if (target.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
372
+ for (const specifier of target.specifiers) {
373
+ names.add(specifier.local.name);
374
+ }
375
+ continue;
376
+ }
377
+ if (target.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
378
+ for (const declaration of target.declarations) {
379
+ addPatternIdentifiersToSet(declaration.id, names);
380
+ }
381
+ continue;
382
+ }
383
+ if (target.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
384
+ target.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
385
+ target.type === utils_1.AST_NODE_TYPES.TSEnumDeclaration) {
386
+ if (target.id) {
387
+ names.add(target.id.name);
388
+ }
389
+ }
390
+ }
391
+ return names;
392
+ }
393
+ function buildHoistFixes(context, callExpression, callback, hoistedIdentifierCache) {
394
+ if (!callExpression.parent ||
395
+ callExpression.parent.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
396
+ return null;
397
+ }
398
+ const declarator = callExpression.parent;
399
+ if (declarator.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
400
+ return null;
401
+ }
402
+ const varDecl = declarator.parent;
403
+ if (!varDecl ||
404
+ varDecl.type !== utils_1.AST_NODE_TYPES.VariableDeclaration ||
405
+ varDecl.declarations.length !== 1) {
406
+ return null;
407
+ }
408
+ if (varDecl.kind !== 'const') {
409
+ return null;
410
+ }
411
+ if (!varDecl.parent ||
412
+ varDecl.parent.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
413
+ return null;
414
+ }
415
+ const hoistTarget = findHoistTarget(varDecl.parent);
416
+ if (!hoistTarget) {
417
+ return null;
418
+ }
419
+ const programNode = getProgramNode(hoistTarget);
420
+ let moduleBindings = null;
421
+ if (programNode) {
422
+ const cachedBindings = hoistedIdentifierCache.get(programNode);
423
+ moduleBindings = cachedBindings ?? getModuleScopeValueBindings(programNode);
424
+ if (!cachedBindings) {
425
+ hoistedIdentifierCache.set(programNode, moduleBindings);
426
+ }
427
+ if (moduleBindings.has(declarator.id.name)) {
428
+ return null;
429
+ }
430
+ // Reserve the identifier so later callbacks in the same file with the same
431
+ // name do not attempt to hoist and produce duplicate declarations.
432
+ moduleBindings.add(declarator.id.name);
433
+ }
434
+ const sourceCode = context.getSourceCode();
435
+ // We use the full range of the id to ensure we capture any type annotations.
436
+ // In @typescript-eslint/parser, the Identifier range already includes the
437
+ // type annotation, but we explicitly calculate the end range for robustness.
438
+ const idRangeEnd = declarator.id.typeAnnotation &&
439
+ declarator.id.typeAnnotation.range[1] > declarator.id.range[1]
440
+ ? declarator.id.typeAnnotation.range[1]
441
+ : declarator.id.range[1];
442
+ const identifierText = sourceCode
443
+ .getText()
444
+ .slice(declarator.id.range[0], idRangeEnd);
445
+ const functionText = sourceCode.getText(callback);
446
+ const hoisted = `const ${identifierText} = ${functionText};\n`;
447
+ const fileText = sourceCode.getText();
448
+ let removeStart = varDecl.range[0];
449
+ const lineStart = fileText.lastIndexOf('\n', removeStart - 1) + 1;
450
+ const leadingSegment = fileText.slice(lineStart, removeStart);
451
+ const hasOnlyIndentBefore = /^[ \t]*$/.test(leadingSegment);
452
+ if (hasOnlyIndentBefore) {
453
+ removeStart = lineStart;
454
+ }
455
+ let removeEnd = varDecl.range[1];
456
+ const lineEnd = fileText.indexOf('\n', removeEnd);
457
+ const segmentEnd = lineEnd === -1 ? fileText.length : lineEnd;
458
+ const trailingSegment = fileText.slice(removeEnd, segmentEnd);
459
+ const hasOnlyWhitespaceAfter = /^[ \t]*$/.test(trailingSegment);
460
+ if (hasOnlyWhitespaceAfter) {
461
+ removeEnd = segmentEnd;
462
+ }
463
+ if (hasOnlyIndentBefore && hasOnlyWhitespaceAfter && lineEnd !== -1) {
464
+ removeEnd = lineEnd + 1;
465
+ }
466
+ return (fixer) => [
467
+ fixer.insertTextBefore(hoistTarget, hoisted),
468
+ fixer.removeRange([removeStart, removeEnd]),
469
+ ];
470
+ }
471
+ exports.noEmptyDependencyUseCallbacks = (0, createRule_1.createRule)({
472
+ name: 'no-empty-dependency-use-callbacks',
473
+ meta: {
474
+ type: 'suggestion',
475
+ docs: {
476
+ description: 'Discourage useCallback([]) or useLatestCallback around static functions. Static callbacks do not need hook machinery—extract them to module-level utilities for clarity and to avoid unnecessary hook overhead.',
477
+ recommended: 'error',
478
+ },
479
+ fixable: 'code',
480
+ schema: [
481
+ {
482
+ type: 'object',
483
+ properties: {
484
+ ignoreTestFiles: { type: 'boolean', default: true },
485
+ testFilePatterns: {
486
+ type: 'array',
487
+ items: { type: 'string' },
488
+ default: DEFAULT_TEST_PATTERNS,
489
+ },
490
+ ignoreUseLatestCallback: { type: 'boolean', default: false },
491
+ },
492
+ additionalProperties: false,
493
+ },
494
+ ],
495
+ messages: {
496
+ preferUtilityFunction: PREFER_UTILITY_FUNCTION_MESSAGE,
497
+ preferUtilityLatest: PREFER_UTILITY_LATEST_MESSAGE,
498
+ },
499
+ },
500
+ defaultOptions: [{}],
501
+ create(context) {
502
+ const [options] = context.options;
503
+ const ignoreTestFiles = options?.ignoreTestFiles !== false;
504
+ const testPatterns = options?.testFilePatterns ?? DEFAULT_TEST_PATTERNS;
505
+ const ignoreUseLatestCallback = options?.ignoreUseLatestCallback === true;
506
+ const hoistedIdentifierCache = new WeakMap();
507
+ const filename = context.getFilename();
508
+ const isTest = ignoreTestFiles && filenameMatchesPatterns(filename, testPatterns);
509
+ function reportIfStaticCallback(callExpression, messageId) {
510
+ const callee = callExpression.callee;
511
+ if (callee.type !== utils_1.AST_NODE_TYPES.Identifier &&
512
+ callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
513
+ return;
514
+ }
515
+ const callback = getCallbackArg(callExpression);
516
+ if (!callback)
517
+ return;
518
+ if (ASTHelpers_1.ASTHelpers.returnsJSX(callback.body, context))
519
+ return;
520
+ const extraTypeRoots = [];
521
+ if (callExpression.parent &&
522
+ callExpression.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
523
+ 'typeAnnotation' in callExpression.parent.id &&
524
+ callExpression.parent.id.typeAnnotation) {
525
+ const typeAnnotation = callExpression.parent.id.typeAnnotation;
526
+ if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
527
+ extraTypeRoots.push(typeAnnotation.typeAnnotation);
528
+ }
529
+ else {
530
+ extraTypeRoots.push(typeAnnotation);
531
+ }
532
+ }
533
+ const { hasComponentScopeRef } = analyzeExternalReferences(context, callback, extraTypeRoots);
534
+ if (hasComponentScopeRef) {
535
+ return;
536
+ }
537
+ const callbackName = callExpression.parent &&
538
+ callExpression.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
539
+ callExpression.parent.id.type === utils_1.AST_NODE_TYPES.Identifier
540
+ ? callExpression.parent.id.name
541
+ : 'this callback';
542
+ const fix = buildHoistFixes(context, callExpression, callback, hoistedIdentifierCache);
543
+ context.report({
544
+ node: callExpression,
545
+ messageId,
546
+ data: { name: callbackName },
547
+ fix,
548
+ });
549
+ }
550
+ return {
551
+ CallExpression(node) {
552
+ if (isTest)
553
+ return;
554
+ const { callee } = node;
555
+ if (callee.type !== utils_1.AST_NODE_TYPES.Identifier &&
556
+ callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
557
+ return;
558
+ }
559
+ if (isUseCallbackCallee(callee)) {
560
+ if (!hasEmptyDependencyArray(node)) {
561
+ return;
562
+ }
563
+ reportIfStaticCallback(node, 'preferUtilityFunction');
564
+ return;
565
+ }
566
+ if (ignoreUseLatestCallback)
567
+ return;
568
+ if (isUseLatestCallbackCallee(callee)) {
569
+ reportIfStaticCallback(node, 'preferUtilityLatest');
570
+ }
571
+ },
572
+ };
573
+ },
574
+ });
575
+ exports.default = exports.noEmptyDependencyUseCallbacks;
576
+ //# sourceMappingURL=no-empty-dependency-use-callbacks.js.map
@@ -61,6 +61,7 @@ context) {
61
61
  const usages = new Map(); // Track usage and its position
62
62
  const visited = new Set();
63
63
  let needsEntireObject = false;
64
+ let isUsed = false;
64
65
  // Built-in array methods that indicate usage of the entire array
65
66
  const ARRAY_METHODS = new Set([
66
67
  'map',
@@ -153,9 +154,7 @@ context) {
153
154
  else {
154
155
  // For other computed properties, use the exact expression
155
156
  try {
156
- const propertyText = context
157
- .getSourceCode()
158
- .getText(memberExpr.property);
157
+ const propertyText = context.sourceCode.getText(memberExpr.property);
159
158
  parts.unshift(`[${propertyText}]`);
160
159
  }
161
160
  catch (e) {
@@ -232,6 +231,52 @@ context) {
232
231
  if (!node || visited.has(node))
233
232
  return;
234
233
  visited.add(node);
234
+ // Direct usage of the target identifier to distinguish between:
235
+ // 1. Never used (not even present) → suggest removal
236
+ // 2. Used in ways requiring entire object → no suggestion
237
+ // 3. Used only via specific fields → suggest replacing with fields
238
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === objectName) {
239
+ const parent = node.parent;
240
+ // Exclude: property name in `other.objectName` (not our target object)
241
+ const isMemberProperty = parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
242
+ parent.property === node &&
243
+ !parent.computed;
244
+ // Exclude: object in `objectName.prop` (handled by MemberExpression visitor for field tracking)
245
+ const isMemberObject = parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
246
+ parent.object === node;
247
+ // Exclude: key in `{ objectName: value }` (not usage, just a label)
248
+ // Include: shorthand `{ objectName }` (actual usage)
249
+ const isPropertyKey = parent?.type === utils_1.AST_NODE_TYPES.Property &&
250
+ parent.key === node &&
251
+ !parent.computed &&
252
+ !parent.shorthand;
253
+ if (!isMemberProperty && !isMemberObject && !isPropertyKey) {
254
+ isUsed = true;
255
+ // Patterns that require the entire object (cannot refactor to specific fields)
256
+ const isTypeAUsage = parent?.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
257
+ parent?.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
258
+ parent?.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
259
+ parent?.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
260
+ parent?.type === utils_1.AST_NODE_TYPES.ConditionalExpression ||
261
+ parent?.type === utils_1.AST_NODE_TYPES.UnaryExpression ||
262
+ (parent?.type === utils_1.AST_NODE_TYPES.Property &&
263
+ (parent.value === node ||
264
+ parent.shorthand ||
265
+ (parent.key === node && parent.computed))) ||
266
+ parent?.type === utils_1.AST_NODE_TYPES.TemplateLiteral ||
267
+ parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
268
+ parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression ||
269
+ parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer ||
270
+ parent?.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute ||
271
+ parent?.type === utils_1.AST_NODE_TYPES.SpreadElement ||
272
+ parent?.type === utils_1.AST_NODE_TYPES.ForInStatement ||
273
+ parent?.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
274
+ parent?.type === utils_1.AST_NODE_TYPES.CallExpression;
275
+ if (isTypeAUsage) {
276
+ needsEntireObject = true;
277
+ }
278
+ }
279
+ }
235
280
  if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
236
281
  // Check if the object is being called as a function
237
282
  if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -433,7 +478,7 @@ context) {
433
478
  return posA - posB;
434
479
  });
435
480
  const filteredUsages = new Set(sortedPaths);
436
- const notUsed = !needsEntireObject && filteredUsages.size === 0;
481
+ const notUsed = !needsEntireObject && !isUsed && filteredUsages.size === 0;
437
482
  return {
438
483
  usages: filteredUsages,
439
484
  needsEntireObject,
@@ -445,15 +490,15 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
445
490
  meta: {
446
491
  type: 'suggestion',
447
492
  docs: {
448
- description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used, as this can cause unnecessary re-renders. When a hook only uses obj.name but obj is in the deps array, any change to obj.age will trigger the hook. Use individual fields (obj.name) instead of the entire object. Requires TypeScript and `parserOptions.project` to be configured.',
493
+ description: 'Avoid using entire objects in React hook dependency arrays.',
449
494
  recommended: 'error',
450
495
  requiresTypeChecking: true,
451
496
  },
452
497
  fixable: 'code',
453
498
  schema: [],
454
499
  messages: {
455
- avoidEntireObject: 'Avoid using entire object "{{objectName}}" in dependency array. Use specific fields: {{fields}}',
456
- removeUnusedDependency: 'Remove unused dependency "{{objectName}}" from dependency array (not used in effect)',
500
+ avoidEntireObject: 'What\'s wrong: Dependency array includes entire object "{{objectName}}". Why it matters: Any change to its other properties reruns the hook even though the hook reads only {{fields}}, creating extra renders and stale memoized values. How to fix: Depend on those fields instead.',
501
+ removeUnusedDependency: 'What\'s wrong: Dependency "{{objectName}}" is listed in the array but never read inside the hook body. Why it matters: The hook reruns when "{{objectName}}" changes without affecting the result and can hide the real missing dependency. How to fix: Remove it or add the specific value that actually drives the hook.',
457
502
  },
458
503
  },
459
504
  defaultOptions: [],