@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.
- package/README.md +140 -99
- package/lib/index.js +111 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -10
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +250 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-exported-function-types.js +3 -3
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
- package/lib/rules/enforce-firestore-facade.js +347 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +96 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +123 -0
- package/lib/rules/enforce-microdiff.js +2 -1
- package/lib/rules/enforce-positive-naming.js +5 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-singular-type-names.js +9 -5
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
- package/lib/rules/enforce-verb-noun-naming.js +85 -10
- package/lib/rules/extract-global-constants.js +57 -45
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +308 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-complex-cloud-params.js +15 -2
- package/lib/rules/no-compositing-layer-props.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +317 -63
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-firestore-object-arrays.js +345 -30
- package/lib/rules/no-hungarian.js +13 -13
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +16 -3
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +170 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +172 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +142 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +6 -0
- package/lib/rules/no-undefined-null-passthrough.js +166 -19
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +3 -0
- package/lib/rules/no-unused-props.js +213 -78
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +234 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +375 -0
- package/lib/rules/prefer-batch-operations.js +199 -33
- package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
- package/lib/rules/prefer-fragment-component.js +3 -3
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +230 -31
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +366 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +228 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +11 -1
- package/lib/rules/require-usememo-object-literals.js +7 -1
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +270 -0
- package/lib/utils/ASTHelpers.js +10 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/package.json +21 -14
- 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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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 (
|
|
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
|