@blumintinc/eslint-plugin-blumint 1.12.5 → 1.13.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 (142) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -20
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  6. package/lib/rules/dynamic-https-errors.js +48 -10
  7. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  8. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
  9. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  10. package/lib/rules/enforce-assert-throws.js +493 -51
  11. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  12. package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
  13. package/lib/rules/enforce-callable-types.js +4 -4
  14. package/lib/rules/enforce-callback-memo.js +143 -18
  15. package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
  16. package/lib/rules/enforce-console-error.d.ts +3 -0
  17. package/lib/rules/enforce-console-error.js +250 -0
  18. package/lib/rules/enforce-css-media-queries.js +24 -46
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  20. package/lib/rules/enforce-dynamic-imports.js +1 -1
  21. package/lib/rules/enforce-exported-function-types.js +3 -3
  22. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  23. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
  24. package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
  25. package/lib/rules/enforce-firestore-facade.js +347 -157
  26. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  27. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  28. package/lib/rules/enforce-global-constants.d.ts +4 -1
  29. package/lib/rules/enforce-global-constants.js +240 -12
  30. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  31. package/lib/rules/enforce-memoize-async.js +96 -27
  32. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  33. package/lib/rules/enforce-memoize-getters.js +123 -0
  34. package/lib/rules/enforce-microdiff.js +2 -1
  35. package/lib/rules/enforce-positive-naming.js +5 -7
  36. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  37. package/lib/rules/enforce-props-argument-name.js +165 -151
  38. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  39. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  40. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  41. package/lib/rules/enforce-querykey-ts.js +274 -0
  42. package/lib/rules/enforce-singular-type-names.js +9 -5
  43. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  44. package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
  45. package/lib/rules/enforce-verb-noun-naming.js +85 -10
  46. package/lib/rules/extract-global-constants.js +57 -45
  47. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
  49. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  50. package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
  51. package/lib/rules/generic-starts-with-t.js +8 -2
  52. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  53. package/lib/rules/memoize-root-level-hocs.js +242 -0
  54. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  55. package/lib/rules/no-array-length-in-deps.js +308 -0
  56. package/lib/rules/no-async-array-filter.js +5 -2
  57. package/lib/rules/no-complex-cloud-params.js +15 -2
  58. package/lib/rules/no-compositing-layer-props.js +4 -2
  59. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  60. package/lib/rules/no-entire-object-hook-deps.js +317 -63
  61. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  62. package/lib/rules/no-excessive-parent-chain.js +273 -0
  63. package/lib/rules/no-firestore-object-arrays.js +345 -30
  64. package/lib/rules/no-hungarian.js +13 -13
  65. package/lib/rules/no-margin-properties.d.ts +7 -1
  66. package/lib/rules/no-margin-properties.js +16 -3
  67. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  68. package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
  69. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  70. package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
  71. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  72. package/lib/rules/no-passthrough-getters.js +170 -0
  73. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  74. package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
  75. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  76. package/lib/rules/no-restricted-properties-fix.js +172 -0
  77. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  78. package/lib/rules/no-separate-loading-state.js +142 -0
  79. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  80. package/lib/rules/no-stale-state-across-await.js +185 -0
  81. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  82. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  83. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  84. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  85. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  86. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  87. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  88. package/lib/rules/no-unused-props.d.ts +3 -0
  89. package/lib/rules/no-unused-props.js +213 -78
  90. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  91. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  92. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  93. package/lib/rules/parallelize-async-operations.js +375 -0
  94. package/lib/rules/prefer-batch-operations.js +199 -33
  95. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  96. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  97. package/lib/rules/prefer-document-flattening.js +239 -0
  98. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  100. package/lib/rules/prefer-fragment-component.js +3 -3
  101. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  102. package/lib/rules/prefer-global-router-state-key.js +230 -31
  103. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  104. package/lib/rules/prefer-next-dynamic.js +366 -0
  105. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  107. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  109. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  110. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  111. package/lib/rules/prefer-settings-object.js +23 -15
  112. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  114. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  116. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  118. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  119. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  120. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  121. package/lib/rules/react-usememo-should-be-component.js +144 -78
  122. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  123. package/lib/rules/require-image-optimized.js +1 -1
  124. package/lib/rules/require-memo.js +11 -1
  125. package/lib/rules/require-usememo-object-literals.js +7 -1
  126. package/lib/rules/semantic-function-prefixes.js +38 -18
  127. package/lib/rules/sync-onwrite-name-func.js +5 -1
  128. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  129. package/lib/rules/test-file-location-enforcement.js +57 -0
  130. package/lib/rules/use-custom-link.js +6 -1
  131. package/lib/rules/use-custom-router.js +6 -1
  132. package/lib/rules/use-latest-callback.d.ts +3 -0
  133. package/lib/rules/use-latest-callback.js +270 -0
  134. package/lib/utils/ASTHelpers.js +10 -0
  135. package/lib/utils/createRule.js +1 -1
  136. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  137. package/lib/utils/ruleTester.d.ts +1 -0
  138. package/lib/utils/ruleTester.js +4 -1
  139. package/package.json +21 -14
  140. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
  141. package/lib/rules/no-restricted-imports-dynamic.d.ts +0 -25
  142. package/lib/rules/no-restricted-imports-dynamic.js +0 -213
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.firestoreTransactionReadsBeforeWrites = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ // Define the operations that are considered reads and writes
7
+ const READ_OPERATIONS = new Set(['get']);
8
+ const WRITE_OPERATIONS = new Set(['set', 'update', 'delete']);
9
+ exports.firestoreTransactionReadsBeforeWrites = (0, createRule_1.createRule)({
10
+ name: 'firestore-transaction-reads-before-writes',
11
+ meta: {
12
+ type: 'problem',
13
+ docs: {
14
+ description: 'Enforce that all Firestore transaction read operations are performed before any write operations',
15
+ recommended: 'error',
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ readsAfterWrites: 'Firestore transaction read operations must be performed before any write operations. Move this read operation before any write operations.',
20
+ },
21
+ },
22
+ defaultOptions: [],
23
+ create(context) {
24
+ // Track transaction objects and their operations
25
+ const transactionScopes = new Map();
26
+ // Helper to check if a node is a transaction parameter
27
+ function isTransactionParameter(param) {
28
+ if (param.type !== utils_1.AST_NODE_TYPES.Identifier)
29
+ return false;
30
+ // Check for type annotation
31
+ const typeAnnotation = param.typeAnnotation;
32
+ if (!typeAnnotation ||
33
+ typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
34
+ // If no type annotation, check the parameter name for common patterns
35
+ // Include 't' as a valid transaction parameter name
36
+ return /^(transaction|tx|t)$/i.test(param.name);
37
+ }
38
+ const type = typeAnnotation.typeAnnotation;
39
+ // Check for Transaction type
40
+ if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
41
+ const typeName = type.typeName;
42
+ // Check for FirebaseFirestore.Transaction
43
+ if (typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
44
+ return (typeName.left.type === utils_1.AST_NODE_TYPES.Identifier &&
45
+ typeName.left.name === 'FirebaseFirestore' &&
46
+ typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
47
+ typeName.right.name === 'Transaction');
48
+ }
49
+ // Check for Transaction or FirestoreTransaction
50
+ if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
51
+ return /Transaction/i.test(typeName.name);
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ // Helper to find the transaction scope for a node
57
+ function findTransactionScope(node) {
58
+ let current = node;
59
+ while (current) {
60
+ // Check if this node is a transaction scope
61
+ if (transactionScopes.has(current)) {
62
+ return current;
63
+ }
64
+ // Check if this is a function with a transaction parameter
65
+ if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
66
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
67
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
68
+ const params = current.params;
69
+ if (params.some(isTransactionParameter)) {
70
+ // If this function has a transaction parameter, track it
71
+ if (!transactionScopes.has(current.body)) {
72
+ const transactionParam = params.find(isTransactionParameter);
73
+ const transactionVariables = new Set();
74
+ if (transactionParam &&
75
+ transactionParam.type === utils_1.AST_NODE_TYPES.Identifier) {
76
+ transactionVariables.add(transactionParam.name);
77
+ }
78
+ transactionScopes.set(current.body, {
79
+ hasWriteOperation: false,
80
+ writeNodes: [],
81
+ transactionVariables,
82
+ });
83
+ }
84
+ return current.body;
85
+ }
86
+ }
87
+ current = current.parent;
88
+ }
89
+ return null;
90
+ }
91
+ // Helper to check if a node is a transaction method call
92
+ function isTransactionMethodCall(node, transactionScope) {
93
+ const callee = node.callee;
94
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
95
+ return { isRead: false, isWrite: false, methodName: null };
96
+ }
97
+ const object = callee.object;
98
+ const property = callee.property;
99
+ // Check if the object is a transaction
100
+ if (object.type !== utils_1.AST_NODE_TYPES.Identifier) {
101
+ return { isRead: false, isWrite: false, methodName: null };
102
+ }
103
+ // If we have a transaction scope, check if the object name is a known transaction variable
104
+ if (transactionScope) {
105
+ const scopeInfo = transactionScopes.get(transactionScope);
106
+ if (scopeInfo && !scopeInfo.transactionVariables.has(object.name)) {
107
+ return { isRead: false, isWrite: false, methodName: null };
108
+ }
109
+ }
110
+ else {
111
+ // Fallback to general pattern matching
112
+ if (!/^(transaction|tx|t)$/i.test(object.name)) {
113
+ return { isRead: false, isWrite: false, methodName: null };
114
+ }
115
+ }
116
+ let methodName = null;
117
+ // Check if the property is a read or write operation
118
+ if (property.type === utils_1.AST_NODE_TYPES.Identifier && !callee.computed) {
119
+ // Normal property access: transaction.get()
120
+ methodName = property.name;
121
+ }
122
+ else if (callee.computed &&
123
+ property.type === utils_1.AST_NODE_TYPES.Literal &&
124
+ typeof property.value === 'string') {
125
+ // Computed property access with string literal: transaction['get']
126
+ methodName = property.value;
127
+ }
128
+ else if (callee.computed &&
129
+ property.type === utils_1.AST_NODE_TYPES.Identifier) {
130
+ // Computed property access with variable: transaction[methodName]
131
+ // This is tricky to analyze statically. For now, we'll be conservative
132
+ // and assume it could be any method. We'll handle this in the caller.
133
+ return { isRead: true, isWrite: true, methodName: null }; // Could be either - let caller decide
134
+ }
135
+ if (!methodName) {
136
+ return { isRead: false, isWrite: false, methodName: null };
137
+ }
138
+ const isRead = READ_OPERATIONS.has(methodName);
139
+ const isWrite = WRITE_OPERATIONS.has(methodName);
140
+ return { isRead, isWrite, methodName };
141
+ }
142
+ return {
143
+ // Track transaction scopes from runTransaction calls
144
+ 'CallExpression[callee.property.name="runTransaction"]'(node) {
145
+ const callback = node.arguments[0];
146
+ if (callback &&
147
+ (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
148
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
149
+ const transactionParam = callback.params.find(isTransactionParameter);
150
+ const transactionVariables = new Set();
151
+ if (transactionParam &&
152
+ transactionParam.type === utils_1.AST_NODE_TYPES.Identifier) {
153
+ transactionVariables.add(transactionParam.name);
154
+ }
155
+ transactionScopes.set(callback.body, {
156
+ hasWriteOperation: false,
157
+ writeNodes: [],
158
+ transactionVariables,
159
+ });
160
+ }
161
+ },
162
+ // Clean up transaction scopes when exiting runTransaction calls
163
+ 'CallExpression[callee.property.name="runTransaction"]:exit'(node) {
164
+ const callback = node.arguments[0];
165
+ if (callback &&
166
+ (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
167
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
168
+ transactionScopes.delete(callback.body);
169
+ }
170
+ },
171
+ // Track variable assignments that reference the transaction
172
+ VariableDeclarator(node) {
173
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
174
+ node.init &&
175
+ node.init.type === utils_1.AST_NODE_TYPES.Identifier) {
176
+ const transactionScope = findTransactionScope(node);
177
+ if (transactionScope) {
178
+ const scopeInfo = transactionScopes.get(transactionScope);
179
+ if (scopeInfo &&
180
+ scopeInfo.transactionVariables.has(node.init.name)) {
181
+ scopeInfo.transactionVariables.add(node.id.name);
182
+ }
183
+ }
184
+ }
185
+ },
186
+ // Check all call expressions for transaction operations
187
+ CallExpression(node) {
188
+ const transactionScope = findTransactionScope(node);
189
+ const { isRead, isWrite, methodName } = isTransactionMethodCall(node, transactionScope);
190
+ if (!isRead && !isWrite) {
191
+ return;
192
+ }
193
+ if (!transactionScope) {
194
+ return;
195
+ }
196
+ const scopeInfo = transactionScopes.get(transactionScope);
197
+ if (!scopeInfo) {
198
+ return;
199
+ }
200
+ // Handle computed property access specially
201
+ if (isRead && isWrite) {
202
+ // This is a computed property access - we need to be more careful
203
+ // Only flag as a read if we already have writes in this scope
204
+ if (scopeInfo.hasWriteOperation) {
205
+ context.report({
206
+ node,
207
+ messageId: 'readsAfterWrites',
208
+ data: { method: methodName ?? 'unknown' },
209
+ });
210
+ }
211
+ // Don't mark as write since we don't know what method it is
212
+ return;
213
+ }
214
+ // If it's a write operation, mark the scope as having a write
215
+ if (isWrite) {
216
+ scopeInfo.hasWriteOperation = true;
217
+ scopeInfo.writeNodes.push(node);
218
+ }
219
+ // If it's a read operation and the scope already has a write, report an error
220
+ if (isRead && scopeInfo.hasWriteOperation) {
221
+ context.report({
222
+ node,
223
+ messageId: 'readsAfterWrites',
224
+ data: { method: methodName ?? 'unknown' },
225
+ });
226
+ }
227
+ },
228
+ };
229
+ },
230
+ });
231
+ //# sourceMappingURL=firestore-transaction-reads-before-writes.js.map
@@ -9,9 +9,15 @@ exports.genericStartsWithT = (0, createRule_1.createRule)({
9
9
  for (const param of node.params) {
10
10
  if (typeof param.name.name === 'string' &&
11
11
  param.name.name[0] !== 'T') {
12
+ const name = param.name.name;
13
+ const suggestedName = `T${name}`;
12
14
  context.report({
13
15
  node: param,
14
16
  messageId: 'genericStartsWithT',
17
+ data: {
18
+ name,
19
+ suggestedName,
20
+ },
15
21
  });
16
22
  }
17
23
  }
@@ -22,12 +28,12 @@ exports.genericStartsWithT = (0, createRule_1.createRule)({
22
28
  meta: {
23
29
  type: 'suggestion',
24
30
  docs: {
25
- description: 'Enforce TypeScript generic types to start with T',
31
+ description: 'Enforce TypeScript generic type parameters to start with T so they stand out from runtime values.',
26
32
  recommended: 'error',
27
33
  },
28
34
  schema: [],
29
35
  messages: {
30
- genericStartsWithT: 'Generic type parameter should start with T.',
36
+ genericStartsWithT: 'Generic type parameter "{{name}}" should start with "T" (e.g., "{{suggestedName}}") so readers immediately recognize it as a generic type rather than a concrete value. T-prefixed generics make type parameters stand out in signatures and prevent confusion with runtime parameters.',
31
37
  },
32
38
  },
33
39
  defaultOptions: [],
@@ -0,0 +1,7 @@
1
+ type Options = [
2
+ {
3
+ additionalHocNames?: string[];
4
+ }
5
+ ];
6
+ export declare const memoizeRootLevelHocs: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"wrapHocInUseMemo", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
7
+ export {};
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoizeRootLevelHocs = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const defaultOptions = [{ additionalHocNames: [] }];
7
+ const isFunctionNode = (node) => node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
8
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
9
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression;
10
+ const isHookName = (name) => /^use[A-Z]/.test(name);
11
+ const isComponentName = (name) => /^[A-Z]/.test(name);
12
+ const forEachChildNode = (node, callback) => {
13
+ for (const key of Object.keys(node)) {
14
+ if (key === 'parent')
15
+ continue;
16
+ const value = node[key];
17
+ if (!value)
18
+ continue;
19
+ if (Array.isArray(value)) {
20
+ for (const child of value) {
21
+ if (child && typeof child.type === 'string') {
22
+ if (callback(child)) {
23
+ return true;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ else if (value && typeof value.type === 'string') {
29
+ if (callback(value)) {
30
+ return true;
31
+ }
32
+ }
33
+ }
34
+ return false;
35
+ };
36
+ const getFunctionName = (node) => {
37
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && node.id) {
38
+ return node.id.name;
39
+ }
40
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
41
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
42
+ const parent = node.parent;
43
+ if (parent &&
44
+ parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
45
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
46
+ return parent.id.name;
47
+ }
48
+ if (parent &&
49
+ parent.type === utils_1.AST_NODE_TYPES.Property &&
50
+ parent.key.type === utils_1.AST_NODE_TYPES.Identifier) {
51
+ return parent.key.name;
52
+ }
53
+ }
54
+ return null;
55
+ };
56
+ const containsJsx = (node, options) => {
57
+ if (!node)
58
+ return false;
59
+ if (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
60
+ node.type === utils_1.AST_NODE_TYPES.JSXFragment) {
61
+ return true;
62
+ }
63
+ return forEachChildNode(node, (child) => {
64
+ if (options?.skipFunctionBodies && isFunctionNode(child)) {
65
+ return false;
66
+ }
67
+ return containsJsx(child, options);
68
+ });
69
+ };
70
+ const hasFunctionParent = (node) => {
71
+ let current = node.parent;
72
+ while (current) {
73
+ if (isFunctionNode(current)) {
74
+ return true;
75
+ }
76
+ current = current.parent;
77
+ }
78
+ return false;
79
+ };
80
+ const getBodyNodeForJsxCheck = (node) => {
81
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
82
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
83
+ return node.body;
84
+ }
85
+ if (node.body && node.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
86
+ return node.body;
87
+ }
88
+ return node.body;
89
+ };
90
+ const isComponentOrHook = (node) => {
91
+ const name = getFunctionName(node);
92
+ const hook = name ? isHookName(name) : false;
93
+ const component = name ? isComponentName(name) : false;
94
+ const nestedFunction = hasFunctionParent(node);
95
+ const jsxBody = containsJsx(getBodyNodeForJsxCheck(node), {
96
+ skipFunctionBodies: !hook && !component,
97
+ });
98
+ if (!hook && !component && nestedFunction) {
99
+ return null;
100
+ }
101
+ if (!hook && !component && !jsxBody) {
102
+ return null;
103
+ }
104
+ if (hook) {
105
+ return { contextLabel: `hook${name ? ` ${name}` : ''}` };
106
+ }
107
+ return { contextLabel: `component${name ? ` ${name}` : ''}` };
108
+ };
109
+ const getCallableIdentifierName = (callee) => {
110
+ const maybeChain = callee;
111
+ if (maybeChain.type === utils_1.AST_NODE_TYPES.ChainExpression) {
112
+ return getCallableIdentifierName(maybeChain.expression);
113
+ }
114
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
115
+ return callee.name;
116
+ }
117
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
118
+ !callee.computed &&
119
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
120
+ return callee.property.name;
121
+ }
122
+ return null;
123
+ };
124
+ const isHocIdentifier = (name, additionalHocs) => {
125
+ if (additionalHocs.has(name)) {
126
+ return true;
127
+ }
128
+ if (!name.startsWith('with')) {
129
+ return false;
130
+ }
131
+ const suffix = name.charAt(4);
132
+ return Boolean(suffix) && /^[A-Z]$/.test(suffix);
133
+ };
134
+ const findHocName = (node, additionalHocs) => {
135
+ const identifier = getCallableIdentifierName(node.callee);
136
+ if (identifier && isHocIdentifier(identifier, additionalHocs)) {
137
+ return identifier;
138
+ }
139
+ if (node.callee.type === utils_1.AST_NODE_TYPES.CallExpression) {
140
+ return findHocName(node.callee, additionalHocs);
141
+ }
142
+ return null;
143
+ };
144
+ /**
145
+ * Detects chained HOC calls where an inner call is immediately invoked by
146
+ * another call (for example, withHoc(Component)()). We only treat calls as
147
+ * part of the same chain when the current CallExpression is the callee of its
148
+ * parent and both resolve to the same HOC name, which prevents duplicate
149
+ * reports for patterns like withHoc(Component)(props).
150
+ */
151
+ const getParentCallExpression = (callExpr) => callExpr.parent &&
152
+ callExpr.parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
153
+ callExpr.parent.callee === callExpr
154
+ ? callExpr.parent
155
+ : null;
156
+ const isPartOfHocChain = (hocName, parentHocName) => Boolean(parentHocName && parentHocName === hocName);
157
+ exports.memoizeRootLevelHocs = (0, createRule_1.createRule)({
158
+ name: 'memoize-root-level-hocs',
159
+ meta: {
160
+ type: 'problem',
161
+ docs: {
162
+ description: 'Prevent creating Higher-Order Components at the root level of React components/hooks without wrapping them in useMemo to keep wrapped component identities stable across renders.',
163
+ recommended: 'error',
164
+ },
165
+ fixable: undefined,
166
+ schema: [
167
+ {
168
+ type: 'object',
169
+ properties: {
170
+ additionalHocNames: {
171
+ type: 'array',
172
+ items: { type: 'string' },
173
+ default: [],
174
+ },
175
+ },
176
+ additionalProperties: false,
177
+ },
178
+ ],
179
+ messages: {
180
+ wrapHocInUseMemo: 'HOC "{{hocName}}" is created inside {{contextLabel}} during render, so every render creates a brand-new wrapped component reference. React treats that as a different component, unmounting/remounting it (resetting internal state) and forcing children to re-render even when props stay the same. Wrap the HOC creation in useMemo with the correct dependencies or hoist it outside the {{contextLabel}} so the wrapped component identity stays stable.',
181
+ },
182
+ },
183
+ defaultOptions,
184
+ create(context, [options]) {
185
+ const additionalHocs = new Set(options?.additionalHocNames ?? []);
186
+ const reportUnmemoizedHoc = (node, hocName, contextInfo) => {
187
+ context.report({
188
+ node,
189
+ messageId: 'wrapHocInUseMemo',
190
+ data: {
191
+ hocName,
192
+ contextLabel: contextInfo.contextLabel,
193
+ },
194
+ });
195
+ };
196
+ const checkHocCall = (callExpr, contextInfo) => {
197
+ const hocName = findHocName(callExpr, additionalHocs);
198
+ const parentCall = getParentCallExpression(callExpr);
199
+ const parentHocName = parentCall && findHocName(parentCall, additionalHocs);
200
+ if (hocName && !isPartOfHocChain(hocName, parentHocName)) {
201
+ reportUnmemoizedHoc(callExpr, hocName, contextInfo);
202
+ }
203
+ };
204
+ const traverseFunctionBody = (node, contextInfo) => {
205
+ const visitNode = (current) => {
206
+ if (isFunctionNode(current) && current !== node) {
207
+ return;
208
+ }
209
+ if (current.type === utils_1.AST_NODE_TYPES.CallExpression) {
210
+ checkHocCall(current, contextInfo);
211
+ }
212
+ forEachChildNode(current, (child) => {
213
+ visitNode(child);
214
+ return false;
215
+ });
216
+ };
217
+ if (!node.body) {
218
+ return;
219
+ }
220
+ if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
221
+ for (const statement of node.body.body) {
222
+ visitNode(statement);
223
+ }
224
+ return;
225
+ }
226
+ visitNode(node.body);
227
+ };
228
+ const analyzeFunction = (node) => {
229
+ const contextInfo = isComponentOrHook(node);
230
+ if (!contextInfo) {
231
+ return;
232
+ }
233
+ traverseFunctionBody(node, contextInfo);
234
+ };
235
+ return {
236
+ FunctionDeclaration: analyzeFunction,
237
+ FunctionExpression: analyzeFunction,
238
+ ArrowFunctionExpression: analyzeFunction,
239
+ };
240
+ },
241
+ });
242
+ //# sourceMappingURL=memoize-root-level-hocs.js.map
@@ -0,0 +1,12 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export type MessageIds = 'noArrayLengthInDeps';
3
+ type Options = [
4
+ {
5
+ hashImport?: {
6
+ source?: string;
7
+ importName?: string;
8
+ };
9
+ }?
10
+ ];
11
+ export declare const noArrayLengthInDeps: TSESLint.RuleModule<"noArrayLengthInDeps", Options, TSESLint.RuleListener>;
12
+ export {};