@blumintinc/eslint-plugin-blumint 1.20.95 → 1.20.97
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 +1 -1
- package/lib/index.js +1 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +38 -3
- package/lib/rules/enforce-css-media-queries.d.ts +2 -1
- package/lib/rules/enforce-css-media-queries.js +203 -8
- package/lib/rules/enforce-exported-function-types.js +209 -6
- package/lib/rules/enforce-firestore-doc-ref-generic.js +71 -18
- package/lib/rules/enforce-mui-rounded-icons.d.ts +2 -1
- package/lib/rules/enforce-mui-rounded-icons.js +194 -13
- package/lib/rules/enforce-positive-naming.js +112 -24
- package/lib/rules/generic-starts-with-t.js +49 -0
- package/lib/rules/global-const-style.js +101 -51
- package/lib/rules/no-always-true-false-conditions.js +231 -62
- package/lib/rules/no-unnecessary-verb-suffix.js +52 -5
- package/lib/rules/parallelize-loop-awaits.d.ts +1 -0
- package/lib/rules/parallelize-loop-awaits.js +233 -43
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.js +259 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +63 -4
- package/lib/rules/require-https-error.js +6 -2
- package/lib/rules/use-custom-link.js +45 -1
- package/lib/rules/use-custom-memo.js +30 -0
- package/lib/rules/use-custom-router.js +32 -2
- package/lib/utils/harvestRuleTesterCases.d.ts +48 -0
- package/lib/utils/harvestRuleTesterCases.js +168 -0
- package/package.json +1 -1
- package/release-manifest.json +151 -0
|
@@ -439,11 +439,7 @@ function contractCoversName(typeName, memberName, index, visited) {
|
|
|
439
439
|
}
|
|
440
440
|
const alias = index.typeAliases.get(typeName);
|
|
441
441
|
if (alias) {
|
|
442
|
-
|
|
443
|
-
// a reference to an imported type) hides its member list from a syntactic
|
|
444
|
-
// reader, so it is treated as unreadable.
|
|
445
|
-
return (alias.typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypeLiteral ||
|
|
446
|
-
membersDeclareName(alias.typeAnnotation.members, memberName));
|
|
442
|
+
return aliasCoversName(alias.typeAnnotation, memberName, index, visited);
|
|
447
443
|
}
|
|
448
444
|
const classDeclaration = index.classes.get(typeName);
|
|
449
445
|
if (classDeclaration) {
|
|
@@ -454,6 +450,57 @@ function contractCoversName(typeName, memberName, index, visited) {
|
|
|
454
450
|
// and its members are unreadable here.
|
|
455
451
|
return true;
|
|
456
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Reports whether the type an alias names accounts for `memberName`.
|
|
455
|
+
*
|
|
456
|
+
* A type literal lists its members outright. An intersection is readable as far
|
|
457
|
+
* as its constituents are, which matters because `prefer-type-over-interface`
|
|
458
|
+
* ships in the same recommended config and rewrites
|
|
459
|
+
* `interface S extends Base { … }` to `type S = Base & { … }` by `--fix`:
|
|
460
|
+
* treating every non-literal alias as unreadable would let that sibling fix
|
|
461
|
+
* retire the member check for every contract written as an interface with a
|
|
462
|
+
* heritage clause (#1679). Anything else — a union, a mapped or conditional
|
|
463
|
+
* type, a reference whose target lives in another module — hides its member
|
|
464
|
+
* list from a syntactic reader and stays unreadable, so the member keeps its
|
|
465
|
+
* exemption.
|
|
466
|
+
*/
|
|
467
|
+
function aliasCoversName(typeNode, memberName, index, visited) {
|
|
468
|
+
switch (typeNode.type) {
|
|
469
|
+
case utils_1.AST_NODE_TYPES.TSTypeLiteral:
|
|
470
|
+
return membersDeclareName(typeNode.members, memberName);
|
|
471
|
+
case utils_1.AST_NODE_TYPES.TSIntersectionType:
|
|
472
|
+
return intersectionCoversName(typeNode, memberName, index, visited);
|
|
473
|
+
default:
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Reports whether an intersection accounts for `memberName`. An intersection
|
|
479
|
+
* contributes every member of every constituent, so one constituent declaring
|
|
480
|
+
* the name settles the question — and a constituent whose members cannot be
|
|
481
|
+
* read settles it too, in the exempting direction: the name may well be that
|
|
482
|
+
* hidden part's, and this plugin prefers a false negative over a false positive
|
|
483
|
+
* (#1350). The member is only left to the class author when every constituent
|
|
484
|
+
* is readable and none of them declares it.
|
|
485
|
+
*/
|
|
486
|
+
function intersectionCoversName(intersection, memberName, index, visited) {
|
|
487
|
+
return intersection.types.some((constituent) => constituentCoversName(constituent, memberName, index, visited));
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Answers `intersectionCoversName` for one constituent. A reference is followed
|
|
491
|
+
* through the file's declarations by the same resolver a heritage clause uses,
|
|
492
|
+
* so a contract split across a local base and an inline literal reads as a
|
|
493
|
+
* whole; a reference the file does not declare, and a qualified or otherwise
|
|
494
|
+
* opaque constituent, count as unreadable.
|
|
495
|
+
*/
|
|
496
|
+
function constituentCoversName(typeNode, memberName, index, visited) {
|
|
497
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
498
|
+
return typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
|
|
499
|
+
? contractCoversName(typeNode.typeName.name, memberName, index, visited)
|
|
500
|
+
: true;
|
|
501
|
+
}
|
|
502
|
+
return aliasCoversName(typeNode, memberName, index, visited);
|
|
503
|
+
}
|
|
457
504
|
function heritageCoversName(heritage, memberName, index, visited) {
|
|
458
505
|
return heritage.some((clause) => {
|
|
459
506
|
const typeName = heritageTypeName(clause.expression);
|
|
@@ -2,6 +2,7 @@ type Options = [
|
|
|
2
2
|
{
|
|
3
3
|
coordinatorPatterns?: string[];
|
|
4
4
|
rateLimitedPatterns?: string[];
|
|
5
|
+
ignoreTestFiles?: boolean;
|
|
5
6
|
}
|
|
6
7
|
];
|
|
7
8
|
export declare const parallelizeLoopAwaits: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"parallelizeLoopAwaits", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -3,6 +3,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parallelizeLoopAwaits = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
// Anchored at the end of the path so multi-part suffixes such as
|
|
7
|
+
// `EventRegistry.integration.test.ts` are recognized while production modules
|
|
8
|
+
// that merely contain the word (`testHelpers.ts`, `latest.ts`, `contest/Thing.ts`)
|
|
9
|
+
// keep their enforcement.
|
|
10
|
+
const TEST_FILE_SUFFIX = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
11
|
+
// Jest convention directories hold test-only modules regardless of file name.
|
|
12
|
+
const TEST_FILE_DIRECTORY = /(^|\/)(__tests__|__mocks__)\//;
|
|
13
|
+
/**
|
|
14
|
+
* A test suite serves no requests and is not latency-critical, so the rule's
|
|
15
|
+
* rationale — that sequential awaits make network and I/O latency add up — does
|
|
16
|
+
* not apply to it. A loop in a suite instead replays one entrypoint to exercise
|
|
17
|
+
* behavior that accumulates across calls: each iteration must observe the state
|
|
18
|
+
* the previous iteration stored, and that state usually lives in a mock closure
|
|
19
|
+
* the loop body never names. The dependency is a side effect rather than a
|
|
20
|
+
* value, so it is invisible to every syntactic barrier below, and
|
|
21
|
+
* `Promise.all` would let all iterations observe the same initial state
|
|
22
|
+
* (issues #1395, #1687).
|
|
23
|
+
*/
|
|
24
|
+
const isTestFile = (filename) => TEST_FILE_SUFFIX.test(filename) || TEST_FILE_DIRECTORY.test(filename);
|
|
6
25
|
const DEFAULT_COORDINATOR_PATTERNS = [
|
|
7
26
|
'batchManager',
|
|
8
27
|
'batch',
|
|
@@ -23,8 +42,16 @@ const defaultOptions = [
|
|
|
23
42
|
{
|
|
24
43
|
coordinatorPatterns: DEFAULT_COORDINATOR_PATTERNS,
|
|
25
44
|
rateLimitedPatterns: DEFAULT_RATE_LIMITED_PATTERNS,
|
|
45
|
+
ignoreTestFiles: true,
|
|
26
46
|
},
|
|
27
47
|
];
|
|
48
|
+
const LOOP_NODE_TYPES = new Set([
|
|
49
|
+
utils_1.AST_NODE_TYPES.ForOfStatement,
|
|
50
|
+
utils_1.AST_NODE_TYPES.ForInStatement,
|
|
51
|
+
utils_1.AST_NODE_TYPES.ForStatement,
|
|
52
|
+
utils_1.AST_NODE_TYPES.WhileStatement,
|
|
53
|
+
utils_1.AST_NODE_TYPES.DoWhileStatement,
|
|
54
|
+
]);
|
|
28
55
|
exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
29
56
|
name: 'parallelize-loop-awaits',
|
|
30
57
|
meta: {
|
|
@@ -48,6 +75,16 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
48
75
|
items: { type: 'string' },
|
|
49
76
|
default: DEFAULT_RATE_LIMITED_PATTERNS,
|
|
50
77
|
},
|
|
78
|
+
// Deliberately carries no schema `default`. ESLint validates rule
|
|
79
|
+
// options with an ajv instance configured `useDefaults: true`, which
|
|
80
|
+
// writes schema defaults INTO the supplied options object before
|
|
81
|
+
// `defaultOptions` are merged, so a schema default here would decide
|
|
82
|
+
// the value for every consumer who passes an options object at all.
|
|
83
|
+
// `defaultOptions` plus the `?? true` read below is the single source
|
|
84
|
+
// of truth.
|
|
85
|
+
ignoreTestFiles: {
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
},
|
|
51
88
|
},
|
|
52
89
|
additionalProperties: false,
|
|
53
90
|
},
|
|
@@ -58,6 +95,14 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
58
95
|
},
|
|
59
96
|
defaultOptions,
|
|
60
97
|
create(context, [options]) {
|
|
98
|
+
// Normalize Windows backslash separators so the forward-slash directory
|
|
99
|
+
// check matches on every platform. Without this, `getFilename()` returns
|
|
100
|
+
// `C:\repo\src\__tests__\Foo.ts` on Windows and the exemption silently
|
|
101
|
+
// fails there.
|
|
102
|
+
const filename = context.getFilename().replace(/\\/g, '/');
|
|
103
|
+
if ((options?.ignoreTestFiles ?? true) && isTestFile(filename)) {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
61
106
|
const coordinatorPatterns = options?.coordinatorPatterns ?? DEFAULT_COORDINATOR_PATTERNS;
|
|
62
107
|
const rateLimitedPatterns = options?.rateLimitedPatterns ?? DEFAULT_RATE_LIMITED_PATTERNS;
|
|
63
108
|
/**
|
|
@@ -72,12 +117,11 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
72
117
|
return lower === pat || lower.startsWith(pat) || lower.endsWith(pat);
|
|
73
118
|
}
|
|
74
119
|
/**
|
|
75
|
-
* Recursively collects
|
|
76
|
-
*
|
|
120
|
+
* Recursively collects the names a node REFERENCES. With `stopAtFunctions`
|
|
121
|
+
* the walk halts at a nested function, so a name written inside a callback
|
|
122
|
+
* describes that callback's scope rather than the surrounding loop's.
|
|
77
123
|
*/
|
|
78
124
|
function collectIdentifiers(node, names, stopAtFunctions = false) {
|
|
79
|
-
if (stopAtFunctions && node.type !== node.type)
|
|
80
|
-
return; // no-op placeholder
|
|
81
125
|
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
82
126
|
names.add(node.name);
|
|
83
127
|
return;
|
|
@@ -89,6 +133,20 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
89
133
|
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
90
134
|
return;
|
|
91
135
|
}
|
|
136
|
+
// A non-computed property key is a label, not a reference: the `lock` in
|
|
137
|
+
// `await send({ lock: true })` names a field of the payload and binds
|
|
138
|
+
// nothing, so letting it reach the coordinator match would exempt the loop
|
|
139
|
+
// on the strength of a string. A computed key (`{ [lock]: true }`) is an
|
|
140
|
+
// expression that really does read the surrounding scope, and shorthand
|
|
141
|
+
// (`{ lock }`) carries the same identifier as its VALUE, so both keep
|
|
142
|
+
// their say. (#1688)
|
|
143
|
+
if (node.type === utils_1.AST_NODE_TYPES.Property) {
|
|
144
|
+
if (node.computed) {
|
|
145
|
+
collectIdentifiers(node.key, names, stopAtFunctions);
|
|
146
|
+
}
|
|
147
|
+
collectIdentifiers(node.value, names, stopAtFunctions);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
92
150
|
for (const key in node) {
|
|
93
151
|
if (key === 'parent' ||
|
|
94
152
|
key === 'range' ||
|
|
@@ -456,9 +514,9 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
456
514
|
}
|
|
457
515
|
}
|
|
458
516
|
/**
|
|
459
|
-
* Walks the loop body looking for the first AwaitExpression that
|
|
460
|
-
*
|
|
461
|
-
* first such AwaitExpression found, or null if none exists.
|
|
517
|
+
* Walks the loop body looking for the first AwaitExpression that belongs to
|
|
518
|
+
* THIS loop — one that sits in neither a nested function nor a nested loop.
|
|
519
|
+
* Returns the first such AwaitExpression found, or null if none exists.
|
|
462
520
|
*/
|
|
463
521
|
function findDirectAwait(node, isRoot) {
|
|
464
522
|
// Do not cross into nested functions; their awaits belong to a
|
|
@@ -469,6 +527,14 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
469
527
|
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
470
528
|
return null;
|
|
471
529
|
}
|
|
530
|
+
// An await inside a nested loop belongs to that loop, which gets its own
|
|
531
|
+
// visit and its own verdict. Claiming it here would anchor a second report
|
|
532
|
+
// on the very same await, and would judge it against the wrong body: the
|
|
533
|
+
// enclosing loop's barriers say nothing about whether the inner
|
|
534
|
+
// iterations can run together. The innermost loop owns the report. (#1688)
|
|
535
|
+
if (!isRoot && LOOP_NODE_TYPES.has(node.type)) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
472
538
|
if (node.type === utils_1.AST_NODE_TYPES.AwaitExpression) {
|
|
473
539
|
return node;
|
|
474
540
|
}
|
|
@@ -498,6 +564,135 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
498
564
|
}
|
|
499
565
|
return null;
|
|
500
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Reports whether a node contains a CallExpression anywhere inside it.
|
|
569
|
+
* Nested functions are deliberately traversed: a call written inside a
|
|
570
|
+
* callback in a loop clause (`items.some(() => check())`) still makes the
|
|
571
|
+
* clause's value depend on invoking something.
|
|
572
|
+
*/
|
|
573
|
+
function containsCallExpression(node) {
|
|
574
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
for (const key in node) {
|
|
578
|
+
if (key === 'parent' ||
|
|
579
|
+
key === 'range' ||
|
|
580
|
+
key === 'loc' ||
|
|
581
|
+
key === 'type')
|
|
582
|
+
continue;
|
|
583
|
+
const child = node[key];
|
|
584
|
+
if (child && typeof child === 'object') {
|
|
585
|
+
if (Array.isArray(child)) {
|
|
586
|
+
for (const item of child) {
|
|
587
|
+
if (item && typeof item === 'object' && 'type' in item) {
|
|
588
|
+
if (containsCallExpression(item))
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else if ('type' in child) {
|
|
594
|
+
if (containsCallExpression(child))
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Reports whether the loop's own continuation machinery invokes a
|
|
603
|
+
* function — a call in a `while` test, or in a `for` test or update clause.
|
|
604
|
+
*
|
|
605
|
+
* Such a loop runs until an observation comes back a certain way, so the
|
|
606
|
+
* iteration count is a function of what each iteration does:
|
|
607
|
+
* `while (!hasSettled()) { await tick(); }` and
|
|
608
|
+
* `for (let i = 0; i < 100 && !findTimer(); i += 1)` both re-read state the
|
|
609
|
+
* awaited work advances. `Promise.all` has to know the iteration count up
|
|
610
|
+
* front, so there is no parallel form of these loops at all. Only the
|
|
611
|
+
* clauses re-evaluated on every iteration count; a `for` loop's `init` and a
|
|
612
|
+
* `for...of` loop's `right` run once, so a call there says nothing about
|
|
613
|
+
* cross-iteration coupling (`for (const [k, v] of map.entries())` keeps its
|
|
614
|
+
* enforcement). A `do...while` test is re-evaluated exactly like a `while`
|
|
615
|
+
* test, so it carries the same meaning. (#1687, #1688)
|
|
616
|
+
*/
|
|
617
|
+
function isConditionCoupled(loopNode) {
|
|
618
|
+
if (loopNode.type === utils_1.AST_NODE_TYPES.WhileStatement ||
|
|
619
|
+
loopNode.type === utils_1.AST_NODE_TYPES.DoWhileStatement) {
|
|
620
|
+
return containsCallExpression(loopNode.test);
|
|
621
|
+
}
|
|
622
|
+
if (loopNode.type === utils_1.AST_NODE_TYPES.ForStatement) {
|
|
623
|
+
return ((!!loopNode.test && containsCallExpression(loopNode.test)) ||
|
|
624
|
+
(!!loopNode.update && containsCallExpression(loopNode.update)));
|
|
625
|
+
}
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Collects every identifier named in the loop's head — the `for...of`/
|
|
630
|
+
* `for...in` left and right, the `for` init, test and update, or the
|
|
631
|
+
* `while`/`do...while` test. These are the names an iteration can hand to
|
|
632
|
+
* the body.
|
|
633
|
+
*/
|
|
634
|
+
function collectLoopHeadIdentifiers(loopNode) {
|
|
635
|
+
const names = new Set();
|
|
636
|
+
const clauses = loopNode.type === utils_1.AST_NODE_TYPES.ForStatement
|
|
637
|
+
? [loopNode.init, loopNode.test, loopNode.update]
|
|
638
|
+
: loopNode.type === utils_1.AST_NODE_TYPES.WhileStatement ||
|
|
639
|
+
loopNode.type === utils_1.AST_NODE_TYPES.DoWhileStatement
|
|
640
|
+
? [loopNode.test]
|
|
641
|
+
: [loopNode.left, loopNode.right];
|
|
642
|
+
for (const clause of clauses) {
|
|
643
|
+
if (clause)
|
|
644
|
+
collectIdentifiers(clause, names, false);
|
|
645
|
+
}
|
|
646
|
+
return names;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Reports whether the loop body is a single discarded `await` of a call
|
|
650
|
+
* that consumes nothing the iteration produces, e.g.
|
|
651
|
+
* `for (let i = 0; i < 21; i += 1) { await postSuggestion(); }`.
|
|
652
|
+
*
|
|
653
|
+
* Such a loop passes nothing from the iteration into the call and keeps
|
|
654
|
+
* nothing the call returns, so the only reason to write it is an ordered
|
|
655
|
+
* side effect the callee owns — replaying one entrypoint so each run
|
|
656
|
+
* observes what the previous run stored. Every barrier below reads syntax
|
|
657
|
+
* inside the body, and this body has none to read: no assignment, no
|
|
658
|
+
* binding, no control flow, and no identifier but the callee. Reporting it
|
|
659
|
+
* would be a verdict passed on zero evidence, and the plugin prefers a
|
|
660
|
+
* false negative to a false positive.
|
|
661
|
+
*
|
|
662
|
+
* A call that names anything from the loop head is excluded, because that
|
|
663
|
+
* name IS the evidence: `for (const doc of snap.docs) { await
|
|
664
|
+
* doc.ref.delete(); }` takes no arguments either, yet each iteration
|
|
665
|
+
* addresses its own document and the loop is exactly the shape the rule
|
|
666
|
+
* exists to flag. (#1687)
|
|
667
|
+
*/
|
|
668
|
+
function isBareDiscardedZeroArgCall(loopNode) {
|
|
669
|
+
const { body } = loopNode;
|
|
670
|
+
const statements = body.type === utils_1.AST_NODE_TYPES.BlockStatement ? body.body : [body];
|
|
671
|
+
if (statements.length !== 1)
|
|
672
|
+
return false;
|
|
673
|
+
const [statement] = statements;
|
|
674
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.ExpressionStatement)
|
|
675
|
+
return false;
|
|
676
|
+
if (statement.expression.type !== utils_1.AST_NODE_TYPES.AwaitExpression) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
const { argument } = statement.expression;
|
|
680
|
+
const callExpr = argument.type === utils_1.AST_NODE_TYPES.ChainExpression
|
|
681
|
+
? argument.expression
|
|
682
|
+
: argument;
|
|
683
|
+
if (callExpr.type !== utils_1.AST_NODE_TYPES.CallExpression ||
|
|
684
|
+
callExpr.arguments.length !== 0) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
const headNames = collectLoopHeadIdentifiers(loopNode);
|
|
688
|
+
const callNames = new Set();
|
|
689
|
+
collectIdentifiers(callExpr, callNames, false);
|
|
690
|
+
for (const name of callNames) {
|
|
691
|
+
if (headNames.has(name))
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
501
696
|
/**
|
|
502
697
|
* Central analysis for a loop node. Returns the AwaitExpression to
|
|
503
698
|
* report on, or null if the loop should not be flagged.
|
|
@@ -506,6 +701,21 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
506
701
|
const body = loopNode.body;
|
|
507
702
|
if (!body)
|
|
508
703
|
return null;
|
|
704
|
+
// Exclusion: `for await (const x of stream)` consumes an async iterable,
|
|
705
|
+
// which the language pulls one value at a time; the sequencing is the
|
|
706
|
+
// construct's meaning rather than an oversight.
|
|
707
|
+
if (loopNode.type === utils_1.AST_NODE_TYPES.ForOfStatement &&
|
|
708
|
+
loopNode.await === true) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
// Exclusion: the loop's continuation condition or update invokes a
|
|
712
|
+
// function, so how many iterations run depends on what they do
|
|
713
|
+
if (isConditionCoupled(loopNode))
|
|
714
|
+
return null;
|
|
715
|
+
// Exclusion: the body is a lone discarded await of a zero-argument call,
|
|
716
|
+
// which carries no evidence either way
|
|
717
|
+
if (isBareDiscardedZeroArgCall(loopNode))
|
|
718
|
+
return null;
|
|
509
719
|
// Find an await directly inside the loop body (not in nested async fns)
|
|
510
720
|
const awaitExpr = findDirectAwait(body, true);
|
|
511
721
|
if (!awaitExpr)
|
|
@@ -539,43 +749,23 @@ exports.parallelizeLoopAwaits = (0, createRule_1.createRule)({
|
|
|
539
749
|
}
|
|
540
750
|
return awaitExpr;
|
|
541
751
|
}
|
|
752
|
+
function checkLoop(loopNode) {
|
|
753
|
+
const awaitExpr = analyzeLoop(loopNode);
|
|
754
|
+
if (awaitExpr) {
|
|
755
|
+
context.report({
|
|
756
|
+
node: awaitExpr,
|
|
757
|
+
messageId: 'parallelizeLoopAwaits',
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
542
761
|
return {
|
|
543
|
-
ForOfStatement
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
},
|
|
552
|
-
ForInStatement(node) {
|
|
553
|
-
const awaitExpr = analyzeLoop(node);
|
|
554
|
-
if (awaitExpr) {
|
|
555
|
-
context.report({
|
|
556
|
-
node: awaitExpr,
|
|
557
|
-
messageId: 'parallelizeLoopAwaits',
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
},
|
|
561
|
-
ForStatement(node) {
|
|
562
|
-
const awaitExpr = analyzeLoop(node);
|
|
563
|
-
if (awaitExpr) {
|
|
564
|
-
context.report({
|
|
565
|
-
node: awaitExpr,
|
|
566
|
-
messageId: 'parallelizeLoopAwaits',
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
},
|
|
570
|
-
WhileStatement(node) {
|
|
571
|
-
const awaitExpr = analyzeLoop(node);
|
|
572
|
-
if (awaitExpr) {
|
|
573
|
-
context.report({
|
|
574
|
-
node: awaitExpr,
|
|
575
|
-
messageId: 'parallelizeLoopAwaits',
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
},
|
|
762
|
+
ForOfStatement: checkLoop,
|
|
763
|
+
ForInStatement: checkLoop,
|
|
764
|
+
ForStatement: checkLoop,
|
|
765
|
+
WhileStatement: checkLoop,
|
|
766
|
+
// A `do...while` repeats one body per iteration exactly as the other four
|
|
767
|
+
// forms do; it is the same target and earns the same analysis. (#1688)
|
|
768
|
+
DoWhileStatement: checkLoop,
|
|
579
769
|
};
|
|
580
770
|
},
|
|
581
771
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
type OptionShape = {
|
|
2
3
|
stripPrefixes?: string[];
|
|
3
4
|
ignoredMethods?: string[];
|
|
@@ -10,5 +11,5 @@ type OptionShape = {
|
|
|
10
11
|
};
|
|
11
12
|
type Options = [OptionShape];
|
|
12
13
|
type MessageIds = 'preferGetter' | 'preferGetterSideEffect';
|
|
13
|
-
export declare const preferGetterOverParameterlessMethod:
|
|
14
|
+
export declare const preferGetterOverParameterlessMethod: TSESLint.RuleModule<MessageIds, Options, TSESLint.RuleListener>;
|
|
14
15
|
export default preferGetterOverParameterlessMethod;
|