@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,495 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reactMemoizeLiterals = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const LITERAL_DESCRIPTOR_BY_TYPE = {
7
+ [utils_1.AST_NODE_TYPES.ObjectExpression]: {
8
+ literalType: 'object literal',
9
+ memoHook: 'useMemo',
10
+ },
11
+ [utils_1.AST_NODE_TYPES.ArrayExpression]: {
12
+ literalType: 'array literal',
13
+ memoHook: 'useMemo',
14
+ },
15
+ [utils_1.AST_NODE_TYPES.ArrowFunctionExpression]: {
16
+ literalType: 'inline function',
17
+ memoHook: 'useCallback',
18
+ },
19
+ [utils_1.AST_NODE_TYPES.FunctionExpression]: {
20
+ literalType: 'inline function',
21
+ memoHook: 'useCallback',
22
+ },
23
+ [utils_1.AST_NODE_TYPES.FunctionDeclaration]: {
24
+ literalType: 'inline function',
25
+ memoHook: 'useCallback',
26
+ },
27
+ };
28
+ const SAFE_HOOK_ARGUMENTS = new Set([
29
+ 'useMemo',
30
+ 'useCallback',
31
+ 'useEffect',
32
+ 'useLayoutEffect',
33
+ 'useInsertionEffect',
34
+ 'useImperativeHandle',
35
+ 'useState',
36
+ 'useReducer',
37
+ 'useRef',
38
+ 'useSyncExternalStore',
39
+ 'useDeferredValue',
40
+ 'useTransition',
41
+ 'useId',
42
+ ]);
43
+ const MEMOIZATION_DEPS_TODO_PLACEHOLDER = '__TODO_MEMOIZATION_DEPENDENCIES__';
44
+ const TODO_DEPS_COMMENT = `/* ${MEMOIZATION_DEPS_TODO_PLACEHOLDER} */`;
45
+ const PARENTHESIZED_EXPRESSION_TYPE = utils_1.AST_NODE_TYPES.ParenthesizedExpression ??
46
+ 'ParenthesizedExpression';
47
+ /**
48
+ * Detects React hook-style identifiers prefixed with "use".
49
+ * @param name Candidate identifier name.
50
+ * @returns True when the name follows the hook naming convention.
51
+ */
52
+ function isHookName(name) {
53
+ return !!name && /^use[A-Z]/.test(name);
54
+ }
55
+ /**
56
+ * Detects PascalCase identifiers commonly used for React components.
57
+ * @param name Candidate identifier name.
58
+ * @returns True when the name begins with an uppercase character.
59
+ */
60
+ function isComponentName(name) {
61
+ return !!name && /^[A-Z]/.test(name);
62
+ }
63
+ /**
64
+ * Type guard for parenthesized expressions to unwrap safely.
65
+ * @param node Node to evaluate.
66
+ * @returns True when the node is a parenthesized expression wrapper.
67
+ */
68
+ function isParenthesizedExpression(node) {
69
+ return node.type === PARENTHESIZED_EXPRESSION_TYPE;
70
+ }
71
+ /**
72
+ * Extracts an identifier name from variable, assignment, or property nodes.
73
+ * @param node AST node that may carry an identifier.
74
+ * @returns Identifier name when present, otherwise null.
75
+ */
76
+ function getNameFromNode(node) {
77
+ if (!node)
78
+ return null;
79
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
80
+ node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
81
+ return node.id.name;
82
+ }
83
+ if (node.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
84
+ node.left.type === utils_1.AST_NODE_TYPES.Identifier) {
85
+ return node.left.name;
86
+ }
87
+ if (node.type === utils_1.AST_NODE_TYPES.Property &&
88
+ node.key.type === utils_1.AST_NODE_TYPES.Identifier) {
89
+ return node.key.name;
90
+ }
91
+ return null;
92
+ }
93
+ /**
94
+ * Determines whether a node should be traversed through when resolving names.
95
+ * @param node Node to evaluate for transparency.
96
+ * @returns True when the node does not introduce a binding boundary.
97
+ */
98
+ function isTransparentNode(node) {
99
+ return (node.type === utils_1.AST_NODE_TYPES.CallExpression ||
100
+ node.type === utils_1.AST_NODE_TYPES.MemberExpression ||
101
+ isExpressionWrapper(node));
102
+ }
103
+ /**
104
+ * Walks ancestors through transparent nodes to find the nearest identifier name.
105
+ * @param startNode Node where the search begins.
106
+ * @returns Resolved identifier name or null if none is located.
107
+ */
108
+ function findNameInAncestors(startNode) {
109
+ let current = startNode;
110
+ while (current) {
111
+ const name = getNameFromNode(current);
112
+ if (name) {
113
+ return name;
114
+ }
115
+ if (!isTransparentNode(current)) {
116
+ break;
117
+ }
118
+ current = current.parent;
119
+ }
120
+ return null;
121
+ }
122
+ /**
123
+ * Resolves a function's display name from its declaration or surrounding
124
+ * assignment/property wrappers so HOC-wrapped and asserted components still map
125
+ * to their intended identifiers.
126
+ */
127
+ function getFunctionName(fn) {
128
+ if (fn.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
129
+ return fn.id?.name ?? null;
130
+ }
131
+ if (fn.type === utils_1.AST_NODE_TYPES.FunctionExpression && fn.id?.name) {
132
+ return fn.id.name;
133
+ }
134
+ const parent = fn.parent;
135
+ if (!parent) {
136
+ return null;
137
+ }
138
+ const immediateName = getNameFromNode(parent);
139
+ if (immediateName) {
140
+ return immediateName;
141
+ }
142
+ return findNameInAncestors(parent);
143
+ }
144
+ /**
145
+ * Checks whether a function should be treated as a React component or hook
146
+ * based on naming conventions. Enables the rule to limit reports to user-facing
147
+ * components and hooks rather than arbitrary functions.
148
+ */
149
+ function isComponentOrHookFunction(fn) {
150
+ const name = getFunctionName(fn);
151
+ return isComponentName(name) || isHookName(name);
152
+ }
153
+ /**
154
+ * Checks whether a function node represents a hook by name.
155
+ * @param fn Function node to inspect.
156
+ * @returns True when the function follows hook naming.
157
+ */
158
+ function isHookFunction(fn) {
159
+ return isHookName(getFunctionName(fn));
160
+ }
161
+ /**
162
+ * Narrows nodes to the supported function-like shapes.
163
+ * @param node Node to test.
164
+ * @returns True when the node is any function declaration or expression.
165
+ */
166
+ function isFunctionNode(node) {
167
+ return (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
168
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
169
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
170
+ }
171
+ /**
172
+ * Extracts a hook name from simple identifier or member-expression callees.
173
+ * @param callee Callee expression from a call.
174
+ * @returns Hook name when available, otherwise null.
175
+ */
176
+ function getHookNameFromCallee(callee) {
177
+ const resolvedCallee = unwrapNestedExpressions(callee);
178
+ if (resolvedCallee.type === utils_1.AST_NODE_TYPES.Identifier) {
179
+ return resolvedCallee.name;
180
+ }
181
+ if (resolvedCallee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
182
+ !resolvedCallee.computed &&
183
+ resolvedCallee.property.type ===
184
+ utils_1.AST_NODE_TYPES.Identifier) {
185
+ const property = resolvedCallee.property;
186
+ return property.name;
187
+ }
188
+ return null;
189
+ }
190
+ /**
191
+ * Detects hook call expressions and returns their hook name.
192
+ * @param node Call expression to inspect.
193
+ * @returns Hook name when the call targets a hook, otherwise null.
194
+ */
195
+ function isHookCall(node) {
196
+ const hookName = getHookNameFromCallee(node.callee);
197
+ return hookName && isHookName(hookName) ? hookName : null;
198
+ }
199
+ /**
200
+ * Maps literal node types to memoization metadata used for reporting.
201
+ * @param node Candidate literal node.
202
+ * @returns Descriptor with literal type and memo hook, or null.
203
+ */
204
+ function getLiteralDescriptor(node) {
205
+ const descriptor = LITERAL_DESCRIPTOR_BY_TYPE[node.type] ?? null;
206
+ return descriptor;
207
+ }
208
+ /**
209
+ * Finds the nearest ancestor function considered a React component or hook.
210
+ * @param node Starting node for the search.
211
+ * @returns Owning component/hook function or null.
212
+ */
213
+ function findEnclosingComponentOrHook(node) {
214
+ let current = node.parent;
215
+ while (current) {
216
+ if (isFunctionNode(current) && isComponentOrHookFunction(current)) {
217
+ return current;
218
+ }
219
+ current = current.parent;
220
+ }
221
+ return null;
222
+ }
223
+ /**
224
+ * Checks whether a node is inside an allowed hook callback that should be skipped.
225
+ * @param node Starting node for traversal.
226
+ * @returns True when enclosed by a callback to an allowed hook.
227
+ */
228
+ function isInsideAllowedHookCallback(node) {
229
+ let current = node;
230
+ while (current) {
231
+ if (current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
232
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
233
+ let parent = current.parent;
234
+ while (parent && isExpressionWrapper(parent)) {
235
+ parent = parent.parent;
236
+ }
237
+ if (parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
238
+ const hookName = getHookNameFromCallee(parent.callee);
239
+ const matchesCallback = parent.arguments.some((arg) => unwrapNestedExpressions(arg) === current);
240
+ if (hookName && SAFE_HOOK_ARGUMENTS.has(hookName) && matchesCallback) {
241
+ return true;
242
+ }
243
+ }
244
+ }
245
+ current = current.parent;
246
+ }
247
+ return false;
248
+ }
249
+ /**
250
+ * Type guard for nodes that wrap an underlying expression (e.g. assertions,
251
+ * parentheses, optional chaining wrappers).
252
+ */
253
+ function isExpressionWrapper(node) {
254
+ return (node.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
255
+ node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
256
+ node.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
257
+ node.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
258
+ node.type === utils_1.AST_NODE_TYPES.ChainExpression ||
259
+ isParenthesizedExpression(node));
260
+ }
261
+ /**
262
+ * Unwraps TypeScript assertions, optional chaining, and parentheses to reach
263
+ * the underlying expression node for stable identity comparisons.
264
+ */
265
+ function unwrapNestedExpressions(node) {
266
+ let current = node;
267
+ while (isExpressionWrapper(current)) {
268
+ current = current.expression;
269
+ }
270
+ return current;
271
+ }
272
+ /**
273
+ * Finds the nearest enclosing hook call for a literal and whether the literal
274
+ * is passed directly as an argument (versus nested inside another expression).
275
+ */
276
+ function findEnclosingHookCall(node) {
277
+ const unwrappedTarget = unwrapNestedExpressions(node);
278
+ let current = node.parent;
279
+ while (current) {
280
+ if (current.type === utils_1.AST_NODE_TYPES.CallExpression) {
281
+ const hookName = isHookCall(current);
282
+ if (hookName) {
283
+ const isDirectArgument = current.arguments.some((arg) => {
284
+ const unwrappedArg = unwrapNestedExpressions(arg);
285
+ return unwrappedArg === unwrappedTarget;
286
+ });
287
+ return { hookName, isDirectArgument };
288
+ }
289
+ }
290
+ current = current.parent;
291
+ }
292
+ return null;
293
+ }
294
+ /**
295
+ * Finds the nearest owning function for the provided node.
296
+ * @param node Node to start from.
297
+ * @returns Enclosing function declaration/expression or null.
298
+ */
299
+ function findOwningFunction(node) {
300
+ let current = node.parent;
301
+ while (current) {
302
+ if (isFunctionNode(current)) {
303
+ return current;
304
+ }
305
+ current = current.parent;
306
+ }
307
+ return null;
308
+ }
309
+ /**
310
+ * Locates the closest return statement ancestor, skipping assertion wrappers.
311
+ * @param node Node to start from.
312
+ * @returns Enclosing return statement or null.
313
+ */
314
+ function findEnclosingReturnStatement(node) {
315
+ let current = node.parent;
316
+ while (current) {
317
+ if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
318
+ return current;
319
+ }
320
+ if (isExpressionWrapper(current)) {
321
+ current = current.parent;
322
+ continue;
323
+ }
324
+ break;
325
+ }
326
+ return null;
327
+ }
328
+ /**
329
+ * Determines whether a node is returned from the provided hook function.
330
+ * @param node Expression candidate for return.
331
+ * @param owner Hook function candidate.
332
+ * @returns True when the expression is the hook's return value.
333
+ */
334
+ function isReturnValueFromHook(node, owner) {
335
+ if (!isHookFunction(owner))
336
+ return false;
337
+ if (owner.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
338
+ owner.body.type !== utils_1.AST_NODE_TYPES.BlockStatement &&
339
+ unwrapNestedExpressions(owner.body) === unwrapNestedExpressions(node)) {
340
+ return true;
341
+ }
342
+ if (node.parent?.type !== utils_1.AST_NODE_TYPES.ReturnStatement ||
343
+ node.parent.argument !== node) {
344
+ const returnStatement = findEnclosingReturnStatement(node);
345
+ if (!returnStatement?.argument) {
346
+ return false;
347
+ }
348
+ if (unwrapNestedExpressions(returnStatement.argument) !==
349
+ unwrapNestedExpressions(node)) {
350
+ return false;
351
+ }
352
+ const owningFunction = findOwningFunction(returnStatement);
353
+ return owningFunction === owner;
354
+ }
355
+ const owningFunction = findOwningFunction(node.parent);
356
+ return owningFunction === owner;
357
+ }
358
+ /**
359
+ * Builds memoization suggestions with dependency placeholders for developers.
360
+ * @param node Literal node to wrap.
361
+ * @param descriptor Literal metadata including memo hook.
362
+ * @param sourceCode Source code utility for text extraction.
363
+ * @returns Suggestion array encouraging memoization with explicit deps TODO.
364
+ */
365
+ function buildMemoSuggestions(node, descriptor, sourceCode) {
366
+ const initializerText = sourceCode.getText(node);
367
+ const wrappedInitializer = descriptor.literalType === 'object literal'
368
+ ? `(${initializerText})`
369
+ : initializerText;
370
+ if (descriptor.literalType === 'inline function') {
371
+ return [
372
+ {
373
+ messageId: 'memoizeLiteralSuggestion',
374
+ data: {
375
+ literalType: descriptor.literalType,
376
+ memoHook: descriptor.memoHook,
377
+ },
378
+ fix(fixer) {
379
+ return fixer.replaceText(node, `${descriptor.memoHook}(${initializerText}, [${TODO_DEPS_COMMENT}])`);
380
+ },
381
+ },
382
+ ];
383
+ }
384
+ return [
385
+ {
386
+ messageId: 'memoizeLiteralSuggestion',
387
+ data: {
388
+ literalType: descriptor.literalType,
389
+ memoHook: descriptor.memoHook,
390
+ },
391
+ fix(fixer) {
392
+ return fixer.replaceText(node, `${descriptor.memoHook}(() => ${wrappedInitializer}, [${TODO_DEPS_COMMENT}])`);
393
+ },
394
+ },
395
+ ];
396
+ }
397
+ /**
398
+ * Formats a readable label for diagnostics based on the owning function.
399
+ * @param fn Owning component or hook function.
400
+ * @returns Human-friendly label for error messages.
401
+ */
402
+ function formatContextLabel(fn) {
403
+ const name = getFunctionName(fn);
404
+ if (!name)
405
+ return 'this component or hook';
406
+ if (isHookName(name))
407
+ return `hook "${name}"`;
408
+ return `component "${name}"`;
409
+ }
410
+ exports.reactMemoizeLiterals = (0, createRule_1.createRule)({
411
+ name: 'react-memoize-literals',
412
+ meta: {
413
+ type: 'suggestion',
414
+ docs: {
415
+ description: 'Detect object, array, and function literals created in React components or hooks that create new references every render. Prefer memoized values (useMemo/useCallback) or module-level constants to keep referential stability.',
416
+ recommended: 'error',
417
+ },
418
+ hasSuggestions: true,
419
+ schema: [],
420
+ messages: {
421
+ componentLiteral: 'New {{literalType}} inside {{context}} is created on every render → Breaks referential stability for hooks/props and can re-run effects or re-render children → Hoist it to a module-level constant or wrap it in {{memoHook}} with the right dependencies.',
422
+ nestedHookLiteral: 'Nested {{literalType}} inside {{hookName}} arguments is recreated on every render → Dependency/reference comparisons change each time and defeat memoization/caching → Extract it into a memoized value (useMemo/useCallback) or hoist it to a module constant before passing it to {{hookName}}.',
423
+ hookReturnLiteral: '{{hookName}} returns a {{literalType}} literal on each render → Callers receive a fresh reference and may re-render or re-run effects → Memoize the returned value with useMemo/useCallback or return pre-memoized pieces so callers see stable references.',
424
+ memoizeLiteralSuggestion: 'This {{literalType}} is created inline → It produces a new reference each render → Wrap it in {{memoHook}} and include every closed-over value in the dependency array.',
425
+ },
426
+ },
427
+ defaultOptions: [],
428
+ create(context) {
429
+ const sourceCode = context.getSourceCode();
430
+ function reportLiteral(node) {
431
+ const descriptor = getLiteralDescriptor(node);
432
+ if (!descriptor)
433
+ return;
434
+ if (isInsideAllowedHookCallback(node)) {
435
+ return;
436
+ }
437
+ const owner = findEnclosingComponentOrHook(node);
438
+ if (!owner)
439
+ return;
440
+ const hookCall = findEnclosingHookCall(node);
441
+ if (hookCall) {
442
+ if (hookCall.isDirectArgument) {
443
+ // Top-level literal passed directly to a hook argument is allowed.
444
+ return;
445
+ }
446
+ context.report({
447
+ node,
448
+ messageId: 'nestedHookLiteral',
449
+ data: {
450
+ literalType: descriptor.literalType,
451
+ hookName: hookCall.hookName,
452
+ },
453
+ });
454
+ return;
455
+ }
456
+ if (isReturnValueFromHook(node, owner)) {
457
+ const hookName = getFunctionName(owner) ?? 'this hook';
458
+ context.report({
459
+ node,
460
+ messageId: 'hookReturnLiteral',
461
+ data: {
462
+ literalType: descriptor.literalType,
463
+ hookName,
464
+ },
465
+ });
466
+ return;
467
+ }
468
+ const contextLabel = formatContextLabel(owner);
469
+ // Only emit auto-fix suggestions for simple variable initializers; other
470
+ // contexts (returns, JSX props, nested expressions) risk unsafe rewrites.
471
+ const suggestions = node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
472
+ node.parent.init === node
473
+ ? buildMemoSuggestions(node, descriptor, sourceCode)
474
+ : undefined;
475
+ context.report({
476
+ node,
477
+ messageId: 'componentLiteral',
478
+ data: {
479
+ literalType: descriptor.literalType,
480
+ context: contextLabel,
481
+ memoHook: descriptor.memoHook,
482
+ },
483
+ suggest: suggestions,
484
+ });
485
+ }
486
+ return {
487
+ ObjectExpression: reportLiteral,
488
+ ArrayExpression: reportLiteral,
489
+ ArrowFunctionExpression: reportLiteral,
490
+ FunctionExpression: reportLiteral,
491
+ FunctionDeclaration: reportLiteral,
492
+ };
493
+ },
494
+ });
495
+ //# sourceMappingURL=react-memoize-literals.js.map
@@ -47,7 +47,7 @@ const isUsedAsComponentProp = (context, variableName, node) => {
47
47
  break;
48
48
  }
49
49
  }
50
- const sourceCode = context.getSourceCode();
50
+ const sourceCode = context.sourceCode;
51
51
  const scopeManager = sourceCode.scopeManager;
52
52
  const scope = scopeManager &&
53
53
  ((fn && scopeManager.acquire(fn)) || scopeManager.acquire(node));
@@ -75,7 +75,7 @@ const isUsedAsComponentProp = (context, variableName, node) => {
75
75
  const isUsedAsJsxChild = (context, variableName, node) => {
76
76
  // Traverse the nearest function (or program) to see if the identifier appears
77
77
  // as a JSX child expression.
78
- const sourceCode = context.getSourceCode();
78
+ const sourceCode = context.sourceCode;
79
79
  let container = sourceCode.ast;
80
80
  let current = node;
81
81
  while (current?.parent) {
@@ -14,7 +14,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- requireDefaultParams: 'React hooks with all optional parameters should default to an empty object',
17
+ requireDefaultParams: 'Hook "{{hookName}}" accepts an options object where every property is optional, but the parameter is not defaulted. When callers omit the argument the hook receives undefined, so destructuring or property access throws even though the fields are optional. Default the parameter to an empty object (e.g., "({ option } = {})") so the hook stays safe to call with no arguments.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -81,8 +81,10 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
81
81
  'ArrowFunctionExpression, FunctionDeclaration'(node) {
82
82
  // Check if it's a hook function
83
83
  let isHook = false;
84
+ let hookName;
84
85
  if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
85
- isHook = node.id ? isHookName(node.id.name) : false;
86
+ hookName = node.id?.name;
87
+ isHook = hookName ? isHookName(hookName) : false;
86
88
  }
87
89
  else {
88
90
  const parent = node.parent;
@@ -90,12 +92,14 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
90
92
  parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
91
93
  parent.id &&
92
94
  parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
95
+ hookName = parent.id.name;
93
96
  isHook = isHookName(parent.id.name);
94
97
  }
95
98
  }
96
99
  if (!isHook) {
97
100
  return;
98
101
  }
102
+ const messageData = { hookName: hookName ?? 'this hook' };
99
103
  // Check if it has exactly one parameter
100
104
  if (node.params.length !== 1) {
101
105
  return;
@@ -121,10 +125,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
121
125
  context.report({
122
126
  node: param,
123
127
  messageId: 'requireDefaultParams',
128
+ data: messageData,
124
129
  fix(fixer) {
125
- const paramText = context
126
- .getSourceCode()
127
- .getText(param);
130
+ const paramText = context.sourceCode.getText(param);
128
131
  return fixer.replaceText(param, `${paramText} = {}`);
129
132
  },
130
133
  });
@@ -140,10 +143,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
140
143
  context.report({
141
144
  node: param,
142
145
  messageId: 'requireDefaultParams',
146
+ data: messageData,
143
147
  fix(fixer) {
144
- const paramText = context
145
- .getSourceCode()
146
- .getText(param);
148
+ const paramText = context.sourceCode.getText(param);
147
149
  return fixer.replaceText(param, `${paramText} = {}`);
148
150
  },
149
151
  });
@@ -152,7 +154,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
152
154
  }
153
155
  else {
154
156
  // If we can't find the type definition, check if it's defined in the same file
155
- const program = context.getSourceCode().ast;
157
+ const program = context.sourceCode.ast;
156
158
  const typeDefinitions = program.body.filter((node) => {
157
159
  if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
158
160
  node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
@@ -172,10 +174,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
172
174
  context.report({
173
175
  node: param,
174
176
  messageId: 'requireDefaultParams',
177
+ data: messageData,
175
178
  fix(fixer) {
176
- const paramText = context
177
- .getSourceCode()
178
- .getText(param);
179
+ const paramText = context.sourceCode.getText(param);
179
180
  return fixer.replaceText(param, `${paramText} = {}`);
180
181
  },
181
182
  });
@@ -191,10 +192,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
191
192
  context.report({
192
193
  node: param,
193
194
  messageId: 'requireDefaultParams',
195
+ data: messageData,
194
196
  fix(fixer) {
195
- const paramText = context
196
- .getSourceCode()
197
- .getText(param);
197
+ const paramText = context.sourceCode.getText(param);
198
198
  return fixer.replaceText(param, `${paramText} = {}`);
199
199
  },
200
200
  });
@@ -214,8 +214,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
214
214
  context.report({
215
215
  node: param,
216
216
  messageId: 'requireDefaultParams',
217
+ data: messageData,
217
218
  fix(fixer) {
218
- const paramText = context.getSourceCode().getText(param);
219
+ const paramText = context.sourceCode.getText(param);
219
220
  return fixer.replaceText(param, `${paramText} = {}`);
220
221
  },
221
222
  });
@@ -0,0 +1,4 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'missingCause' | 'causeNotCatchBinding' | 'missingCatchBinding';
3
+ export declare const requireHttpsErrorCause: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
4
+ export {};