eslint-plugin-ts-data-forge 0.10.4 → 0.10.6
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 +5 -5
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +3 -3
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/import-binding.d.mts +38 -0
- package/dist/rules/import-binding.d.mts.map +1 -0
- package/dist/rules/import-binding.mjs +53 -0
- package/dist/rules/import-binding.mjs.map +1 -0
- package/dist/rules/mutable-result.mjs +37 -37
- package/dist/rules/mutable-result.mjs.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.mjs +146 -143
- package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
- package/dist/rules/prefer-arr-generate.d.mts.map +1 -1
- package/dist/rules/prefer-arr-generate.mjs +129 -131
- package/dist/rules/prefer-arr-generate.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 +14 -16
- 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 +15 -17
- 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 +14 -16
- 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 +14 -16
- 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 +14 -16
- 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 +9 -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 +16 -21
- 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 +38 -43
- 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 +19 -26
- 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 +49 -15
- package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-cast.mjs +15 -15
- package/dist/rules/prefer-canonical-length-cast.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 +100 -99
- package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
- package/dist/rules/prefer-pipe-map-nullable.d.mts.map +1 -1
- package/dist/rules/prefer-pipe-map-nullable.mjs +2 -4
- package/dist/rules/prefer-pipe-map-nullable.mjs.map +1 -1
- package/dist/rules/prefer-range-for-loop.d.mts.map +1 -1
- package/dist/rules/prefer-range-for-loop.mjs +18 -24
- package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
- package/package.json +5 -5
- package/src/rules/ast-utils.mts +11 -11
- package/src/rules/import-binding.mts +94 -0
- package/src/rules/mutable-result.mts +56 -56
- package/src/rules/no-unnecessary-type-guard.mts +199 -203
- package/src/rules/no-unnecessary-type-guard.test.mts +62 -2
- package/src/rules/prefer-arr-generate.mts +184 -194
- package/src/rules/prefer-arr-generate.test.mts +262 -134
- package/src/rules/prefer-arr-is-array.test.mts +87 -5
- package/src/rules/prefer-arr-is-bounded-length-array.mts +22 -33
- package/src/rules/prefer-arr-is-fixed-length-array.mts +24 -36
- package/src/rules/prefer-arr-is-fixed-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-max-length-array.mts +21 -34
- package/src/rules/prefer-arr-is-max-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-min-length-array.mts +21 -34
- package/src/rules/prefer-arr-is-min-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-non-empty.mts +17 -34
- package/src/rules/prefer-arr-is-non-empty.test.mts +110 -0
- package/src/rules/prefer-arr-scan.mts +11 -36
- package/src/rules/prefer-arr-scan.test.mts +55 -0
- package/src/rules/prefer-arr-sum.mts +19 -33
- package/src/rules/prefer-arr-uniq.mts +49 -60
- package/src/rules/prefer-arr-uniq.test.mts +46 -20
- package/src/rules/prefer-as-int.mts +27 -52
- package/src/rules/prefer-as-int.test.mts +79 -29
- package/src/rules/prefer-canonical-array-slicing.mts +86 -24
- package/src/rules/prefer-canonical-array-slicing.test.mts +133 -34
- package/src/rules/prefer-canonical-length-cast.mts +18 -18
- package/src/rules/prefer-is-non-null-object.test.mts +62 -37
- package/src/rules/prefer-is-record-and-has-key.test.mts +32 -17
- package/src/rules/prefer-num-safe-parse-float.test.mts +45 -21
- package/src/rules/prefer-num-safe-parse-int.test.mts +29 -14
- package/src/rules/prefer-obj-over-entries-round-trip.mts +152 -170
- package/src/rules/prefer-obj-over-entries-round-trip.test.mts +48 -6
- package/src/rules/prefer-pipe-map-nullable.mts +6 -8
- package/src/rules/prefer-range-for-loop.mts +31 -55
|
@@ -6,11 +6,8 @@ import {
|
|
|
6
6
|
type TSESTree,
|
|
7
7
|
} from '@typescript-eslint/utils';
|
|
8
8
|
import {
|
|
9
|
-
buildNamedImportFix,
|
|
10
9
|
createGetLayerTypes,
|
|
11
10
|
getArgumentText,
|
|
12
|
-
getImportDeclarations,
|
|
13
|
-
getImportedLocalName,
|
|
14
11
|
negationOf,
|
|
15
12
|
prefixedNegationOf,
|
|
16
13
|
skipTypeWrappers,
|
|
@@ -19,7 +16,7 @@ import {
|
|
|
19
16
|
import { Arr, hasKey, isRecord } from 'ts-data-forge';
|
|
20
17
|
import type { FixedLengthTuple } from 'ts-type-forge';
|
|
21
18
|
import * as ts from 'typescript';
|
|
22
|
-
import {
|
|
19
|
+
import { tsDataForgeValuesAt } from './import-binding.mjs';
|
|
23
20
|
import { needsMutableResult } from './mutable-result.mjs';
|
|
24
21
|
|
|
25
22
|
type Options = readonly [];
|
|
@@ -106,13 +103,6 @@ export const preferArrGenerate: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
106
103
|
create: (context) => {
|
|
107
104
|
const sourceCode = context.sourceCode;
|
|
108
105
|
|
|
109
|
-
const program = sourceCode.ast;
|
|
110
|
-
|
|
111
|
-
const tsDataForgeImports = getImportDeclarations(
|
|
112
|
-
program,
|
|
113
|
-
TS_DATA_FORGE_MODULE,
|
|
114
|
-
);
|
|
115
|
-
|
|
116
106
|
const services = sourceCode.parserServices;
|
|
117
107
|
|
|
118
108
|
const tsProgram = services?.program ?? undefined;
|
|
@@ -146,9 +136,10 @@ export const preferArrGenerate: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
146
136
|
node: TSESTree.Node,
|
|
147
137
|
): ts.Node | undefined => esTreeNodeToTSNodeMap.get(node);
|
|
148
138
|
|
|
149
|
-
|
|
139
|
+
// The name the rewrite writes, and, at a node, the edits that bind it.
|
|
140
|
+
const arr = tsDataForgeValuesAt(sourceCode, ['Arr']);
|
|
150
141
|
|
|
151
|
-
const arrName =
|
|
142
|
+
const arrName = arr.localNames[0] ?? 'Arr';
|
|
152
143
|
|
|
153
144
|
const mut_matches: Match[] = [];
|
|
154
145
|
|
|
@@ -297,39 +288,38 @@ export const preferArrGenerate: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
297
288
|
mut_matches.push({ node, replacement, autofixable });
|
|
298
289
|
},
|
|
299
290
|
'Program:exit': () => {
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
// autofixable, the first autofixable one carries it instead.
|
|
303
|
-
const firstAutofixable = mut_matches.find((m) => m.autofixable);
|
|
304
|
-
|
|
291
|
+
// Every fix brings `Arr` into scope itself, so that one applied alone
|
|
292
|
+
// (an editor's quick fix) is complete.
|
|
305
293
|
for (const match of mut_matches) {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
];
|
|
294
|
+
const binding = arr.at(match.node);
|
|
295
|
+
|
|
296
|
+
const fix: TSESLint.ReportFixFunction | null =
|
|
297
|
+
binding === undefined
|
|
298
|
+
? null
|
|
299
|
+
: (fixer) => [
|
|
300
|
+
...binding.edits(fixer),
|
|
301
|
+
fixer.replaceText(match.node, match.replacement),
|
|
302
|
+
];
|
|
316
303
|
|
|
317
304
|
if (match.autofixable) {
|
|
318
305
|
context.report({
|
|
319
306
|
node: match.node,
|
|
320
307
|
messageId: 'useArrGenerate',
|
|
321
|
-
fix
|
|
308
|
+
fix,
|
|
322
309
|
});
|
|
323
310
|
} else {
|
|
324
311
|
context.report({
|
|
325
312
|
node: match.node,
|
|
326
313
|
messageId: 'useArrGenerate',
|
|
327
|
-
suggest:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
314
|
+
suggest:
|
|
315
|
+
fix === null
|
|
316
|
+
? []
|
|
317
|
+
: [
|
|
318
|
+
{
|
|
319
|
+
messageId: 'suggestArrGenerate',
|
|
320
|
+
fix,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
333
323
|
});
|
|
334
324
|
}
|
|
335
325
|
}
|
|
@@ -452,6 +442,63 @@ const getFilterMapShape = (
|
|
|
452
442
|
};
|
|
453
443
|
};
|
|
454
444
|
|
|
445
|
+
/** The whitespace at the start of the line that `index` is on. */
|
|
446
|
+
const lineIndentOf = (
|
|
447
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
448
|
+
sourceCode: TSESLint.SourceCode,
|
|
449
|
+
index: number,
|
|
450
|
+
): string => {
|
|
451
|
+
const { line } = sourceCode.getLocFromIndex(index);
|
|
452
|
+
|
|
453
|
+
const lineText = sourceCode.lines[line - 1] ?? '';
|
|
454
|
+
|
|
455
|
+
return /^\s*/u.exec(lineText)?.[0] ?? '';
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* `text`, the source at `range`, with every line after the first shifted from
|
|
460
|
+
* the indentation of the line `range` starts on to `targetIndent`. Left as it
|
|
461
|
+
* is when a template literal or string spans lines inside it, since shifting
|
|
462
|
+
* those lines would change the string.
|
|
463
|
+
*/
|
|
464
|
+
const reindent = (
|
|
465
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
466
|
+
sourceCode: TSESLint.SourceCode,
|
|
467
|
+
range: FixedLengthTuple<2, number>,
|
|
468
|
+
targetIndent: string,
|
|
469
|
+
text: string,
|
|
470
|
+
): string => {
|
|
471
|
+
if (!text.includes('\n')) {
|
|
472
|
+
return text;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const spansLines = sourceCode.ast.tokens.some(
|
|
476
|
+
(token) =>
|
|
477
|
+
range[0] <= token.range[0] &&
|
|
478
|
+
token.range[1] <= range[1] &&
|
|
479
|
+
(token.type === AST_TOKEN_TYPES.Template ||
|
|
480
|
+
token.type === AST_TOKEN_TYPES.String) &&
|
|
481
|
+
token.loc.start.line !== token.loc.end.line,
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
if (spansLines) {
|
|
485
|
+
return text;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const sourceIndent = lineIndentOf(sourceCode, range[0]);
|
|
489
|
+
|
|
490
|
+
const [first, ...rest] = text.split('\n');
|
|
491
|
+
|
|
492
|
+
return [
|
|
493
|
+
first,
|
|
494
|
+
...rest.map((line) =>
|
|
495
|
+
line.startsWith(sourceIndent)
|
|
496
|
+
? `${targetIndent}${line.slice(sourceIndent.length)}`
|
|
497
|
+
: line,
|
|
498
|
+
),
|
|
499
|
+
].join('\n');
|
|
500
|
+
};
|
|
501
|
+
|
|
455
502
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
456
503
|
const buildReplacement = ({
|
|
457
504
|
sourceCode,
|
|
@@ -563,63 +610,6 @@ const losesComments = (
|
|
|
563
610
|
);
|
|
564
611
|
};
|
|
565
612
|
|
|
566
|
-
/**
|
|
567
|
-
* `text`, the source at `range`, with every line after the first shifted from
|
|
568
|
-
* the indentation of the line `range` starts on to `targetIndent`. Left as it
|
|
569
|
-
* is when a template literal or string spans lines inside it, since shifting
|
|
570
|
-
* those lines would change the string.
|
|
571
|
-
*/
|
|
572
|
-
const reindent = (
|
|
573
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
574
|
-
sourceCode: TSESLint.SourceCode,
|
|
575
|
-
range: FixedLengthTuple<2, number>,
|
|
576
|
-
targetIndent: string,
|
|
577
|
-
text: string,
|
|
578
|
-
): string => {
|
|
579
|
-
if (!text.includes('\n')) {
|
|
580
|
-
return text;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const spansLines = sourceCode.ast.tokens.some(
|
|
584
|
-
(token) =>
|
|
585
|
-
range[0] <= token.range[0] &&
|
|
586
|
-
token.range[1] <= range[1] &&
|
|
587
|
-
(token.type === AST_TOKEN_TYPES.Template ||
|
|
588
|
-
token.type === AST_TOKEN_TYPES.String) &&
|
|
589
|
-
token.loc.start.line !== token.loc.end.line,
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
if (spansLines) {
|
|
593
|
-
return text;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
const sourceIndent = lineIndentOf(sourceCode, range[0]);
|
|
597
|
-
|
|
598
|
-
const [first, ...rest] = text.split('\n');
|
|
599
|
-
|
|
600
|
-
return [
|
|
601
|
-
first,
|
|
602
|
-
...rest.map((line) =>
|
|
603
|
-
line.startsWith(sourceIndent)
|
|
604
|
-
? `${targetIndent}${line.slice(sourceIndent.length)}`
|
|
605
|
-
: line,
|
|
606
|
-
),
|
|
607
|
-
].join('\n');
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
/** The whitespace at the start of the line that `index` is on. */
|
|
611
|
-
const lineIndentOf = (
|
|
612
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
613
|
-
sourceCode: TSESLint.SourceCode,
|
|
614
|
-
index: number,
|
|
615
|
-
): string => {
|
|
616
|
-
const { line } = sourceCode.getLocFromIndex(index);
|
|
617
|
-
|
|
618
|
-
const lineText = sourceCode.lines[line - 1] ?? '';
|
|
619
|
-
|
|
620
|
-
return /^\s*/u.exec(lineText)?.[0] ?? '';
|
|
621
|
-
};
|
|
622
|
-
|
|
623
613
|
/** Whether `node`, with its type wrappers taken off, is a `….filter(…)` call. */
|
|
624
614
|
const isFilterCall = (
|
|
625
615
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -642,72 +632,25 @@ const isFilterCall = (
|
|
|
642
632
|
};
|
|
643
633
|
|
|
644
634
|
/**
|
|
645
|
-
* Whether `
|
|
646
|
-
*
|
|
647
|
-
* see. TypeScript keeps the narrowing of a `const`, and of a parameter or `let`
|
|
648
|
-
* that is never reassigned, inside a closure; it drops every other one,
|
|
649
|
-
* including any narrowing of a property.
|
|
635
|
+
* Whether `type` is an array or tuple, or a union of them, or an intersection
|
|
636
|
+
* with one in it (`readonly T[] & Brand`).
|
|
650
637
|
*/
|
|
651
|
-
const
|
|
652
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
653
|
-
sourceCode: TSESLint.SourceCode,
|
|
638
|
+
const isArrayOrTupleType = (
|
|
654
639
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
655
640
|
checker: ts.TypeChecker,
|
|
656
641
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
657
|
-
|
|
658
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
659
|
-
node: TSESTree.Node,
|
|
660
|
-
isReceiver: boolean,
|
|
642
|
+
type: ts.Type,
|
|
661
643
|
): boolean => {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return false;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// A method about to be called (`xs.flat()`): its type is that of the method,
|
|
670
|
-
// which no narrowing of interest changes.
|
|
671
|
-
if (
|
|
672
|
-
node.parent.type === AST_NODE_TYPES.CallExpression &&
|
|
673
|
-
node.parent.callee === node
|
|
674
|
-
) {
|
|
675
|
-
return false;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
if (node.type === AST_NODE_TYPES.Identifier) {
|
|
679
|
-
if (!isReference(node)) {
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const variable = ASTUtils.findVariable(sourceCode.getScope(node), node);
|
|
684
|
-
|
|
685
|
-
if (
|
|
686
|
-
variable?.references.every(
|
|
687
|
-
(reference) => !reference.isWrite() || reference.init === true,
|
|
688
|
-
) === true
|
|
689
|
-
) {
|
|
690
|
-
return false;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const tsNode = getTsNode(node);
|
|
695
|
-
|
|
696
|
-
if (tsNode === undefined) {
|
|
697
|
-
return false;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const declaredType = getDeclaredType(checker, tsNode);
|
|
701
|
-
|
|
702
|
-
const type = checker.getTypeAtLocation(tsNode);
|
|
644
|
+
const resolved =
|
|
645
|
+
(type.flags & ts.TypeFlags.TypeParameter) !== 0
|
|
646
|
+
? (checker.getBaseConstraintOfType(type) ?? type)
|
|
647
|
+
: type;
|
|
703
648
|
|
|
704
|
-
return (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
!(isReceiver && iteratesTheSame(checker, declaredType, type))
|
|
710
|
-
);
|
|
649
|
+
return resolved.isUnion()
|
|
650
|
+
? resolved.types.every((t) => isArrayOrTupleType(checker, t))
|
|
651
|
+
: resolved.isIntersection()
|
|
652
|
+
? resolved.types.some((t) => isArrayOrTupleType(checker, t))
|
|
653
|
+
: checker.isArrayType(resolved) || checker.isTupleType(resolved);
|
|
711
654
|
};
|
|
712
655
|
|
|
713
656
|
/**
|
|
@@ -787,6 +730,93 @@ const getDeclaredType = (
|
|
|
787
730
|
: checker.getTypeOfSymbolAtLocation(symbol, declaration);
|
|
788
731
|
};
|
|
789
732
|
|
|
733
|
+
/** Whether an identifier reads a binding, rather than naming a property or a key. */
|
|
734
|
+
const isReference = (
|
|
735
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
736
|
+
node: TSESTree.Identifier,
|
|
737
|
+
): boolean => {
|
|
738
|
+
const { parent } = node;
|
|
739
|
+
|
|
740
|
+
return !(
|
|
741
|
+
(parent.type === AST_NODE_TYPES.MemberExpression &&
|
|
742
|
+
parent.property === node &&
|
|
743
|
+
!parent.computed) ||
|
|
744
|
+
(parent.type === AST_NODE_TYPES.Property &&
|
|
745
|
+
parent.key === node &&
|
|
746
|
+
!parent.computed &&
|
|
747
|
+
!parent.shorthand)
|
|
748
|
+
);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Whether `node`, a reference or a member access, has a type here narrower
|
|
753
|
+
* than its declared one, by a narrowing that a function created here would not
|
|
754
|
+
* see. TypeScript keeps the narrowing of a `const`, and of a parameter or `let`
|
|
755
|
+
* that is never reassigned, inside a closure; it drops every other one,
|
|
756
|
+
* including any narrowing of a property.
|
|
757
|
+
*/
|
|
758
|
+
const isNarrowedHere = (
|
|
759
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
760
|
+
sourceCode: TSESLint.SourceCode,
|
|
761
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
762
|
+
checker: ts.TypeChecker,
|
|
763
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
764
|
+
getTsNode: (esNode: TSESTree.Node) => ts.Node | undefined,
|
|
765
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
766
|
+
node: TSESTree.Node,
|
|
767
|
+
isReceiver: boolean,
|
|
768
|
+
): boolean => {
|
|
769
|
+
if (
|
|
770
|
+
node.type !== AST_NODE_TYPES.Identifier &&
|
|
771
|
+
node.type !== AST_NODE_TYPES.MemberExpression
|
|
772
|
+
) {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// A method about to be called (`xs.flat()`): its type is that of the method,
|
|
777
|
+
// which no narrowing of interest changes.
|
|
778
|
+
if (
|
|
779
|
+
node.parent.type === AST_NODE_TYPES.CallExpression &&
|
|
780
|
+
node.parent.callee === node
|
|
781
|
+
) {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (node.type === AST_NODE_TYPES.Identifier) {
|
|
786
|
+
if (!isReference(node)) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const variable = ASTUtils.findVariable(sourceCode.getScope(node), node);
|
|
791
|
+
|
|
792
|
+
if (
|
|
793
|
+
variable?.references.every(
|
|
794
|
+
(reference) => !reference.isWrite() || reference.init === true,
|
|
795
|
+
) === true
|
|
796
|
+
) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const tsNode = getTsNode(node);
|
|
802
|
+
|
|
803
|
+
if (tsNode === undefined) {
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const declaredType = getDeclaredType(checker, tsNode);
|
|
808
|
+
|
|
809
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
810
|
+
|
|
811
|
+
return (
|
|
812
|
+
declaredType !== undefined &&
|
|
813
|
+
declaredType !== type &&
|
|
814
|
+
// The loop only iterates the receiver itself, so a narrowing that leaves
|
|
815
|
+
// it an array of the same elements (`Arr.isNonEmpty(xs)`) loses nothing.
|
|
816
|
+
!(isReceiver && iteratesTheSame(checker, declaredType, type))
|
|
817
|
+
);
|
|
818
|
+
};
|
|
819
|
+
|
|
790
820
|
/**
|
|
791
821
|
* Whether the binding `identifier` reads is declared inside `container`, as
|
|
792
822
|
* the parameter of an arrow function in the receiver is: the loop variable
|
|
@@ -816,23 +846,8 @@ const isBoundWithin = (
|
|
|
816
846
|
);
|
|
817
847
|
};
|
|
818
848
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
822
|
-
node: TSESTree.Identifier,
|
|
823
|
-
): boolean => {
|
|
824
|
-
const { parent } = node;
|
|
825
|
-
|
|
826
|
-
return !(
|
|
827
|
-
(parent.type === AST_NODE_TYPES.MemberExpression &&
|
|
828
|
-
parent.property === node &&
|
|
829
|
-
!parent.computed) ||
|
|
830
|
-
(parent.type === AST_NODE_TYPES.Property &&
|
|
831
|
-
parent.key === node &&
|
|
832
|
-
!parent.computed &&
|
|
833
|
-
!parent.shorthand)
|
|
834
|
-
);
|
|
835
|
-
};
|
|
849
|
+
const isNode = (value: unknown): value is TSESTree.Node =>
|
|
850
|
+
isRecord(value) && hasKey(value, 'type') && typeof value.type === 'string';
|
|
836
851
|
|
|
837
852
|
/**
|
|
838
853
|
* Whether `node` or a node inside it satisfies `predicate`. A nested
|
|
@@ -877,9 +892,6 @@ const someDescendant = (
|
|
|
877
892
|
});
|
|
878
893
|
};
|
|
879
894
|
|
|
880
|
-
const isNode = (value: unknown): value is TSESTree.Node =>
|
|
881
|
-
isRecord(value) && hasKey(value, 'type') && typeof value.type === 'string';
|
|
882
|
-
|
|
883
895
|
const isReturnStatement = (
|
|
884
896
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
885
897
|
node: TSESTree.Node,
|
|
@@ -894,25 +906,3 @@ const rebindsInGenerator = (
|
|
|
894
906
|
node.type === AST_NODE_TYPES.Super ||
|
|
895
907
|
node.type === AST_NODE_TYPES.MetaProperty ||
|
|
896
908
|
(node.type === AST_NODE_TYPES.Identifier && node.name === 'arguments');
|
|
897
|
-
|
|
898
|
-
/**
|
|
899
|
-
* Whether `type` is an array or tuple, or a union of them, or an intersection
|
|
900
|
-
* with one in it (`readonly T[] & Brand`).
|
|
901
|
-
*/
|
|
902
|
-
const isArrayOrTupleType = (
|
|
903
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
904
|
-
checker: ts.TypeChecker,
|
|
905
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
906
|
-
type: ts.Type,
|
|
907
|
-
): boolean => {
|
|
908
|
-
const resolved =
|
|
909
|
-
(type.flags & ts.TypeFlags.TypeParameter) !== 0
|
|
910
|
-
? (checker.getBaseConstraintOfType(type) ?? type)
|
|
911
|
-
: type;
|
|
912
|
-
|
|
913
|
-
return resolved.isUnion()
|
|
914
|
-
? resolved.types.every((t) => isArrayOrTupleType(checker, t))
|
|
915
|
-
: resolved.isIntersection()
|
|
916
|
-
? resolved.types.some((t) => isArrayOrTupleType(checker, t))
|
|
917
|
-
: checker.isArrayType(resolved) || checker.isTupleType(resolved);
|
|
918
|
-
};
|