eslint-plugin-ts-data-forge 0.1.7 → 0.2.0
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/README.md +47 -19
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/plugin.d.mts +44 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +34 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +3 -63
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.d.mts +6 -0
- package/dist/rules/prefer-canonical-length-guard.d.mts.map +1 -0
- package/dist/rules/prefer-canonical-length-guard.mjs +125 -0
- package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -0
- package/dist/rules/rules.d.mts +1 -5
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +2 -10
- package/dist/rules/rules.mjs.map +1 -1
- package/dist/types.d.mts +1 -0
- package/dist/types.d.mts.map +1 -1
- package/package.json +6 -2
- package/src/index.mts +1 -1
- package/src/plugin.mts +46 -3
- package/src/plugin.test.mts +24 -0
- package/src/rule-types.mts +3 -67
- package/src/rules/prefer-canonical-length-guard.mts +231 -0
- package/src/rules/prefer-canonical-length-guard.test.mts +307 -0
- package/src/rules/rules.mts +2 -10
- package/src/types.mts +2 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import parser from '@typescript-eslint/parser';
|
|
2
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
3
|
+
import dedent from 'dedent';
|
|
4
|
+
import { preferCanonicalLengthGuard } from './prefer-canonical-length-guard.mjs';
|
|
5
|
+
|
|
6
|
+
const tester = new RuleTester({
|
|
7
|
+
languageOptions: {
|
|
8
|
+
parser,
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// The folded-in comparison rules are type-aware, so they need a TypeScript
|
|
17
|
+
// program; the guard-normalization half does not.
|
|
18
|
+
const typedTester = new RuleTester({
|
|
19
|
+
languageOptions: {
|
|
20
|
+
parser,
|
|
21
|
+
parserOptions: {
|
|
22
|
+
ecmaVersion: 2020,
|
|
23
|
+
sourceType: 'module',
|
|
24
|
+
projectService: {
|
|
25
|
+
allowDefaultProject: ['*.ts*'],
|
|
26
|
+
},
|
|
27
|
+
tsconfigRootDir: `${import.meta.dirname}/../..`,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('prefer-canonical-length-guard (folded-in length comparisons)', () => {
|
|
33
|
+
typedTester.run('prefer-canonical-length-guard', preferCanonicalLengthGuard, {
|
|
34
|
+
valid: [
|
|
35
|
+
{
|
|
36
|
+
name: 'ignores non-array length comparisons',
|
|
37
|
+
code: dedent`
|
|
38
|
+
const str = 'hello';
|
|
39
|
+
const ok = str.length > 0;
|
|
40
|
+
`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
invalid: [
|
|
44
|
+
{
|
|
45
|
+
name: 'xs.length > 0 becomes Arr.isNonEmpty(xs)',
|
|
46
|
+
code: dedent`
|
|
47
|
+
const xs = [1, 2, 3];
|
|
48
|
+
const ok = xs.length > 0;
|
|
49
|
+
`,
|
|
50
|
+
output: dedent`
|
|
51
|
+
import { Arr } from 'ts-data-forge';
|
|
52
|
+
const xs = [1, 2, 3];
|
|
53
|
+
const ok = Arr.isNonEmpty(xs);
|
|
54
|
+
`,
|
|
55
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'xs.length >= 2 becomes Arr.isMinLengthArray(xs, 2)',
|
|
59
|
+
code: dedent`
|
|
60
|
+
const xs = [1, 2, 3];
|
|
61
|
+
const ok = xs.length >= 2;
|
|
62
|
+
`,
|
|
63
|
+
output: dedent`
|
|
64
|
+
import { Arr } from 'ts-data-forge';
|
|
65
|
+
const xs = [1, 2, 3];
|
|
66
|
+
const ok = Arr.isMinLengthArray(xs, 2);
|
|
67
|
+
`,
|
|
68
|
+
errors: [{ messageId: 'useIsMinLengthArray' }],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'xs.length === 3 becomes Arr.isFixedLengthArray(xs, 3)',
|
|
72
|
+
code: dedent`
|
|
73
|
+
const xs = [1, 2, 3];
|
|
74
|
+
const ok = xs.length === 3;
|
|
75
|
+
`,
|
|
76
|
+
output: dedent`
|
|
77
|
+
import { Arr } from 'ts-data-forge';
|
|
78
|
+
const xs = [1, 2, 3];
|
|
79
|
+
const ok = Arr.isFixedLengthArray(xs, 3);
|
|
80
|
+
`,
|
|
81
|
+
errors: [{ messageId: 'useIsFixedLengthArray' }],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('prefer-canonical-length-guard', () => {
|
|
88
|
+
tester.run('prefer-canonical-length-guard', preferCanonicalLengthGuard, {
|
|
89
|
+
valid: [
|
|
90
|
+
{
|
|
91
|
+
name: 'ignores non-degenerate bounds',
|
|
92
|
+
code: dedent`
|
|
93
|
+
import { Arr } from 'ts-data-forge';
|
|
94
|
+
|
|
95
|
+
declare const xs: readonly number[];
|
|
96
|
+
const ok = Arr.isFixedLengthTuple(xs, 2);
|
|
97
|
+
`,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'ignores isMinLengthArray with a bound other than 1',
|
|
101
|
+
code: dedent`
|
|
102
|
+
import { Arr } from 'ts-data-forge';
|
|
103
|
+
|
|
104
|
+
declare const xs: readonly number[];
|
|
105
|
+
const ok = Arr.isMinLengthArray(xs, 2);
|
|
106
|
+
`,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'ignores isBoundedLengthTuple unless both bounds are 0',
|
|
110
|
+
code: dedent`
|
|
111
|
+
import { Arr } from 'ts-data-forge';
|
|
112
|
+
|
|
113
|
+
declare const xs: readonly number[];
|
|
114
|
+
const ok = Arr.isBoundedLengthTuple(xs, 0, 1);
|
|
115
|
+
`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'ignores a non-literal bound',
|
|
119
|
+
code: dedent`
|
|
120
|
+
import { Arr } from 'ts-data-forge';
|
|
121
|
+
|
|
122
|
+
declare const xs: readonly number[];
|
|
123
|
+
const n = 0;
|
|
124
|
+
const ok = Arr.isFixedLengthTuple(xs, n);
|
|
125
|
+
`,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'ignores an Arr that is not from ts-data-forge',
|
|
129
|
+
code: dedent`
|
|
130
|
+
import { Arr } from './my-helpers.mjs';
|
|
131
|
+
|
|
132
|
+
declare const xs: readonly number[];
|
|
133
|
+
const ok = Arr.isFixedLengthTuple(xs, 0);
|
|
134
|
+
`,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'ignores already-canonical guards',
|
|
138
|
+
code: dedent`
|
|
139
|
+
import { Arr } from 'ts-data-forge';
|
|
140
|
+
|
|
141
|
+
declare const xs: readonly number[];
|
|
142
|
+
const ok = Arr.isEmpty(xs);
|
|
143
|
+
`,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'ignores spread arguments',
|
|
147
|
+
code: dedent`
|
|
148
|
+
import { Arr } from 'ts-data-forge';
|
|
149
|
+
|
|
150
|
+
declare const args: readonly [readonly number[], 0];
|
|
151
|
+
const ok = Arr.isFixedLengthTuple(...args);
|
|
152
|
+
`,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
invalid: [
|
|
156
|
+
{
|
|
157
|
+
name: 'isFixedLengthArray(xs, 0) becomes isEmpty(xs)',
|
|
158
|
+
code: dedent`
|
|
159
|
+
import { Arr } from 'ts-data-forge';
|
|
160
|
+
|
|
161
|
+
declare const xs: readonly number[];
|
|
162
|
+
const ok = Arr.isFixedLengthArray(xs, 0);
|
|
163
|
+
`,
|
|
164
|
+
output: dedent`
|
|
165
|
+
import { Arr } from 'ts-data-forge';
|
|
166
|
+
|
|
167
|
+
declare const xs: readonly number[];
|
|
168
|
+
const ok = Arr.isEmpty(xs);
|
|
169
|
+
`,
|
|
170
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'isMinLengthTuple(xs, 1) becomes isNonEmpty(xs)',
|
|
174
|
+
code: dedent`
|
|
175
|
+
import { Arr } from 'ts-data-forge';
|
|
176
|
+
|
|
177
|
+
declare const xs: readonly number[];
|
|
178
|
+
const ok = Arr.isMinLengthTuple(xs, 1);
|
|
179
|
+
`,
|
|
180
|
+
output: dedent`
|
|
181
|
+
import { Arr } from 'ts-data-forge';
|
|
182
|
+
|
|
183
|
+
declare const xs: readonly number[];
|
|
184
|
+
const ok = Arr.isNonEmpty(xs);
|
|
185
|
+
`,
|
|
186
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'isFixedLengthTuple(xs, 0) becomes isEmpty(xs)',
|
|
190
|
+
code: dedent`
|
|
191
|
+
import { Arr } from 'ts-data-forge';
|
|
192
|
+
|
|
193
|
+
declare const xs: readonly number[];
|
|
194
|
+
const ok = Arr.isFixedLengthTuple(xs, 0);
|
|
195
|
+
`,
|
|
196
|
+
output: dedent`
|
|
197
|
+
import { Arr } from 'ts-data-forge';
|
|
198
|
+
|
|
199
|
+
declare const xs: readonly number[];
|
|
200
|
+
const ok = Arr.isEmpty(xs);
|
|
201
|
+
`,
|
|
202
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'isMaxLengthTuple(xs, 0) becomes isEmpty(xs)',
|
|
206
|
+
code: dedent`
|
|
207
|
+
import { Arr } from 'ts-data-forge';
|
|
208
|
+
|
|
209
|
+
declare const xs: readonly number[];
|
|
210
|
+
const ok = Arr.isMaxLengthTuple(xs, 0);
|
|
211
|
+
`,
|
|
212
|
+
output: dedent`
|
|
213
|
+
import { Arr } from 'ts-data-forge';
|
|
214
|
+
|
|
215
|
+
declare const xs: readonly number[];
|
|
216
|
+
const ok = Arr.isEmpty(xs);
|
|
217
|
+
`,
|
|
218
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'isBoundedLengthTuple(xs, 0, 0) becomes isEmpty(xs)',
|
|
222
|
+
code: dedent`
|
|
223
|
+
import { Arr } from 'ts-data-forge';
|
|
224
|
+
|
|
225
|
+
declare const xs: readonly number[];
|
|
226
|
+
const ok = Arr.isBoundedLengthTuple(xs, 0, 0);
|
|
227
|
+
`,
|
|
228
|
+
output: dedent`
|
|
229
|
+
import { Arr } from 'ts-data-forge';
|
|
230
|
+
|
|
231
|
+
declare const xs: readonly number[];
|
|
232
|
+
const ok = Arr.isEmpty(xs);
|
|
233
|
+
`,
|
|
234
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'isMinLengthArray(xs, 1) becomes isNonEmpty(xs)',
|
|
238
|
+
code: dedent`
|
|
239
|
+
import { Arr } from 'ts-data-forge';
|
|
240
|
+
|
|
241
|
+
declare const xs: readonly number[];
|
|
242
|
+
const ok = Arr.isMinLengthArray(xs, 1);
|
|
243
|
+
`,
|
|
244
|
+
output: dedent`
|
|
245
|
+
import { Arr } from 'ts-data-forge';
|
|
246
|
+
|
|
247
|
+
declare const xs: readonly number[];
|
|
248
|
+
const ok = Arr.isNonEmpty(xs);
|
|
249
|
+
`,
|
|
250
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'keeps a complex array expression intact',
|
|
254
|
+
code: dedent`
|
|
255
|
+
import { Arr } from 'ts-data-forge';
|
|
256
|
+
|
|
257
|
+
declare const obj: Readonly<{ items: readonly number[] }>;
|
|
258
|
+
const ok = Arr.isFixedLengthTuple(obj.items.filter((x) => x > 0), 0);
|
|
259
|
+
`,
|
|
260
|
+
output: dedent`
|
|
261
|
+
import { Arr } from 'ts-data-forge';
|
|
262
|
+
|
|
263
|
+
declare const obj: Readonly<{ items: readonly number[] }>;
|
|
264
|
+
const ok = Arr.isEmpty(obj.items.filter((x) => x > 0));
|
|
265
|
+
`,
|
|
266
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'resolves an aliased Arr import',
|
|
270
|
+
code: dedent`
|
|
271
|
+
import { Arr as A } from 'ts-data-forge';
|
|
272
|
+
|
|
273
|
+
declare const xs: readonly number[];
|
|
274
|
+
const ok = A.isFixedLengthTuple(xs, 0);
|
|
275
|
+
`,
|
|
276
|
+
output: dedent`
|
|
277
|
+
import { Arr as A } from 'ts-data-forge';
|
|
278
|
+
|
|
279
|
+
declare const xs: readonly number[];
|
|
280
|
+
const ok = A.isEmpty(xs);
|
|
281
|
+
`,
|
|
282
|
+
errors: [{ messageId: 'useCanonicalGuard' }],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: 'rewrites several guards in one file',
|
|
286
|
+
code: dedent`
|
|
287
|
+
import { Arr } from 'ts-data-forge';
|
|
288
|
+
|
|
289
|
+
declare const xs: readonly number[];
|
|
290
|
+
const a = Arr.isFixedLengthTuple(xs, 0);
|
|
291
|
+
const b = Arr.isMinLengthArray(xs, 1);
|
|
292
|
+
`,
|
|
293
|
+
output: dedent`
|
|
294
|
+
import { Arr } from 'ts-data-forge';
|
|
295
|
+
|
|
296
|
+
declare const xs: readonly number[];
|
|
297
|
+
const a = Arr.isEmpty(xs);
|
|
298
|
+
const b = Arr.isNonEmpty(xs);
|
|
299
|
+
`,
|
|
300
|
+
errors: [
|
|
301
|
+
{ messageId: 'useCanonicalGuard' },
|
|
302
|
+
{ messageId: 'useCanonicalGuard' },
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
});
|
package/src/rules/rules.mts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { type ESLintPlugin } from '../types.mjs';
|
|
2
2
|
import { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';
|
|
3
3
|
import { preferArrIsArray } from './prefer-arr-is-array.mjs';
|
|
4
|
-
import { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';
|
|
5
|
-
import { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';
|
|
6
|
-
import { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';
|
|
7
|
-
import { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';
|
|
8
|
-
import { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';
|
|
9
4
|
import { preferArrSum } from './prefer-arr-sum.mjs';
|
|
10
5
|
import { preferAsInt } from './prefer-as-int.mjs';
|
|
11
6
|
import { preferCanonicalArraySlicing } from './prefer-canonical-array-slicing.mjs';
|
|
7
|
+
import { preferCanonicalLengthGuard } from './prefer-canonical-length-guard.mjs';
|
|
12
8
|
import { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';
|
|
13
9
|
import { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';
|
|
14
10
|
import { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';
|
|
@@ -18,12 +14,8 @@ import { preferRangeForLoop } from './prefer-range-for-loop.mjs';
|
|
|
18
14
|
|
|
19
15
|
export const tsDataForgeRules = {
|
|
20
16
|
'prefer-canonical-array-slicing': preferCanonicalArraySlicing,
|
|
21
|
-
'prefer-
|
|
22
|
-
'prefer-arr-is-max-length-array': preferArrIsMaxLengthArray,
|
|
23
|
-
'prefer-arr-is-bounded-length-array': preferArrIsBoundedLengthArray,
|
|
24
|
-
'prefer-arr-is-fixed-length-array': preferArrIsFixedLengthArray,
|
|
17
|
+
'prefer-canonical-length-guard': preferCanonicalLengthGuard,
|
|
25
18
|
'prefer-arr-is-array': preferArrIsArray,
|
|
26
|
-
'prefer-arr-is-non-empty': preferArrIsNonEmpty,
|
|
27
19
|
'prefer-arr-sum': preferArrSum,
|
|
28
20
|
'prefer-as-int': preferAsInt,
|
|
29
21
|
'prefer-is-non-null-object': preferIsNonNullObject,
|
package/src/types.mts
CHANGED