@blumintinc/eslint-plugin-blumint 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -11
- package/lib/index.js +14 -12
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/consistent-callback-naming.js +42 -3
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +132 -41
- package/lib/rules/enforce-boolean-naming-prefixes.js +0 -212
- package/lib/rules/enforce-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- package/lib/rules/enforce-global-constants.js +3 -3
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-memoize-async.js +69 -15
- package/lib/rules/enforce-props-argument-name.js +42 -16
- package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
- package/lib/rules/enforce-transform-memoization.js +1 -1
- package/lib/rules/enforce-verb-noun-naming.js +3814 -4641
- package/lib/rules/global-const-style.js +16 -4
- package/lib/rules/memo-compare-deeply-complex-props.js +16 -3
- package/lib/rules/memo-nested-react-components.js +108 -103
- package/lib/rules/no-async-foreach.js +7 -2
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +13 -15
- package/lib/rules/no-console-error.js +12 -10
- package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +48 -1
- package/lib/rules/no-excessive-parent-chain.js +3 -0
- package/lib/rules/no-inline-component-prop.js +16 -7
- package/lib/rules/no-passthrough-getters.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +83 -1
- package/lib/rules/no-redundant-this-params.js +50 -1
- package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
- package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
- package/lib/rules/parallelize-async-operations.js +119 -54
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
- package/lib/rules/prefer-params-over-parent-id.js +1 -1
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
- package/lib/rules/prevent-children-clobber.js +9 -5
- package/lib/rules/react-memoize-literals.js +131 -12
- package/lib/rules/require-https-error-cause.js +30 -11
- package/lib/rules/require-memo.js +17 -9
- package/lib/utils/ASTHelpers.d.ts +34 -1
- package/lib/utils/ASTHelpers.js +346 -112
- package/package.json +1 -1
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
exports.noPassthroughGetters = void 0;
|
|
4
27
|
const createRule_1 = require("../utils/createRule");
|
|
5
28
|
const utils_1 = require("@typescript-eslint/utils");
|
|
6
29
|
const getMethodName_1 = require("../utils/getMethodName");
|
|
30
|
+
const ts = __importStar(require("typescript"));
|
|
7
31
|
const COMPLEX_EXPRESSION_TYPES = new Set([
|
|
8
32
|
utils_1.AST_NODE_TYPES.CallExpression,
|
|
9
33
|
utils_1.AST_NODE_TYPES.TemplateLiteral,
|
|
@@ -14,7 +38,7 @@ const COMPLEX_EXPRESSION_TYPES = new Set([
|
|
|
14
38
|
]);
|
|
15
39
|
exports.noPassthroughGetters = (0, createRule_1.createRule)({
|
|
16
40
|
create(context) {
|
|
17
|
-
const sourceCode = context.
|
|
41
|
+
const sourceCode = context.sourceCode;
|
|
18
42
|
return {
|
|
19
43
|
// Target getter methods in classes
|
|
20
44
|
MethodDefinition(node) {
|
|
@@ -26,6 +50,10 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
|
|
|
26
50
|
if (node.decorators && node.decorators.length > 0) {
|
|
27
51
|
return;
|
|
28
52
|
}
|
|
53
|
+
// Check if this getter satisfies an interface or overrides a base class member
|
|
54
|
+
if (isRequiredByInterfaceOrBaseClass(node, sourceCode)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
29
57
|
const methodBody = node.value.body;
|
|
30
58
|
if (!methodBody) {
|
|
31
59
|
return;
|
|
@@ -74,6 +102,60 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
|
|
|
74
102
|
}
|
|
75
103
|
},
|
|
76
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* Check if the getter is required by an implemented interface or overrides a base class member
|
|
107
|
+
*/
|
|
108
|
+
function isRequiredByInterfaceOrBaseClass(node, sourceCode) {
|
|
109
|
+
const parserServices = sourceCode.parserServices;
|
|
110
|
+
if (!parserServices ||
|
|
111
|
+
!parserServices.program ||
|
|
112
|
+
!parserServices.esTreeNodeToTSNodeMap) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const checker = parserServices.program.getTypeChecker();
|
|
116
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
117
|
+
if (!tsNode) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const symbol = checker.getSymbolAtLocation(tsNode.name);
|
|
121
|
+
if (!symbol ||
|
|
122
|
+
!tsNode.parent ||
|
|
123
|
+
!(ts.isClassDeclaration(tsNode.parent) ||
|
|
124
|
+
ts.isClassExpression(tsNode.parent))) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const classNode = tsNode.parent;
|
|
128
|
+
const classType = checker.getTypeAtLocation(classNode);
|
|
129
|
+
const classSymbol = classType.getSymbol();
|
|
130
|
+
if (!classSymbol) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const instanceType = checker.getDeclaredTypeOfSymbol(classSymbol);
|
|
134
|
+
const name = symbol.getName();
|
|
135
|
+
// Check base classes
|
|
136
|
+
const baseTypes = instanceType.getBaseTypes() || [];
|
|
137
|
+
for (const baseType of baseTypes) {
|
|
138
|
+
if (baseType.getProperty(name)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Check interfaces
|
|
143
|
+
if (classNode.heritageClauses) {
|
|
144
|
+
for (const clause of classNode.heritageClauses) {
|
|
145
|
+
// Only check implemented interfaces as base classes are already checked via getBaseTypes()
|
|
146
|
+
if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
for (const typeNode of clause.types) {
|
|
150
|
+
const type = checker.getTypeAtLocation(typeNode);
|
|
151
|
+
if (type.getProperty(name)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
77
159
|
/**
|
|
78
160
|
* Check if the node is a simple property access from a constructor parameter
|
|
79
161
|
* like this.settings.property or this.settings['property'] or this.settings.nested.deep.property
|
|
@@ -21,6 +21,7 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
21
21
|
defaultOptions: [],
|
|
22
22
|
create(context) {
|
|
23
23
|
const classInfoMap = new WeakMap();
|
|
24
|
+
const classStatsMap = new WeakMap();
|
|
24
25
|
function isFunctionLike(node) {
|
|
25
26
|
return (node?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
26
27
|
node?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
@@ -412,6 +413,24 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
412
413
|
return {
|
|
413
414
|
'ClassDeclaration, ClassExpression'(node) {
|
|
414
415
|
collectClassInfo(node);
|
|
416
|
+
classStatsMap.set(node, { methods: new Map() });
|
|
417
|
+
},
|
|
418
|
+
'ClassDeclaration, ClassExpression:exit'(node) {
|
|
419
|
+
const stats = classStatsMap.get(node);
|
|
420
|
+
if (!stats) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
for (const [methodName, methodStats] of stats.methods) {
|
|
424
|
+
for (const [argIndex, argProperties] of methodStats.argStats) {
|
|
425
|
+
for (const [, propStats] of argProperties) {
|
|
426
|
+
if (propStats.callsWithProperty === methodStats.totalCalls) {
|
|
427
|
+
for (const violation of propStats.violations) {
|
|
428
|
+
reportAccess(methodName, violation.methodMeta, argIndex, violation.access);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
415
434
|
},
|
|
416
435
|
CallExpression(node) {
|
|
417
436
|
const methodName = getMethodNameFromCallee(node.callee);
|
|
@@ -442,6 +461,18 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
442
461
|
return;
|
|
443
462
|
}
|
|
444
463
|
}
|
|
464
|
+
const stats = classStatsMap.get(classNode);
|
|
465
|
+
if (!stats) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
let methodStats = stats.methods.get(methodName);
|
|
469
|
+
if (!methodStats) {
|
|
470
|
+
methodStats = { totalCalls: 0, argStats: new Map() };
|
|
471
|
+
stats.methods.set(methodName, methodStats);
|
|
472
|
+
}
|
|
473
|
+
const currentMethodStats = methodStats;
|
|
474
|
+
currentMethodStats.totalCalls++;
|
|
475
|
+
const seenInThisCall = new Set();
|
|
445
476
|
node.arguments.forEach((arg, index) => {
|
|
446
477
|
if (!arg) {
|
|
447
478
|
return;
|
|
@@ -449,7 +480,25 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
449
480
|
const targetNode = arg.type === utils_1.AST_NODE_TYPES.SpreadElement ? arg.argument : arg;
|
|
450
481
|
const accesses = collectThisAccesses(targetNode);
|
|
451
482
|
for (const access of accesses) {
|
|
452
|
-
|
|
483
|
+
const key = `${index}:${access.propertyName}`;
|
|
484
|
+
let argMap = currentMethodStats.argStats.get(index);
|
|
485
|
+
if (!argMap) {
|
|
486
|
+
argMap = new Map();
|
|
487
|
+
currentMethodStats.argStats.set(index, argMap);
|
|
488
|
+
}
|
|
489
|
+
let propStats = argMap.get(access.propertyName);
|
|
490
|
+
if (!propStats) {
|
|
491
|
+
propStats = { callsWithProperty: 0, violations: [] };
|
|
492
|
+
argMap.set(access.propertyName, propStats);
|
|
493
|
+
}
|
|
494
|
+
propStats.violations.push({
|
|
495
|
+
methodMeta: methodMeta,
|
|
496
|
+
access,
|
|
497
|
+
});
|
|
498
|
+
if (!seenInThisCall.has(key)) {
|
|
499
|
+
propStats.callsWithProperty++;
|
|
500
|
+
seenInThisCall.add(key);
|
|
501
|
+
}
|
|
453
502
|
}
|
|
454
503
|
});
|
|
455
504
|
},
|
|
@@ -274,7 +274,7 @@ exports.noUnmemoizedMemoWithoutProps = (0, createRule_1.createRule)({
|
|
|
274
274
|
if (!node.body) {
|
|
275
275
|
return;
|
|
276
276
|
}
|
|
277
|
-
const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body);
|
|
277
|
+
const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
|
|
278
278
|
if (!returnsJsx) {
|
|
279
279
|
return;
|
|
280
280
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.noUnnecessaryDestructuringRename = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
6
7
|
const BINDABLE_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
7
8
|
const RESERVED_BINDINGS = new Set([
|
|
8
9
|
'break',
|
|
@@ -113,11 +114,7 @@ exports.noUnnecessaryDestructuringRename = (0, createRule_1.createRule)({
|
|
|
113
114
|
const sourceCode = context.getSourceCode();
|
|
114
115
|
const candidates = [];
|
|
115
116
|
const getDeclaredVariables = (node) => {
|
|
116
|
-
|
|
117
|
-
if (typeof sourceCodeWithDeclarations.getDeclaredVariables === 'function') {
|
|
118
|
-
return sourceCodeWithDeclarations.getDeclaredVariables(node);
|
|
119
|
-
}
|
|
120
|
-
return context.getDeclaredVariables(node);
|
|
117
|
+
return ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, node);
|
|
121
118
|
};
|
|
122
119
|
function collectDeclaredVariablesUpTree(node) {
|
|
123
120
|
const variables = [];
|
|
@@ -13,6 +13,9 @@ const defaultOptions = [
|
|
|
13
13
|
'updatethreshold',
|
|
14
14
|
'setthreshold',
|
|
15
15
|
'checkthreshold',
|
|
16
|
+
'commit',
|
|
17
|
+
'flush',
|
|
18
|
+
'saveall',
|
|
16
19
|
],
|
|
17
20
|
},
|
|
18
21
|
];
|
|
@@ -95,81 +98,149 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
|
|
|
95
98
|
return null;
|
|
96
99
|
}
|
|
97
100
|
/**
|
|
98
|
-
*
|
|
101
|
+
* Generic AST visitor for identifier collection
|
|
99
102
|
*/
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
function visitIdentifiers(node, callback, options = {}) {
|
|
104
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
105
|
+
const parent = node.parent;
|
|
106
|
+
// Skip non-computed properties in MemberExpressions as they are not value uses
|
|
107
|
+
if (parent && parent.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
108
|
+
if (parent.property === node &&
|
|
109
|
+
!parent.computed &&
|
|
110
|
+
!options.includeMemberProperties) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Skip non-shorthand keys in object literals as they are not value uses
|
|
115
|
+
if (parent && parent.type === utils_1.AST_NODE_TYPES.Property) {
|
|
116
|
+
if (parent.key === node &&
|
|
117
|
+
!parent.computed &&
|
|
118
|
+
!parent.shorthand) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
107
121
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
if (callback(node.name) === true)
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Recursively traverses child nodes while skipping 'parent' to avoid
|
|
127
|
+
* circular back-references, and 'range'/'loc' which are metadata.
|
|
128
|
+
*/
|
|
129
|
+
for (const key in node) {
|
|
130
|
+
if (key === 'parent' || key === 'range' || key === 'loc')
|
|
131
|
+
continue;
|
|
132
|
+
const child = node[key];
|
|
133
|
+
if (child && typeof child === 'object') {
|
|
134
|
+
if (Array.isArray(child)) {
|
|
135
|
+
for (const item of child) {
|
|
136
|
+
if (item && typeof item === 'object' && 'type' in item) {
|
|
137
|
+
if (visitIdentifiers(item, callback, options)) {
|
|
138
|
+
return true;
|
|
118
139
|
}
|
|
119
140
|
}
|
|
120
141
|
}
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
}
|
|
143
|
+
else if ('type' in child) {
|
|
144
|
+
if (visitIdentifiers(child, callback, options)) {
|
|
145
|
+
return true;
|
|
123
146
|
}
|
|
124
147
|
}
|
|
125
148
|
}
|
|
126
149
|
}
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Checks if an identifier is used in a node
|
|
154
|
+
*/
|
|
155
|
+
function isIdentifierUsedInNode(identifier, node) {
|
|
156
|
+
return visitIdentifiers(node, (name) => name === identifier);
|
|
129
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Extracts all identifiers used in a node
|
|
160
|
+
*/
|
|
161
|
+
function getAllIdentifiers(node, options) {
|
|
162
|
+
const identifiers = new Set();
|
|
163
|
+
visitIdentifiers(node, (name) => {
|
|
164
|
+
identifiers.add(name);
|
|
165
|
+
}, options);
|
|
166
|
+
return identifiers;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Matches coordinator-like identifiers. Intentionally uses substring
|
|
170
|
+
* matching (no word boundaries) to catch patterns like "batchManager",
|
|
171
|
+
* "transactionCollector", etc. This may produce false positives for
|
|
172
|
+
* unrelated managers, but errs on the side of safety.
|
|
173
|
+
*/
|
|
174
|
+
const COORDINATOR_PATTERN = /batch|manager|collector|transaction|tx|unitofwork|accumulator/i;
|
|
130
175
|
/**
|
|
131
176
|
* Checks if there are dependencies between await expressions
|
|
132
177
|
*/
|
|
133
178
|
function hasDependencies(awaitNodes, variableNames, sideEffectPatterns) {
|
|
134
|
-
// If we have fewer than 2 nodes, there are no dependencies to check
|
|
135
179
|
if (awaitNodes.length < 2) {
|
|
136
180
|
return false;
|
|
137
181
|
}
|
|
138
|
-
|
|
139
|
-
|
|
182
|
+
const allIdentifiers = awaitNodes.map((node) => {
|
|
183
|
+
const awaitExpr = getAwaitExpression(node);
|
|
184
|
+
return awaitExpr
|
|
185
|
+
? getAllIdentifiers(awaitExpr.argument, {
|
|
186
|
+
includeMemberProperties: true,
|
|
187
|
+
})
|
|
188
|
+
: new Set();
|
|
189
|
+
});
|
|
190
|
+
// Check all nodes for side effects and shared coordinators
|
|
191
|
+
for (let i = 0; i < awaitNodes.length; i++) {
|
|
140
192
|
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
193
|
const awaitExpr = getAwaitExpression(currentNode);
|
|
152
|
-
if (awaitExpr
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const methodName = callee.property.name;
|
|
159
|
-
if (sideEffectPatterns.some((pattern) => pattern.test(methodName))) {
|
|
194
|
+
if (!awaitExpr)
|
|
195
|
+
continue;
|
|
196
|
+
// 1. Check if current node depends on variables DECLARED in previous awaits
|
|
197
|
+
if (i > 0) {
|
|
198
|
+
for (const varName of variableNames) {
|
|
199
|
+
if (isIdentifierUsedInNode(varName, awaitExpr.argument)) {
|
|
160
200
|
return true;
|
|
161
201
|
}
|
|
162
202
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
203
|
+
}
|
|
204
|
+
// 2. Check for shared coordinators between this node and ANY previous node
|
|
205
|
+
if (i > 0) {
|
|
206
|
+
const currentIds = allIdentifiers[i];
|
|
207
|
+
for (const id of currentIds) {
|
|
208
|
+
if (COORDINATOR_PATTERN.test(id)) {
|
|
209
|
+
for (let j = 0; j < i; j++) {
|
|
210
|
+
if (allIdentifiers[j].has(id)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
167
214
|
}
|
|
168
215
|
}
|
|
169
216
|
}
|
|
217
|
+
// 3. Check for operations that might have side effects
|
|
218
|
+
// If any node has a side effect, we should not parallelize the sequence
|
|
219
|
+
let callExpr = null;
|
|
220
|
+
if (awaitExpr.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
221
|
+
callExpr = awaitExpr.argument;
|
|
222
|
+
}
|
|
223
|
+
else if (awaitExpr.argument.type === utils_1.AST_NODE_TYPES.ChainExpression &&
|
|
224
|
+
awaitExpr.argument.expression.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
225
|
+
callExpr = awaitExpr.argument.expression;
|
|
226
|
+
}
|
|
227
|
+
if (callExpr) {
|
|
228
|
+
const callee = callExpr.callee;
|
|
229
|
+
let methodName = null;
|
|
230
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
231
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
232
|
+
methodName = callee.property.name;
|
|
233
|
+
}
|
|
234
|
+
else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
235
|
+
methodName = callee.name;
|
|
236
|
+
}
|
|
237
|
+
if (methodName &&
|
|
238
|
+
sideEffectPatterns.some((pattern) => pattern.test(methodName))) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
170
242
|
}
|
|
171
243
|
// 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
244
|
for (const node of awaitNodes) {
|
|
174
245
|
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
175
246
|
for (const declaration of node.declarations) {
|
|
@@ -234,7 +305,6 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
|
|
|
234
305
|
function areInTryCatchBlocks(nodes) {
|
|
235
306
|
for (const node of nodes) {
|
|
236
307
|
let current = node;
|
|
237
|
-
// Traverse up to find if the node is in a try block
|
|
238
308
|
while (current && current.parent) {
|
|
239
309
|
if (current.parent.type === utils_1.AST_NODE_TYPES.TryStatement &&
|
|
240
310
|
current.parent.block === current) {
|
|
@@ -253,7 +323,6 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
|
|
|
253
323
|
function areInLoop(nodes) {
|
|
254
324
|
for (const node of nodes) {
|
|
255
325
|
let current = node;
|
|
256
|
-
// Traverse up to find if the node is in a loop
|
|
257
326
|
while (current && current.parent) {
|
|
258
327
|
if (current.parent.type === utils_1.AST_NODE_TYPES.ForStatement ||
|
|
259
328
|
current.parent.type === utils_1.AST_NODE_TYPES.ForInStatement ||
|
|
@@ -276,14 +345,12 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
|
|
|
276
345
|
if (awaitNodes.length < 2) {
|
|
277
346
|
return null;
|
|
278
347
|
}
|
|
279
|
-
// Extract the await expressions
|
|
280
348
|
const awaitExpressions = awaitNodes
|
|
281
349
|
.map((node) => getAwaitExpression(node))
|
|
282
350
|
.filter((node) => node !== null);
|
|
283
351
|
if (awaitExpressions.length < 2) {
|
|
284
352
|
return null;
|
|
285
353
|
}
|
|
286
|
-
// Get the text of each await argument
|
|
287
354
|
const awaitArguments = awaitExpressions.map((expr) => sourceCode.getText(expr.argument));
|
|
288
355
|
const idsText = [];
|
|
289
356
|
const declKinds = new Set();
|
|
@@ -316,9 +383,7 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
|
|
|
316
383
|
// Simple Promise.all without variable assignments
|
|
317
384
|
promiseAllText = `await Promise.all([\n ${awaitArguments.join(',\n ')}\n]);`;
|
|
318
385
|
}
|
|
319
|
-
// Find the start position, accounting for leading comments
|
|
320
386
|
const startPos = awaitNodes[0].range[0];
|
|
321
|
-
// Replace the range from the start of the first await to the end of the last await
|
|
322
387
|
const endPos = awaitNodes[awaitNodes.length - 1].range[1];
|
|
323
388
|
return fixer.replaceTextRange([startPos, endPos], promiseAllText);
|
|
324
389
|
}
|
|
@@ -1,9 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
exports.preferNullishCoalescingBooleanProps = void 0;
|
|
4
27
|
const utils_1 = require("@typescript-eslint/utils");
|
|
28
|
+
const ts = __importStar(require("typescript"));
|
|
5
29
|
const createRule_1 = require("../utils/createRule");
|
|
6
30
|
const BOOLEAN_PROP_REGEX = /^(is|has|should|can|will|do|does|did|was|were|enable|disable)/;
|
|
31
|
+
function isBooleanType(type, checker) {
|
|
32
|
+
if (type.isUnion()) {
|
|
33
|
+
return type.types.every((t) => isBooleanType(t, checker));
|
|
34
|
+
}
|
|
35
|
+
// For type parameters, check if the constraint is a boolean type
|
|
36
|
+
if (type.getFlags() & ts.TypeFlags.TypeParameter) {
|
|
37
|
+
if (checker) {
|
|
38
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
39
|
+
if (constraint && constraint !== type) {
|
|
40
|
+
return isBooleanType(constraint, checker);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return ((type.getFlags() & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) !==
|
|
46
|
+
0);
|
|
47
|
+
}
|
|
48
|
+
function isPossiblyNullish(type, checker) {
|
|
49
|
+
if (type.isUnion()) {
|
|
50
|
+
return type.types.some((t) => isPossiblyNullish(t, checker));
|
|
51
|
+
}
|
|
52
|
+
// For type parameters, check if the constraint excludes nullish
|
|
53
|
+
if (type.getFlags() & ts.TypeFlags.TypeParameter) {
|
|
54
|
+
if (checker) {
|
|
55
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
56
|
+
if (constraint && constraint !== type) {
|
|
57
|
+
return isPossiblyNullish(constraint, checker);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// If we can't determine the constraint, assume it could be nullish
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return ((type.getFlags() &
|
|
64
|
+
(ts.TypeFlags.Null |
|
|
65
|
+
ts.TypeFlags.Undefined |
|
|
66
|
+
ts.TypeFlags.Void |
|
|
67
|
+
ts.TypeFlags.Any |
|
|
68
|
+
ts.TypeFlags.Unknown)) !==
|
|
69
|
+
0);
|
|
70
|
+
}
|
|
7
71
|
function isInJSXBooleanAttribute(node) {
|
|
8
72
|
const parent = node.parent;
|
|
9
73
|
if (parent?.type !== utils_1.AST_NODE_TYPES.JSXAttribute)
|
|
@@ -57,7 +121,34 @@ function isInConditionalContext(node) {
|
|
|
57
121
|
/**
|
|
58
122
|
* Determines if a node is within a boolean context in JSX props or other boolean contexts
|
|
59
123
|
*/
|
|
60
|
-
function isInBooleanContext(node) {
|
|
124
|
+
function isInBooleanContext(node, checker, parserServices) {
|
|
125
|
+
if (checker && parserServices) {
|
|
126
|
+
try {
|
|
127
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
128
|
+
const contextualType = checker.getContextualType(tsNode);
|
|
129
|
+
if (contextualType && isBooleanType(contextualType, checker)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
// Also check if the expression itself is a boolean type
|
|
133
|
+
const actualType = checker.getTypeAtLocation(tsNode);
|
|
134
|
+
if (actualType && isBooleanType(actualType, checker)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
// If this is a logical expression, also check if the left operand is a boolean
|
|
138
|
+
if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
139
|
+
const leftTSNode = parserServices.esTreeNodeToTSNodeMap.get(node.left);
|
|
140
|
+
const leftType = checker.getTypeAtLocation(leftTSNode);
|
|
141
|
+
if (leftType && isBooleanType(leftType, checker)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
|
|
148
|
+
// source positions; getContextualType may return undefined for non-expression
|
|
149
|
+
// contexts. Fall back to AST-based heuristics in these cases.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
61
152
|
let current = node;
|
|
62
153
|
// Traverse up the AST to find if we're in a boolean context
|
|
63
154
|
while (current && current.parent) {
|
|
@@ -365,7 +456,19 @@ function isInBooleanContext(node) {
|
|
|
365
456
|
/**
|
|
366
457
|
* Checks if the left operand could be nullish (null or undefined)
|
|
367
458
|
*/
|
|
368
|
-
function couldBeNullish(node) {
|
|
459
|
+
function couldBeNullish(node, checker, parserServices) {
|
|
460
|
+
if (checker && parserServices) {
|
|
461
|
+
try {
|
|
462
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
463
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
464
|
+
return isPossiblyNullish(type, checker);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
|
|
468
|
+
// source positions; getTypeAtLocation may throw for nodes without
|
|
469
|
+
// type information. Fall back to manual check.
|
|
470
|
+
}
|
|
471
|
+
}
|
|
369
472
|
// For literals, check the actual value
|
|
370
473
|
if (node.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
371
474
|
return node.value === null || node.value === undefined;
|
|
@@ -402,16 +505,18 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
402
505
|
},
|
|
403
506
|
defaultOptions: [],
|
|
404
507
|
create(context) {
|
|
508
|
+
const parserServices = utils_1.ESLintUtils.getParserServices(context, true);
|
|
509
|
+
const checker = parserServices?.program?.getTypeChecker();
|
|
405
510
|
return {
|
|
406
511
|
LogicalExpression(node) {
|
|
407
512
|
if (node.operator === '||') {
|
|
408
513
|
// If the node is in a boolean context, we allow logical OR
|
|
409
|
-
if (isInBooleanContext(node)) {
|
|
514
|
+
if (isInBooleanContext(node, checker, parserServices)) {
|
|
410
515
|
return;
|
|
411
516
|
}
|
|
412
517
|
// Check if this could benefit from nullish coalescing
|
|
413
518
|
// We only suggest nullish coalescing when the left operand could be nullish
|
|
414
|
-
if (couldBeNullish(node.left)) {
|
|
519
|
+
if (couldBeNullish(node.left, checker, parserServices)) {
|
|
415
520
|
const sourceCode = context.getSourceCode();
|
|
416
521
|
const leftText = sourceCode.getText(node.left);
|
|
417
522
|
const rightText = sourceCode.getText(node.right);
|
|
@@ -202,7 +202,7 @@ exports.preferParamsOverParentId = (0, createRule_1.createRule)({
|
|
|
202
202
|
meta: {
|
|
203
203
|
type: 'suggestion',
|
|
204
204
|
docs: {
|
|
205
|
-
description: 'Prefer
|
|
205
|
+
description: 'Prefer event.params over ref.parent.id for type-safe Firebase trigger paths.',
|
|
206
206
|
recommended: 'error',
|
|
207
207
|
},
|
|
208
208
|
fixable: 'code',
|