@blumintinc/eslint-plugin-blumint 1.12.6 → 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 (141) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -17
  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-type-assertion-returns.js +6 -0
  86. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  87. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  88. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  89. package/lib/rules/no-unused-props.d.ts +3 -0
  90. package/lib/rules/no-unused-props.js +213 -78
  91. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  92. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  93. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  94. package/lib/rules/parallelize-async-operations.js +375 -0
  95. package/lib/rules/prefer-batch-operations.js +199 -33
  96. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  97. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  98. package/lib/rules/prefer-document-flattening.js +239 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  100. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  101. package/lib/rules/prefer-fragment-component.js +3 -3
  102. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  103. package/lib/rules/prefer-global-router-state-key.js +230 -31
  104. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  105. package/lib/rules/prefer-next-dynamic.js +366 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  107. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  109. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  110. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  111. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  112. package/lib/rules/prefer-settings-object.js +23 -15
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  114. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  116. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  118. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  119. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  120. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  121. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  122. package/lib/rules/react-usememo-should-be-component.js +144 -78
  123. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  124. package/lib/rules/require-image-optimized.js +1 -1
  125. package/lib/rules/require-memo.js +11 -1
  126. package/lib/rules/require-usememo-object-literals.js +7 -1
  127. package/lib/rules/semantic-function-prefixes.js +38 -18
  128. package/lib/rules/sync-onwrite-name-func.js +5 -1
  129. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  130. package/lib/rules/test-file-location-enforcement.js +57 -0
  131. package/lib/rules/use-custom-link.js +6 -1
  132. package/lib/rules/use-custom-router.js +6 -1
  133. package/lib/rules/use-latest-callback.d.ts +3 -0
  134. package/lib/rules/use-latest-callback.js +270 -0
  135. package/lib/utils/ASTHelpers.js +10 -0
  136. package/lib/utils/createRule.js +1 -1
  137. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  138. package/lib/utils/ruleTester.d.ts +1 -0
  139. package/lib/utils/ruleTester.js +4 -1
  140. package/package.json +21 -14
  141. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
@@ -0,0 +1,375 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parallelizeAsyncOperations = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const defaultOptions = [
7
+ {
8
+ sideEffectPatterns: [
9
+ 'updatecounter',
10
+ 'setcounter',
11
+ 'incrementcounter',
12
+ 'decrementcounter',
13
+ 'updatethreshold',
14
+ 'setthreshold',
15
+ 'checkthreshold',
16
+ ],
17
+ },
18
+ ];
19
+ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
20
+ name: 'parallelize-async-operations',
21
+ meta: {
22
+ type: 'suggestion',
23
+ docs: {
24
+ description: 'Enforce the use of Promise.all() when multiple independent asynchronous operations are awaited sequentially',
25
+ recommended: 'error',
26
+ },
27
+ fixable: 'code',
28
+ schema: [
29
+ {
30
+ type: 'object',
31
+ properties: {
32
+ sideEffectPatterns: {
33
+ type: 'array',
34
+ items: {
35
+ anyOf: [
36
+ { type: 'string' },
37
+ { type: 'object', instanceof: 'RegExp' },
38
+ ],
39
+ },
40
+ default: [],
41
+ },
42
+ },
43
+ additionalProperties: false,
44
+ },
45
+ ],
46
+ messages: {
47
+ parallelizeAsyncOperations: 'Multiple sequential awaits detected. Consider using Promise.all() to parallelize independent async operations for better performance.',
48
+ },
49
+ },
50
+ defaultOptions,
51
+ create(context, [options]) {
52
+ const sourceCode = context.getSourceCode();
53
+ const sideEffectMatchers = (options?.sideEffectPatterns ?? []).map((pattern) => typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern);
54
+ const reportedRanges = new Set();
55
+ /**
56
+ * Checks if a node is an await expression
57
+ */
58
+ function isAwaitExpression(node) {
59
+ return node.type === utils_1.AST_NODE_TYPES.AwaitExpression;
60
+ }
61
+ /**
62
+ * Checks if a node is a variable declaration with an await expression initializer
63
+ */
64
+ function isVariableDeclarationWithAwait(node) {
65
+ if (node.type !== utils_1.AST_NODE_TYPES.VariableDeclaration) {
66
+ return false;
67
+ }
68
+ return node.declarations.some((declaration) => declaration.init && isAwaitExpression(declaration.init));
69
+ }
70
+ /**
71
+ * Checks if a node is an expression statement with an await expression
72
+ */
73
+ function isExpressionStatementWithAwait(node) {
74
+ return (node.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
75
+ node.expression.type === utils_1.AST_NODE_TYPES.AwaitExpression);
76
+ }
77
+ /**
78
+ * Extracts the await expression from a node
79
+ */
80
+ function getAwaitExpression(node) {
81
+ if (isAwaitExpression(node)) {
82
+ return node;
83
+ }
84
+ if (node.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
85
+ isAwaitExpression(node.expression)) {
86
+ return node.expression;
87
+ }
88
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
89
+ for (const declaration of node.declarations) {
90
+ if (declaration.init && isAwaitExpression(declaration.init)) {
91
+ return declaration.init;
92
+ }
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ /**
98
+ * Checks if an identifier is used in a node
99
+ */
100
+ function isIdentifierUsedInNode(identifier, node) {
101
+ let isUsed = false;
102
+ function visit(node) {
103
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier &&
104
+ node.name === identifier) {
105
+ isUsed = true;
106
+ return;
107
+ }
108
+ // Recursively visit all child nodes
109
+ for (const key in node) {
110
+ if (key === 'parent' || key === 'range' || key === 'loc')
111
+ continue;
112
+ const child = node[key];
113
+ if (child && typeof child === 'object') {
114
+ if (Array.isArray(child)) {
115
+ for (const item of child) {
116
+ if (item && typeof item === 'object' && 'type' in item) {
117
+ visit(item);
118
+ }
119
+ }
120
+ }
121
+ else if ('type' in child) {
122
+ visit(child);
123
+ }
124
+ }
125
+ }
126
+ }
127
+ visit(node);
128
+ return isUsed;
129
+ }
130
+ /**
131
+ * Checks if there are dependencies between await expressions
132
+ */
133
+ function hasDependencies(awaitNodes, variableNames, sideEffectPatterns) {
134
+ // If we have fewer than 2 nodes, there are no dependencies to check
135
+ if (awaitNodes.length < 2) {
136
+ return false;
137
+ }
138
+ // For each await node (except the first), check if it depends on previous variables
139
+ for (let i = 1; i < awaitNodes.length; i++) {
140
+ const currentNode = awaitNodes[i];
141
+ // Check if any previous variable is used in the current await expression
142
+ for (const varName of variableNames) {
143
+ const awaitExpr = getAwaitExpression(currentNode);
144
+ if (awaitExpr &&
145
+ isIdentifierUsedInNode(varName, awaitExpr.argument)) {
146
+ return true;
147
+ }
148
+ }
149
+ // Check for operations that might have side effects that affect subsequent operations
150
+ // This is a conservative heuristic - we only flag very specific patterns
151
+ const awaitExpr = getAwaitExpression(currentNode);
152
+ if (awaitExpr &&
153
+ awaitExpr.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
154
+ const callee = awaitExpr.argument.callee;
155
+ // Check for method calls that might indicate side effects
156
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
157
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
158
+ const methodName = callee.property.name;
159
+ if (sideEffectPatterns.some((pattern) => pattern.test(methodName))) {
160
+ return true;
161
+ }
162
+ }
163
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
164
+ const functionName = callee.name;
165
+ if (sideEffectPatterns.some((pattern) => pattern.test(functionName))) {
166
+ return true;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ // If any node is a variable declaration with destructuring, consider it as having dependencies
172
+ // This is because destructuring often creates variables that are used later
173
+ for (const node of awaitNodes) {
174
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
175
+ for (const declaration of node.declarations) {
176
+ if (declaration.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
177
+ return true;
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return false;
183
+ }
184
+ /**
185
+ * Extracts variable names from variable declarations
186
+ */
187
+ function extractVariableNames(nodes) {
188
+ const variableNames = new Set();
189
+ /**
190
+ * Recursively extract identifiers from patterns
191
+ */
192
+ function extractIdentifiersFromPattern(pattern) {
193
+ switch (pattern.type) {
194
+ case utils_1.AST_NODE_TYPES.Identifier:
195
+ variableNames.add(pattern.name);
196
+ break;
197
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
198
+ for (const property of pattern.properties) {
199
+ if (property.type === utils_1.AST_NODE_TYPES.Property) {
200
+ extractIdentifiersFromPattern(property.value);
201
+ }
202
+ else if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
203
+ extractIdentifiersFromPattern(property.argument);
204
+ }
205
+ }
206
+ break;
207
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
208
+ for (const element of pattern.elements) {
209
+ if (element) {
210
+ extractIdentifiersFromPattern(element);
211
+ }
212
+ }
213
+ break;
214
+ case utils_1.AST_NODE_TYPES.RestElement:
215
+ extractIdentifiersFromPattern(pattern.argument);
216
+ break;
217
+ case utils_1.AST_NODE_TYPES.AssignmentPattern:
218
+ extractIdentifiersFromPattern(pattern.left);
219
+ break;
220
+ }
221
+ }
222
+ for (const node of nodes) {
223
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
224
+ for (const declaration of node.declarations) {
225
+ extractIdentifiersFromPattern(declaration.id);
226
+ }
227
+ }
228
+ }
229
+ return variableNames;
230
+ }
231
+ /**
232
+ * Checks if nodes are in try-catch blocks (either individual or shared)
233
+ */
234
+ function areInTryCatchBlocks(nodes) {
235
+ for (const node of nodes) {
236
+ let current = node;
237
+ // Traverse up to find if the node is in a try block
238
+ while (current && current.parent) {
239
+ if (current.parent.type === utils_1.AST_NODE_TYPES.TryStatement &&
240
+ current.parent.block === current) {
241
+ // If we find a try block, we should not parallelize
242
+ // This applies to both individual and shared try-catch blocks
243
+ return true;
244
+ }
245
+ current = current.parent;
246
+ }
247
+ }
248
+ return false;
249
+ }
250
+ /**
251
+ * Checks if nodes are in a loop
252
+ */
253
+ function areInLoop(nodes) {
254
+ for (const node of nodes) {
255
+ let current = node;
256
+ // Traverse up to find if the node is in a loop
257
+ while (current && current.parent) {
258
+ if (current.parent.type === utils_1.AST_NODE_TYPES.ForStatement ||
259
+ current.parent.type === utils_1.AST_NODE_TYPES.ForInStatement ||
260
+ current.parent.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
261
+ current.parent.type === utils_1.AST_NODE_TYPES.WhileStatement ||
262
+ current.parent.type === utils_1.AST_NODE_TYPES.DoWhileStatement) {
263
+ return true;
264
+ }
265
+ current = current.parent;
266
+ }
267
+ }
268
+ return false;
269
+ }
270
+ /**
271
+ * Generates a fix for sequential awaits
272
+ */
273
+ function generateFix(fixer, awaitNodes) {
274
+ // Extract the await expressions
275
+ const awaitExpressions = awaitNodes
276
+ .map((node) => getAwaitExpression(node))
277
+ .filter((node) => node !== null);
278
+ if (awaitExpressions.length < 2) {
279
+ return null;
280
+ }
281
+ // Get the text of each await argument
282
+ const awaitArguments = awaitExpressions.map((expr) => sourceCode.getText(expr.argument));
283
+ const idsText = [];
284
+ const declKinds = new Set();
285
+ let hasVariableDeclarations = false;
286
+ for (const node of awaitNodes) {
287
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
288
+ hasVariableDeclarations = true;
289
+ declKinds.add(node.kind);
290
+ for (const declarator of node.declarations) {
291
+ idsText.push(sourceCode.getText(declarator.id));
292
+ }
293
+ }
294
+ else if (node.type === utils_1.AST_NODE_TYPES.ExpressionStatement) {
295
+ idsText.push('');
296
+ }
297
+ }
298
+ let promiseAllText;
299
+ if (hasVariableDeclarations) {
300
+ if (declKinds.size !== 1) {
301
+ return null;
302
+ }
303
+ if (idsText.length !== awaitArguments.length) {
304
+ return null;
305
+ }
306
+ const destructuringPattern = idsText.join(', ');
307
+ const declKind = Array.from(declKinds)[0];
308
+ promiseAllText = `${declKind} [${destructuringPattern}] = await Promise.all([\n ${awaitArguments.join(',\n ')}\n]);`;
309
+ }
310
+ else {
311
+ // Simple Promise.all without variable assignments
312
+ promiseAllText = `await Promise.all([\n ${awaitArguments.join(',\n ')}\n]);`;
313
+ }
314
+ // Find the start position, accounting for leading comments
315
+ let startPos = awaitNodes[0].range[0];
316
+ // Replace the range from the start of the first await to the end of the last await
317
+ const endPos = awaitNodes[awaitNodes.length - 1].range[1];
318
+ return fixer.replaceTextRange([startPos, endPos], promiseAllText);
319
+ }
320
+ const processStatementList = (statements) => {
321
+ const awaitNodes = [];
322
+ for (const statement of statements) {
323
+ if (isExpressionStatementWithAwait(statement) ||
324
+ isVariableDeclarationWithAwait(statement)) {
325
+ awaitNodes.push(statement);
326
+ }
327
+ else if (awaitNodes.length >= 2) {
328
+ const variableNames = extractVariableNames(awaitNodes);
329
+ if (!hasDependencies(awaitNodes, variableNames, sideEffectMatchers) &&
330
+ !areInTryCatchBlocks(awaitNodes) &&
331
+ !areInLoop(awaitNodes)) {
332
+ const key = `${awaitNodes[0].range?.[0]}-${awaitNodes[awaitNodes.length - 1].range?.[1]}`;
333
+ if (!reportedRanges.has(key)) {
334
+ reportedRanges.add(key);
335
+ context.report({
336
+ node: awaitNodes[0],
337
+ messageId: 'parallelizeAsyncOperations',
338
+ fix: (fixer) => generateFix(fixer, awaitNodes),
339
+ });
340
+ }
341
+ }
342
+ awaitNodes.length = 0;
343
+ }
344
+ else {
345
+ awaitNodes.length = 0;
346
+ }
347
+ }
348
+ if (awaitNodes.length >= 2) {
349
+ const variableNames = extractVariableNames(awaitNodes);
350
+ if (!hasDependencies(awaitNodes, variableNames, sideEffectMatchers) &&
351
+ !areInTryCatchBlocks(awaitNodes) &&
352
+ !areInLoop(awaitNodes)) {
353
+ const key = `${awaitNodes[0].range?.[0]}-${awaitNodes[awaitNodes.length - 1].range?.[1]}`;
354
+ if (!reportedRanges.has(key)) {
355
+ reportedRanges.add(key);
356
+ context.report({
357
+ node: awaitNodes[0],
358
+ messageId: 'parallelizeAsyncOperations',
359
+ fix: (fixer) => generateFix(fixer, awaitNodes),
360
+ });
361
+ }
362
+ }
363
+ }
364
+ };
365
+ return {
366
+ Program(node) {
367
+ processStatementList(node.body);
368
+ },
369
+ BlockStatement(node) {
370
+ processStatementList(node.body);
371
+ },
372
+ };
373
+ },
374
+ });
375
+ //# sourceMappingURL=parallelize-async-operations.js.map
@@ -33,6 +33,106 @@ function isPromiseAll(node) {
33
33
  callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
34
34
  callee.property.name === 'all');
35
35
  }
36
+ function isSetterCall(call) {
37
+ if (call.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
38
+ return false;
39
+ if (call.callee.property.type !== utils_1.AST_NODE_TYPES.Identifier)
40
+ return false;
41
+ return SETTER_METHODS.has(call.callee.property.name);
42
+ }
43
+ function getAllCallsFromExpression(expr) {
44
+ const calls = [];
45
+ function traverse(node) {
46
+ switch (node.type) {
47
+ case utils_1.AST_NODE_TYPES.CallExpression:
48
+ calls.push(node);
49
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
50
+ const obj = node.callee.object;
51
+ if (obj.type !== utils_1.AST_NODE_TYPES.Identifier &&
52
+ obj.type !== utils_1.AST_NODE_TYPES.ThisExpression &&
53
+ obj.type !== utils_1.AST_NODE_TYPES.Literal) {
54
+ traverse(obj);
55
+ }
56
+ }
57
+ for (const arg of node.arguments) {
58
+ if (!arg)
59
+ continue;
60
+ if (arg.type === utils_1.AST_NODE_TYPES.SpreadElement) {
61
+ traverse(arg.argument);
62
+ }
63
+ else {
64
+ traverse(arg);
65
+ }
66
+ }
67
+ break;
68
+ case utils_1.AST_NODE_TYPES.AwaitExpression:
69
+ traverse(node.argument);
70
+ break;
71
+ case utils_1.AST_NODE_TYPES.ArrayExpression:
72
+ for (const el of node.elements) {
73
+ if (!el)
74
+ continue;
75
+ if (el.type === utils_1.AST_NODE_TYPES.SpreadElement) {
76
+ traverse(el.argument);
77
+ }
78
+ else {
79
+ traverse(el);
80
+ }
81
+ }
82
+ break;
83
+ case utils_1.AST_NODE_TYPES.ObjectExpression:
84
+ for (const prop of node.properties) {
85
+ if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
86
+ traverse(prop.argument);
87
+ }
88
+ else if (prop.type === utils_1.AST_NODE_TYPES.Property) {
89
+ const val = prop.value;
90
+ if (val.type !== utils_1.AST_NODE_TYPES.Identifier &&
91
+ val.type !== utils_1.AST_NODE_TYPES.Literal &&
92
+ val.type !== utils_1.AST_NODE_TYPES.ThisExpression) {
93
+ traverse(val);
94
+ }
95
+ }
96
+ }
97
+ break;
98
+ case utils_1.AST_NODE_TYPES.SequenceExpression:
99
+ for (const expr of node.expressions) {
100
+ traverse(expr);
101
+ }
102
+ break;
103
+ case utils_1.AST_NODE_TYPES.UnaryExpression:
104
+ traverse(node.argument);
105
+ break;
106
+ case utils_1.AST_NODE_TYPES.LogicalExpression:
107
+ traverse(node.left);
108
+ traverse(node.right);
109
+ break;
110
+ case utils_1.AST_NODE_TYPES.ConditionalExpression:
111
+ traverse(node.test);
112
+ traverse(node.consequent);
113
+ traverse(node.alternate);
114
+ break;
115
+ case utils_1.AST_NODE_TYPES.MemberExpression:
116
+ if (node.object.type !== utils_1.AST_NODE_TYPES.Identifier &&
117
+ node.object.type !== utils_1.AST_NODE_TYPES.ThisExpression &&
118
+ node.object.type !== utils_1.AST_NODE_TYPES.Literal) {
119
+ traverse(node.object);
120
+ }
121
+ break;
122
+ // For function expressions, we don't traverse inside them
123
+ case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
124
+ case utils_1.AST_NODE_TYPES.FunctionExpression:
125
+ case utils_1.AST_NODE_TYPES.Identifier:
126
+ case utils_1.AST_NODE_TYPES.Literal:
127
+ case utils_1.AST_NODE_TYPES.ThisExpression:
128
+ break;
129
+ default:
130
+ break;
131
+ }
132
+ }
133
+ traverse(expr);
134
+ return calls;
135
+ }
36
136
  function findLoopNode(node) {
37
137
  let current = node;
38
138
  let loopNode = null;
@@ -48,7 +148,52 @@ function findLoopNode(node) {
48
148
  case utils_1.AST_NODE_TYPES.CallExpression:
49
149
  // Check for Promise.all
50
150
  if (isPromiseAll(current)) {
51
- return { node: current, isArrayMethod: 'map' };
151
+ // Check if Promise.all contains mixed operation types
152
+ if (current.arguments.length > 0) {
153
+ let hasSetterCalls = false;
154
+ let hasNonSetterCalls = false;
155
+ // Handle direct array expressions
156
+ if (current.arguments[0].type === utils_1.AST_NODE_TYPES.ArrayExpression) {
157
+ const arrayElements = current.arguments[0].elements;
158
+ // Analyze each element in the Promise.all array
159
+ for (const element of arrayElements) {
160
+ if (!element)
161
+ continue;
162
+ // Skip spread elements for now
163
+ if (element.type === utils_1.AST_NODE_TYPES.SpreadElement)
164
+ continue;
165
+ // Check all possible calls in this element
166
+ const allCalls = getAllCallsFromExpression(element);
167
+ let hasSetterInElement = false;
168
+ let hasNonSetterInElement = false;
169
+ for (const call of allCalls) {
170
+ if (isSetterCall(call)) {
171
+ hasSetterInElement = true;
172
+ }
173
+ else {
174
+ hasNonSetterInElement = true;
175
+ }
176
+ }
177
+ if (hasSetterInElement) {
178
+ hasSetterCalls = true;
179
+ }
180
+ if (hasNonSetterInElement) {
181
+ hasNonSetterCalls = true;
182
+ }
183
+ }
184
+ }
185
+ else {
186
+ // For non-array expressions (e.g., variables, method calls),
187
+ // we can't easily analyze the contents, so assume mixed operations
188
+ hasNonSetterCalls = true;
189
+ }
190
+ // If we have both setter calls and non-setter calls, don't flag
191
+ // This handles the case where different operation types are mixed
192
+ if (hasSetterCalls && hasNonSetterCalls) {
193
+ return undefined;
194
+ }
195
+ }
196
+ return { node: current, isArrayMethod: 'map', isPromiseAll: true };
52
197
  }
53
198
  // Check for array methods
54
199
  const { isValid, methodName: currentMethodName } = isArrayMethod(current);
@@ -144,6 +289,7 @@ function isSetterMethodCall(node) {
144
289
  return { isValid: true, methodName, setterInstance };
145
290
  }
146
291
  const loopSetterCalls = new Map();
292
+ const reportedLoops = new Set();
147
293
  exports.preferBatchOperations = (0, createRule_1.createRule)({
148
294
  name: 'prefer-batch-operations',
149
295
  meta: {
@@ -165,6 +311,7 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
165
311
  'Program:exit'() {
166
312
  // Clear the maps for the next file
167
313
  loopSetterCalls.clear();
314
+ reportedLoops.clear();
168
315
  },
169
316
  CallExpression(node) {
170
317
  const { isValid, methodName, setterInstance } = isSetterMethodCall(node);
@@ -183,48 +330,67 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
183
330
  // Track setter instance and method calls for this loop
184
331
  const key = setterInstance;
185
332
  const existing = setterCalls.get(key);
333
+ // Determine if this is a traditional loop
334
+ const isTraditionalLoop = !loopInfo.isArrayMethod &&
335
+ !loopInfo.isPromiseAll &&
336
+ (loopInfo.node.type === utils_1.AST_NODE_TYPES.ForStatement ||
337
+ loopInfo.node.type === utils_1.AST_NODE_TYPES.ForInStatement ||
338
+ loopInfo.node.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
339
+ loopInfo.node.type === utils_1.AST_NODE_TYPES.WhileStatement ||
340
+ loopInfo.node.type === utils_1.AST_NODE_TYPES.DoWhileStatement);
186
341
  if (existing) {
187
342
  // If we see a different method on the same setter instance, don't report
188
343
  if (existing.methodName !== methodName)
189
344
  return;
190
345
  existing.count++;
346
+ // For Promise.all contexts, report only once per loop context, on the second occurrence
347
+ if (loopInfo.isPromiseAll &&
348
+ existing.count === 2 &&
349
+ !reportedLoops.has(loopInfo.node)) {
350
+ reportedLoops.add(loopInfo.node);
351
+ const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
352
+ context.report({
353
+ node: existing.firstNode,
354
+ messageId,
355
+ fix: () => null, // We can't provide a fix because we don't know the array structure
356
+ });
357
+ }
191
358
  }
192
359
  else {
193
- setterCalls.set(key, { methodName, count: 1 });
194
- }
195
- // Report on the first occurrence of a repeated call
196
- // For Promise.all and array methods, report on the first occurrence
197
- // For regular loops, report on the first occurrence too since we know it's in a loop
198
- const shouldReport = loopInfo.isArrayMethod
199
- ? ['forEach', 'reduce', 'filter', 'map'].includes(loopInfo.isArrayMethod)
200
- : setterCalls.get(key).count === 1;
201
- // Don't report if we have multiple different setter instances in a loop
202
- // Only check this for regular loops, not array methods or Promise.all
203
- if (shouldReport && !loopInfo.isArrayMethod) {
204
- const setterInstances = new Set(Array.from(setterCalls.keys()));
205
- if (setterInstances.size > 1) {
206
- // This is a valid use case when using multiple setters in a loop
207
- // For example: userSetter.set(doc.user) and orderSetter.set(doc.order)
208
- // Each setter operates on a different collection, so they can't be batched together
209
- // We only want to report when using the same setter instance multiple times
210
- // For example: userSetter.set(doc.user) multiple times should use userSetter.setAll()
211
- if (loopInfo.node.type.startsWith('For') ||
212
- loopInfo.node.type.startsWith('While') ||
213
- loopInfo.node.type.startsWith('Do')) {
360
+ setterCalls.set(key, { methodName, count: 1, firstNode: node });
361
+ // Check if we now have multiple different setter instances in a traditional loop
362
+ if (isTraditionalLoop) {
363
+ const setterInstances = new Set(Array.from(setterCalls.keys()));
364
+ if (setterInstances.size > 1) {
365
+ // This is a valid use case when using multiple setters in a loop
366
+ // For example: userSetter.set(doc.user) and orderSetter.set(doc.order)
367
+ // Each setter operates on a different collection, so they can't be batched together
368
+ // Don't report anything for this loop
214
369
  return;
215
370
  }
371
+ // For traditional loops, report on the first occurrence since we know it's in a loop
372
+ if (!reportedLoops.has(loopInfo.node)) {
373
+ reportedLoops.add(loopInfo.node);
374
+ const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
375
+ context.report({
376
+ node,
377
+ messageId,
378
+ fix: () => null, // We can't provide a fix because we don't know the array structure
379
+ });
380
+ }
381
+ }
382
+ // For array methods, report on the first occurrence
383
+ else if (loopInfo.isArrayMethod) {
384
+ if (!reportedLoops.has(loopInfo.node)) {
385
+ reportedLoops.add(loopInfo.node);
386
+ const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
387
+ context.report({
388
+ node,
389
+ messageId,
390
+ fix: () => null, // We can't provide a fix because we don't know the array structure
391
+ });
392
+ }
216
393
  }
217
- }
218
- // Report on the first occurrence of a repeated call
219
- // For Promise.all and array methods, report on the first occurrence
220
- // For regular loops, report on the first occurrence too since we know it's in a loop
221
- if (shouldReport) {
222
- const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
223
- context.report({
224
- node,
225
- messageId,
226
- fix: () => null, // We can't provide a fix because we don't know the array structure
227
- });
228
394
  }
229
395
  },
230
396
  };
@@ -16,13 +16,23 @@ exports.preferBlockCommentsForDeclarations = (0, createRule_1.createRule)({
16
16
  if (comment.type !== 'Line') {
17
17
  return false;
18
18
  }
19
- // Ignore ESLint directive comments
19
+ // Ignore ESLint & TypeScript directive comments
20
20
  const commentText = comment.value.trim();
21
- if (commentText.startsWith('eslint-disable') ||
21
+ if (
22
+ // ESLint
23
+ commentText.startsWith('eslint-disable') ||
22
24
  commentText.startsWith('eslint-enable') ||
23
25
  commentText.startsWith('eslint-env') ||
26
+ commentText.startsWith('eslint ') ||
24
27
  commentText.startsWith('global ') ||
25
- commentText.startsWith('globals ')) {
28
+ commentText.startsWith('globals ') ||
29
+ commentText.startsWith('exported ') ||
30
+ // TypeScript line directives (keep as line comments)
31
+ /^@ts-(ignore|expect-error|check|nocheck)\b/.test(commentText) ||
32
+ // TypeScript triple-slash directives (value of a 'Line' comment that started with '///')
33
+ commentText.startsWith('/ <reference') ||
34
+ commentText.startsWith('/ <amd-') ||
35
+ commentText.startsWith('/ <jsxImportSource')) {
26
36
  return false;
27
37
  }
28
38
  // Check if the comment is directly before the node