eslint-plugin-ts-data-forge 0.8.1 → 0.8.3
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/plugin.d.mts +1 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +3 -3
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rules/ast-utils.d.mts +59 -3
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +142 -31
- 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 +1 -1
- package/dist/rules/import-utils.d.mts.map +1 -1
- package/dist/rules/import-utils.mjs +26 -13
- package/dist/rules/import-utils.mjs.map +1 -1
- package/dist/rules/no-side-effect-import.d.mts +1 -1
- package/dist/rules/no-side-effect-import.d.mts.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.mjs +161 -101
- 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 +25 -11
- package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.d.mts +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 +11 -21
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.d.mts +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 +16 -28
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.d.mts +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 +16 -24
- package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.d.mts +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 +16 -24
- 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 +9 -22
- package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
- package/dist/rules/prefer-arr-scan.d.mts +6 -5
- package/dist/rules/prefer-arr-scan.d.mts.map +1 -1
- package/dist/rules/prefer-arr-scan.mjs +68 -31
- 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 +57 -40
- 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 +59 -30
- 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 +27 -24
- 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 +59 -28
- 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 +41 -16
- 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 +18 -8
- 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 +31 -6
- 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 +56 -40
- 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 +18 -8
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-num-safe-parse-float.d.mts +3 -1
- package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -1
- package/dist/rules/prefer-num-safe-parse-float.mjs +27 -10
- 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 +24 -11
- 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 +15 -8
- package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
- package/dist/types.d.mts +2 -2
- package/dist/types.d.mts.map +1 -1
- package/package.json +25 -23
- package/src/plugin.mts +5 -4
- package/src/plugin.test.mts +0 -13
- package/src/rule-types.mts +3 -3
- package/src/rules/ast-utils.mts +192 -37
- package/src/rules/branded-number-types.mts +1 -1
- package/src/rules/import-utils.mts +41 -17
- package/src/rules/no-side-effect-import.mts +1 -1
- package/src/rules/no-unnecessary-type-guard.mts +249 -148
- package/src/rules/no-unnecessary-type-guard.test.mts +100 -0
- package/src/rules/prefer-arr-is-array.mts +32 -13
- package/src/rules/prefer-arr-is-array.test.mts +40 -0
- package/src/rules/prefer-arr-is-bounded-length-array.mts +22 -26
- package/src/rules/prefer-arr-is-bounded-length-array.test.mts +68 -0
- package/src/rules/prefer-arr-is-fixed-length-array.mts +26 -38
- package/src/rules/prefer-arr-is-fixed-length-array.test.mts +63 -0
- package/src/rules/prefer-arr-is-max-length-array.mts +26 -27
- package/src/rules/prefer-arr-is-max-length-array.test.mts +67 -0
- package/src/rules/prefer-arr-is-min-length-array.mts +26 -27
- package/src/rules/prefer-arr-is-min-length-array.test.mts +70 -0
- package/src/rules/prefer-arr-is-non-empty.mts +19 -24
- package/src/rules/prefer-arr-is-non-empty.test.mts +79 -0
- package/src/rules/prefer-arr-scan.mts +107 -37
- package/src/rules/prefer-arr-scan.test.mts +149 -0
- package/src/rules/prefer-arr-sum.mts +87 -50
- package/src/rules/prefer-arr-sum.test.mts +97 -0
- package/src/rules/prefer-arr-uniq.mts +83 -33
- package/src/rules/prefer-arr-uniq.test.mts +71 -0
- package/src/rules/prefer-as-int.mts +38 -38
- package/src/rules/prefer-as-int.test.mts +51 -0
- package/src/rules/prefer-canonical-array-slicing.mts +84 -29
- package/src/rules/prefer-canonical-array-slicing.test.mts +92 -0
- package/src/rules/prefer-canonical-length-cast.mts +56 -18
- package/src/rules/prefer-canonical-length-cast.test.mts +111 -0
- package/src/rules/prefer-canonical-length-guard.mts +25 -11
- package/src/rules/prefer-canonical-length-guard.test.mts +99 -0
- package/src/rules/prefer-comparison-over-nullish-guard.mts +42 -9
- package/src/rules/prefer-comparison-over-nullish-guard.test.mts +136 -0
- package/src/rules/prefer-is-non-null-object.mts +57 -33
- package/src/rules/prefer-is-non-null-object.test.mts +42 -0
- package/src/rules/prefer-is-record-and-has-key.mts +27 -12
- package/src/rules/prefer-is-record-and-has-key.test.mts +34 -0
- package/src/rules/prefer-num-safe-parse-float.mts +37 -13
- package/src/rules/prefer-num-safe-parse-float.test.mts +79 -0
- package/src/rules/prefer-num-safe-parse-int.mts +41 -15
- package/src/rules/prefer-num-safe-parse-int.test.mts +64 -0
- package/src/rules/prefer-obj-over-entries-round-trip.mts +34 -12
- package/src/rules/prefer-range-for-loop.mts +21 -9
- package/src/rules/prefer-range-for-loop.test.mts +75 -0
- package/src/rules/rules.mts +1 -1
- package/src/types.mts +2 -2
|
@@ -293,3 +293,152 @@ describe('prefer-arr-scan', () => {
|
|
|
293
293
|
],
|
|
294
294
|
});
|
|
295
295
|
});
|
|
296
|
+
|
|
297
|
+
describe('prefer-arr-scan through type wrappers', () => {
|
|
298
|
+
tester.run('prefer-arr-scan', preferArrScan, {
|
|
299
|
+
valid: [],
|
|
300
|
+
invalid: [
|
|
301
|
+
{
|
|
302
|
+
name: 'a seed whose `!` hides that it may be undefined keeps the `??`',
|
|
303
|
+
code: dedent`
|
|
304
|
+
import { Arr } from 'ts-data-forge';
|
|
305
|
+
declare const xs: readonly number[];
|
|
306
|
+
declare const init: number | undefined;
|
|
307
|
+
const totals = xs.reduce(
|
|
308
|
+
(acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
|
|
309
|
+
[init!],
|
|
310
|
+
);
|
|
311
|
+
`,
|
|
312
|
+
output: dedent`
|
|
313
|
+
import { Arr } from 'ts-data-forge';
|
|
314
|
+
declare const xs: readonly number[];
|
|
315
|
+
declare const init: number | undefined;
|
|
316
|
+
const totals = Arr.scan(xs, (acc, x) => (acc ?? 0) + x, init!);
|
|
317
|
+
`,
|
|
318
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'an element cast away from undefined keeps the `??`',
|
|
322
|
+
code: dedent`
|
|
323
|
+
import { Arr } from 'ts-data-forge';
|
|
324
|
+
declare const xs: readonly (number | undefined)[];
|
|
325
|
+
const totals = xs.reduce<readonly number[]>(
|
|
326
|
+
(acc, x) => [...acc, ((acc.at(-1) ?? 0) && x) as number],
|
|
327
|
+
[0],
|
|
328
|
+
);
|
|
329
|
+
`,
|
|
330
|
+
output: dedent`
|
|
331
|
+
import { Arr } from 'ts-data-forge';
|
|
332
|
+
declare const xs: readonly (number | undefined)[];
|
|
333
|
+
const totals = Arr.scan(xs, (acc, x) => ((acc ?? 0) && x) as number, 0);
|
|
334
|
+
`,
|
|
335
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'a spread accumulator wrapped in `as`',
|
|
339
|
+
code: dedent`
|
|
340
|
+
import { Arr } from 'ts-data-forge';
|
|
341
|
+
declare const xs: readonly number[];
|
|
342
|
+
const totals = xs.reduce<readonly number[]>(
|
|
343
|
+
(acc, x) => [...(acc as readonly number[]), (acc.at(-1) ?? 0) + x],
|
|
344
|
+
[0],
|
|
345
|
+
);
|
|
346
|
+
`,
|
|
347
|
+
output: dedent`
|
|
348
|
+
import { Arr } from 'ts-data-forge';
|
|
349
|
+
declare const xs: readonly number[];
|
|
350
|
+
const totals = Arr.scan(xs, (acc, x) => (acc) + x, 0);
|
|
351
|
+
`,
|
|
352
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: 'an Arr.toPushed target wrapped in `!`',
|
|
356
|
+
code: dedent`
|
|
357
|
+
import { Arr } from 'ts-data-forge';
|
|
358
|
+
declare const xs: readonly number[];
|
|
359
|
+
const totals = xs.reduce<readonly number[]>(
|
|
360
|
+
(acc, x) => Arr.toPushed(acc!, (acc.at(-1) ?? 0) + x),
|
|
361
|
+
[0],
|
|
362
|
+
);
|
|
363
|
+
`,
|
|
364
|
+
output: dedent`
|
|
365
|
+
import { Arr } from 'ts-data-forge';
|
|
366
|
+
declare const xs: readonly number[];
|
|
367
|
+
const totals = Arr.scan(xs, (acc, x) => (acc) + x, 0);
|
|
368
|
+
`,
|
|
369
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'the sliced array wrapped in `as`',
|
|
373
|
+
code: dedent`
|
|
374
|
+
declare const xs: readonly string[];
|
|
375
|
+
const ys = xs.map((_, i) =>
|
|
376
|
+
(xs as readonly string[]).slice(0, i + 1).join(''),
|
|
377
|
+
);
|
|
378
|
+
`,
|
|
379
|
+
errors: [{ messageId: 'preferArrScanPrefix' }],
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe('prefer-arr-scan with parenthesized operands', () => {
|
|
386
|
+
tester.run('prefer-arr-scan', preferArrScan, {
|
|
387
|
+
valid: [],
|
|
388
|
+
invalid: [
|
|
389
|
+
{
|
|
390
|
+
name: 'an object literal element stays an expression body',
|
|
391
|
+
code: dedent`
|
|
392
|
+
import { Arr } from 'ts-data-forge';
|
|
393
|
+
declare const xs: readonly number[];
|
|
394
|
+
const totals = xs.reduce(
|
|
395
|
+
(acc, x) => [...acc, { sum: acc.at(-1)!.sum + x }],
|
|
396
|
+
[{ sum: 0 }],
|
|
397
|
+
);
|
|
398
|
+
`,
|
|
399
|
+
output: dedent`
|
|
400
|
+
import { Arr } from 'ts-data-forge';
|
|
401
|
+
declare const xs: readonly number[];
|
|
402
|
+
const totals = Arr.scan(xs, (acc, x) => ({ sum: acc!.sum + x }), { sum: 0 });
|
|
403
|
+
`,
|
|
404
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: 'an object literal element under a wrapper stays an expression body',
|
|
408
|
+
code: dedent`
|
|
409
|
+
import { Arr } from 'ts-data-forge';
|
|
410
|
+
declare const xs: readonly number[];
|
|
411
|
+
const totals = xs.reduce(
|
|
412
|
+
(acc, x) => [...acc, { sum: acc.at(-1)!.sum + x } as const],
|
|
413
|
+
[{ sum: 0 }],
|
|
414
|
+
);
|
|
415
|
+
`,
|
|
416
|
+
output: dedent`
|
|
417
|
+
import { Arr } from 'ts-data-forge';
|
|
418
|
+
declare const xs: readonly number[];
|
|
419
|
+
const totals = Arr.scan(xs, (acc, x) => ({ sum: acc!.sum + x } as const), { sum: 0 });
|
|
420
|
+
`,
|
|
421
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: 'a sequence element and seed keep their parentheses',
|
|
425
|
+
code: dedent`
|
|
426
|
+
import { Arr } from 'ts-data-forge';
|
|
427
|
+
declare const xs: readonly number[];
|
|
428
|
+
declare const log: (n: number) => void;
|
|
429
|
+
const totals = xs.reduce<readonly number[]>(
|
|
430
|
+
(acc, x) => [...acc, (log(x), (acc.at(-1) ?? 0) + x)],
|
|
431
|
+
[(log(0), 0)],
|
|
432
|
+
);
|
|
433
|
+
`,
|
|
434
|
+
output: dedent`
|
|
435
|
+
import { Arr } from 'ts-data-forge';
|
|
436
|
+
declare const xs: readonly number[];
|
|
437
|
+
declare const log: (n: number) => void;
|
|
438
|
+
const totals = Arr.scan(xs, (acc, x) => (log(x), (acc) + x), (log(0), 0));
|
|
439
|
+
`,
|
|
440
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
});
|
|
@@ -4,7 +4,12 @@ import {
|
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
6
|
import { Arr, pipe } from 'ts-data-forge';
|
|
7
|
-
import {
|
|
7
|
+
import type { TypeReference } from 'typescript';
|
|
8
|
+
import {
|
|
9
|
+
skipTypeWrappers,
|
|
10
|
+
toArgumentText,
|
|
11
|
+
typeWrapperLayers,
|
|
12
|
+
} from './ast-utils.mjs';
|
|
8
13
|
import {
|
|
9
14
|
buildImportFixes,
|
|
10
15
|
getNamedImports,
|
|
@@ -50,8 +55,10 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
50
55
|
return {
|
|
51
56
|
CallExpression: (node) => {
|
|
52
57
|
// Check for xs.reduce(...)
|
|
58
|
+
// (`xs[reduce]` calls whatever method the variable `reduce` names)
|
|
53
59
|
if (
|
|
54
60
|
node.callee.type !== AST_NODE_TYPES.MemberExpression ||
|
|
61
|
+
node.callee.computed ||
|
|
55
62
|
node.callee.property.type !== AST_NODE_TYPES.Identifier ||
|
|
56
63
|
node.callee.property.name !== 'reduce'
|
|
57
64
|
) {
|
|
@@ -61,11 +68,16 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
61
68
|
const arrayExpression = node.callee.object;
|
|
62
69
|
|
|
63
70
|
// Check if we have 2 arguments: reducer function and initial value 0
|
|
64
|
-
if (!Arr.isFixedLengthTuple(2, node.arguments))
|
|
71
|
+
if (!Arr.isFixedLengthTuple(2, node.arguments)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
65
74
|
|
|
66
75
|
const reducer = node.arguments[0];
|
|
67
76
|
|
|
68
|
-
|
|
77
|
+
// The initial value and the operands of the body are read through
|
|
78
|
+
// type wrappers: `0 as number` is still 0, `(b satisfies number)` is
|
|
79
|
+
// still `b`. Types are asked of the values underneath too.
|
|
80
|
+
const initialValue = skipTypeWrappers(node.arguments[1]);
|
|
69
81
|
|
|
70
82
|
// Check initial value is 0
|
|
71
83
|
if (
|
|
@@ -96,27 +108,47 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
96
108
|
|
|
97
109
|
const checker = services?.program?.getTypeChecker();
|
|
98
110
|
|
|
99
|
-
if (checker === undefined)
|
|
111
|
+
if (checker === undefined) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
100
114
|
|
|
101
|
-
// Case 1: (a, b) => a + b
|
|
102
115
|
if (
|
|
103
|
-
body.type
|
|
104
|
-
body.operator
|
|
105
|
-
body.left.type === AST_NODE_TYPES.Identifier &&
|
|
106
|
-
body.left.name === param1.name &&
|
|
107
|
-
body.right.type === AST_NODE_TYPES.Identifier &&
|
|
108
|
-
body.right.name === param2.name
|
|
116
|
+
body.type !== AST_NODE_TYPES.BinaryExpression ||
|
|
117
|
+
body.operator !== '+'
|
|
109
118
|
) {
|
|
110
|
-
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
const left = skipTypeWrappers(body.left);
|
|
123
|
+
|
|
124
|
+
const right = skipTypeWrappers(body.right);
|
|
125
|
+
|
|
126
|
+
// Case 1: (a, b) => a + b
|
|
127
|
+
if (
|
|
128
|
+
left.type === AST_NODE_TYPES.Identifier &&
|
|
129
|
+
left.name === param1.name &&
|
|
130
|
+
right.type === AST_NODE_TYPES.Identifier &&
|
|
131
|
+
right.name === param2.name
|
|
132
|
+
) {
|
|
133
|
+
// Check if arrayExpression type is number[] or compatible at every
|
|
134
|
+
// layer of type wrappers: a cast does not turn strings, which `+`
|
|
135
|
+
// concatenates, into numbers.
|
|
136
|
+
const isNumberArray = (
|
|
137
|
+
// AST nodes hold mutable child arrays, so they are not deeply readonly.
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
139
|
+
expression: TSESTree.Node,
|
|
140
|
+
): boolean => {
|
|
141
|
+
const type = pipe(
|
|
142
|
+
services?.esTreeNodeToTSNodeMap?.get(expression),
|
|
143
|
+
).mapNullable((tsNode) => checker.getTypeAtLocation(tsNode)).value;
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
type === undefined || // Check if it's an array type or tuple type
|
|
147
|
+
(!checker.isArrayType(type) && !checker.isTupleType(type))
|
|
148
|
+
) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
115
151
|
|
|
116
|
-
if (
|
|
117
|
-
type !== undefined && // Check if it's an array type or tuple type
|
|
118
|
-
(checker.isArrayType(type) || checker.isTupleType(type))
|
|
119
|
-
) {
|
|
120
152
|
// Get type arguments using the official API
|
|
121
153
|
// TypeReference has typeArguments that we can access
|
|
122
154
|
const typeArguments = checker.getTypeArguments(
|
|
@@ -124,20 +156,22 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
124
156
|
type as TypeReference,
|
|
125
157
|
);
|
|
126
158
|
|
|
127
|
-
if
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
// Check if element type is assignable to number
|
|
160
|
+
return (
|
|
161
|
+
Arr.isNonEmpty(typeArguments) &&
|
|
162
|
+
checker.isTypeAssignableTo(
|
|
163
|
+
typeArguments[0],
|
|
164
|
+
checker.getNumberType(),
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (typeWrapperLayers(arrayExpression).every(isNumberArray)) {
|
|
170
|
+
mut_nodesToFix.push({
|
|
171
|
+
node,
|
|
172
|
+
arrayExpression,
|
|
173
|
+
messageId: 'useArrSum',
|
|
174
|
+
});
|
|
141
175
|
}
|
|
142
176
|
|
|
143
177
|
return;
|
|
@@ -145,14 +179,12 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
145
179
|
|
|
146
180
|
// Case 2: (a, b) => a.prop + b.prop or a['prop'] + b['prop']
|
|
147
181
|
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
body.left.type === AST_NODE_TYPES.MemberExpression &&
|
|
151
|
-
body.right.type === AST_NODE_TYPES.MemberExpression
|
|
182
|
+
left.type === AST_NODE_TYPES.MemberExpression &&
|
|
183
|
+
right.type === AST_NODE_TYPES.MemberExpression
|
|
152
184
|
) {
|
|
153
|
-
const leftObj =
|
|
185
|
+
const leftObj = skipTypeWrappers(left.object);
|
|
154
186
|
|
|
155
|
-
const rightObj =
|
|
187
|
+
const rightObj = skipTypeWrappers(right.object);
|
|
156
188
|
|
|
157
189
|
if (
|
|
158
190
|
leftObj.type !== AST_NODE_TYPES.Identifier ||
|
|
@@ -164,38 +196,40 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
164
196
|
}
|
|
165
197
|
|
|
166
198
|
// Check if both access the same property
|
|
167
|
-
const leftProp =
|
|
199
|
+
const leftProp = left.property;
|
|
168
200
|
|
|
169
|
-
const rightProp =
|
|
201
|
+
const rightProp = right.property;
|
|
170
202
|
|
|
171
203
|
let mut_propName: string | undefined;
|
|
172
204
|
|
|
173
205
|
if (
|
|
174
206
|
leftProp.type === AST_NODE_TYPES.Identifier &&
|
|
175
|
-
!
|
|
207
|
+
!left.computed &&
|
|
176
208
|
rightProp.type === AST_NODE_TYPES.Identifier &&
|
|
177
|
-
!
|
|
209
|
+
!right.computed &&
|
|
178
210
|
leftProp.name === rightProp.name
|
|
179
211
|
) {
|
|
180
212
|
mut_propName = leftProp.name;
|
|
181
213
|
} else if (
|
|
182
214
|
leftProp.type === AST_NODE_TYPES.Literal &&
|
|
183
|
-
|
|
215
|
+
left.computed &&
|
|
184
216
|
rightProp.type === AST_NODE_TYPES.Literal &&
|
|
185
|
-
|
|
217
|
+
right.computed &&
|
|
186
218
|
leftProp.value === rightProp.value &&
|
|
187
219
|
typeof leftProp.value === 'string'
|
|
188
220
|
) {
|
|
189
221
|
mut_propName = leftProp.value;
|
|
190
222
|
}
|
|
191
223
|
|
|
192
|
-
if (mut_propName === undefined)
|
|
224
|
+
if (mut_propName === undefined) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
193
227
|
|
|
194
228
|
// Check if property type is number
|
|
195
229
|
if (services?.program !== undefined && services.program !== null) {
|
|
196
|
-
const tsLeftProp = services.esTreeNodeToTSNodeMap?.get(
|
|
230
|
+
const tsLeftProp = services.esTreeNodeToTSNodeMap?.get(left);
|
|
197
231
|
|
|
198
|
-
const tsRightProp = services.esTreeNodeToTSNodeMap?.get(
|
|
232
|
+
const tsRightProp = services.esTreeNodeToTSNodeMap?.get(right);
|
|
199
233
|
|
|
200
234
|
if (tsLeftProp !== undefined && tsRightProp !== undefined) {
|
|
201
235
|
const leftPropType = checker.getTypeAtLocation(tsLeftProp);
|
|
@@ -217,7 +251,7 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
217
251
|
// Generate mapper function
|
|
218
252
|
const mapperParam = param1.name;
|
|
219
253
|
|
|
220
|
-
const mapper =
|
|
254
|
+
const mapper = left.computed
|
|
221
255
|
? `${mapperParam} => ${mapperParam}['${mut_propName}']`
|
|
222
256
|
: `${mapperParam} => ${mapperParam}.${mut_propName}`;
|
|
223
257
|
|
|
@@ -235,7 +269,10 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
235
269
|
const hasArrImport = namedImports.includes('Arr');
|
|
236
270
|
|
|
237
271
|
for (const [index, nodeInfo] of mut_nodesToFix.entries()) {
|
|
238
|
-
const arrayText =
|
|
272
|
+
const arrayText = toArgumentText(
|
|
273
|
+
nodeInfo.arrayExpression,
|
|
274
|
+
sourceCode,
|
|
275
|
+
);
|
|
239
276
|
|
|
240
277
|
context.report({
|
|
241
278
|
node: nodeInfo.node,
|
|
@@ -169,3 +169,100 @@ describe('prefer-arr-sum', () => {
|
|
|
169
169
|
],
|
|
170
170
|
});
|
|
171
171
|
}, 20000);
|
|
172
|
+
|
|
173
|
+
describe('prefer-arr-sum through type wrappers', () => {
|
|
174
|
+
tester.run('prefer-arr-sum', preferArrSum, {
|
|
175
|
+
valid: [
|
|
176
|
+
{
|
|
177
|
+
name: 'strings cast to numbers are concatenated, not summed',
|
|
178
|
+
code: dedent`
|
|
179
|
+
declare const xs: readonly string[];
|
|
180
|
+
const sum = (xs as unknown as readonly number[]).reduce(
|
|
181
|
+
(a, b) => a + b,
|
|
182
|
+
0,
|
|
183
|
+
);
|
|
184
|
+
`,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'a string property cast to number',
|
|
188
|
+
code: dedent`
|
|
189
|
+
declare const xs: readonly Readonly<{ n: string }>[];
|
|
190
|
+
const sum = xs.reduce(
|
|
191
|
+
(a, b) => (a.n as unknown as number) + (b.n as unknown as number),
|
|
192
|
+
0,
|
|
193
|
+
);
|
|
194
|
+
`,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'a computed `reduce` is some other method',
|
|
198
|
+
code: dedent`
|
|
199
|
+
declare const xs: readonly number[];
|
|
200
|
+
declare const reduce: 'reduce';
|
|
201
|
+
const sum = xs[reduce]((a, b) => a + b, 0);
|
|
202
|
+
`,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
invalid: [
|
|
206
|
+
{
|
|
207
|
+
name: 'the initial value or an operand wrapped',
|
|
208
|
+
code: dedent`
|
|
209
|
+
import { Arr } from 'ts-data-forge';
|
|
210
|
+
declare const xs: readonly number[];
|
|
211
|
+
const a = xs.reduce((a, b) => a + b, 0 as number);
|
|
212
|
+
const b = xs.reduce((a, b) => a + (b satisfies number), 0);
|
|
213
|
+
const c = xs!.reduce((a, b) => a! + b, 0);
|
|
214
|
+
`,
|
|
215
|
+
output: dedent`
|
|
216
|
+
import { Arr } from 'ts-data-forge';
|
|
217
|
+
declare const xs: readonly number[];
|
|
218
|
+
const a = Arr.sum(xs);
|
|
219
|
+
const b = Arr.sum(xs);
|
|
220
|
+
const c = Arr.sum(xs!);
|
|
221
|
+
`,
|
|
222
|
+
errors: [
|
|
223
|
+
{ messageId: 'useArrSum' },
|
|
224
|
+
{ messageId: 'useArrSum' },
|
|
225
|
+
{ messageId: 'useArrSum' },
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: 'a property read through wrappers',
|
|
230
|
+
code: dedent`
|
|
231
|
+
import { Arr } from 'ts-data-forge';
|
|
232
|
+
declare const xs: readonly Readonly<{ n: number }>[];
|
|
233
|
+
const sum = xs.reduce((a, b) => (a.n satisfies number) + b!.n, 0);
|
|
234
|
+
`,
|
|
235
|
+
output: dedent`
|
|
236
|
+
import { Arr } from 'ts-data-forge';
|
|
237
|
+
declare const xs: readonly Readonly<{ n: number }>[];
|
|
238
|
+
const sum = Arr.sumBy(xs, a => a.n);
|
|
239
|
+
`,
|
|
240
|
+
errors: [{ messageId: 'useArrSumBy' }],
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
}, 20000);
|
|
245
|
+
|
|
246
|
+
describe('prefer-arr-sum with parenthesized operands', () => {
|
|
247
|
+
tester.run('prefer-arr-sum', preferArrSum, {
|
|
248
|
+
valid: [],
|
|
249
|
+
invalid: [
|
|
250
|
+
{
|
|
251
|
+
name: 'a sequence array keeps its parentheses',
|
|
252
|
+
code: dedent`
|
|
253
|
+
import { Arr } from 'ts-data-forge';
|
|
254
|
+
declare const xs: readonly number[];
|
|
255
|
+
declare const log: () => void;
|
|
256
|
+
const sum = (log(), xs).reduce((a, b) => a + b, 0);
|
|
257
|
+
`,
|
|
258
|
+
output: dedent`
|
|
259
|
+
import { Arr } from 'ts-data-forge';
|
|
260
|
+
declare const xs: readonly number[];
|
|
261
|
+
declare const log: () => void;
|
|
262
|
+
const sum = Arr.sum((log(), xs));
|
|
263
|
+
`,
|
|
264
|
+
errors: [{ messageId: 'useArrSum' }],
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
});
|
|
268
|
+
}, 20000);
|