@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +169 -139
  2. package/lib/index.js +200 -9
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/avoid-utils-directory.js +0 -4
  5. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +43 -4
  7. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  8. package/lib/rules/dynamic-https-errors.js +134 -44
  9. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  10. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
  11. package/lib/rules/enforce-callback-memo.js +1 -1
  12. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  13. package/lib/rules/enforce-console-error.js +35 -7
  14. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  15. package/lib/rules/enforce-date-ttime.js +156 -0
  16. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  17. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  18. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  20. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  21. package/lib/rules/enforce-early-destructuring.js +980 -0
  22. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  23. package/lib/rules/enforce-empty-object-check.js +502 -0
  24. package/lib/rules/enforce-exported-function-types.js +1 -1
  25. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  26. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  27. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  28. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  29. package/lib/rules/enforce-firestore-facade.js +4 -2
  30. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  31. package/lib/rules/enforce-global-constants.js +4 -4
  32. package/lib/rules/enforce-id-capitalization.js +1 -1
  33. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  34. package/lib/rules/enforce-memoize-async.js +71 -21
  35. package/lib/rules/enforce-memoize-getters.js +39 -15
  36. package/lib/rules/enforce-microdiff.js +1 -1
  37. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  38. package/lib/rules/enforce-positive-naming.js +4 -0
  39. package/lib/rules/enforce-props-argument-name.js +44 -18
  40. package/lib/rules/enforce-safe-stringify.js +1 -1
  41. package/lib/rules/enforce-serializable-params.js +3 -3
  42. package/lib/rules/enforce-singular-type-names.js +1 -1
  43. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  44. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  45. package/lib/rules/enforce-storage-context.d.ts +9 -0
  46. package/lib/rules/enforce-storage-context.js +646 -0
  47. package/lib/rules/enforce-timestamp-now.js +23 -7
  48. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  49. package/lib/rules/enforce-transform-memoization.js +416 -0
  50. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  51. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  52. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  53. package/lib/rules/enforce-verb-noun-naming.js +3818 -4644
  54. package/lib/rules/ensure-pointer-events-none.js +28 -2
  55. package/lib/rules/export-if-in-doubt.js +27 -4
  56. package/lib/rules/extract-global-constants.js +40 -13
  57. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  58. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  59. package/lib/rules/flatten-push-calls.d.ts +2 -0
  60. package/lib/rules/flatten-push-calls.js +428 -0
  61. package/lib/rules/global-const-style.js +66 -23
  62. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  63. package/lib/rules/jsdoc-above-field.js +208 -0
  64. package/lib/rules/key-only-outermost-element.js +21 -3
  65. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  66. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  67. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  68. package/lib/rules/memo-compare-deeply-complex-props.js +801 -0
  69. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  70. package/lib/rules/memo-nested-react-components.js +415 -0
  71. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  72. package/lib/rules/no-always-true-false-conditions.js +68 -8
  73. package/lib/rules/no-array-length-in-deps.js +9 -3
  74. package/lib/rules/no-async-foreach.js +215 -12
  75. package/lib/rules/no-circular-references.d.ts +2 -1
  76. package/lib/rules/no-circular-references.js +31 -32
  77. package/lib/rules/no-class-instance-destructuring.js +55 -12
  78. package/lib/rules/no-complex-cloud-params.js +7 -3
  79. package/lib/rules/no-compositing-layer-props.js +2 -2
  80. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  81. package/lib/rules/no-console-error.d.ts +9 -0
  82. package/lib/rules/no-console-error.js +527 -0
  83. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  84. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  85. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  86. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  87. package/lib/rules/no-entire-object-hook-deps.js +52 -7
  88. package/lib/rules/no-excessive-parent-chain.js +4 -1
  89. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  90. package/lib/rules/no-explicit-return-type.js +215 -26
  91. package/lib/rules/no-filter-without-return.js +5 -1
  92. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  93. package/lib/rules/no-firestore-jest-mock.js +126 -8
  94. package/lib/rules/no-firestore-object-arrays.js +67 -12
  95. package/lib/rules/no-handler-suffix.d.ts +12 -0
  96. package/lib/rules/no-handler-suffix.js +305 -0
  97. package/lib/rules/no-hungarian.js +1 -1
  98. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  99. package/lib/rules/no-inline-component-prop.js +465 -0
  100. package/lib/rules/no-jsx-in-hooks.js +6 -1
  101. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  102. package/lib/rules/no-margin-properties.js +6 -6
  103. package/lib/rules/no-memoize-on-static.js +9 -1
  104. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  105. package/lib/rules/no-misused-switch-case.js +22 -1
  106. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  107. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  108. package/lib/rules/no-mock-firebase-admin.js +5 -2
  109. package/lib/rules/no-object-values-on-strings.js +10 -2
  110. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  111. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  112. package/lib/rules/no-passthrough-getters.js +120 -2
  113. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  114. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  115. package/lib/rules/no-redundant-param-types.js +16 -6
  116. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  117. package/lib/rules/no-redundant-this-params.js +508 -0
  118. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  119. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  120. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  121. package/lib/rules/no-restricted-properties-fix.js +11 -10
  122. package/lib/rules/no-separate-loading-state.js +7 -16
  123. package/lib/rules/no-stale-state-across-await.js +1 -1
  124. package/lib/rules/no-type-assertion-returns.js +43 -49
  125. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  126. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  127. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  128. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  129. package/lib/rules/no-unnecessary-destructuring-rename.js +344 -0
  130. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  131. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  132. package/lib/rules/no-unpinned-dependencies.js +36 -5
  133. package/lib/rules/no-unused-props.d.ts +2 -2
  134. package/lib/rules/no-unused-props.js +295 -91
  135. package/lib/rules/no-unused-usestate.js +6 -2
  136. package/lib/rules/no-useless-fragment.js +28 -2
  137. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  138. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  139. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  140. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  141. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  142. package/lib/rules/omit-index-html.d.ts +2 -1
  143. package/lib/rules/omit-index-html.js +62 -7
  144. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  145. package/lib/rules/parallelize-async-operations.js +143 -59
  146. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  147. package/lib/rules/prefer-batch-operations.js +32 -5
  148. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  149. package/lib/rules/prefer-clone-deep.js +3 -3
  150. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  151. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  152. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  153. package/lib/rules/prefer-docsetter-setall.js +243 -0
  154. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  155. package/lib/rules/prefer-fragment-component.js +1 -1
  156. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  157. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  158. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  159. package/lib/rules/prefer-global-router-state-key.js +88 -13
  160. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  161. package/lib/rules/prefer-memoized-props.js +445 -0
  162. package/lib/rules/prefer-next-dynamic.js +60 -69
  163. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +118 -8
  164. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  165. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  166. package/lib/rules/prefer-params-over-parent-id.js +221 -172
  167. package/lib/rules/prefer-settings-object.js +27 -10
  168. package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
  169. package/lib/rules/prefer-type-over-interface.js +7 -2
  170. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  171. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  172. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  173. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  174. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  175. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  176. package/lib/rules/prevent-children-clobber.js +540 -0
  177. package/lib/rules/react-memoize-literals.d.ts +4 -0
  178. package/lib/rules/react-memoize-literals.js +614 -0
  179. package/lib/rules/react-usememo-should-be-component.js +2 -2
  180. package/lib/rules/require-hooks-default-params.js +17 -16
  181. package/lib/rules/require-https-error-cause.d.ts +4 -0
  182. package/lib/rules/require-https-error-cause.js +155 -0
  183. package/lib/rules/require-https-error.js +43 -21
  184. package/lib/rules/require-image-optimized.js +1 -1
  185. package/lib/rules/require-memo.js +18 -10
  186. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  187. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  188. package/lib/rules/require-usememo-object-literals.js +2 -3
  189. package/lib/rules/test-file-location-enforcement.js +1 -1
  190. package/lib/rules/use-latest-callback.js +6 -5
  191. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  192. package/lib/rules/vertically-group-related-functions.js +480 -0
  193. package/lib/utils/ASTHelpers.d.ts +47 -2
  194. package/lib/utils/ASTHelpers.js +370 -112
  195. package/lib/utils/getMethodName.d.ts +27 -0
  196. package/lib/utils/getMethodName.js +35 -0
  197. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  198. package/lib/utils/tsTypeClassifier.js +149 -0
  199. package/package.json +3 -1
@@ -0,0 +1,801 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoCompareDeeplyComplexProps = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ function isUtilMemoModulePath(path) {
7
+ return /(?:^|\/|\\)util\/memo$/.test(path);
8
+ }
9
+ /**
10
+ * Checks if a property name is handled by the default deep equality logic
11
+ * in our custom memo implementation (blumintAreEqual).
12
+ */
13
+ function isDefaultDeepCompareProp(name) {
14
+ return (name === 'sx' ||
15
+ name.endsWith('Sx') ||
16
+ name === 'style' ||
17
+ name.endsWith('Style'));
18
+ }
19
+ function unwrapExpression(expression) {
20
+ let node = expression;
21
+ // Unwrap harmless wrappers so detection treats casted/parenthesized expressions the same.
22
+ while (node.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
23
+ node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
24
+ node.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
25
+ node.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression) {
26
+ node = node.expression;
27
+ }
28
+ return node;
29
+ }
30
+ function escapeStringForCodeGeneration(value) {
31
+ const escaped = value
32
+ .replace(/\\/g, '\\\\')
33
+ .replace(/'/g, "\\'")
34
+ .replace(/\r/g, '\\r')
35
+ .replace(/\n/g, '\\n')
36
+ .replace(/\t/g, '\\t')
37
+ .replace(/\u2028/g, '\\u2028')
38
+ .replace(/\u2029/g, '\\u2029');
39
+ return `'${escaped}'`;
40
+ }
41
+ function isComponentExpression(expr) {
42
+ return (expr.type === utils_1.AST_NODE_TYPES.Identifier ||
43
+ expr.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
44
+ expr.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
45
+ expr.type === utils_1.AST_NODE_TYPES.CallExpression ||
46
+ expr.type === utils_1.AST_NODE_TYPES.MemberExpression);
47
+ }
48
+ function componentDisplayName(expr) {
49
+ if (expr.type === utils_1.AST_NODE_TYPES.Identifier)
50
+ return expr.name;
51
+ if (expr.type === utils_1.AST_NODE_TYPES.FunctionExpression && expr.id) {
52
+ return expr.id.name;
53
+ }
54
+ if (expr.type === utils_1.AST_NODE_TYPES.MemberExpression &&
55
+ !expr.computed &&
56
+ expr.property.type === utils_1.AST_NODE_TYPES.Identifier) {
57
+ return expr.property.name;
58
+ }
59
+ return null;
60
+ }
61
+ function isUndefinedShadowed(scope) {
62
+ let currentScope = scope;
63
+ while (currentScope) {
64
+ if (currentScope.variables.some((variable) => variable.name === 'undefined' && variable.defs.length > 0)) {
65
+ return true;
66
+ }
67
+ currentScope = currentScope.upper ?? null;
68
+ }
69
+ return false;
70
+ }
71
+ function isNullishComparatorArgument(arg, scope) {
72
+ if (arg.type === utils_1.AST_NODE_TYPES.SpreadElement)
73
+ return false;
74
+ const node = unwrapExpression(arg);
75
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === 'undefined') {
76
+ if (scope && isUndefinedShadowed(scope))
77
+ return false;
78
+ return true;
79
+ }
80
+ if (node.type === utils_1.AST_NODE_TYPES.Literal && node.value === null) {
81
+ return true;
82
+ }
83
+ if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
84
+ node.operator === 'void') {
85
+ return true;
86
+ }
87
+ return false;
88
+ }
89
+ function isComparatorProvided(arg, scope) {
90
+ if (!arg)
91
+ return false;
92
+ if (arg.type === utils_1.AST_NODE_TYPES.SpreadElement)
93
+ return true;
94
+ return !isNullishComparatorArgument(arg, scope);
95
+ }
96
+ function rangeWithParentheses(sourceCode, node) {
97
+ const previous = sourceCode.getTokenBefore(node);
98
+ const next = sourceCode.getTokenAfter(node);
99
+ if (previous?.value === '(' && next?.value === ')') {
100
+ return [previous.range[0], next.range[1]];
101
+ }
102
+ return node.range;
103
+ }
104
+ function addBindingNames(pattern, target) {
105
+ switch (pattern.type) {
106
+ case utils_1.AST_NODE_TYPES.Identifier:
107
+ target.add(pattern.name);
108
+ return;
109
+ case utils_1.AST_NODE_TYPES.AssignmentPattern:
110
+ addBindingNames(pattern.left, target);
111
+ return;
112
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
113
+ for (const prop of pattern.properties) {
114
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
115
+ addBindingNames(prop.value, target);
116
+ }
117
+ else if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
118
+ addBindingNames(prop.argument, target);
119
+ }
120
+ }
121
+ return;
122
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
123
+ for (const element of pattern.elements) {
124
+ if (element) {
125
+ addBindingNames(element, target);
126
+ }
127
+ }
128
+ return;
129
+ case utils_1.AST_NODE_TYPES.RestElement:
130
+ addBindingNames(pattern.argument, target);
131
+ return;
132
+ default:
133
+ return;
134
+ }
135
+ }
136
+ function isTopLevelDeclaration(node) {
137
+ if (!node)
138
+ return false;
139
+ return (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
140
+ node.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
141
+ node.type === utils_1.AST_NODE_TYPES.TSEnumDeclaration ||
142
+ node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
143
+ node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
144
+ node.type === utils_1.AST_NODE_TYPES.VariableDeclaration ||
145
+ node.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration);
146
+ }
147
+ function addNamesFromDeclaration(declaration, target) {
148
+ if (declaration.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
149
+ declaration.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
150
+ declaration.type === utils_1.AST_NODE_TYPES.TSEnumDeclaration ||
151
+ declaration.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
152
+ declaration.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
153
+ if (declaration.id) {
154
+ target.add(declaration.id.name);
155
+ }
156
+ return;
157
+ }
158
+ if (declaration.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
159
+ for (const decl of declaration.declarations) {
160
+ addBindingNames(decl.id, target);
161
+ }
162
+ return;
163
+ }
164
+ if (declaration.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration &&
165
+ declaration.id.type === utils_1.AST_NODE_TYPES.Identifier) {
166
+ target.add(declaration.id.name);
167
+ }
168
+ }
169
+ function pickAvailableCompareDeeplyLocalName(used) {
170
+ if (!used.has('compareDeeply'))
171
+ return 'compareDeeply';
172
+ for (let i = 2;; i += 1) {
173
+ const candidate = `compareDeeply${i}`;
174
+ if (!used.has(candidate))
175
+ return candidate;
176
+ }
177
+ }
178
+ function collectUsedNames(sourceCode) {
179
+ const used = new Set();
180
+ for (const stmt of sourceCode.ast.body) {
181
+ if (stmt.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
182
+ for (const spec of stmt.specifiers) {
183
+ used.add(spec.local.name);
184
+ }
185
+ continue;
186
+ }
187
+ if (stmt.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
188
+ if (isTopLevelDeclaration(stmt.declaration)) {
189
+ addNamesFromDeclaration(stmt.declaration, used);
190
+ }
191
+ continue;
192
+ }
193
+ if (stmt.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
194
+ const decl = stmt.declaration;
195
+ if (decl &&
196
+ (decl.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
197
+ decl.type === utils_1.AST_NODE_TYPES.ClassDeclaration)) {
198
+ if (decl.id)
199
+ used.add(decl.id.name);
200
+ }
201
+ else if (decl && decl.type === utils_1.AST_NODE_TYPES.Identifier) {
202
+ used.add(decl.name);
203
+ }
204
+ continue;
205
+ }
206
+ if (isTopLevelDeclaration(stmt)) {
207
+ addNamesFromDeclaration(stmt, used);
208
+ continue;
209
+ }
210
+ }
211
+ for (const scope of sourceCode.scopeManager?.scopes ?? []) {
212
+ for (const variable of scope.variables) {
213
+ used.add(variable.name);
214
+ }
215
+ }
216
+ return used;
217
+ }
218
+ function ensureCompareDeeplyImportFixes(sourceCode, fixer, usedNames, preferredSource) {
219
+ const program = sourceCode.ast;
220
+ const preferredLocalName = pickAvailableCompareDeeplyLocalName(usedNames);
221
+ const memoImports = program.body.filter((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
222
+ node.importKind !== 'type' &&
223
+ typeof node.source.value === 'string' &&
224
+ isUtilMemoModulePath(node.source.value));
225
+ const typeOnlyMemoImports = program.body.filter((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
226
+ node.importKind === 'type' &&
227
+ typeof node.source.value === 'string' &&
228
+ isUtilMemoModulePath(node.source.value));
229
+ for (const memoImport of memoImports) {
230
+ const compareDeeplySpecifier = memoImport.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
231
+ spec.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
232
+ spec.imported.name === 'compareDeeply');
233
+ if (compareDeeplySpecifier) {
234
+ return {
235
+ fixes: [],
236
+ localName: compareDeeplySpecifier.local.name,
237
+ };
238
+ }
239
+ }
240
+ const importSource = preferredSource && isUtilMemoModulePath(preferredSource)
241
+ ? preferredSource
242
+ : memoImports[0]?.source.value ??
243
+ typeOnlyMemoImports[0]?.source.value ??
244
+ 'src/util/memo';
245
+ const compareDeeplySpecifierText = preferredLocalName === 'compareDeeply'
246
+ ? 'compareDeeply'
247
+ : `compareDeeply as ${preferredLocalName}`;
248
+ const importWithNamed = memoImports.find((memoImport) => memoImport.source.value === importSource &&
249
+ memoImport.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier));
250
+ const fallbackImportWithNamed = memoImports.find((memoImport) => memoImport.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier));
251
+ const namedImportTarget = importWithNamed ?? fallbackImportWithNamed;
252
+ if (namedImportTarget) {
253
+ const namedSpecifiers = namedImportTarget.specifiers.filter((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier);
254
+ const lastNamedSpecifier = namedSpecifiers[namedSpecifiers.length - 1] ?? namedSpecifiers[0];
255
+ return {
256
+ fixes: [
257
+ fixer.insertTextAfter(lastNamedSpecifier, `, ${compareDeeplySpecifierText}`),
258
+ ],
259
+ localName: preferredLocalName,
260
+ };
261
+ }
262
+ const importWithDefault = memoImports.find((memoImport) => memoImport.source.value === importSource &&
263
+ memoImport.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier));
264
+ const fallbackImportWithDefault = memoImports.find((memoImport) => memoImport.specifiers.some((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier));
265
+ const defaultImportTarget = importWithDefault ?? fallbackImportWithDefault;
266
+ if (defaultImportTarget) {
267
+ const defaultSpecifier = defaultImportTarget.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier);
268
+ if (defaultSpecifier) {
269
+ return {
270
+ fixes: [
271
+ fixer.insertTextAfter(defaultSpecifier, `, { ${compareDeeplySpecifierText} }`),
272
+ ],
273
+ localName: preferredLocalName,
274
+ };
275
+ }
276
+ }
277
+ const importForInsertion = memoImports.find((memoImport) => memoImport.source.value === importSource) ?? memoImports[0];
278
+ if (importForInsertion) {
279
+ return {
280
+ fixes: [
281
+ fixer.insertTextAfter(importForInsertion, `\nimport { ${compareDeeplySpecifierText} } from '${importSource}';`),
282
+ ],
283
+ localName: preferredLocalName,
284
+ };
285
+ }
286
+ const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
287
+ const importText = `import { ${compareDeeplySpecifierText} } from '${importSource}';\n`;
288
+ if (firstImport) {
289
+ return {
290
+ fixes: [fixer.insertTextBefore(firstImport, importText)],
291
+ localName: preferredLocalName,
292
+ };
293
+ }
294
+ return {
295
+ fixes: [fixer.insertTextBeforeRange([0, 0], importText)],
296
+ localName: preferredLocalName,
297
+ };
298
+ }
299
+ function isComplexType(ts, type, checker) {
300
+ return isComplexTypeInternal(ts, type, checker, new Set());
301
+ }
302
+ function isComplexTypeInternal(ts, type, checker, visited) {
303
+ if (visited.has(type))
304
+ return false;
305
+ visited.add(type);
306
+ const flags = type.flags ?? 0;
307
+ if (isUnionType(ts, flags)) {
308
+ return checkUnionType(ts, type, checker, visited);
309
+ }
310
+ if (isIntersectionType(ts, flags)) {
311
+ return checkIntersectionType(ts, type, checker, visited);
312
+ }
313
+ if (isPrimitiveType(ts, flags)) {
314
+ return false;
315
+ }
316
+ if (isTypeParameter(ts, flags)) {
317
+ return checkTypeParameter(ts, type, checker, visited);
318
+ }
319
+ if (hasCallSignatures(type)) {
320
+ return false;
321
+ }
322
+ if (isArrayOrTupleType(checker, type)) {
323
+ return true;
324
+ }
325
+ if (isObjectType(ts, flags)) {
326
+ return true;
327
+ }
328
+ return false;
329
+ }
330
+ function isUnionType(ts, flags) {
331
+ return (flags & ts.TypeFlags.Union) !== 0;
332
+ }
333
+ function checkUnionType(ts, unionType, checker, visited) {
334
+ return unionType.types.some((t) => isComplexTypeInternal(ts, t, checker, visited));
335
+ }
336
+ function isIntersectionType(ts, flags) {
337
+ return (flags & ts.TypeFlags.Intersection) !== 0;
338
+ }
339
+ function checkIntersectionType(ts, intersectionType, checker, visited) {
340
+ return intersectionType.types.some((t) => isComplexTypeInternal(ts, t, checker, visited));
341
+ }
342
+ function isPrimitiveType(ts, flags) {
343
+ return ((flags &
344
+ (ts.TypeFlags.StringLike |
345
+ ts.TypeFlags.NumberLike |
346
+ ts.TypeFlags.BooleanLike |
347
+ ts.TypeFlags.BigIntLike |
348
+ ts.TypeFlags.ESSymbolLike |
349
+ ts.TypeFlags.Null |
350
+ ts.TypeFlags.Undefined |
351
+ ts.TypeFlags.Void |
352
+ ts.TypeFlags.Never |
353
+ ts.TypeFlags.EnumLike)) !==
354
+ 0);
355
+ }
356
+ function isTypeParameter(ts, flags) {
357
+ return (flags & ts.TypeFlags.TypeParameter) !== 0;
358
+ }
359
+ function checkTypeParameter(ts, type, checker, visited) {
360
+ const constraint = type.getConstraint?.();
361
+ return constraint
362
+ ? isComplexTypeInternal(ts, constraint, checker, visited)
363
+ : false;
364
+ }
365
+ function hasCallSignatures(type) {
366
+ const callSignatures = type.getCallSignatures?.() ?? [];
367
+ return callSignatures.length > 0;
368
+ }
369
+ function isArrayOrTupleType(checker, type) {
370
+ return Boolean(checker.isArrayType?.(type) || checker.isTupleType?.(type));
371
+ }
372
+ function isObjectType(ts, flags) {
373
+ return (flags & ts.TypeFlags.Object) !== 0;
374
+ }
375
+ function getTypeFromSymbol(symbol, checker, tsNode) {
376
+ const decl = symbol.valueDeclaration ??
377
+ symbol.declarations?.[0];
378
+ return checker.getTypeOfSymbolAtLocation(symbol, decl ?? tsNode);
379
+ }
380
+ function extractPropertyDeclaration(prop) {
381
+ return (prop.valueDeclaration ??
382
+ prop.declarations?.[0]);
383
+ }
384
+ function extractAnnotationType(propDeclaration) {
385
+ if (!propDeclaration)
386
+ return undefined;
387
+ if ('type' in propDeclaration) {
388
+ return propDeclaration.type;
389
+ }
390
+ return undefined;
391
+ }
392
+ function shouldTreatAnyAsComplex(prop, propType, ts, treatAnyAsComplex, parentTypeFlags) {
393
+ if (!(propType.flags & ts.TypeFlags.Any))
394
+ return false;
395
+ if (treatAnyAsComplex)
396
+ return true;
397
+ const propDeclaration = extractPropertyDeclaration(prop);
398
+ const annotationType = extractAnnotationType(propDeclaration);
399
+ return ((annotationType && annotationType.kind !== ts.SyntaxKind.AnyKeyword) ||
400
+ (!annotationType && Boolean(parentTypeFlags & ts.TypeFlags.Object)));
401
+ }
402
+ function isPropertyComplex(prop, checker, tsNode, ts, treatAnyAsComplex, parentTypeFlags) {
403
+ const propType = getTypeFromSymbol(prop, checker, tsNode);
404
+ if (isComplexType(ts, propType, checker)) {
405
+ return true;
406
+ }
407
+ return shouldTreatAnyAsComplex(prop, propType, ts, treatAnyAsComplex, parentTypeFlags);
408
+ }
409
+ function getComplexPropertiesFromType(type, checker, tsNode, ts, treatAnyAsComplex = false, parentTypeFlags = 0) {
410
+ const properties = checker.getPropertiesOfType(type);
411
+ const complexProps = [];
412
+ for (const prop of properties) {
413
+ if (prop.name === 'children')
414
+ continue;
415
+ if (isPropertyComplex(prop, checker, tsNode, ts, treatAnyAsComplex, parentTypeFlags)) {
416
+ complexProps.push(prop.name);
417
+ }
418
+ }
419
+ return complexProps;
420
+ }
421
+ function normalizePropsOrder(props) {
422
+ return Array.from(new Set(props)).sort((a, b) => a.localeCompare(b));
423
+ }
424
+ function extractComplexPropsFromSignature(signature, checker, tsNode, ts) {
425
+ const params = signature.getParameters?.() ?? [];
426
+ if (params.length === 0)
427
+ return [];
428
+ const propsSymbol = params[0];
429
+ const propsType = getTypeFromSymbol(propsSymbol, checker, tsNode);
430
+ return getComplexPropertiesFromType(propsType, checker, tsNode, ts, false, propsType.flags ?? 0);
431
+ }
432
+ function extractPropsFromTypeArguments(paramTsNode, checker, ts) {
433
+ let cursor = paramTsNode;
434
+ while (cursor && !ts.isCallExpression(cursor)) {
435
+ cursor = cursor.parent;
436
+ }
437
+ const parentCall = cursor && ts.isCallExpression(cursor) ? cursor : undefined;
438
+ const propsTypeArg = parentCall?.typeArguments?.[1];
439
+ if (propsTypeArg) {
440
+ const typeFromArg = checker.getTypeFromTypeNode(propsTypeArg);
441
+ return getComplexPropertiesFromType(typeFromArg, checker, propsTypeArg, ts, true, typeFromArg.flags ?? 0);
442
+ }
443
+ return [];
444
+ }
445
+ function extractPropsFromFunctionParam(param, services, ts) {
446
+ const checker = services.program.getTypeChecker();
447
+ const paramTsNode = services.esTreeNodeToTSNodeMap.get(param);
448
+ if (!paramTsNode)
449
+ return [];
450
+ const paramType = checker.getTypeAtLocation(paramTsNode);
451
+ if (paramType.flags & ts.TypeFlags.Any) {
452
+ const propsFromTypeArgs = extractPropsFromTypeArguments(paramTsNode, checker, ts);
453
+ if (propsFromTypeArgs.length > 0) {
454
+ return normalizePropsOrder(propsFromTypeArgs);
455
+ }
456
+ }
457
+ const treatAnyAsComplex = Boolean(paramType.flags & ts.TypeFlags.Any);
458
+ return normalizePropsOrder(getComplexPropertiesFromType(paramType, checker, paramTsNode, ts, treatAnyAsComplex, paramType.flags ?? 0));
459
+ }
460
+ function extractPropsFromComponentSignatures(tsNode, checker, ts) {
461
+ const componentType = checker.getTypeAtLocation(tsNode);
462
+ const signatures = componentType.getCallSignatures?.() ?? [];
463
+ const complexProps = new Set();
464
+ for (const signature of signatures) {
465
+ const propsFromSignature = extractComplexPropsFromSignature(signature, checker, tsNode, ts);
466
+ propsFromSignature.forEach((prop) => complexProps.add(prop));
467
+ }
468
+ return normalizePropsOrder(Array.from(complexProps));
469
+ }
470
+ function loadTypeScriptModule() {
471
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
472
+ return require('typescript');
473
+ }
474
+ function toComponentTsNode(componentExpr, services) {
475
+ return services.esTreeNodeToTSNodeMap.get(componentExpr) ?? null;
476
+ }
477
+ function isFunctionComponentWithProps(componentExpr) {
478
+ return ((componentExpr.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
479
+ componentExpr.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
480
+ Boolean(componentExpr.params[0]));
481
+ }
482
+ function analyzeComponentTypes(componentExpr, services) {
483
+ const tsNode = toComponentTsNode(componentExpr, services);
484
+ if (!tsNode)
485
+ return null;
486
+ const ts = loadTypeScriptModule();
487
+ const checker = services.program.getTypeChecker();
488
+ return { ts, tsNode, checker };
489
+ }
490
+ function getComplexPropsFromComponent(componentExpr, services) {
491
+ const analysis = analyzeComponentTypes(componentExpr, services);
492
+ if (!analysis)
493
+ return [];
494
+ const { ts, tsNode, checker } = analysis;
495
+ if (isFunctionComponentWithProps(componentExpr)) {
496
+ return extractPropsFromFunctionParam(componentExpr.params[0], services, ts);
497
+ }
498
+ return extractPropsFromComponentSignatures(tsNode, checker, ts);
499
+ }
500
+ function collectComplexProps(componentExpr, sourceCode, complexPropsCache) {
501
+ const cached = complexPropsCache.get(componentExpr);
502
+ if (cached)
503
+ return cached;
504
+ const services = sourceCode.parserServices;
505
+ if (!services?.program || !services?.esTreeNodeToTSNodeMap) {
506
+ return [];
507
+ }
508
+ try {
509
+ const result = getComplexPropsFromComponent(componentExpr, services);
510
+ complexPropsCache.set(componentExpr, result);
511
+ return result;
512
+ }
513
+ catch (error) {
514
+ if (process.env.ESLINT_CUSTOM_RULES_DEBUG === '1') {
515
+ const componentText = sourceCode.getText(componentExpr);
516
+ // Log to aid debugging when type analysis unexpectedly fails.
517
+ // eslint-disable-next-line no-console
518
+ console.warn('[memo-compare-deeply-complex-props] Type analysis failed for component expression:', componentText, error instanceof Error ? error.stack ?? error.message : String(error));
519
+ }
520
+ return [];
521
+ }
522
+ }
523
+ function findComponentAnalysisTargets(componentArg, getInitializer) {
524
+ const unwrappedComponent = unwrapExpression(componentArg);
525
+ const initializerExpression = unwrappedComponent.type === utils_1.AST_NODE_TYPES.Identifier
526
+ ? getInitializer(unwrappedComponent.name)
527
+ : null;
528
+ const unwrappedInitializer = initializerExpression
529
+ ? unwrapExpression(initializerExpression)
530
+ : null;
531
+ const initializerWrappedCandidate = unwrappedInitializer &&
532
+ unwrappedInitializer.type === utils_1.AST_NODE_TYPES.CallExpression &&
533
+ unwrappedInitializer.arguments.length > 0 &&
534
+ unwrappedInitializer.arguments[0]?.type !== utils_1.AST_NODE_TYPES.SpreadElement
535
+ ? unwrapExpression(unwrappedInitializer.arguments[0])
536
+ : null;
537
+ const wrappedInnerCandidate = unwrappedComponent.type === utils_1.AST_NODE_TYPES.CallExpression &&
538
+ unwrappedComponent.arguments.length > 0 &&
539
+ unwrappedComponent.arguments[0]?.type !== utils_1.AST_NODE_TYPES.SpreadElement
540
+ ? unwrapExpression(unwrappedComponent.arguments[0])
541
+ : null;
542
+ const analysisTarget = (initializerWrappedCandidate &&
543
+ isComponentExpression(initializerWrappedCandidate)
544
+ ? initializerWrappedCandidate
545
+ : null) ??
546
+ (unwrappedInitializer && isComponentExpression(unwrappedInitializer)
547
+ ? unwrappedInitializer
548
+ : null) ??
549
+ (isComponentExpression(unwrappedComponent) ? unwrappedComponent : null) ??
550
+ (wrappedInnerCandidate && isComponentExpression(wrappedInnerCandidate)
551
+ ? wrappedInnerCandidate
552
+ : null);
553
+ if (!analysisTarget)
554
+ return null;
555
+ const preferredAnalysisTarget = initializerWrappedCandidate ?? wrappedInnerCandidate ?? analysisTarget;
556
+ return {
557
+ unwrappedComponent,
558
+ unwrappedInitializer,
559
+ initializerWrappedCandidate,
560
+ wrappedInnerCandidate,
561
+ analysisTarget,
562
+ preferredAnalysisTarget,
563
+ };
564
+ }
565
+ function collectComplexPropsForTargets(targets, getComplexProps) {
566
+ let complexProps = getComplexProps(targets.preferredAnalysisTarget);
567
+ if (complexProps.length === 0 &&
568
+ targets.preferredAnalysisTarget !== targets.analysisTarget) {
569
+ complexProps = getComplexProps(targets.analysisTarget);
570
+ }
571
+ if (complexProps.length === 0 &&
572
+ targets.wrappedInnerCandidate &&
573
+ targets.wrappedInnerCandidate !== targets.preferredAnalysisTarget) {
574
+ complexProps = getComplexProps(targets.wrappedInnerCandidate);
575
+ }
576
+ return complexProps;
577
+ }
578
+ function resolveComponentName(targets, componentArg) {
579
+ return ((targets.initializerWrappedCandidate
580
+ ? componentDisplayName(targets.initializerWrappedCandidate)
581
+ : null) ??
582
+ (targets.unwrappedInitializer
583
+ ? componentDisplayName(targets.unwrappedInitializer)
584
+ : null) ??
585
+ componentDisplayName(targets.unwrappedComponent) ??
586
+ (targets.wrappedInnerCandidate
587
+ ? componentDisplayName(targets.wrappedInnerCandidate)
588
+ : null) ??
589
+ componentDisplayName(componentArg) ??
590
+ 'component');
591
+ }
592
+ function buildMemoFixes(sourceCode, fixer, callExpression, comparatorArg, propsCall, memoSource, scope, resolveCompareDeeplyImport) {
593
+ const fixes = [];
594
+ const importResult = resolveCompareDeeplyImport(fixer, memoSource);
595
+ if (comparatorArg &&
596
+ comparatorArg.type !== utils_1.AST_NODE_TYPES.SpreadElement &&
597
+ isNullishComparatorArgument(comparatorArg, scope)) {
598
+ fixes.push(fixer.replaceTextRange(rangeWithParentheses(sourceCode, comparatorArg), `${importResult.localName}(${propsCall})`));
599
+ }
600
+ else {
601
+ const closingParen = sourceCode.getLastToken(callExpression);
602
+ if (closingParen) {
603
+ const tokenBeforeParen = sourceCode.getTokenBefore(closingParen);
604
+ const comparatorText = `${importResult.localName}(${propsCall})`;
605
+ if (tokenBeforeParen?.value === ',') {
606
+ fixes.push(fixer.replaceText(tokenBeforeParen, `, ${comparatorText}`));
607
+ }
608
+ else {
609
+ fixes.push(fixer.insertTextBefore(closingParen, `, ${comparatorText}`));
610
+ }
611
+ }
612
+ }
613
+ fixes.push(...importResult.fixes);
614
+ return fixes;
615
+ }
616
+ function createMemoImportTracking() {
617
+ const memoIdentifiers = new Map();
618
+ const memoNamespaces = new Map();
619
+ function recordImport(node) {
620
+ if (node.importKind === 'type')
621
+ return;
622
+ if (typeof node.source.value !== 'string')
623
+ return;
624
+ const importPath = node.source.value;
625
+ for (const specifier of node.specifiers) {
626
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
627
+ specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
628
+ specifier.imported.name === 'memo' &&
629
+ (importPath === 'react' || isUtilMemoModulePath(importPath))) {
630
+ memoIdentifiers.set(specifier.local.name, importPath);
631
+ }
632
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
633
+ specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
634
+ if (importPath === 'react') {
635
+ memoNamespaces.set(specifier.local.name, importPath);
636
+ }
637
+ else if (isUtilMemoModulePath(importPath)) {
638
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
639
+ memoNamespaces.set(specifier.local.name, importPath);
640
+ }
641
+ else {
642
+ memoIdentifiers.set(specifier.local.name, importPath);
643
+ }
644
+ }
645
+ }
646
+ }
647
+ }
648
+ function isMemoCall(node) {
649
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
650
+ memoIdentifiers.has(node.callee.name)) {
651
+ return {
652
+ source: memoIdentifiers.get(node.callee.name),
653
+ callee: node.callee,
654
+ };
655
+ }
656
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
657
+ !node.callee.computed &&
658
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
659
+ node.callee.property.name === 'memo' &&
660
+ node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
661
+ memoNamespaces.has(node.callee.object.name)) {
662
+ return {
663
+ source: memoNamespaces.get(node.callee.object.name),
664
+ callee: node.callee,
665
+ };
666
+ }
667
+ return null;
668
+ }
669
+ return {
670
+ memoIdentifiers,
671
+ memoNamespaces,
672
+ recordImport,
673
+ isMemoCall,
674
+ };
675
+ }
676
+ function createComponentInitializerTracker() {
677
+ const componentInitializers = new WeakMap();
678
+ function recordComponentInitializer(node, scope) {
679
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier || !node.init)
680
+ return;
681
+ const currentScopeInitializers = componentInitializers.get(scope) ??
682
+ new Map();
683
+ currentScopeInitializers.set(node.id.name, node.init);
684
+ componentInitializers.set(scope, currentScopeInitializers);
685
+ }
686
+ function getInitializer(name, scope) {
687
+ let currentScope = scope;
688
+ while (currentScope) {
689
+ const initializerMap = componentInitializers.get(currentScope);
690
+ if (initializerMap?.has(name)) {
691
+ return initializerMap.get(name) ?? null;
692
+ }
693
+ currentScope =
694
+ currentScope.upper ?? null;
695
+ }
696
+ return null;
697
+ }
698
+ return {
699
+ recordComponentInitializer,
700
+ getInitializer,
701
+ };
702
+ }
703
+ exports.memoCompareDeeplyComplexProps = (0, createRule_1.createRule)({
704
+ name: 'memo-compare-deeply-complex-props',
705
+ meta: {
706
+ type: 'suggestion',
707
+ docs: {
708
+ description: 'Suggest compareDeeply for memoized components that receive object/array props to avoid shallow comparison re-renders.',
709
+ recommended: 'error',
710
+ requiresTypeChecking: true,
711
+ },
712
+ fixable: 'code',
713
+ schema: [],
714
+ messages: {
715
+ useCompareDeeply: 'What\'s wrong: Memoized component "{{componentName}}" receives complex prop(s) {{propsList}} but memo still uses shallow reference comparison → Why it matters: Objects/arrays are often recreated on each render, so shallow comparison treats them as "changed" and triggers avoidable re-renders → How to fix: Pass compareDeeply({{propsCall}}) as memo\'s second argument to compare those props by value.',
716
+ },
717
+ },
718
+ defaultOptions: [],
719
+ create(context) {
720
+ const sourceCode = context.getSourceCode();
721
+ const usedNames = collectUsedNames(sourceCode);
722
+ const complexPropsCache = new WeakMap();
723
+ const memoImportTracking = createMemoImportTracking();
724
+ const initializerTracking = createComponentInitializerTracker();
725
+ let cachedCompareDeeplyImportLocalName = null;
726
+ function resolveCompareDeeplyImport(fixer, memoSource) {
727
+ if (cachedCompareDeeplyImportLocalName) {
728
+ return { fixes: [], localName: cachedCompareDeeplyImportLocalName };
729
+ }
730
+ const result = ensureCompareDeeplyImportFixes(sourceCode, fixer, usedNames, memoSource);
731
+ cachedCompareDeeplyImportLocalName = result.localName;
732
+ return result;
733
+ }
734
+ function validateMemoCall(node) {
735
+ const memoCall = memoImportTracking.isMemoCall(node);
736
+ if (!memoCall)
737
+ return null;
738
+ if (node.arguments.length === 0 || node.arguments.length > 2)
739
+ return null;
740
+ const componentArg = node.arguments[0];
741
+ if (!componentArg || componentArg.type === utils_1.AST_NODE_TYPES.SpreadElement)
742
+ return null;
743
+ const comparatorArg = node.arguments[1];
744
+ return { memoCall, componentArg, comparatorArg };
745
+ }
746
+ function analyzeComponentAndProps(componentArg, comparatorArg, currentScope, memoSource) {
747
+ const componentTargets = findComponentAnalysisTargets(componentArg, (name) => initializerTracking.getInitializer(name, currentScope));
748
+ if (!componentTargets)
749
+ return null;
750
+ if (isComparatorProvided(comparatorArg, currentScope))
751
+ return null;
752
+ let complexProps = collectComplexPropsForTargets(componentTargets, (expr) => collectComplexProps(expr, sourceCode, complexPropsCache));
753
+ if (isUtilMemoModulePath(memoSource)) {
754
+ complexProps = complexProps.filter((prop) => !isDefaultDeepCompareProp(prop));
755
+ }
756
+ if (complexProps.length === 0)
757
+ return null;
758
+ const componentName = resolveComponentName(componentTargets, componentArg);
759
+ return { complexProps, componentName };
760
+ }
761
+ function generateReportData(complexProps, componentName) {
762
+ const propsList = `[${complexProps.join(', ')}]`;
763
+ const propsCall = complexProps
764
+ .map((prop) => escapeStringForCodeGeneration(prop))
765
+ .join(', ');
766
+ return { componentName, propsList, propsCall };
767
+ }
768
+ return {
769
+ ImportDeclaration: memoImportTracking.recordImport,
770
+ VariableDeclarator(node) {
771
+ initializerTracking.recordComponentInitializer(node, context.getScope());
772
+ },
773
+ CallExpression(node) {
774
+ const validationResult = validateMemoCall(node);
775
+ if (!validationResult)
776
+ return;
777
+ const { memoCall, componentArg, comparatorArg } = validationResult;
778
+ const currentScope = context.getScope();
779
+ const analysisResult = analyzeComponentAndProps(componentArg, comparatorArg, currentScope, memoCall.source);
780
+ if (!analysisResult)
781
+ return;
782
+ const { complexProps, componentName } = analysisResult;
783
+ const { propsList, propsCall } = generateReportData(complexProps, componentName);
784
+ context.report({
785
+ node,
786
+ messageId: 'useCompareDeeply',
787
+ data: {
788
+ componentName,
789
+ propsList,
790
+ propsCall,
791
+ },
792
+ fix(fixer) {
793
+ return buildMemoFixes(sourceCode, fixer, node, comparatorArg, propsCall, memoCall.source, currentScope, resolveCompareDeeplyImport);
794
+ },
795
+ });
796
+ },
797
+ };
798
+ },
799
+ });
800
+ exports.default = exports.memoCompareDeeplyComplexProps;
801
+ //# sourceMappingURL=memo-compare-deeply-complex-props.js.map