@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
@@ -25,7 +25,9 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
25
25
  },
26
26
  defaultOptions: [],
27
27
  create(context) {
28
- const sourceCode = context.getSourceCode();
28
+ const sourceCode = context.sourceCode;
29
+ // Prevent duplicate import insertions when multiple fixes target the same query key constant.
30
+ const scheduledQueryKeyNamedImports = new Set();
29
31
  // Track imports from queryKeys.ts
30
32
  const queryKeyImports = new Map();
31
33
  // Track namespace imports (import * as QueryKeys from ...)
@@ -172,15 +174,42 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
172
174
  .replace(/^_|_$/g, '');
173
175
  return `QUERY_KEY_${normalizedKey}`;
174
176
  }
177
+ /**
178
+ * Helper to find a key in an import map based on a predicate
179
+ */
180
+ function findImportKey(importMap, predicate) {
181
+ return Array.from(importMap.entries()).find(([, value]) => predicate(value))?.[0];
182
+ }
183
+ /**
184
+ * Helper to find the last directive prologue in a statement list
185
+ */
186
+ function findLastDirective(body) {
187
+ let lastDirective;
188
+ for (const stmt of body) {
189
+ if (stmt.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
190
+ stmt.expression.type === utils_1.AST_NODE_TYPES.Literal &&
191
+ typeof stmt.expression.value === 'string' &&
192
+ typeof stmt.directive === 'string') {
193
+ lastDirective = stmt;
194
+ continue;
195
+ }
196
+ break;
197
+ }
198
+ return lastDirective;
199
+ }
175
200
  return {
176
201
  // Track imports from queryKeys.ts
177
202
  ImportDeclaration(node) {
203
+ if (node.importKind === 'type')
204
+ return;
178
205
  if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
179
206
  typeof node.source.value === 'string') {
180
207
  const source = node.source.value;
181
208
  if (isQueryKeysSource(source)) {
182
209
  node.specifiers.forEach((spec) => {
183
210
  if (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
211
+ if (spec.importKind === 'type')
212
+ return;
184
213
  const imported = spec.imported.name;
185
214
  const local = spec.local.name;
186
215
  queryKeyImports.set(local, { source, imported });
@@ -235,23 +264,69 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
235
264
  const suggestedConstant = generateAutoFix(keyValue.value);
236
265
  if (suggestedConstant) {
237
266
  const fixes = [];
238
- // 1) Replace the literal with the constant
239
- fixes.push(fixer.replaceText(keyValue, suggestedConstant));
240
- // 2) Ensure an import exists for the suggested constant
241
- const sourceCode = context.getSourceCode();
242
- const alreadyImportedNamed = Array.from(queryKeyImports.values()).some((info) => isQueryKeysSource(info.source) &&
267
+ const namespaceAlias = findImportKey(namespaceImports, isQueryKeysSource);
268
+ const defaultAlias = findImportKey(defaultImports, isQueryKeysSource);
269
+ // Check if the constant is already imported (possibly with an alias)
270
+ const existingNamedImport = findImportKey(queryKeyImports, (info) => isQueryKeysSource(info.source) &&
243
271
  info.imported === suggestedConstant);
244
- const hasNamespaceOrDefault = namespaceImports.size > 0 ||
245
- defaultImports.size > 0;
246
- if (!alreadyImportedNamed && !hasNamespaceOrDefault) {
272
+ const localName = existingNamedImport;
273
+ const importAlias = namespaceAlias ?? defaultAlias;
274
+ const formatConstantReference = (alias, constant) => alias ? `${alias}.${constant}` : constant;
275
+ const replacementText = localName
276
+ ? localName
277
+ : formatConstantReference(importAlias, suggestedConstant);
278
+ // 1) Replace the literal with the constant (qualify if alias exists)
279
+ fixes.push(fixer.replaceText(keyValue, replacementText));
280
+ // 2) Ensure an import exists for the suggested constant
281
+ const hasNamespaceOrDefault = Boolean(importAlias);
282
+ if (!existingNamedImport &&
283
+ !hasNamespaceOrDefault) {
284
+ if (scheduledQueryKeyNamedImports.has(suggestedConstant)) {
285
+ return fixes;
286
+ }
247
287
  const importText = `import { ${suggestedConstant} } from '@/util/routing/queryKeys';\n`;
248
- const firstImport = sourceCode.ast.body.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
249
- if (firstImport) {
250
- fixes.push(fixer.insertTextBefore(firstImport, importText));
288
+ const queryKeysNamedImport = sourceCode.ast.body.find((n) => n.type ===
289
+ utils_1.AST_NODE_TYPES.ImportDeclaration &&
290
+ n.importKind !== 'type' &&
291
+ n.source.type === utils_1.AST_NODE_TYPES.Literal &&
292
+ typeof n.source.value === 'string' &&
293
+ isQueryKeysSource(n.source.value) &&
294
+ n.specifiers.some((s) => s.type ===
295
+ utils_1.AST_NODE_TYPES.ImportSpecifier));
296
+ const sideEffectImport = sourceCode.ast.body.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
297
+ n.source.type === utils_1.AST_NODE_TYPES.Literal &&
298
+ typeof n.source.value === 'string' &&
299
+ isQueryKeysSource(n.source.value) &&
300
+ n.specifiers.length === 0);
301
+ if (queryKeysNamedImport) {
302
+ const importSpecifiers = queryKeysNamedImport.specifiers.filter((spec) => spec.type ===
303
+ utils_1.AST_NODE_TYPES.ImportSpecifier);
304
+ const lastSpecifier = importSpecifiers[importSpecifiers.length - 1];
305
+ fixes.push(fixer.insertTextAfter(lastSpecifier, `, ${suggestedConstant}`));
306
+ }
307
+ else if (sideEffectImport) {
308
+ fixes.push(fixer.replaceText(sideEffectImport, importText.trimEnd()));
251
309
  }
252
310
  else {
253
- fixes.push(fixer.insertTextBeforeRange([0, 0], importText));
311
+ const firstImport = sourceCode.ast.body.find((n) => n.type ===
312
+ utils_1.AST_NODE_TYPES.ImportDeclaration &&
313
+ n.importKind !== 'type') ||
314
+ sourceCode.ast.body.find((n) => n.type ===
315
+ utils_1.AST_NODE_TYPES.ImportDeclaration);
316
+ if (firstImport) {
317
+ fixes.push(fixer.insertTextBefore(firstImport, importText));
318
+ }
319
+ else {
320
+ const lastDirective = findLastDirective(sourceCode.ast.body);
321
+ if (lastDirective) {
322
+ fixes.push(fixer.insertTextAfter(lastDirective, `\n${importText}`));
323
+ }
324
+ else {
325
+ fixes.push(fixer.insertTextBeforeRange([0, 0], importText));
326
+ }
327
+ }
254
328
  }
329
+ scheduledQueryKeyNamedImports.add(suggestedConstant);
255
330
  }
256
331
  return fixes;
257
332
  }
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'memoizeReferenceProp' | 'avoidPrimitiveMemo';
2
+ export declare const preferMemoizedProps: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferMemoizedProps = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const HOOK_NAMES = new Set(['useMemo', 'useCallback']);
7
+ exports.preferMemoizedProps = (0, createRule_1.createRule)({
8
+ name: 'prefer-memoized-props',
9
+ meta: {
10
+ type: 'suggestion',
11
+ docs: {
12
+ description: 'Require memoizing reference props (objects, arrays, functions) inside React.memo components while avoiding unnecessary useMemo for pass-through values.',
13
+ recommended: 'error',
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ memoizeReferenceProp: 'Prop "{{propName}}" in a React.memo component receives a {{kind}} that is recreated every render, so memoized children lose referential equality and re-render. Memoize this {{kind}} with useMemo/useCallback or hoist a stable constant so the prop reference stays stable.',
18
+ avoidPrimitiveMemo: 'useMemo around "{{value}}" only wraps a pass-through value. Primitives already compare by value, and wrapping existing references without creating new objects does not improve stability. Return the value directly instead of adding memoization noise.',
19
+ },
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ const sourceCode = context.getSourceCode();
24
+ const visitorKeys = sourceCode.visitorKeys;
25
+ const memoizedComponents = new WeakSet();
26
+ const memoScopes = [];
27
+ const componentStack = [];
28
+ const scopedFunctions = new WeakSet();
29
+ function pushMemoScope() {
30
+ memoScopes.push(new Map());
31
+ }
32
+ function popMemoScope() {
33
+ memoScopes.pop();
34
+ }
35
+ function currentMemoScope() {
36
+ return memoScopes[memoScopes.length - 1];
37
+ }
38
+ function declareFunctionBinding(name, node) {
39
+ const scope = currentMemoScope();
40
+ if (!scope)
41
+ return;
42
+ scope.set(name, node);
43
+ }
44
+ function resolveFunctionBinding(name) {
45
+ for (let i = memoScopes.length - 1; i >= 0; i -= 1) {
46
+ const binding = memoScopes[i].get(name);
47
+ if (binding) {
48
+ return binding;
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ function hoistFunctionDeclarations(statements) {
54
+ const scope = currentMemoScope();
55
+ if (!scope)
56
+ return;
57
+ for (const statement of statements) {
58
+ if (statement.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
59
+ statement.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
60
+ scope.set(statement.id.name, statement);
61
+ }
62
+ }
63
+ }
64
+ function registerFunctionVariable(node) {
65
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
66
+ return;
67
+ const { init } = node;
68
+ if (init &&
69
+ (init.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
70
+ init.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
71
+ declareFunctionBinding(node.id.name, init);
72
+ }
73
+ }
74
+ function isReactMemoCallee(callee) {
75
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === 'memo') {
76
+ return true;
77
+ }
78
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
79
+ !callee.computed &&
80
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
81
+ callee.property.name === 'memo') {
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+ function isHookCall(node, name) {
87
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
88
+ node.callee.name === name) {
89
+ return true;
90
+ }
91
+ return (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
92
+ !node.callee.computed &&
93
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
94
+ node.callee.property.name === name);
95
+ }
96
+ function collectMemoizedComponents(node) {
97
+ pushMemoScope();
98
+ if (node.type === utils_1.AST_NODE_TYPES.Program) {
99
+ hoistFunctionDeclarations(node.body);
100
+ }
101
+ function traverse(current) {
102
+ const isFunctionLike = current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
103
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
104
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression;
105
+ if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
106
+ current.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
107
+ declareFunctionBinding(current.id.name, current);
108
+ }
109
+ let pushedScope = false;
110
+ if (current.type === utils_1.AST_NODE_TYPES.BlockStatement || isFunctionLike) {
111
+ pushMemoScope();
112
+ pushedScope = true;
113
+ if (isFunctionLike &&
114
+ 'id' in current &&
115
+ current.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
116
+ declareFunctionBinding(current.id.name, current);
117
+ }
118
+ if (current.type === utils_1.AST_NODE_TYPES.BlockStatement) {
119
+ hoistFunctionDeclarations(current.body);
120
+ }
121
+ else if (isFunctionLike &&
122
+ current.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
123
+ hoistFunctionDeclarations(current.body.body);
124
+ }
125
+ }
126
+ if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
127
+ registerFunctionVariable(current);
128
+ }
129
+ if (current.type === utils_1.AST_NODE_TYPES.CallExpression &&
130
+ isReactMemoCallee(current.callee)) {
131
+ const [firstArg] = current.arguments;
132
+ if (firstArg &&
133
+ (firstArg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
134
+ firstArg.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
135
+ memoizedComponents.add(firstArg);
136
+ }
137
+ else if (firstArg && firstArg.type === utils_1.AST_NODE_TYPES.Identifier) {
138
+ const binding = resolveFunctionBinding(firstArg.name);
139
+ if (binding) {
140
+ memoizedComponents.add(binding);
141
+ }
142
+ }
143
+ }
144
+ const keys = visitorKeys[current.type] ?? [];
145
+ for (const key of keys) {
146
+ const value = current[key];
147
+ if (Array.isArray(value)) {
148
+ for (const child of value) {
149
+ if (child && typeof child === 'object') {
150
+ traverse(child);
151
+ }
152
+ }
153
+ }
154
+ else if (value && typeof value === 'object') {
155
+ traverse(value);
156
+ }
157
+ }
158
+ if (pushedScope) {
159
+ popMemoScope();
160
+ }
161
+ }
162
+ traverse(node);
163
+ popMemoScope();
164
+ }
165
+ collectMemoizedComponents(sourceCode.ast);
166
+ function isMemoizedComponent(node) {
167
+ return memoizedComponents.has(node);
168
+ }
169
+ function pushComponent(node) {
170
+ componentStack.push({ node, scopes: [new Map()] });
171
+ }
172
+ function popComponent(node) {
173
+ const top = componentStack[componentStack.length - 1];
174
+ if (top && top.node === node) {
175
+ componentStack.pop();
176
+ }
177
+ }
178
+ function currentComponent() {
179
+ return componentStack[componentStack.length - 1];
180
+ }
181
+ function currentScope() {
182
+ const component = currentComponent();
183
+ if (!component)
184
+ return undefined;
185
+ return component.scopes[component.scopes.length - 1];
186
+ }
187
+ function pushScopeForNestedFunction(node) {
188
+ const component = currentComponent();
189
+ if (!component)
190
+ return;
191
+ component.scopes.push(new Map());
192
+ scopedFunctions.add(node);
193
+ }
194
+ function popScopeForNestedFunction(node) {
195
+ const component = currentComponent();
196
+ if (!component)
197
+ return;
198
+ if (!scopedFunctions.has(node))
199
+ return;
200
+ component.scopes.pop();
201
+ scopedFunctions.delete(node);
202
+ }
203
+ function unwrapExpression(expression) {
204
+ let current = expression;
205
+ while (true) {
206
+ if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
207
+ current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
208
+ current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
209
+ current = current.expression;
210
+ continue;
211
+ }
212
+ if (current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
213
+ current = current.expression;
214
+ continue;
215
+ }
216
+ break;
217
+ }
218
+ return current;
219
+ }
220
+ function getBindingKind(init) {
221
+ if (!init) {
222
+ return null;
223
+ }
224
+ const unwrapped = unwrapExpression(init);
225
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.CallExpression &&
226
+ ((unwrapped.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
227
+ HOOK_NAMES.has(unwrapped.callee.name)) ||
228
+ (unwrapped.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
229
+ !unwrapped.callee.computed &&
230
+ unwrapped.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
231
+ HOOK_NAMES.has(unwrapped.callee.property.name)))) {
232
+ return null;
233
+ }
234
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.ObjectExpression)
235
+ return 'object';
236
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.ArrayExpression)
237
+ return 'array';
238
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
239
+ unwrapped.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
240
+ return 'function';
241
+ }
242
+ return null;
243
+ }
244
+ function recordBinding(node) {
245
+ const scope = currentScope();
246
+ if (!scope)
247
+ return;
248
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier || !node.init) {
249
+ return;
250
+ }
251
+ const initializer = unwrapExpression(node.init);
252
+ const aliasKind = initializer.type === utils_1.AST_NODE_TYPES.Identifier
253
+ ? findBindingKind(initializer.name)
254
+ : null;
255
+ const kind = aliasKind ?? getBindingKind(initializer);
256
+ if (kind)
257
+ scope.set(node.id.name, kind);
258
+ }
259
+ function findBindingKind(name) {
260
+ for (let i = componentStack.length - 1; i >= 0; i -= 1) {
261
+ const component = componentStack[i];
262
+ for (let j = component.scopes.length - 1; j >= 0; j -= 1) {
263
+ const binding = component.scopes[j].get(name);
264
+ if (binding)
265
+ return binding;
266
+ }
267
+ }
268
+ return null;
269
+ }
270
+ function getAttributeName(name) {
271
+ if (name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
272
+ return name.name;
273
+ }
274
+ if (name.type === utils_1.AST_NODE_TYPES.JSXNamespacedName) {
275
+ return name.name.name;
276
+ }
277
+ return 'prop';
278
+ }
279
+ function reportReference(node, propName, kind) {
280
+ context.report({
281
+ node,
282
+ messageId: 'memoizeReferenceProp',
283
+ data: { propName, kind },
284
+ });
285
+ }
286
+ function isEnvironmentFreeLiteral(expression) {
287
+ const unwrapped = unwrapExpression(expression);
288
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Literal)
289
+ return true;
290
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
291
+ unwrapped.expressions.length === 0) {
292
+ return true;
293
+ }
294
+ return false;
295
+ }
296
+ function isStableReference(expression) {
297
+ const unwrapped = unwrapExpression(expression);
298
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Literal)
299
+ return true;
300
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
301
+ return unwrapped.expressions.every((expr) => isStableReference(expr));
302
+ }
303
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier)
304
+ return true;
305
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression)
306
+ return true;
307
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.ChainExpression &&
308
+ unwrapped.expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
309
+ return true;
310
+ }
311
+ return false;
312
+ }
313
+ function extractReturnExpression(callback) {
314
+ if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
315
+ return callback.body;
316
+ }
317
+ const [statement] = callback.body.body;
318
+ if (statement &&
319
+ statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
320
+ statement.argument) {
321
+ return statement.argument;
322
+ }
323
+ return null;
324
+ }
325
+ function handleUseMemo(node) {
326
+ if (!currentComponent() || !isHookCall(node, 'useMemo')) {
327
+ return;
328
+ }
329
+ const [callback, deps] = node.arguments;
330
+ if (!callback ||
331
+ (callback.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
332
+ callback.type !== utils_1.AST_NODE_TYPES.FunctionExpression)) {
333
+ return;
334
+ }
335
+ const returnExpression = extractReturnExpression(callback);
336
+ if (!returnExpression)
337
+ return;
338
+ const dependencies = deps && deps.type === utils_1.AST_NODE_TYPES.ArrayExpression ? deps : null;
339
+ if (dependencies &&
340
+ dependencies.elements.length === 0 &&
341
+ !isEnvironmentFreeLiteral(returnExpression)) {
342
+ return;
343
+ }
344
+ if (isStableReference(returnExpression)) {
345
+ const valueText = sourceCode.getText(returnExpression).slice(0, 80);
346
+ context.report({
347
+ node,
348
+ messageId: 'avoidPrimitiveMemo',
349
+ data: { value: valueText },
350
+ });
351
+ }
352
+ }
353
+ function handleJSXAttribute(node) {
354
+ if (!currentComponent())
355
+ return;
356
+ if (!node.value ||
357
+ node.value.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
358
+ return;
359
+ }
360
+ const rawExpression = node.value.expression;
361
+ if (rawExpression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression) {
362
+ return;
363
+ }
364
+ const expression = unwrapExpression(rawExpression);
365
+ const propName = getAttributeName(node.name);
366
+ if (expression.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
367
+ reportReference(expression, propName, 'object');
368
+ return;
369
+ }
370
+ if (expression.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
371
+ reportReference(expression, propName, 'array');
372
+ return;
373
+ }
374
+ if (expression.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
375
+ expression.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
376
+ reportReference(expression, propName, 'function');
377
+ return;
378
+ }
379
+ if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
380
+ const bindingKind = findBindingKind(expression.name);
381
+ if (bindingKind) {
382
+ reportReference(expression, propName, bindingKind);
383
+ }
384
+ }
385
+ }
386
+ return {
387
+ FunctionDeclaration(node) {
388
+ if (isMemoizedComponent(node)) {
389
+ pushComponent(node);
390
+ return;
391
+ }
392
+ const scope = currentScope();
393
+ if (scope && node.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
394
+ scope.set(node.id.name, 'function');
395
+ }
396
+ if (currentComponent()) {
397
+ pushScopeForNestedFunction(node);
398
+ }
399
+ },
400
+ 'FunctionDeclaration:exit'(node) {
401
+ if (isMemoizedComponent(node)) {
402
+ popComponent(node);
403
+ return;
404
+ }
405
+ popScopeForNestedFunction(node);
406
+ },
407
+ FunctionExpression(node) {
408
+ if (isMemoizedComponent(node)) {
409
+ pushComponent(node);
410
+ return;
411
+ }
412
+ if (currentComponent()) {
413
+ pushScopeForNestedFunction(node);
414
+ }
415
+ },
416
+ 'FunctionExpression:exit'(node) {
417
+ if (isMemoizedComponent(node)) {
418
+ popComponent(node);
419
+ return;
420
+ }
421
+ popScopeForNestedFunction(node);
422
+ },
423
+ ArrowFunctionExpression(node) {
424
+ if (isMemoizedComponent(node)) {
425
+ pushComponent(node);
426
+ return;
427
+ }
428
+ if (currentComponent()) {
429
+ pushScopeForNestedFunction(node);
430
+ }
431
+ },
432
+ 'ArrowFunctionExpression:exit'(node) {
433
+ if (isMemoizedComponent(node)) {
434
+ popComponent(node);
435
+ return;
436
+ }
437
+ popScopeForNestedFunction(node);
438
+ },
439
+ VariableDeclarator: recordBinding,
440
+ CallExpression: handleUseMemo,
441
+ JSXAttribute: handleJSXAttribute,
442
+ };
443
+ },
444
+ });
445
+ //# sourceMappingURL=prefer-memoized-props.js.map