eslint-plugin-ts-data-forge 0.6.1 → 0.7.1

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.
Files changed (77) hide show
  1. package/README.md +1 -0
  2. package/dist/index.mjs +2 -2
  3. package/dist/index.mjs.map +1 -1
  4. package/dist/plugin.d.mts +2 -0
  5. package/dist/plugin.d.mts.map +1 -1
  6. package/dist/plugin.mjs +45 -41
  7. package/dist/plugin.mjs.map +1 -1
  8. package/dist/rule-types.d.mts +15 -0
  9. package/dist/rule-types.d.mts.map +1 -1
  10. package/dist/rule-types.mjs +6 -2
  11. package/dist/rule-types.mjs.map +1 -1
  12. package/dist/rules/ast-utils.mjs +107 -50
  13. package/dist/rules/ast-utils.mjs.map +1 -1
  14. package/dist/rules/branded-number-types.mjs +41 -37
  15. package/dist/rules/branded-number-types.mjs.map +1 -1
  16. package/dist/rules/constants.mjs +2 -4
  17. package/dist/rules/constants.mjs.map +1 -1
  18. package/dist/rules/import-utils.mjs +80 -56
  19. package/dist/rules/import-utils.mjs.map +1 -1
  20. package/dist/rules/index.mjs +2 -2
  21. package/dist/rules/index.mjs.map +1 -1
  22. package/dist/rules/no-side-effect-import.mjs +46 -34
  23. package/dist/rules/no-side-effect-import.mjs.map +1 -1
  24. package/dist/rules/no-unnecessary-type-guard.mjs +376 -265
  25. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
  26. package/dist/rules/prefer-arr-is-array.mjs +64 -52
  27. package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
  28. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +91 -85
  29. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  30. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +92 -83
  31. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  32. package/dist/rules/prefer-arr-is-max-length-array.mjs +92 -80
  33. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  34. package/dist/rules/prefer-arr-is-min-length-array.mjs +92 -80
  35. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  36. package/dist/rules/prefer-arr-is-non-empty.mjs +85 -77
  37. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
  38. package/dist/rules/prefer-arr-scan.d.mts +96 -0
  39. package/dist/rules/prefer-arr-scan.d.mts.map +1 -0
  40. package/dist/rules/prefer-arr-scan.mjs +482 -0
  41. package/dist/rules/prefer-arr-scan.mjs.map +1 -0
  42. package/dist/rules/prefer-arr-sum.mjs +176 -132
  43. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  44. package/dist/rules/prefer-as-int.mjs +77 -71
  45. package/dist/rules/prefer-as-int.mjs.map +1 -1
  46. package/dist/rules/prefer-canonical-array-slicing.mjs +503 -343
  47. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  48. package/dist/rules/prefer-canonical-length-cast.mjs +217 -144
  49. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
  50. package/dist/rules/prefer-canonical-length-guard.mjs +160 -109
  51. package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
  52. package/dist/rules/prefer-comparison-over-nullish-guard.mjs +89 -71
  53. package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
  54. package/dist/rules/prefer-is-non-null-object.mjs +91 -72
  55. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  56. package/dist/rules/prefer-is-record-and-has-key.mjs +148 -105
  57. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  58. package/dist/rules/prefer-num-safe-parse-float.mjs +101 -77
  59. package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
  60. package/dist/rules/prefer-num-safe-parse-int.mjs +103 -81
  61. package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
  62. package/dist/rules/prefer-obj-over-entries-round-trip.mjs +299 -194
  63. package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
  64. package/dist/rules/prefer-range-for-loop.mjs +121 -99
  65. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  66. package/dist/rules/rules.d.mts +1 -0
  67. package/dist/rules/rules.d.mts.map +1 -1
  68. package/dist/rules/rules.mjs +19 -20
  69. package/dist/rules/rules.mjs.map +1 -1
  70. package/dist/types.mjs +2 -2
  71. package/dist/types.mjs.map +1 -1
  72. package/package.json +6 -7
  73. package/src/plugin.mts +1 -0
  74. package/src/rule-types.mts +16 -0
  75. package/src/rules/prefer-arr-scan.mts +693 -0
  76. package/src/rules/prefer-arr-scan.test.mts +295 -0
  77. package/src/rules/rules.mts +2 -0
@@ -0,0 +1,295 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { preferArrScan } from './prefer-arr-scan.mjs';
5
+
6
+ const tester = new RuleTester({
7
+ languageOptions: {
8
+ parser,
9
+ parserOptions: {
10
+ ecmaVersion: 2020,
11
+ sourceType: 'module',
12
+ projectService: {
13
+ allowDefaultProject: ['*.ts*'],
14
+ },
15
+ tsconfigRootDir: `${import.meta.dirname}/../..`,
16
+ },
17
+ },
18
+ });
19
+
20
+ describe('prefer-arr-scan', () => {
21
+ tester.run('prefer-arr-scan', preferArrScan, {
22
+ valid: [
23
+ {
24
+ name: 'map callback that reads its element',
25
+ code: dedent`
26
+ declare const xs: readonly string[];
27
+ const ys = xs.map((x, i) => [x, ...xs.slice(0, i + 1)]);
28
+ `,
29
+ },
30
+ {
31
+ name: 'map callback with no slicing at all',
32
+ code: dedent`
33
+ declare const xs: readonly string[];
34
+ const ys = xs.map((_, i) => i * 2);
35
+ `,
36
+ },
37
+ {
38
+ name: 'slicing a different array',
39
+ code: dedent`
40
+ declare const xs: readonly string[];
41
+ declare const ys: readonly string[];
42
+ const zs = xs.map((_, i) => ys.slice(0, i + 1).join(''));
43
+ `,
44
+ },
45
+ {
46
+ name: 'slice bounds that do not come from the index',
47
+ code: dedent`
48
+ declare const xs: readonly string[];
49
+ const ys = xs.map((_, i) => xs.slice(0, 2).join(String(i)));
50
+ `,
51
+ },
52
+ {
53
+ name: 'map with only one parameter',
54
+ code: dedent`
55
+ declare const xs: readonly string[];
56
+ const ys = xs.map((x) => x.length);
57
+ `,
58
+ },
59
+ {
60
+ name: 'reduce that does not rebuild its accumulator',
61
+ code: dedent`
62
+ declare const xs: readonly number[];
63
+ const total = xs.reduce((acc, x) => acc + x, 0);
64
+ `,
65
+ },
66
+ {
67
+ name: 'reduce spreading its accumulator but with no initial value',
68
+ code: dedent`
69
+ declare const xs: readonly number[];
70
+ const ys = xs.reduce<readonly number[]>((acc, x) => [...acc, x]);
71
+ `,
72
+ },
73
+ {
74
+ name: 'toPushed applied to an array other than the accumulator',
75
+ code: dedent`
76
+ import { Arr } from 'ts-data-forge';
77
+ declare const xs: readonly number[];
78
+ declare const seed: readonly number[];
79
+ const ys = xs.reduce<readonly number[]>(
80
+ (acc, x) => Arr.toPushed(seed, x),
81
+ [],
82
+ );
83
+ `,
84
+ },
85
+ {
86
+ name: 'reduce spreading something other than the accumulator',
87
+ code: dedent`
88
+ declare const xs: readonly number[];
89
+ declare const seed: readonly number[];
90
+ const ys = xs.reduce<readonly number[]>((acc, x) => [...seed, x], []);
91
+ `,
92
+ },
93
+ ],
94
+ invalid: [
95
+ {
96
+ name: 'prefix rebuilt with slice(0, i + 1)',
97
+ code: dedent`
98
+ declare const segments: readonly string[];
99
+ declare const resolve: (...parts: readonly string[]) => string;
100
+ const ancestors = segments.map((_, index) =>
101
+ resolve(...segments.slice(0, index + 1)),
102
+ );
103
+ `,
104
+ errors: [{ messageId: 'preferArrScanPrefix' }],
105
+ },
106
+ {
107
+ name: 'prefix rebuilt backwards, from the length',
108
+ code: dedent`
109
+ declare const segments: readonly string[];
110
+ const ancestors = segments.map((_, index) =>
111
+ segments.slice(0, segments.length - index).join('/'),
112
+ );
113
+ `,
114
+ errors: [{ messageId: 'preferArrScanPrefix' }],
115
+ },
116
+ {
117
+ name: 'a member expression as the array, spelled the same way twice',
118
+ code: dedent`
119
+ declare const state: { readonly parts: readonly string[] };
120
+ const rows = state.parts.map((_, i) => state.parts.slice(0, i).join(''));
121
+ `,
122
+ errors: [{ messageId: 'preferArrScanPrefix' }],
123
+ },
124
+ {
125
+ name: 'reduce accumulating into a new array on every step',
126
+ code: dedent`
127
+ declare const xs: readonly number[];
128
+ const totals = xs.reduce<readonly number[]>(
129
+ (acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
130
+ [0],
131
+ );
132
+ `,
133
+ output: dedent`
134
+ import { Arr } from 'ts-data-forge';
135
+ declare const xs: readonly number[];
136
+ const totals = Arr.scan(xs, (acc, x) => (acc) + x, 0);
137
+ `,
138
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
139
+ },
140
+ {
141
+ name: 'the fix reuses an Arr already imported',
142
+ code: dedent`
143
+ import { Arr } from 'ts-data-forge';
144
+ declare const xs: readonly number[];
145
+ const totals = xs.reduce<readonly number[]>(
146
+ (acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
147
+ [0],
148
+ );
149
+ `,
150
+ output: dedent`
151
+ import { Arr } from 'ts-data-forge';
152
+ declare const xs: readonly number[];
153
+ const totals = Arr.scan(xs, (acc, x) => (acc) + x, 0);
154
+ `,
155
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
156
+ },
157
+ {
158
+ name: 'the index parameter is carried over',
159
+ code: dedent`
160
+ import { Arr } from 'ts-data-forge';
161
+ declare const xs: readonly number[];
162
+ const totals = xs.reduce<readonly number[]>(
163
+ (acc, x, i) => [...acc, (acc.at(-1) ?? 0) + x * i],
164
+ [0],
165
+ );
166
+ `,
167
+ output: dedent`
168
+ import { Arr } from 'ts-data-forge';
169
+ declare const xs: readonly number[];
170
+ const totals = Arr.scan(xs, (acc, x, i) => (acc) + x * i, 0);
171
+ `,
172
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
173
+ },
174
+ {
175
+ name: 'more than one accumulator read is rewritten',
176
+ code: dedent`
177
+ import { Arr } from 'ts-data-forge';
178
+ declare const xs: readonly number[];
179
+ const totals = xs.reduce<readonly number[]>(
180
+ (acc, x) => [...acc, (acc.at(-1) ?? 0) + x * (acc.at(-1) ?? 1)],
181
+ [0],
182
+ );
183
+ `,
184
+ output: dedent`
185
+ import { Arr } from 'ts-data-forge';
186
+ declare const xs: readonly number[];
187
+ const totals = Arr.scan(xs, (acc, x) => (acc) + x * (acc), 0);
188
+ `,
189
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
190
+ },
191
+ {
192
+ name: 'the Arr.toPushed spelling, which prefer-canonical-array-slicing produces',
193
+ code: dedent`
194
+ import { Arr } from 'ts-data-forge';
195
+ declare const xs: readonly number[];
196
+ const totals = xs.reduce<readonly number[]>(
197
+ (acc, x) => Arr.toPushed(acc, (acc.at(-1) ?? 0) + x),
198
+ [0],
199
+ );
200
+ `,
201
+ output: dedent`
202
+ import { Arr } from 'ts-data-forge';
203
+ declare const xs: readonly number[];
204
+ const totals = Arr.scan(xs, (acc, x) => (acc) + x, 0);
205
+ `,
206
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
207
+ },
208
+ {
209
+ name: 'identifiers other than the accumulator are left alone',
210
+ code: dedent`
211
+ import { Arr } from 'ts-data-forge';
212
+ declare const xs: readonly number[];
213
+ declare const seed: readonly number[];
214
+ const totals = xs.reduce<readonly number[]>(
215
+ (acc, x) => Arr.toPushed(acc, (acc.at(-1) ?? 0) + x + seed.length),
216
+ [0],
217
+ );
218
+ `,
219
+ output: dedent`
220
+ import { Arr } from 'ts-data-forge';
221
+ declare const xs: readonly number[];
222
+ declare const seed: readonly number[];
223
+ const totals = Arr.scan(xs, (acc, x) => (acc) + x + seed.length, 0);
224
+ `,
225
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
226
+ },
227
+ {
228
+ name: 'reported without a fix: the accumulator is read as an array',
229
+ code: dedent`
230
+ declare const xs: readonly number[];
231
+ const totals = xs.reduce<readonly number[]>(
232
+ (acc, x) => [...acc, acc.length + x],
233
+ [0],
234
+ );
235
+ `,
236
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
237
+ },
238
+ {
239
+ name: 'reported without a fix: the initial value is not a one-element array',
240
+ code: dedent`
241
+ declare const xs: readonly number[];
242
+ const totals = xs.reduce<readonly number[]>(
243
+ (acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
244
+ [0, 1],
245
+ );
246
+ `,
247
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
248
+ },
249
+ {
250
+ name: 'reported without a fix: two elements appended per step',
251
+ code: dedent`
252
+ declare const xs: readonly number[];
253
+ const totals = xs.reduce<readonly number[]>(
254
+ (acc, x) => [...acc, (acc.at(-1) ?? 0) + x, x],
255
+ [0],
256
+ );
257
+ `,
258
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
259
+ },
260
+ {
261
+ name: 'reported without a fix: the previous value is never read',
262
+ code: dedent`
263
+ declare const xs: readonly number[];
264
+ const doubled = xs.reduce<readonly number[]>(
265
+ (acc, x) => [...acc, x * 2],
266
+ [0],
267
+ );
268
+ `,
269
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
270
+ },
271
+ {
272
+ name: 'reported without a fix: an annotated accumulator parameter',
273
+ code: dedent`
274
+ declare const xs: readonly number[];
275
+ const totals = xs.reduce<readonly number[]>(
276
+ (acc: readonly number[], x) => [...acc, (acc.at(-1) ?? 0) + x],
277
+ [0],
278
+ );
279
+ `,
280
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
281
+ },
282
+ {
283
+ name: 'reported without a fix: an index other than -1',
284
+ code: dedent`
285
+ declare const xs: readonly number[];
286
+ const totals = xs.reduce<readonly number[]>(
287
+ (acc, x) => [...acc, (acc.at(-2) ?? 0) + x],
288
+ [0],
289
+ );
290
+ `,
291
+ errors: [{ messageId: 'preferArrScanAccumulate' }],
292
+ },
293
+ ],
294
+ });
295
+ });
@@ -2,6 +2,7 @@ import { type ESLintPlugin } from '../types.mjs';
2
2
  import { noSideEffectImport } from './no-side-effect-import.mjs';
3
3
  import { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';
4
4
  import { preferArrIsArray } from './prefer-arr-is-array.mjs';
5
+ import { preferArrScan } from './prefer-arr-scan.mjs';
5
6
  import { preferArrSum } from './prefer-arr-sum.mjs';
6
7
  import { preferAsInt } from './prefer-as-int.mjs';
7
8
  import { preferCanonicalArraySlicing } from './prefer-canonical-array-slicing.mjs';
@@ -20,6 +21,7 @@ export const tsDataForgeRules = {
20
21
  'prefer-canonical-length-guard': preferCanonicalLengthGuard,
21
22
  'prefer-canonical-length-cast': preferCanonicalLengthCast,
22
23
  'prefer-arr-is-array': preferArrIsArray,
24
+ 'prefer-arr-scan': preferArrScan,
23
25
  'prefer-arr-sum': preferArrSum,
24
26
  'prefer-as-int': preferAsInt,
25
27
  'prefer-is-non-null-object': preferIsNonNullObject,