eslint-plugin-ts-data-forge 0.8.1 → 0.8.2
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/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +24 -24
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/branded-number-types.mjs.map +1 -1
- package/dist/rules/import-utils.d.mts.map +1 -1
- package/dist/rules/import-utils.mjs +4 -2
- package/dist/rules/import-utils.mjs.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.mjs +37 -23
- package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-array.mjs +14 -7
- package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs +8 -4
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs +8 -4
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.mjs +10 -5
- package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.mjs +10 -5
- package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-non-empty.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-non-empty.mjs +4 -2
- package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
- package/dist/rules/prefer-arr-scan.d.mts.map +1 -1
- package/dist/rules/prefer-arr-scan.mjs +35 -18
- package/dist/rules/prefer-arr-scan.mjs.map +1 -1
- package/dist/rules/prefer-arr-sum.d.mts.map +1 -1
- package/dist/rules/prefer-arr-sum.mjs +6 -3
- package/dist/rules/prefer-arr-sum.mjs.map +1 -1
- package/dist/rules/prefer-arr-uniq.d.mts.map +1 -1
- package/dist/rules/prefer-arr-uniq.mjs +30 -24
- package/dist/rules/prefer-arr-uniq.mjs.map +1 -1
- package/dist/rules/prefer-as-int.d.mts.map +1 -1
- package/dist/rules/prefer-as-int.mjs +11 -14
- package/dist/rules/prefer-as-int.mjs.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.mjs +32 -16
- package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-cast.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-length-cast.mjs +11 -7
- package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.mjs +8 -4
- package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
- package/dist/rules/prefer-comparison-over-nullish-guard.d.mts.map +1 -1
- package/dist/rules/prefer-comparison-over-nullish-guard.mjs +6 -3
- package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
- package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
- package/dist/rules/prefer-is-non-null-object.mjs +29 -31
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.mjs +10 -5
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -1
- package/dist/rules/prefer-num-safe-parse-float.mjs +12 -6
- package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
- package/dist/rules/prefer-num-safe-parse-int.d.mts.map +1 -1
- package/dist/rules/prefer-num-safe-parse-int.mjs +12 -6
- package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
- package/dist/rules/prefer-obj-over-entries-round-trip.d.mts.map +1 -1
- package/dist/rules/prefer-obj-over-entries-round-trip.mjs +22 -11
- package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
- package/dist/rules/prefer-range-for-loop.d.mts.map +1 -1
- package/dist/rules/prefer-range-for-loop.mjs +2 -1
- package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
- package/package.json +25 -23
- package/src/rules/ast-utils.mts +38 -26
- package/src/rules/branded-number-types.mts +1 -1
- package/src/rules/import-utils.mts +6 -2
- package/src/rules/no-unnecessary-type-guard.mts +56 -23
- package/src/rules/prefer-arr-is-array.mts +21 -7
- package/src/rules/prefer-arr-is-bounded-length-array.mts +12 -4
- package/src/rules/prefer-arr-is-fixed-length-array.mts +12 -4
- package/src/rules/prefer-arr-is-max-length-array.mts +15 -5
- package/src/rules/prefer-arr-is-min-length-array.mts +15 -5
- package/src/rules/prefer-arr-is-non-empty.mts +6 -2
- package/src/rules/prefer-arr-scan.mts +52 -18
- package/src/rules/prefer-arr-sum.mts +9 -3
- package/src/rules/prefer-arr-uniq.mts +42 -26
- package/src/rules/prefer-as-int.mts +17 -25
- package/src/rules/prefer-canonical-array-slicing.mts +48 -16
- package/src/rules/prefer-canonical-length-cast.mts +15 -7
- package/src/rules/prefer-canonical-length-guard.mts +12 -4
- package/src/rules/prefer-comparison-over-nullish-guard.mts +9 -3
- package/src/rules/prefer-is-non-null-object.mts +35 -31
- package/src/rules/prefer-is-record-and-has-key.mts +15 -5
- package/src/rules/prefer-num-safe-parse-float.mts +17 -6
- package/src/rules/prefer-num-safe-parse-int.mts +18 -6
- package/src/rules/prefer-obj-over-entries-round-trip.mts +33 -11
- package/src/rules/prefer-range-for-loop.mts +3 -1
|
@@ -156,7 +156,9 @@ export const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options> =
|
|
|
156
156
|
const strictNullChecks =
|
|
157
157
|
compilerOptions.strictNullChecks ?? compilerOptions.strict ?? false;
|
|
158
158
|
|
|
159
|
-
if (!strictNullChecks)
|
|
159
|
+
if (!strictNullChecks) {
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
160
162
|
|
|
161
163
|
const checker = parserServices.program.getTypeChecker();
|
|
162
164
|
|
|
@@ -249,7 +251,9 @@ export const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options> =
|
|
|
249
251
|
|
|
250
252
|
return {
|
|
251
253
|
CallExpression: (node) => {
|
|
252
|
-
if (node.arguments.length !== 1)
|
|
254
|
+
if (node.arguments.length !== 1) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
253
257
|
|
|
254
258
|
const argument = node.arguments[0];
|
|
255
259
|
|
|
@@ -262,15 +266,21 @@ export const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options> =
|
|
|
262
266
|
|
|
263
267
|
const resolved = resolveGuard(node.callee);
|
|
264
268
|
|
|
265
|
-
if (resolved === undefined)
|
|
269
|
+
if (resolved === undefined) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
266
272
|
|
|
267
273
|
const { canonicalName, propertyNode, isNamespace } = resolved;
|
|
268
274
|
|
|
269
|
-
if (ignored.has(canonicalName))
|
|
275
|
+
if (ignored.has(canonicalName)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
270
278
|
|
|
271
279
|
const spec = GUARD_SPECS[canonicalName];
|
|
272
280
|
|
|
273
|
-
if (spec === undefined)
|
|
281
|
+
if (spec === undefined) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
274
284
|
|
|
275
285
|
const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);
|
|
276
286
|
|
|
@@ -278,7 +288,9 @@ export const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options> =
|
|
|
278
288
|
|
|
279
289
|
const parts = collectUnionParts(argType);
|
|
280
290
|
|
|
281
|
-
if (parts === undefined)
|
|
291
|
+
if (parts === undefined) {
|
|
292
|
+
return; // opaque / generic → bail conservatively
|
|
293
|
+
}
|
|
282
294
|
|
|
283
295
|
const inputAtoms = new Set(parts.map(classifyAtom));
|
|
284
296
|
|
|
@@ -413,7 +425,9 @@ const collectUnionParts = (type: ts.Type): readonly ts.Type[] | undefined => {
|
|
|
413
425
|
const parts = type.isUnion() ? type.types : ([type] as const);
|
|
414
426
|
|
|
415
427
|
for (const part of parts) {
|
|
416
|
-
if ((part.flags & DEFERRED_OR_OPAQUE_FLAGS) !== 0)
|
|
428
|
+
if ((part.flags & DEFERRED_OR_OPAQUE_FLAGS) !== 0) {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
417
431
|
}
|
|
418
432
|
|
|
419
433
|
return parts;
|
|
@@ -473,19 +487,33 @@ const isCallableType = (
|
|
|
473
487
|
const classifyAtom = (type: ts.Type): Atom => {
|
|
474
488
|
const { flags } = type;
|
|
475
489
|
|
|
476
|
-
if ((flags & ts.TypeFlags.Undefined) !== 0)
|
|
490
|
+
if ((flags & ts.TypeFlags.Undefined) !== 0) {
|
|
491
|
+
return 'undefined';
|
|
492
|
+
}
|
|
477
493
|
|
|
478
|
-
if ((flags & ts.TypeFlags.Null) !== 0)
|
|
494
|
+
if ((flags & ts.TypeFlags.Null) !== 0) {
|
|
495
|
+
return 'null';
|
|
496
|
+
}
|
|
479
497
|
|
|
480
|
-
if ((flags & ts.TypeFlags.BooleanLike) !== 0)
|
|
498
|
+
if ((flags & ts.TypeFlags.BooleanLike) !== 0) {
|
|
499
|
+
return 'boolean';
|
|
500
|
+
}
|
|
481
501
|
|
|
482
|
-
if ((flags & ts.TypeFlags.NumberLike) !== 0)
|
|
502
|
+
if ((flags & ts.TypeFlags.NumberLike) !== 0) {
|
|
503
|
+
return 'number';
|
|
504
|
+
}
|
|
483
505
|
|
|
484
|
-
if ((flags & ts.TypeFlags.StringLike) !== 0)
|
|
506
|
+
if ((flags & ts.TypeFlags.StringLike) !== 0) {
|
|
507
|
+
return 'string';
|
|
508
|
+
}
|
|
485
509
|
|
|
486
|
-
if ((flags & ts.TypeFlags.BigIntLike) !== 0)
|
|
510
|
+
if ((flags & ts.TypeFlags.BigIntLike) !== 0) {
|
|
511
|
+
return 'bigint';
|
|
512
|
+
}
|
|
487
513
|
|
|
488
|
-
if ((flags & ts.TypeFlags.ESSymbolLike) !== 0)
|
|
514
|
+
if ((flags & ts.TypeFlags.ESSymbolLike) !== 0) {
|
|
515
|
+
return 'symbol';
|
|
516
|
+
}
|
|
489
517
|
|
|
490
518
|
// Branded primitives (e.g. `NonEmptyString = string & {...}`) are
|
|
491
519
|
// intersections; classify them by their underlying primitive constituent.
|
|
@@ -493,7 +521,9 @@ const classifyAtom = (type: ts.Type): Atom => {
|
|
|
493
521
|
for (const constituent of type.types) {
|
|
494
522
|
const atom = classifyAtom(constituent);
|
|
495
523
|
|
|
496
|
-
if (atom !== 'other')
|
|
524
|
+
if (atom !== 'other') {
|
|
525
|
+
return atom;
|
|
526
|
+
}
|
|
497
527
|
}
|
|
498
528
|
}
|
|
499
529
|
|
|
@@ -525,13 +555,19 @@ const isGuaranteedNonEmptyString = (
|
|
|
525
555
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
526
556
|
checker: ts.TypeChecker,
|
|
527
557
|
): boolean => {
|
|
528
|
-
if (type.isStringLiteral())
|
|
558
|
+
if (type.isStringLiteral()) {
|
|
559
|
+
return type.value !== '';
|
|
560
|
+
}
|
|
529
561
|
|
|
530
|
-
if (type.getProperty(BRAND_MARKER_PROPERTY) === undefined)
|
|
562
|
+
if (type.getProperty(BRAND_MARKER_PROPERTY) === undefined) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
531
565
|
|
|
532
566
|
const minLengthSymbol = type.getProperty(MIN_LENGTH_BRAND_KEY);
|
|
533
567
|
|
|
534
|
-
if (minLengthSymbol === undefined)
|
|
568
|
+
if (minLengthSymbol === undefined) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
535
571
|
|
|
536
572
|
// The `MinLength` brand value is `MinLengthTuple<N, 0>`; it has a required
|
|
537
573
|
// first element (`'0'`) exactly when `N >= 1`, i.e. the string is non-empty.
|
|
@@ -547,11 +583,8 @@ const isGuaranteedNonEmptyString = (
|
|
|
547
583
|
* literal and non-string types cannot.
|
|
548
584
|
*/
|
|
549
585
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
550
|
-
const couldBeNonEmptyString = (type: ts.Type): boolean =>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
return classifyAtom(type) === 'string';
|
|
554
|
-
};
|
|
586
|
+
const couldBeNonEmptyString = (type: ts.Type): boolean =>
|
|
587
|
+
type.isStringLiteral() ? type.value !== '' : classifyAtom(type) === 'string';
|
|
555
588
|
|
|
556
589
|
/**
|
|
557
590
|
* Returns `true` when evaluating the expression cannot have observable side
|
|
@@ -40,7 +40,9 @@ export const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
40
40
|
|
|
41
41
|
return {
|
|
42
42
|
CallExpression: (node) => {
|
|
43
|
-
if (!isArrayIsArrayCall(node))
|
|
43
|
+
if (!isArrayIsArrayCall(node)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
44
46
|
|
|
45
47
|
mut_nodesToFix.push(node);
|
|
46
48
|
},
|
|
@@ -59,7 +61,9 @@ export const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
59
61
|
fix: (fixer) => {
|
|
60
62
|
const callee = node.callee;
|
|
61
63
|
|
|
62
|
-
if (callee.type !== AST_NODE_TYPES.MemberExpression)
|
|
64
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
63
67
|
|
|
64
68
|
const replacement = `Arr.isArray${sourceCode.getText(node).slice(sourceCode.getText(callee).length)}`;
|
|
65
69
|
|
|
@@ -82,17 +86,27 @@ export const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
82
86
|
const isArrayIsArrayCall = (
|
|
83
87
|
node: DeepReadonly<TSESTree.CallExpression>,
|
|
84
88
|
): boolean => {
|
|
85
|
-
if (node.callee.type !== AST_NODE_TYPES.MemberExpression)
|
|
89
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
86
92
|
|
|
87
93
|
const { object, property } = node.callee;
|
|
88
94
|
|
|
89
|
-
if (object.type !== AST_NODE_TYPES.Identifier)
|
|
95
|
+
if (object.type !== AST_NODE_TYPES.Identifier) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
90
98
|
|
|
91
|
-
if (object.name !== 'Array')
|
|
99
|
+
if (object.name !== 'Array') {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
92
102
|
|
|
93
|
-
if (property.type !== AST_NODE_TYPES.Identifier)
|
|
103
|
+
if (property.type !== AST_NODE_TYPES.Identifier) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
94
106
|
|
|
95
|
-
if (property.name !== 'isArray')
|
|
107
|
+
if (property.name !== 'isArray') {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
96
110
|
|
|
97
111
|
return true;
|
|
98
112
|
};
|
|
@@ -47,16 +47,22 @@ export const preferArrIsBoundedLengthArray: TSESLint.RuleModule<
|
|
|
47
47
|
return {
|
|
48
48
|
LogicalExpression: (node) => {
|
|
49
49
|
// Only `a && b` can encode a `min && max` length range.
|
|
50
|
-
if (node.operator !== '&&')
|
|
50
|
+
if (node.operator !== '&&') {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
51
53
|
|
|
52
54
|
const left = parseLengthComparison(node.left, sourceCode);
|
|
53
55
|
|
|
54
56
|
const right = parseLengthComparison(node.right, sourceCode);
|
|
55
57
|
|
|
56
|
-
if (left === undefined || right === undefined)
|
|
58
|
+
if (left === undefined || right === undefined) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
57
61
|
|
|
58
62
|
// Need one lower bound and one upper bound on the same array.
|
|
59
|
-
if (left.kind === right.kind)
|
|
63
|
+
if (left.kind === right.kind) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
60
66
|
|
|
61
67
|
if (
|
|
62
68
|
sourceCode.getText(left.array) !== sourceCode.getText(right.array)
|
|
@@ -83,7 +89,9 @@ export const preferArrIsBoundedLengthArray: TSESLint.RuleModule<
|
|
|
83
89
|
const isArrayType =
|
|
84
90
|
checker.isArrayType(type) || checker.isTupleType(type);
|
|
85
91
|
|
|
86
|
-
if (!isArrayType)
|
|
92
|
+
if (!isArrayType) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
87
95
|
} else {
|
|
88
96
|
return;
|
|
89
97
|
}
|
|
@@ -52,7 +52,9 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
|
|
|
52
52
|
return {
|
|
53
53
|
BinaryExpression: (node) => {
|
|
54
54
|
// Check for `xs.length === n` or `n === xs.length` or `xs.length !== n`
|
|
55
|
-
if (node.operator !== '===' && node.operator !== '!==')
|
|
55
|
+
if (node.operator !== '===' && node.operator !== '!==') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
56
58
|
|
|
57
59
|
const isNegated = node.operator === '!==';
|
|
58
60
|
|
|
@@ -62,10 +64,14 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
|
|
|
62
64
|
|
|
63
65
|
const valueSide = node[isLengthOnLeft ? 'right' : 'left'];
|
|
64
66
|
|
|
65
|
-
if (!isLengthAccess(lengthSide))
|
|
67
|
+
if (!isLengthAccess(lengthSide)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
66
70
|
|
|
67
71
|
// Only match integer literals or const variables initialized with integer literals
|
|
68
|
-
if (!isIntegerLiteralOrConstant(valueSide, sourceCode))
|
|
72
|
+
if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
69
75
|
|
|
70
76
|
// lengthSide is MemberExpression accessing .length
|
|
71
77
|
const arrayExpression = lengthSide.object;
|
|
@@ -83,7 +89,9 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
|
|
|
83
89
|
const isArrayType =
|
|
84
90
|
checker.isArrayType(type) || checker.isTupleType(type);
|
|
85
91
|
|
|
86
|
-
if (!isArrayType)
|
|
92
|
+
if (!isArrayType) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
87
95
|
} else {
|
|
88
96
|
return;
|
|
89
97
|
}
|
|
@@ -50,11 +50,15 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
|
|
|
50
50
|
return {
|
|
51
51
|
BinaryExpression: (node) => {
|
|
52
52
|
// Check for `xs.length <= n` or `n >= xs.length`
|
|
53
|
-
if (node.operator !== '>=' && node.operator !== '<=')
|
|
53
|
+
if (node.operator !== '>=' && node.operator !== '<=') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
54
56
|
|
|
55
57
|
// Defer to `prefer-arr-is-bounded-length-array` when this comparison is
|
|
56
58
|
// the upper bound of a `min && max` pair on the same array.
|
|
57
|
-
if (isPartOfBoundedLengthCheck(node, sourceCode))
|
|
59
|
+
if (isPartOfBoundedLengthCheck(node, sourceCode)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
58
62
|
|
|
59
63
|
// xs.length <= n or n >= xs.length
|
|
60
64
|
const isLengthOnLeft = node.operator === '<=';
|
|
@@ -64,10 +68,14 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
|
|
|
64
68
|
const valueSide = node[isLengthOnLeft ? 'right' : 'left'];
|
|
65
69
|
|
|
66
70
|
// Check if lengthSide is accessing .length
|
|
67
|
-
if (!isLengthAccess(lengthSide))
|
|
71
|
+
if (!isLengthAccess(lengthSide)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
68
74
|
|
|
69
75
|
// Only match integer literals or const variables initialized with integer literals
|
|
70
|
-
if (!isIntegerLiteralOrConstant(valueSide, sourceCode))
|
|
76
|
+
if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
71
79
|
|
|
72
80
|
// lengthSide is MemberExpression accessing .length
|
|
73
81
|
const arrayExpression = lengthSide.object;
|
|
@@ -85,7 +93,9 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
|
|
|
85
93
|
const isArrayType =
|
|
86
94
|
checker.isArrayType(type) || checker.isTupleType(type);
|
|
87
95
|
|
|
88
|
-
if (!isArrayType)
|
|
96
|
+
if (!isArrayType) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
89
99
|
} else {
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
@@ -50,11 +50,15 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
|
|
|
50
50
|
return {
|
|
51
51
|
BinaryExpression: (node) => {
|
|
52
52
|
// Check for `xs.length >= n` or `n <= xs.length`
|
|
53
|
-
if (node.operator !== '>=' && node.operator !== '<=')
|
|
53
|
+
if (node.operator !== '>=' && node.operator !== '<=') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
54
56
|
|
|
55
57
|
// Defer to `prefer-arr-is-bounded-length-array` when this comparison is
|
|
56
58
|
// the lower bound of a `min && max` pair on the same array.
|
|
57
|
-
if (isPartOfBoundedLengthCheck(node, sourceCode))
|
|
59
|
+
if (isPartOfBoundedLengthCheck(node, sourceCode)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
58
62
|
|
|
59
63
|
// xs.length >= n or n <= xs.length
|
|
60
64
|
const isLengthOnLeft = node.operator === '>=';
|
|
@@ -64,10 +68,14 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
|
|
|
64
68
|
const valueSide = node[isLengthOnLeft ? 'right' : 'left'];
|
|
65
69
|
|
|
66
70
|
// Check if lengthSide is accessing .length
|
|
67
|
-
if (!isLengthAccess(lengthSide))
|
|
71
|
+
if (!isLengthAccess(lengthSide)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
68
74
|
|
|
69
75
|
// Only match integer literals or const variables initialized with integer literals
|
|
70
|
-
if (!isIntegerLiteralOrConstant(valueSide, sourceCode))
|
|
76
|
+
if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
71
79
|
|
|
72
80
|
// lengthSide is MemberExpression accessing .length
|
|
73
81
|
const arrayExpression = lengthSide.object;
|
|
@@ -85,7 +93,9 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
|
|
|
85
93
|
const isArrayType =
|
|
86
94
|
checker.isArrayType(type) || checker.isTupleType(type);
|
|
87
95
|
|
|
88
|
-
if (!isArrayType)
|
|
96
|
+
if (!isArrayType) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
89
99
|
} else {
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
@@ -45,7 +45,9 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
45
45
|
return {
|
|
46
46
|
BinaryExpression: (node) => {
|
|
47
47
|
// Check for `xs.length > 0` or `0 < xs.length`
|
|
48
|
-
if (node.operator !== '>' && node.operator !== '<')
|
|
48
|
+
if (node.operator !== '>' && node.operator !== '<') {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
49
51
|
|
|
50
52
|
const isLengthOnLeft = node.operator === '>';
|
|
51
53
|
|
|
@@ -79,7 +81,9 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
79
81
|
const isArrayType =
|
|
80
82
|
checker.isArrayType(type) || checker.isTupleType(type);
|
|
81
83
|
|
|
82
|
-
if (!isArrayType)
|
|
84
|
+
if (!isArrayType) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
83
87
|
} else {
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
@@ -190,7 +190,9 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
190
190
|
): boolean => {
|
|
191
191
|
const tsNode = services?.esTreeNodeToTSNodeMap?.get(node);
|
|
192
192
|
|
|
193
|
-
if (checker === undefined || tsNode === undefined)
|
|
193
|
+
if (checker === undefined || tsNode === undefined) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
194
196
|
|
|
195
197
|
const type = checker.getTypeAtLocation(tsNode);
|
|
196
198
|
|
|
@@ -227,14 +229,16 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
227
229
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
228
230
|
accumulation: Accumulation,
|
|
229
231
|
): string | undefined => {
|
|
230
|
-
if (callback.async || callback.generator)
|
|
232
|
+
if (callback.async || callback.generator) {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
231
235
|
|
|
232
236
|
// `reduce`'s fourth parameter is the whole array, which `scan` never
|
|
233
237
|
// hands its callback; an annotation on the accumulator describes that
|
|
234
238
|
// array, and would be wrong on `scan`'s value parameter.
|
|
235
239
|
if (
|
|
236
240
|
callback.params.length < 2 ||
|
|
237
|
-
callback.params.length
|
|
241
|
+
3 < callback.params.length ||
|
|
238
242
|
callback.params.some(
|
|
239
243
|
(param) =>
|
|
240
244
|
param.type !== AST_NODE_TYPES.Identifier ||
|
|
@@ -266,7 +270,9 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
266
270
|
(variable) => variable.name === accumulatorParam.name,
|
|
267
271
|
);
|
|
268
272
|
|
|
269
|
-
if (accumulator === undefined)
|
|
273
|
+
if (accumulator === undefined) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
270
276
|
|
|
271
277
|
// `acc.at(-1)` is `T | undefined` only because `at` can go out of bounds,
|
|
272
278
|
// which a seeded accumulator never does. So once the value `scan` carries
|
|
@@ -281,13 +287,19 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
281
287
|
const identifier = reference.identifier;
|
|
282
288
|
|
|
283
289
|
// The `...acc` / `Arr.toPushed(acc, …)` target the rewrite drops.
|
|
284
|
-
if (identifier === accumulatorRef)
|
|
290
|
+
if (identifier === accumulatorRef) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
285
293
|
|
|
286
|
-
if (identifier.type !== AST_NODE_TYPES.Identifier)
|
|
294
|
+
if (identifier.type !== AST_NODE_TYPES.Identifier) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
287
297
|
|
|
288
298
|
const lastRead = enclosingLastRead(identifier);
|
|
289
299
|
|
|
290
|
-
if (lastRead === undefined)
|
|
300
|
+
if (lastRead === undefined) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
291
303
|
|
|
292
304
|
const guard = carriesNullish ? undefined : nullishGuardAround(lastRead);
|
|
293
305
|
|
|
@@ -299,11 +311,15 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
299
311
|
|
|
300
312
|
// Two reads inside one `?? ` — `acc.at(-1) ?? acc.at(-1)` — would give
|
|
301
313
|
// ranges that contain one another, which no ordering of splices resolves.
|
|
302
|
-
if (overlaps(mut_lastReads))
|
|
314
|
+
if (overlaps(mut_lastReads)) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
303
317
|
|
|
304
318
|
// With no read of the previous value this is a `map` with a seed rather
|
|
305
319
|
// than a scan, and the accumulator parameter would be left unused.
|
|
306
|
-
if (mut_lastReads.length === 0)
|
|
320
|
+
if (mut_lastReads.length === 0) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
307
323
|
|
|
308
324
|
const parameterText = callback.params
|
|
309
325
|
.map((param) => sourceCode.getText(param))
|
|
@@ -333,7 +349,9 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
333
349
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
334
350
|
callback: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
|
|
335
351
|
): readonly TSESTree.Node[] | undefined => {
|
|
336
|
-
if (!Arr.isFixedLengthArray(2, callback.params))
|
|
352
|
+
if (!Arr.isFixedLengthArray(2, callback.params)) {
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
337
355
|
|
|
338
356
|
const [elementParam, indexParam] = callback.params;
|
|
339
357
|
|
|
@@ -358,7 +376,9 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
358
376
|
return undefined;
|
|
359
377
|
}
|
|
360
378
|
|
|
361
|
-
if (elementVariable.references.length > 0)
|
|
379
|
+
if (elementVariable.references.length > 0) {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
362
382
|
|
|
363
383
|
return indexVariable.references.map(
|
|
364
384
|
(reference) => reference.identifier as TSESTree.Node,
|
|
@@ -395,11 +415,15 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
395
415
|
|
|
396
416
|
switch (methodName) {
|
|
397
417
|
case 'map': {
|
|
398
|
-
if (rest.length > 0)
|
|
418
|
+
if (rest.length > 0) {
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
399
421
|
|
|
400
422
|
const indexReads = indexReadsOf(callback);
|
|
401
423
|
|
|
402
|
-
if (indexReads === undefined)
|
|
424
|
+
if (indexReads === undefined) {
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
403
427
|
|
|
404
428
|
mut_candidates.push({
|
|
405
429
|
call: node,
|
|
@@ -415,7 +439,9 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
415
439
|
// Two arguments, so that the initial value is explicit: a `reduce`
|
|
416
440
|
// without one starts from the first element and has no `scan`
|
|
417
441
|
// spelling at all.
|
|
418
|
-
if (rest.length !== 1 || !appendsToItsAccumulator(callback))
|
|
442
|
+
if (rest.length !== 1 || !appendsToItsAccumulator(callback)) {
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
419
445
|
|
|
420
446
|
const [initial] = rest;
|
|
421
447
|
|
|
@@ -483,7 +509,9 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
483
509
|
),
|
|
484
510
|
);
|
|
485
511
|
|
|
486
|
-
if (rebuild === undefined)
|
|
512
|
+
if (rebuild === undefined) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
487
515
|
|
|
488
516
|
context.report({
|
|
489
517
|
node: candidate.call,
|
|
@@ -605,7 +633,9 @@ const appendsToItsAccumulator = (
|
|
|
605
633
|
): boolean => {
|
|
606
634
|
const [accumulatorParam] = callback.params;
|
|
607
635
|
|
|
608
|
-
if (accumulatorParam?.type !== AST_NODE_TYPES.Identifier)
|
|
636
|
+
if (accumulatorParam?.type !== AST_NODE_TYPES.Identifier) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
609
639
|
|
|
610
640
|
const { body } = callback;
|
|
611
641
|
|
|
@@ -645,14 +675,18 @@ const accumulationOf = (
|
|
|
645
675
|
): Accumulation | undefined => {
|
|
646
676
|
const [accumulatorParam] = callback.params;
|
|
647
677
|
|
|
648
|
-
if (accumulatorParam?.type !== AST_NODE_TYPES.Identifier)
|
|
678
|
+
if (accumulatorParam?.type !== AST_NODE_TYPES.Identifier) {
|
|
679
|
+
return undefined;
|
|
680
|
+
}
|
|
649
681
|
|
|
650
682
|
const { body } = callback;
|
|
651
683
|
|
|
652
684
|
// `[...acc, ELEMENT]` — exactly one spread and one new value, since `scan`
|
|
653
685
|
// appends exactly one per step.
|
|
654
686
|
if (body.type === AST_NODE_TYPES.ArrayExpression) {
|
|
655
|
-
if (!Arr.isFixedLengthArray(2, body.elements))
|
|
687
|
+
if (!Arr.isFixedLengthArray(2, body.elements)) {
|
|
688
|
+
return undefined;
|
|
689
|
+
}
|
|
656
690
|
|
|
657
691
|
const [spread, element] = body.elements;
|
|
658
692
|
|
|
@@ -61,7 +61,9 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
61
61
|
const arrayExpression = node.callee.object;
|
|
62
62
|
|
|
63
63
|
// Check if we have 2 arguments: reducer function and initial value 0
|
|
64
|
-
if (!Arr.isFixedLengthTuple(2, node.arguments))
|
|
64
|
+
if (!Arr.isFixedLengthTuple(2, node.arguments)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
65
67
|
|
|
66
68
|
const reducer = node.arguments[0];
|
|
67
69
|
|
|
@@ -96,7 +98,9 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
96
98
|
|
|
97
99
|
const checker = services?.program?.getTypeChecker();
|
|
98
100
|
|
|
99
|
-
if (checker === undefined)
|
|
101
|
+
if (checker === undefined) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
100
104
|
|
|
101
105
|
// Case 1: (a, b) => a + b
|
|
102
106
|
if (
|
|
@@ -189,7 +193,9 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
189
193
|
mut_propName = leftProp.value;
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
if (mut_propName === undefined)
|
|
196
|
+
if (mut_propName === undefined) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
193
199
|
|
|
194
200
|
// Check if property type is number
|
|
195
201
|
if (services?.program !== undefined && services.program !== null) {
|