@blumintinc/eslint-plugin-blumint 1.20.118 → 1.20.119
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/lib/index.js +1 -1
- package/lib/rules/enforce-firestore-doc-ref-generic.js +9 -30
- package/lib/rules/enforce-firestore-set-merge.js +9 -45
- package/lib/rules/no-direct-function-state.js +9 -30
- package/lib/rules/no-explicit-return-type.js +10 -37
- package/lib/rules/no-firestore-object-arrays.js +14 -65
- package/lib/rules/prefer-batch-operations.js +24 -45
- package/lib/rules/prefer-block-comments-for-declarations.js +99 -15
- package/lib/rules/prefer-spread-over-reassembly.js +3 -34
- package/lib/rules/prevent-children-clobber.js +9 -38
- package/lib/rules/require-hooks-default-params.js +16 -20
- package/lib/rules/require-props-composition.js +35 -72
- package/lib/utils/lexicalScope.d.ts +84 -0
- package/lib/utils/lexicalScope.js +131 -0
- package/package.json +1 -1
- package/release-manifest.json +23 -0
package/lib/index.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.enforceFirestoreDocRefGeneric = void 0;
|
|
|
11
11
|
const utils_1 = require("@typescript-eslint/utils");
|
|
12
12
|
const createRule_1 = require("../utils/createRule");
|
|
13
13
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
14
|
+
const lexicalScope_1 = require("../utils/lexicalScope");
|
|
14
15
|
/** The Firestore reference types that carry a document-shape generic. */
|
|
15
16
|
const REFERENCE_TYPE_NAMES = new Set([
|
|
16
17
|
'DocumentReference',
|
|
@@ -38,20 +39,6 @@ const referenceTypeNameOf = (typeName) => {
|
|
|
38
39
|
}
|
|
39
40
|
return undefined;
|
|
40
41
|
};
|
|
41
|
-
/** Statement containers a type declaration can be a direct child of. */
|
|
42
|
-
function statementsOf(node) {
|
|
43
|
-
switch (node.type) {
|
|
44
|
-
case utils_1.AST_NODE_TYPES.Program:
|
|
45
|
-
case utils_1.AST_NODE_TYPES.BlockStatement:
|
|
46
|
-
case utils_1.AST_NODE_TYPES.TSModuleBlock:
|
|
47
|
-
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
48
|
-
return node.body;
|
|
49
|
-
case utils_1.AST_NODE_TYPES.SwitchCase:
|
|
50
|
-
return node.consequent;
|
|
51
|
-
default:
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
42
|
/**
|
|
56
43
|
* The type declaration a statement makes, looking through `export`.
|
|
57
44
|
*
|
|
@@ -61,10 +48,7 @@ function statementsOf(node) {
|
|
|
61
48
|
* distinction the document shape knows anything about.
|
|
62
49
|
*/
|
|
63
50
|
function typeDeclarationNamed(statement, name) {
|
|
64
|
-
const declared =
|
|
65
|
-
statement.declaration
|
|
66
|
-
? statement.declaration
|
|
67
|
-
: statement;
|
|
51
|
+
const declared = (0, lexicalScope_1.declarationOf)(statement);
|
|
68
52
|
if ((declared.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
|
|
69
53
|
declared.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) &&
|
|
70
54
|
declared.id.name === name) {
|
|
@@ -83,20 +67,15 @@ function typeDeclarationNamed(statement, name) {
|
|
|
83
67
|
* dropped the nested-`any` check rather than reporting anything.
|
|
84
68
|
*/
|
|
85
69
|
function declarationOfType(from, name) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const declaration = typeDeclarationNamed(statement, name);
|
|
92
|
-
if (declaration) {
|
|
93
|
-
return declaration;
|
|
94
|
-
}
|
|
70
|
+
return (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => {
|
|
71
|
+
for (const statement of statements) {
|
|
72
|
+
const declaration = typeDeclarationNamed(statement, name);
|
|
73
|
+
if (declaration) {
|
|
74
|
+
return declaration;
|
|
95
75
|
}
|
|
96
76
|
}
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
return undefined;
|
|
77
|
+
return undefined;
|
|
78
|
+
});
|
|
100
79
|
}
|
|
101
80
|
/**
|
|
102
81
|
* @type {import('eslint').Rule.RuleModule}
|
|
@@ -5,6 +5,7 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
7
|
const disableDirectives_1 = require("../utils/disableDirectives");
|
|
8
|
+
const lexicalScope_1 = require("../utils/lexicalScope");
|
|
8
9
|
const FIRESTORE_MODULES = new Set(['firebase/firestore', 'firebase-admin']);
|
|
9
10
|
const UPDATE_DOC = 'updateDoc';
|
|
10
11
|
const SET_DOC = 'setDoc';
|
|
@@ -192,20 +193,6 @@ function isPrimitiveLiteral(node) {
|
|
|
192
193
|
typeof value === 'boolean' ||
|
|
193
194
|
typeof value === 'bigint');
|
|
194
195
|
}
|
|
195
|
-
/** Statement containers a declaration can be a direct child of. */
|
|
196
|
-
function statementsOf(node) {
|
|
197
|
-
switch (node.type) {
|
|
198
|
-
case utils_1.AST_NODE_TYPES.Program:
|
|
199
|
-
case utils_1.AST_NODE_TYPES.BlockStatement:
|
|
200
|
-
case utils_1.AST_NODE_TYPES.TSModuleBlock:
|
|
201
|
-
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
202
|
-
return node.body;
|
|
203
|
-
case utils_1.AST_NODE_TYPES.SwitchCase:
|
|
204
|
-
return node.consequent;
|
|
205
|
-
default:
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
196
|
/** Whether a declarator is initialized from a `<x>.firestore()` call. */
|
|
210
197
|
function initializesFirestore(declarator) {
|
|
211
198
|
const { init } = declarator;
|
|
@@ -225,10 +212,7 @@ function initializesFirestore(declarator) {
|
|
|
225
212
|
* "find the in-file declaration that carries the evidence" purpose.
|
|
226
213
|
*/
|
|
227
214
|
function declaresFirestoreInstance(statement) {
|
|
228
|
-
const declaration =
|
|
229
|
-
statement.declaration
|
|
230
|
-
? statement.declaration
|
|
231
|
-
: statement;
|
|
215
|
+
const declaration = (0, lexicalScope_1.declarationOf)(statement);
|
|
232
216
|
return (declaration.type === utils_1.AST_NODE_TYPES.VariableDeclaration &&
|
|
233
217
|
declaration.declarations.some(initializesFirestore));
|
|
234
218
|
}
|
|
@@ -244,15 +228,9 @@ function declaresFirestoreInstance(statement) {
|
|
|
244
228
|
* falls through to the next one out instead of answering for the whole chain.
|
|
245
229
|
*/
|
|
246
230
|
function hasFirestoreInstanceInScope(node) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (statements?.some(declaresFirestoreInstance)) {
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
current = current.parent;
|
|
254
|
-
}
|
|
255
|
-
return false;
|
|
231
|
+
return ((0, lexicalScope_1.resolveInEnclosingScopes)(node, (statements) => statements.some((statement) => declaresFirestoreInstance(statement))
|
|
232
|
+
? true
|
|
233
|
+
: undefined) === true);
|
|
256
234
|
}
|
|
257
235
|
exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
258
236
|
name: 'enforce-firestore-set-merge',
|
|
@@ -303,16 +281,13 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
303
281
|
}
|
|
304
282
|
const classes = new Map();
|
|
305
283
|
for (const statement of statements) {
|
|
306
|
-
const declaration =
|
|
307
|
-
|
|
308
|
-
? statement.declaration
|
|
309
|
-
: statement;
|
|
310
|
-
if (declaration?.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
|
|
284
|
+
const declaration = (0, lexicalScope_1.declarationOf)(statement);
|
|
285
|
+
if (declaration.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
|
|
311
286
|
declaration.id) {
|
|
312
287
|
classes.set(declaration.id.name, declaration.body);
|
|
313
288
|
continue;
|
|
314
289
|
}
|
|
315
|
-
if (declaration
|
|
290
|
+
if (declaration.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
316
291
|
for (const declarator of declaration.declarations) {
|
|
317
292
|
if (declarator.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
318
293
|
declarator.init?.type === utils_1.AST_NODE_TYPES.ClassExpression) {
|
|
@@ -340,18 +315,7 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
|
|
|
340
315
|
* the shadow.
|
|
341
316
|
*/
|
|
342
317
|
function resolveClassBody(name, reference) {
|
|
343
|
-
|
|
344
|
-
while (current) {
|
|
345
|
-
const statements = statementsOf(current);
|
|
346
|
-
if (statements) {
|
|
347
|
-
const found = containerClasses(current, statements).get(name);
|
|
348
|
-
if (found) {
|
|
349
|
-
return found;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
current = current.parent;
|
|
353
|
-
}
|
|
354
|
-
return null;
|
|
318
|
+
return ((0, lexicalScope_1.resolveInEnclosingScopes)(reference, (statements, container) => containerClasses(container, statements).get(name)) ?? null);
|
|
355
319
|
}
|
|
356
320
|
function enclosingClassBody(node) {
|
|
357
321
|
let current = node.parent;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.noDirectFunctionState = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const lexicalScope_1 = require("../utils/lexicalScope");
|
|
6
7
|
const DEFAULT_FUNCTION_PATTERNS = [
|
|
7
8
|
'callback',
|
|
8
9
|
'handler',
|
|
@@ -10,20 +11,6 @@ const DEFAULT_FUNCTION_PATTERNS = [
|
|
|
10
11
|
'func',
|
|
11
12
|
'on[A-Z].*',
|
|
12
13
|
];
|
|
13
|
-
/** Statement containers a type alias can be a direct child of. */
|
|
14
|
-
function statementsOf(node) {
|
|
15
|
-
switch (node.type) {
|
|
16
|
-
case utils_1.AST_NODE_TYPES.Program:
|
|
17
|
-
case utils_1.AST_NODE_TYPES.BlockStatement:
|
|
18
|
-
case utils_1.AST_NODE_TYPES.TSModuleBlock:
|
|
19
|
-
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
20
|
-
return node.body;
|
|
21
|
-
case utils_1.AST_NODE_TYPES.SwitchCase:
|
|
22
|
-
return node.consequent;
|
|
23
|
-
default:
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
14
|
/**
|
|
28
15
|
* The type alias a statement declares under `name`, looking through `export`.
|
|
29
16
|
*
|
|
@@ -33,10 +20,7 @@ function statementsOf(node) {
|
|
|
33
20
|
* readable, which says nothing about what the state holds.
|
|
34
21
|
*/
|
|
35
22
|
function typeAliasNamed(statement, name) {
|
|
36
|
-
const declaration =
|
|
37
|
-
statement.declaration
|
|
38
|
-
? statement.declaration
|
|
39
|
-
: statement;
|
|
23
|
+
const declaration = (0, lexicalScope_1.declarationOf)(statement);
|
|
40
24
|
return declaration.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration &&
|
|
41
25
|
declaration.id.name === name
|
|
42
26
|
? declaration
|
|
@@ -56,20 +40,15 @@ function typeAliasNamed(statement, name) {
|
|
|
56
40
|
* hoisting of type declarations.
|
|
57
41
|
*/
|
|
58
42
|
function resolveTypeAlias(from, name) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const alias = typeAliasNamed(statement, name);
|
|
65
|
-
if (alias) {
|
|
66
|
-
return alias;
|
|
67
|
-
}
|
|
43
|
+
return (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => {
|
|
44
|
+
for (const statement of statements) {
|
|
45
|
+
const alias = typeAliasNamed(statement, name);
|
|
46
|
+
if (alias) {
|
|
47
|
+
return alias;
|
|
68
48
|
}
|
|
69
49
|
}
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
return undefined;
|
|
50
|
+
return undefined;
|
|
51
|
+
});
|
|
73
52
|
}
|
|
74
53
|
/**
|
|
75
54
|
* Returns true if the TSTypeAnnotation node (from useState's type parameter)
|
|
@@ -5,6 +5,7 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const disableDirectives_1 = require("../utils/disableDirectives");
|
|
7
7
|
const importRemoval_1 = require("../utils/importRemoval");
|
|
8
|
+
const lexicalScope_1 = require("../utils/lexicalScope");
|
|
8
9
|
const defaultOptions = {
|
|
9
10
|
allowRecursiveFunctions: true,
|
|
10
11
|
allowOverloadedFunctions: true,
|
|
@@ -401,24 +402,6 @@ function collectReturnIdentifierNames(fn, visitorKeys) {
|
|
|
401
402
|
}
|
|
402
403
|
return names;
|
|
403
404
|
}
|
|
404
|
-
/**
|
|
405
|
-
* The statement list a container holds, or undefined for a node that holds
|
|
406
|
-
* none. These are every place a function can be declared as a direct child, so
|
|
407
|
-
* walking them outward reproduces TypeScript's own scope chain.
|
|
408
|
-
*/
|
|
409
|
-
function statementsOf(node) {
|
|
410
|
-
switch (node.type) {
|
|
411
|
-
case utils_1.AST_NODE_TYPES.Program:
|
|
412
|
-
case utils_1.AST_NODE_TYPES.BlockStatement:
|
|
413
|
-
case utils_1.AST_NODE_TYPES.TSModuleBlock:
|
|
414
|
-
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
415
|
-
return node.body;
|
|
416
|
-
case utils_1.AST_NODE_TYPES.SwitchCase:
|
|
417
|
-
return node.consequent;
|
|
418
|
-
default:
|
|
419
|
-
return undefined;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
405
|
/**
|
|
423
406
|
* What a statement list binds each of its names to: the function when the name
|
|
424
407
|
* denotes one, `null` when it denotes anything else.
|
|
@@ -437,11 +420,7 @@ function scopeBindings(statements) {
|
|
|
437
420
|
for (const rawStatement of statements) {
|
|
438
421
|
// `export function f() {}` is the same declaration one AST node deeper, and
|
|
439
422
|
// the `export` keyword alone cannot decide whether a name is resolvable.
|
|
440
|
-
const statement = (
|
|
441
|
-
rawStatement.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) &&
|
|
442
|
-
rawStatement.declaration
|
|
443
|
-
? rawStatement.declaration
|
|
444
|
-
: rawStatement;
|
|
423
|
+
const statement = (0, lexicalScope_1.declarationOf)(rawStatement);
|
|
445
424
|
if (statement.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && statement.id) {
|
|
446
425
|
bind(statement.id.name, statement.body ? statement : null);
|
|
447
426
|
continue;
|
|
@@ -510,20 +489,14 @@ function createReturnCycleResolver(visitorKeys) {
|
|
|
510
489
|
bindings.set(container, computed);
|
|
511
490
|
return computed;
|
|
512
491
|
};
|
|
513
|
-
const resolveFunctionInScope = (from, name) => {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
current = current.parent;
|
|
524
|
-
}
|
|
525
|
-
return undefined;
|
|
526
|
-
};
|
|
492
|
+
const resolveFunctionInScope = (from, name) => (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements, container) => {
|
|
493
|
+
const scope = bindingsOf(container, statements);
|
|
494
|
+
if (!scope.has(name))
|
|
495
|
+
return undefined;
|
|
496
|
+
// A non-function binding still shadows a same-named outer function, so
|
|
497
|
+
// it ends the search without yielding one.
|
|
498
|
+
return scope.get(name) ?? lexicalScope_1.BOUND_UNPROVABLE;
|
|
499
|
+
});
|
|
527
500
|
const edgesOf = (fn) => {
|
|
528
501
|
const cached = edges.get(fn);
|
|
529
502
|
if (cached)
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.noFirestoreObjectArrays = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const lexicalScope_1 = require("../utils/lexicalScope");
|
|
6
7
|
const PRIMITIVE_TYPES = new Set([
|
|
7
8
|
'string',
|
|
8
9
|
'number',
|
|
@@ -14,36 +15,6 @@ const PRIMITIVE_TYPES = new Set([
|
|
|
14
15
|
'GeoPoint',
|
|
15
16
|
]);
|
|
16
17
|
const UNKNOWN_FIELD_LABEL = '<unknown field>';
|
|
17
|
-
/** Statements a declaration can be a direct child of, innermost outward. */
|
|
18
|
-
const statementsOf = (node) => {
|
|
19
|
-
switch (node.type) {
|
|
20
|
-
case utils_1.AST_NODE_TYPES.Program:
|
|
21
|
-
case utils_1.AST_NODE_TYPES.BlockStatement:
|
|
22
|
-
case utils_1.AST_NODE_TYPES.TSModuleBlock:
|
|
23
|
-
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
24
|
-
return node.body;
|
|
25
|
-
case utils_1.AST_NODE_TYPES.SwitchCase:
|
|
26
|
-
return node.consequent;
|
|
27
|
-
default:
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* The declaration a statement carries, looking through `export`.
|
|
33
|
-
*
|
|
34
|
-
* `export type Comment = ...` is the same declaration one AST node deeper,
|
|
35
|
-
* inside an `ExportNamedDeclaration`. Reading the statement without unwrapping
|
|
36
|
-
* would let the `export` keyword alone decide whether an element type is
|
|
37
|
-
* resolvable, which says nothing about the shape it denotes.
|
|
38
|
-
*/
|
|
39
|
-
const declarationOf = (statement) => {
|
|
40
|
-
if ((statement.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
|
|
41
|
-
statement.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) &&
|
|
42
|
-
statement.declaration) {
|
|
43
|
-
return statement.declaration;
|
|
44
|
-
}
|
|
45
|
-
return statement;
|
|
46
|
-
};
|
|
47
18
|
/**
|
|
48
19
|
* Interface beats enum beats alias, matching the order the file-wide tables are
|
|
49
20
|
* consulted in. The orderings can only differ for a name declared twice in one
|
|
@@ -55,7 +26,7 @@ const typeDeclarationIn = (statements, name) => {
|
|
|
55
26
|
let enumMatch;
|
|
56
27
|
let aliasMatch;
|
|
57
28
|
for (const statement of statements) {
|
|
58
|
-
const declaration = declarationOf(statement);
|
|
29
|
+
const declaration = (0, lexicalScope_1.declarationOf)(statement);
|
|
59
30
|
switch (declaration.type) {
|
|
60
31
|
case utils_1.AST_NODE_TYPES.TSInterfaceDeclaration:
|
|
61
32
|
if (declaration.id.name === name)
|
|
@@ -146,13 +117,13 @@ const unwrapExpressionAssertions = (node) => {
|
|
|
146
117
|
};
|
|
147
118
|
/**
|
|
148
119
|
* A same-named `const` binding shadows any outer one whether or not it holds an
|
|
149
|
-
* array literal, so the lexical search must stop at it
|
|
150
|
-
* it to a binding the reference
|
|
120
|
+
* array literal, so the lexical search must stop at it — reported as
|
|
121
|
+
* `BOUND_UNPROVABLE` — rather than reaching past it to a binding the reference
|
|
122
|
+
* cannot denote, or falling back to the file-wide table.
|
|
151
123
|
*/
|
|
152
|
-
const CONST_ARRAY_SHADOWED = 'shadowed';
|
|
153
124
|
const constArrayIn = (statements, name) => {
|
|
154
125
|
for (const statement of statements) {
|
|
155
|
-
const declaration = declarationOf(statement);
|
|
126
|
+
const declaration = (0, lexicalScope_1.declarationOf)(statement);
|
|
156
127
|
if (declaration.type !== utils_1.AST_NODE_TYPES.VariableDeclaration ||
|
|
157
128
|
declaration.kind !== 'const') {
|
|
158
129
|
continue;
|
|
@@ -163,11 +134,11 @@ const constArrayIn = (statements, name) => {
|
|
|
163
134
|
continue;
|
|
164
135
|
}
|
|
165
136
|
if (!declarator.init)
|
|
166
|
-
return
|
|
137
|
+
return lexicalScope_1.BOUND_UNPROVABLE;
|
|
167
138
|
const init = unwrapExpressionAssertions(declarator.init);
|
|
168
139
|
return init.type === utils_1.AST_NODE_TYPES.ArrayExpression
|
|
169
140
|
? init
|
|
170
|
-
:
|
|
141
|
+
: lexicalScope_1.BOUND_UNPROVABLE;
|
|
171
142
|
}
|
|
172
143
|
}
|
|
173
144
|
return undefined;
|
|
@@ -395,39 +366,17 @@ exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
|
|
|
395
366
|
* declaration written below its own reference still resolves, matching
|
|
396
367
|
* TypeScript's hoisting of type declarations.
|
|
397
368
|
*/
|
|
398
|
-
const resolveTypeName = (from, name) => {
|
|
399
|
-
let current = from;
|
|
400
|
-
while (current) {
|
|
401
|
-
const statements = statementsOf(current);
|
|
402
|
-
if (statements) {
|
|
403
|
-
const declared = typeDeclarationIn(statements, name);
|
|
404
|
-
if (declared)
|
|
405
|
-
return declared;
|
|
406
|
-
}
|
|
407
|
-
current = current.parent;
|
|
408
|
-
}
|
|
369
|
+
const resolveTypeName = (from, name) => (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => typeDeclarationIn(statements, name), () => {
|
|
409
370
|
if (interfaceNames.has(name))
|
|
410
371
|
return { kind: 'interface' };
|
|
411
372
|
if (enumNames.has(name))
|
|
412
373
|
return { kind: 'enum' };
|
|
413
374
|
const aliased = aliasNameToType.get(name);
|
|
414
|
-
return aliased
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const statements = statementsOf(current);
|
|
420
|
-
if (statements) {
|
|
421
|
-
const declared = constArrayIn(statements, name);
|
|
422
|
-
if (declared === CONST_ARRAY_SHADOWED)
|
|
423
|
-
return undefined;
|
|
424
|
-
if (declared)
|
|
425
|
-
return declared;
|
|
426
|
-
}
|
|
427
|
-
current = current.parent;
|
|
428
|
-
}
|
|
429
|
-
return constArrayNameToLiteral.get(name);
|
|
430
|
-
};
|
|
375
|
+
return aliased
|
|
376
|
+
? { kind: 'alias', typeAnnotation: aliased }
|
|
377
|
+
: undefined;
|
|
378
|
+
});
|
|
379
|
+
const resolveConstArray = (from, name) => (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => constArrayIn(statements, name), () => constArrayNameToLiteral.get(name));
|
|
431
380
|
// Keyed by resolved declaration rather than by name: one name can denote
|
|
432
381
|
// different declarations in different scopes, so a name-keyed memo would
|
|
433
382
|
// answer for whichever scope asked first.
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.preferBatchOperations = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const lexicalScope_1 = require("../utils/lexicalScope");
|
|
6
7
|
const SETTER_METHODS = new Set(['set', 'overwrite']);
|
|
7
8
|
const ARRAY_METHODS = new Set([
|
|
8
9
|
'map',
|
|
@@ -261,20 +262,6 @@ function isMapInstance(node) {
|
|
|
261
262
|
}
|
|
262
263
|
return false;
|
|
263
264
|
}
|
|
264
|
-
/** Statements a declaration can be a direct child of, innermost outward. */
|
|
265
|
-
function statementsOf(node) {
|
|
266
|
-
switch (node.type) {
|
|
267
|
-
case utils_1.AST_NODE_TYPES.Program:
|
|
268
|
-
case utils_1.AST_NODE_TYPES.BlockStatement:
|
|
269
|
-
case utils_1.AST_NODE_TYPES.TSModuleBlock:
|
|
270
|
-
case utils_1.AST_NODE_TYPES.StaticBlock:
|
|
271
|
-
return node.body;
|
|
272
|
-
case utils_1.AST_NODE_TYPES.SwitchCase:
|
|
273
|
-
return node.consequent;
|
|
274
|
-
default:
|
|
275
|
-
return undefined;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
265
|
/**
|
|
279
266
|
* Lexical lookup of a local declaration, innermost scope outward.
|
|
280
267
|
*
|
|
@@ -285,28 +272,20 @@ function statementsOf(node) {
|
|
|
285
272
|
* searched instead, and the first match wins, which is what shadowing requires.
|
|
286
273
|
*/
|
|
287
274
|
function findVariableDeclaration(node, varName) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (declaration.type !== utils_1.AST_NODE_TYPES.VariableDeclaration)
|
|
298
|
-
continue;
|
|
299
|
-
for (const decl of declaration.declarations) {
|
|
300
|
-
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
301
|
-
decl.id.name === varName) {
|
|
302
|
-
return decl;
|
|
303
|
-
}
|
|
275
|
+
return (0, lexicalScope_1.resolveInEnclosingScopes)(node, (statements) => {
|
|
276
|
+
for (const statement of statements) {
|
|
277
|
+
const declaration = (0, lexicalScope_1.declarationOf)(statement);
|
|
278
|
+
if (declaration.type !== utils_1.AST_NODE_TYPES.VariableDeclaration)
|
|
279
|
+
continue;
|
|
280
|
+
for (const decl of declaration.declarations) {
|
|
281
|
+
if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
282
|
+
decl.id.name === varName) {
|
|
283
|
+
return decl;
|
|
304
284
|
}
|
|
305
285
|
}
|
|
306
286
|
}
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
return undefined;
|
|
287
|
+
return undefined;
|
|
288
|
+
});
|
|
310
289
|
}
|
|
311
290
|
function isSetterMethodCall(node) {
|
|
312
291
|
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
@@ -431,19 +410,19 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
|
431
410
|
});
|
|
432
411
|
}
|
|
433
412
|
}
|
|
434
|
-
/**
|
|
435
|
-
* For array methods, report on the first occurrence — one syntactic
|
|
436
|
-
* call inside a `map` callback still runs once per element.
|
|
437
|
-
*
|
|
438
|
-
* A direct `Promise.all([...])` is not that: its elements are written
|
|
439
|
-
* out, so a lone `set()` is a single write and the docs call it
|
|
440
|
-
* valid. `findLoopNode` stamps `isArrayMethod: 'map'` onto the
|
|
441
|
-
* Promise.all result, which used to route it here and report on the
|
|
442
|
-
* first call — and, by adding the node to `reportedLoops`, made the
|
|
443
|
-
* deferred second-occurrence branch below unreachable for every
|
|
444
|
-
* input. Excluding it hands the decision back to that branch.
|
|
445
|
-
*/
|
|
446
413
|
else if (loopInfo.isArrayMethod && !loopInfo.isPromiseAll) {
|
|
414
|
+
/**
|
|
415
|
+
* For array methods, report on the first occurrence — one syntactic
|
|
416
|
+
* call inside a `map` callback still runs once per element.
|
|
417
|
+
*
|
|
418
|
+
* A direct `Promise.all([...])` is not that: its elements are written
|
|
419
|
+
* out, so a lone `set()` is a single write and the docs call it
|
|
420
|
+
* valid. `findLoopNode` stamps `isArrayMethod: 'map'` onto the
|
|
421
|
+
* Promise.all result, which used to route it here and report on the
|
|
422
|
+
* first call — and, by adding the node to `reportedLoops`, made the
|
|
423
|
+
* deferred second-occurrence branch below unreachable for every
|
|
424
|
+
* input. Excluding it hands the decision back to that branch.
|
|
425
|
+
*/
|
|
447
426
|
if (!reportedLoops.has(loopInfo.node)) {
|
|
448
427
|
reportedLoops.add(loopInfo.node);
|
|
449
428
|
context.report({
|