@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.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 (186) hide show
  1. package/README.md +170 -139
  2. package/lib/index.js +190 -1
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  5. package/lib/rules/consistent-callback-naming.js +1 -1
  6. package/lib/rules/dynamic-https-errors.js +3 -4
  7. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
  9. package/lib/rules/enforce-callback-memo.js +1 -1
  10. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  11. package/lib/rules/enforce-console-error.js +35 -7
  12. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  13. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  14. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  15. package/lib/rules/enforce-early-destructuring.js +980 -0
  16. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  17. package/lib/rules/enforce-empty-object-check.js +502 -0
  18. package/lib/rules/enforce-exported-function-types.js +1 -1
  19. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  20. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  21. package/lib/rules/enforce-firestore-facade.js +4 -2
  22. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  23. package/lib/rules/enforce-global-constants.js +1 -1
  24. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  25. package/lib/rules/enforce-memoize-async.js +2 -6
  26. package/lib/rules/enforce-memoize-getters.js +39 -15
  27. package/lib/rules/enforce-microdiff.js +1 -1
  28. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  29. package/lib/rules/enforce-positive-naming.js +4 -0
  30. package/lib/rules/enforce-props-argument-name.js +2 -2
  31. package/lib/rules/enforce-safe-stringify.js +1 -1
  32. package/lib/rules/enforce-serializable-params.js +3 -3
  33. package/lib/rules/enforce-singular-type-names.js +1 -1
  34. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  35. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  36. package/lib/rules/enforce-storage-context.d.ts +9 -0
  37. package/lib/rules/enforce-storage-context.js +646 -0
  38. package/lib/rules/enforce-timestamp-now.js +23 -7
  39. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  40. package/lib/rules/enforce-transform-memoization.js +416 -0
  41. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  42. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  43. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  44. package/lib/rules/enforce-verb-noun-naming.js +5 -4
  45. package/lib/rules/ensure-pointer-events-none.js +28 -2
  46. package/lib/rules/export-if-in-doubt.js +27 -4
  47. package/lib/rules/extract-global-constants.js +40 -13
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  49. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  50. package/lib/rules/flatten-push-calls.d.ts +2 -0
  51. package/lib/rules/flatten-push-calls.js +428 -0
  52. package/lib/rules/global-const-style.js +50 -19
  53. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  54. package/lib/rules/jsdoc-above-field.js +208 -0
  55. package/lib/rules/key-only-outermost-element.js +21 -3
  56. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  57. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  58. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  59. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  60. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  61. package/lib/rules/memo-nested-react-components.js +410 -0
  62. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  63. package/lib/rules/no-always-true-false-conditions.js +68 -8
  64. package/lib/rules/no-array-length-in-deps.js +9 -3
  65. package/lib/rules/no-async-foreach.js +210 -12
  66. package/lib/rules/no-circular-references.js +18 -17
  67. package/lib/rules/no-class-instance-destructuring.js +55 -12
  68. package/lib/rules/no-complex-cloud-params.js +7 -3
  69. package/lib/rules/no-compositing-layer-props.js +2 -2
  70. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  71. package/lib/rules/no-console-error.d.ts +9 -0
  72. package/lib/rules/no-console-error.js +525 -0
  73. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  74. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  75. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  76. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  77. package/lib/rules/no-entire-object-hook-deps.js +4 -6
  78. package/lib/rules/no-excessive-parent-chain.js +1 -1
  79. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  80. package/lib/rules/no-explicit-return-type.js +215 -26
  81. package/lib/rules/no-filter-without-return.js +5 -1
  82. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  83. package/lib/rules/no-firestore-jest-mock.js +126 -8
  84. package/lib/rules/no-firestore-object-arrays.js +67 -12
  85. package/lib/rules/no-handler-suffix.d.ts +12 -0
  86. package/lib/rules/no-handler-suffix.js +305 -0
  87. package/lib/rules/no-hungarian.js +1 -1
  88. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  89. package/lib/rules/no-inline-component-prop.js +456 -0
  90. package/lib/rules/no-jsx-in-hooks.js +6 -1
  91. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  92. package/lib/rules/no-margin-properties.js +6 -6
  93. package/lib/rules/no-memoize-on-static.js +9 -1
  94. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  95. package/lib/rules/no-misused-switch-case.js +22 -1
  96. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  97. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  98. package/lib/rules/no-mock-firebase-admin.js +5 -2
  99. package/lib/rules/no-object-values-on-strings.js +10 -2
  100. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  101. package/lib/rules/no-passthrough-getters.js +38 -2
  102. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  103. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  104. package/lib/rules/no-redundant-param-types.js +16 -6
  105. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  106. package/lib/rules/no-redundant-this-params.js +459 -0
  107. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  108. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  109. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  110. package/lib/rules/no-restricted-properties-fix.js +11 -10
  111. package/lib/rules/no-separate-loading-state.js +7 -16
  112. package/lib/rules/no-stale-state-across-await.js +1 -1
  113. package/lib/rules/no-type-assertion-returns.js +43 -49
  114. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  115. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  116. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  117. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  118. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  119. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  120. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  121. package/lib/rules/no-unpinned-dependencies.js +36 -5
  122. package/lib/rules/no-unused-props.d.ts +2 -2
  123. package/lib/rules/no-unused-props.js +295 -91
  124. package/lib/rules/no-unused-usestate.js +6 -2
  125. package/lib/rules/no-useless-fragment.js +28 -2
  126. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  127. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  128. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  129. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  130. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  131. package/lib/rules/omit-index-html.d.ts +2 -1
  132. package/lib/rules/omit-index-html.js +62 -7
  133. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  134. package/lib/rules/parallelize-async-operations.js +24 -5
  135. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  136. package/lib/rules/prefer-batch-operations.js +32 -5
  137. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  138. package/lib/rules/prefer-clone-deep.js +3 -3
  139. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  140. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  141. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  142. package/lib/rules/prefer-docsetter-setall.js +243 -0
  143. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  144. package/lib/rules/prefer-fragment-component.js +1 -1
  145. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  146. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  147. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  148. package/lib/rules/prefer-global-router-state-key.js +88 -13
  149. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  150. package/lib/rules/prefer-memoized-props.js +445 -0
  151. package/lib/rules/prefer-next-dynamic.js +60 -69
  152. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
  153. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  154. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  155. package/lib/rules/prefer-params-over-parent-id.js +220 -171
  156. package/lib/rules/prefer-settings-object.js +2 -2
  157. package/lib/rules/prefer-type-over-interface.js +7 -2
  158. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  159. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  160. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  161. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  162. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  163. package/lib/rules/prevent-children-clobber.js +536 -0
  164. package/lib/rules/react-memoize-literals.d.ts +4 -0
  165. package/lib/rules/react-memoize-literals.js +495 -0
  166. package/lib/rules/react-usememo-should-be-component.js +2 -2
  167. package/lib/rules/require-hooks-default-params.js +17 -16
  168. package/lib/rules/require-https-error-cause.d.ts +4 -0
  169. package/lib/rules/require-https-error-cause.js +136 -0
  170. package/lib/rules/require-https-error.js +43 -21
  171. package/lib/rules/require-image-optimized.js +1 -1
  172. package/lib/rules/require-memo.js +1 -1
  173. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  174. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  175. package/lib/rules/require-usememo-object-literals.js +2 -3
  176. package/lib/rules/test-file-location-enforcement.js +1 -1
  177. package/lib/rules/use-latest-callback.js +6 -5
  178. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  179. package/lib/rules/vertically-group-related-functions.js +480 -0
  180. package/lib/utils/ASTHelpers.d.ts +13 -1
  181. package/lib/utils/ASTHelpers.js +24 -0
  182. package/lib/utils/getMethodName.d.ts +27 -0
  183. package/lib/utils/getMethodName.js +35 -0
  184. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  185. package/lib/utils/tsTypeClassifier.js +149 -0
  186. 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))
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
@@ -153,9 +153,7 @@ context) {
153
153
  else {
154
154
  // For other computed properties, use the exact expression
155
155
  try {
156
- const propertyText = context
157
- .getSourceCode()
158
- .getText(memberExpr.property);
156
+ const propertyText = context.sourceCode.getText(memberExpr.property);
159
157
  parts.unshift(`[${propertyText}]`);
160
158
  }
161
159
  catch (e) {
@@ -445,15 +443,15 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
445
443
  meta: {
446
444
  type: 'suggestion',
447
445
  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.',
446
+ description: 'Avoid using entire objects in React hook dependency arrays.',
449
447
  recommended: 'error',
450
448
  requiresTypeChecking: true,
451
449
  },
452
450
  fixable: 'code',
453
451
  schema: [],
454
452
  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)',
453
+ 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.',
454
+ 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
455
  },
458
456
  },
459
457
  defaultOptions: [],
@@ -34,7 +34,7 @@ exports.noExcessiveParentChain = (0, createRule_1.createRule)({
34
34
  },
35
35
  ],
36
36
  messages: {
37
- excessiveParentChain: 'Avoid using long chains of .parent calls ({{count}} found). Use the params object from the event parameter instead, which provides type-safe access to path segments.',
37
+ excessiveParentChain: 'Found {{count}} consecutive ref.parent hops in this handler. Long parent chains break when Firestore/RealtimeDB paths change and bypass the typed params the trigger already provides. Read path components from event.params (for example, params.userId) instead of walking ref.parent repeatedly.',
38
38
  },
39
39
  },
40
40
  defaultOptions: [{}],
@@ -1,4 +1,5 @@
1
1
  import { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'noExplicitReturnTypeInferable' | 'noExplicitReturnTypeNonInferable';
2
3
  type Options = [
3
4
  {
4
5
  allowRecursiveFunctions?: boolean;
@@ -9,5 +10,5 @@ type Options = [
9
10
  allowFirestoreFunctionFiles?: boolean;
10
11
  }
11
12
  ];
12
- export declare const noExplicitReturnType: TSESLint.RuleModule<'noExplicitReturnType', Options>;
13
+ export declare const noExplicitReturnType: TSESLint.RuleModule<MessageIds, Options>;
13
14
  export {};