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
|
@@ -442,3 +442,58 @@ describe('prefer-arr-scan with parenthesized operands', () => {
|
|
|
442
442
|
],
|
|
443
443
|
});
|
|
444
444
|
});
|
|
445
|
+
|
|
446
|
+
describe('prefer-arr-scan and the import it adds', () => {
|
|
447
|
+
tester.run('prefer-arr-scan', preferArrScan, {
|
|
448
|
+
valid: [],
|
|
449
|
+
invalid: [
|
|
450
|
+
{
|
|
451
|
+
name: 'writes the local name of an aliased import',
|
|
452
|
+
code: dedent`
|
|
453
|
+
import { Arr as A } from 'ts-data-forge';
|
|
454
|
+
declare const xs: readonly number[];
|
|
455
|
+
const totals = xs.reduce<readonly number[]>(
|
|
456
|
+
(acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
|
|
457
|
+
[0],
|
|
458
|
+
);
|
|
459
|
+
`,
|
|
460
|
+
output: dedent`
|
|
461
|
+
import { Arr as A } from 'ts-data-forge';
|
|
462
|
+
declare const xs: readonly number[];
|
|
463
|
+
const totals = A.scan(xs, (acc, x) => (acc) + x, 0);
|
|
464
|
+
`,
|
|
465
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'turns a type-only specifier into a value import',
|
|
469
|
+
code: dedent`
|
|
470
|
+
import { type Arr } from 'ts-data-forge';
|
|
471
|
+
declare const xs: readonly number[];
|
|
472
|
+
const totals = xs.reduce<readonly number[]>(
|
|
473
|
+
(acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
|
|
474
|
+
[0],
|
|
475
|
+
);
|
|
476
|
+
`,
|
|
477
|
+
output: dedent`
|
|
478
|
+
import { Arr } from 'ts-data-forge';
|
|
479
|
+
declare const xs: readonly number[];
|
|
480
|
+
const totals = Arr.scan(xs, (acc, x) => (acc) + x, 0);
|
|
481
|
+
`,
|
|
482
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'no fix when an import type binds the name',
|
|
486
|
+
code: dedent`
|
|
487
|
+
import type { Arr } from 'ts-data-forge';
|
|
488
|
+
declare const xs: readonly number[];
|
|
489
|
+
const totals = xs.reduce<readonly number[]>(
|
|
490
|
+
(acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
|
|
491
|
+
[0],
|
|
492
|
+
);
|
|
493
|
+
`,
|
|
494
|
+
output: null,
|
|
495
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
});
|
|
499
|
+
});
|
|
@@ -4,16 +4,13 @@ import {
|
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
6
|
import {
|
|
7
|
-
buildNamedImportFix,
|
|
8
7
|
getArgumentText,
|
|
9
|
-
getImportDeclarations,
|
|
10
|
-
getNamedImports,
|
|
11
8
|
skipTypeWrappers,
|
|
12
9
|
typeWrapperLayers,
|
|
13
10
|
} from 'eslint-rule-forge';
|
|
14
11
|
import { Arr, pipe } from 'ts-data-forge';
|
|
15
12
|
import type { TypeReference } from 'typescript';
|
|
16
|
-
import {
|
|
13
|
+
import { tsDataForgeValueAt } from './import-binding.mjs';
|
|
17
14
|
|
|
18
15
|
type Options = readonly [];
|
|
19
16
|
|
|
@@ -39,13 +36,6 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
39
36
|
create: (context) => {
|
|
40
37
|
const sourceCode = context.sourceCode;
|
|
41
38
|
|
|
42
|
-
const program = sourceCode.ast;
|
|
43
|
-
|
|
44
|
-
const tsDataForgeImports = getImportDeclarations(
|
|
45
|
-
program,
|
|
46
|
-
TS_DATA_FORGE_MODULE,
|
|
47
|
-
);
|
|
48
|
-
|
|
49
39
|
const services = context.sourceCode.parserServices;
|
|
50
40
|
|
|
51
41
|
const mut_nodesToFix: {
|
|
@@ -267,16 +257,16 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
267
257
|
}
|
|
268
258
|
},
|
|
269
259
|
'Program:exit': () => {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
const hasArrImport = namedImports.includes('Arr');
|
|
260
|
+
const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
|
|
273
261
|
|
|
274
|
-
for (const
|
|
262
|
+
for (const nodeInfo of mut_nodesToFix) {
|
|
275
263
|
const arrayText = getArgumentText(
|
|
276
264
|
sourceCode,
|
|
277
265
|
nodeInfo.arrayExpression,
|
|
278
266
|
);
|
|
279
267
|
|
|
268
|
+
const arr = arrAt(nodeInfo.node);
|
|
269
|
+
|
|
280
270
|
context.report({
|
|
281
271
|
node: nodeInfo.node,
|
|
282
272
|
messageId: nodeInfo.messageId,
|
|
@@ -284,24 +274,20 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
284
274
|
arrayName: arrayText,
|
|
285
275
|
mapper: nodeInfo.mapper ?? '',
|
|
286
276
|
},
|
|
287
|
-
fix:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
...importFixes,
|
|
302
|
-
fixer.replaceText(nodeInfo.node, replacement),
|
|
303
|
-
];
|
|
304
|
-
},
|
|
277
|
+
fix:
|
|
278
|
+
arr === undefined
|
|
279
|
+
? null
|
|
280
|
+
: (fixer) => {
|
|
281
|
+
const replacement =
|
|
282
|
+
nodeInfo.messageId === 'useArrSum'
|
|
283
|
+
? `${arr.localName}.sum(${arrayText})`
|
|
284
|
+
: `${arr.localName}.sumBy(${arrayText}, ${nodeInfo.mapper})`;
|
|
285
|
+
|
|
286
|
+
return [
|
|
287
|
+
...arr.edits(fixer),
|
|
288
|
+
fixer.replaceText(nodeInfo.node, replacement),
|
|
289
|
+
];
|
|
290
|
+
},
|
|
305
291
|
});
|
|
306
292
|
}
|
|
307
293
|
},
|
|
@@ -5,16 +5,14 @@ import {
|
|
|
5
5
|
type TSESTree,
|
|
6
6
|
} from '@typescript-eslint/utils';
|
|
7
7
|
import {
|
|
8
|
-
buildNamedImportFix,
|
|
9
8
|
getArgumentText,
|
|
10
|
-
getImportDeclarations,
|
|
11
|
-
getImportedLocalName,
|
|
12
9
|
skipTypeWrappers,
|
|
13
10
|
typeWrapperLayers,
|
|
14
11
|
} from 'eslint-rule-forge';
|
|
15
12
|
import { Arr } from 'ts-data-forge';
|
|
16
13
|
import * as ts from 'typescript';
|
|
17
|
-
import { primitiveTypeFlags
|
|
14
|
+
import { primitiveTypeFlags } from './constants.mjs';
|
|
15
|
+
import { tsDataForgeValueAt } from './import-binding.mjs';
|
|
18
16
|
import { needsMutableResult } from './mutable-result.mjs';
|
|
19
17
|
|
|
20
18
|
type Options = readonly [];
|
|
@@ -51,13 +49,6 @@ export const preferArrUniq: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
51
49
|
create: (context) => {
|
|
52
50
|
const sourceCode = context.sourceCode;
|
|
53
51
|
|
|
54
|
-
const program = sourceCode.ast;
|
|
55
|
-
|
|
56
|
-
const tsDataForgeImports = getImportDeclarations(
|
|
57
|
-
program,
|
|
58
|
-
TS_DATA_FORGE_MODULE,
|
|
59
|
-
);
|
|
60
|
-
|
|
61
52
|
const services = sourceCode.parserServices;
|
|
62
53
|
|
|
63
54
|
const checker = services?.program?.getTypeChecker();
|
|
@@ -159,14 +150,7 @@ export const preferArrUniq: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
159
150
|
check(node, element.argument);
|
|
160
151
|
},
|
|
161
152
|
'Program:exit': () => {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const arrName = arrLocalName ?? 'Arr';
|
|
165
|
-
|
|
166
|
-
// The import is added by the first fix only, so that several fixes do
|
|
167
|
-
// not insert it at the same position. When the first match is not
|
|
168
|
-
// autofixable, the first autofixable one carries it instead.
|
|
169
|
-
const firstAutofixable = mut_matches.find((m) => m.autofixable);
|
|
153
|
+
const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
|
|
170
154
|
|
|
171
155
|
for (const match of mut_matches) {
|
|
172
156
|
const arrayText = sourceCode.getText(match.arrayExpression);
|
|
@@ -176,16 +160,18 @@ export const preferArrUniq: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
176
160
|
match.arrayExpression,
|
|
177
161
|
);
|
|
178
162
|
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
163
|
+
const arr = arrAt(match.node);
|
|
164
|
+
|
|
165
|
+
const fix: TSESLint.ReportFixFunction | null =
|
|
166
|
+
arr === undefined
|
|
167
|
+
? null
|
|
168
|
+
: (fixer) => [
|
|
169
|
+
...arr.edits(fixer),
|
|
170
|
+
fixer.replaceText(
|
|
171
|
+
match.node,
|
|
172
|
+
`${arr.localName}.uniq(${argumentText})`,
|
|
173
|
+
),
|
|
174
|
+
];
|
|
189
175
|
|
|
190
176
|
const data = { arrayName: arrayText } as const;
|
|
191
177
|
|
|
@@ -194,20 +180,23 @@ export const preferArrUniq: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
194
180
|
node: match.node,
|
|
195
181
|
messageId: 'useArrUniq',
|
|
196
182
|
data,
|
|
197
|
-
fix
|
|
183
|
+
fix,
|
|
198
184
|
});
|
|
199
185
|
} else {
|
|
200
186
|
context.report({
|
|
201
187
|
node: match.node,
|
|
202
188
|
messageId: 'useArrUniq',
|
|
203
189
|
data,
|
|
204
|
-
suggest:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
190
|
+
suggest:
|
|
191
|
+
fix === null
|
|
192
|
+
? []
|
|
193
|
+
: [
|
|
194
|
+
{
|
|
195
|
+
messageId: 'suggestArrUniq',
|
|
196
|
+
data,
|
|
197
|
+
fix,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
211
200
|
});
|
|
212
201
|
}
|
|
213
202
|
}
|
|
@@ -257,6 +246,29 @@ const isGlobal = (
|
|
|
257
246
|
return variable === null || variable.defs.length === 0;
|
|
258
247
|
};
|
|
259
248
|
|
|
249
|
+
const resolveConstraint = (
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
251
|
+
checker: ts.TypeChecker,
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
253
|
+
type: ts.Type,
|
|
254
|
+
): ts.Type =>
|
|
255
|
+
(type.flags & ts.TypeFlags.TypeParameter) !== 0
|
|
256
|
+
? (checker.getBaseConstraintOfType(type) ?? type)
|
|
257
|
+
: type;
|
|
258
|
+
|
|
259
|
+
const isPrimitiveType = (
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
261
|
+
checker: ts.TypeChecker,
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
263
|
+
type: ts.Type,
|
|
264
|
+
): boolean => {
|
|
265
|
+
const resolved = resolveConstraint(checker, type);
|
|
266
|
+
|
|
267
|
+
return resolved.isUnion()
|
|
268
|
+
? resolved.types.every((t) => isPrimitiveType(checker, t))
|
|
269
|
+
: (resolved.flags & primitiveTypeFlags) !== 0;
|
|
270
|
+
};
|
|
271
|
+
|
|
260
272
|
/**
|
|
261
273
|
* Whether `type` is an array or tuple (or a union or intersection of them) whose elements are
|
|
262
274
|
* all primitives, i.e. something `Arr.uniq` accepts.
|
|
@@ -291,26 +303,3 @@ const isPrimitiveArrayType = (
|
|
|
291
303
|
// An empty tuple has nothing to deduplicate, but is still accepted.
|
|
292
304
|
return typeArguments.every((t) => isPrimitiveType(checker, t));
|
|
293
305
|
};
|
|
294
|
-
|
|
295
|
-
const isPrimitiveType = (
|
|
296
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
297
|
-
checker: ts.TypeChecker,
|
|
298
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
299
|
-
type: ts.Type,
|
|
300
|
-
): boolean => {
|
|
301
|
-
const resolved = resolveConstraint(checker, type);
|
|
302
|
-
|
|
303
|
-
return resolved.isUnion()
|
|
304
|
-
? resolved.types.every((t) => isPrimitiveType(checker, t))
|
|
305
|
-
: (resolved.flags & primitiveTypeFlags) !== 0;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const resolveConstraint = (
|
|
309
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
310
|
-
checker: ts.TypeChecker,
|
|
311
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
312
|
-
type: ts.Type,
|
|
313
|
-
): ts.Type =>
|
|
314
|
-
(type.flags & ts.TypeFlags.TypeParameter) !== 0
|
|
315
|
-
? (checker.getBaseConstraintOfType(type) ?? type)
|
|
316
|
-
: type;
|
|
@@ -188,12 +188,20 @@ describe('prefer-arr-uniq', () => {
|
|
|
188
188
|
const ys = [...new Set(xs)];
|
|
189
189
|
const zs = Array.from(new Set(xs));
|
|
190
190
|
`,
|
|
191
|
-
output:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
191
|
+
output: [
|
|
192
|
+
dedent`
|
|
193
|
+
import { Arr } from 'ts-data-forge';
|
|
194
|
+
const xs = ['a', 'b'];
|
|
195
|
+
const ys = Arr.uniq(xs);
|
|
196
|
+
const zs = Array.from(new Set(xs));
|
|
197
|
+
`,
|
|
198
|
+
dedent`
|
|
199
|
+
import { Arr } from 'ts-data-forge';
|
|
200
|
+
const xs = ['a', 'b'];
|
|
201
|
+
const ys = Arr.uniq(xs);
|
|
202
|
+
const zs = Arr.uniq(xs);
|
|
203
|
+
`,
|
|
204
|
+
],
|
|
197
205
|
errors: [{ messageId: 'useArrUniq' }, { messageId: 'useArrUniq' }],
|
|
198
206
|
},
|
|
199
207
|
{
|
|
@@ -205,14 +213,24 @@ describe('prefer-arr-uniq', () => {
|
|
|
205
213
|
const b = [...new Set(u)];
|
|
206
214
|
const c = Array.from(new Set([1, 2, 1]));
|
|
207
215
|
`,
|
|
208
|
-
output:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
output: [
|
|
217
|
+
dedent`
|
|
218
|
+
import { Arr } from 'ts-data-forge';
|
|
219
|
+
const t = [1, 'a', 1] as const;
|
|
220
|
+
const u: readonly ('x' | 'y' | null | undefined | boolean | bigint)[] = [];
|
|
221
|
+
const a = Arr.uniq(t);
|
|
222
|
+
const b = [...new Set(u)];
|
|
223
|
+
const c = Array.from(new Set([1, 2, 1]));
|
|
224
|
+
`,
|
|
225
|
+
dedent`
|
|
226
|
+
import { Arr } from 'ts-data-forge';
|
|
227
|
+
const t = [1, 'a', 1] as const;
|
|
228
|
+
const u: readonly ('x' | 'y' | null | undefined | boolean | bigint)[] = [];
|
|
229
|
+
const a = Arr.uniq(t);
|
|
230
|
+
const b = Arr.uniq(u);
|
|
231
|
+
const c = Arr.uniq([1, 2, 1]);
|
|
232
|
+
`,
|
|
233
|
+
],
|
|
216
234
|
errors: [
|
|
217
235
|
{ messageId: 'useArrUniq' },
|
|
218
236
|
{ messageId: 'useArrUniq' },
|
|
@@ -465,12 +483,20 @@ describe('prefer-arr-uniq through type wrappers', () => {
|
|
|
465
483
|
const a = Array.from(new Set(xs) as ReadonlySet<string>);
|
|
466
484
|
const b = [...(new Set(xs) satisfies ReadonlySet<string>)];
|
|
467
485
|
`,
|
|
468
|
-
output:
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
486
|
+
output: [
|
|
487
|
+
dedent`
|
|
488
|
+
import { Arr } from 'ts-data-forge';
|
|
489
|
+
declare const xs: readonly string[];
|
|
490
|
+
const a = Arr.uniq(xs);
|
|
491
|
+
const b = [...(new Set(xs) satisfies ReadonlySet<string>)];
|
|
492
|
+
`,
|
|
493
|
+
dedent`
|
|
494
|
+
import { Arr } from 'ts-data-forge';
|
|
495
|
+
declare const xs: readonly string[];
|
|
496
|
+
const a = Arr.uniq(xs);
|
|
497
|
+
const b = Arr.uniq(xs);
|
|
498
|
+
`,
|
|
499
|
+
],
|
|
474
500
|
errors: [{ messageId: 'useArrUniq' }, { messageId: 'useArrUniq' }],
|
|
475
501
|
},
|
|
476
502
|
{
|
|
@@ -3,15 +3,10 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
-
import {
|
|
7
|
-
buildNamedImportFix,
|
|
8
|
-
getArgumentText,
|
|
9
|
-
getImportDeclarations,
|
|
10
|
-
getNamedImports,
|
|
11
|
-
} from 'eslint-rule-forge';
|
|
6
|
+
import { getArgumentText } from 'eslint-rule-forge';
|
|
12
7
|
import type { DeepReadonly } from 'ts-type-forge';
|
|
13
8
|
import { brandedNumberTypeNameToFunctionName } from './branded-number-types.mjs';
|
|
14
|
-
import {
|
|
9
|
+
import { tsDataForgeValueAtByName } from './import-binding.mjs';
|
|
15
10
|
|
|
16
11
|
type Options = readonly [];
|
|
17
12
|
|
|
@@ -36,13 +31,6 @@ export const preferAsInt: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
36
31
|
create: (context) => {
|
|
37
32
|
const sourceCode = context.sourceCode;
|
|
38
33
|
|
|
39
|
-
const program = sourceCode.ast;
|
|
40
|
-
|
|
41
|
-
const tsDataForgeImports = getImportDeclarations(
|
|
42
|
-
program,
|
|
43
|
-
TS_DATA_FORGE_MODULE,
|
|
44
|
-
);
|
|
45
|
-
|
|
46
34
|
const mut_nodesToFix: {
|
|
47
35
|
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion;
|
|
48
36
|
typeName: string;
|
|
@@ -71,44 +59,31 @@ export const preferAsInt: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
71
59
|
TSAsExpression: collect,
|
|
72
60
|
TSTypeAssertion: collect,
|
|
73
61
|
'Program:exit': () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
index === 0 && !hasImport
|
|
100
|
-
? buildNamedImportFix(
|
|
101
|
-
fixer,
|
|
102
|
-
program,
|
|
103
|
-
TS_DATA_FORGE_MODULE,
|
|
104
|
-
[functionName],
|
|
105
|
-
)
|
|
106
|
-
: [];
|
|
107
|
-
|
|
108
|
-
return [...importFixes, fixer.replaceText(node, replacement)];
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
}
|
|
62
|
+
// One binding per cast function, however many casts use it.
|
|
63
|
+
const castAt = tsDataForgeValueAtByName(sourceCode);
|
|
64
|
+
|
|
65
|
+
for (const { node, typeName, functionName } of mut_nodesToFix) {
|
|
66
|
+
const cast = castAt(functionName)(node);
|
|
67
|
+
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'useBrandedNumberCastFunction',
|
|
71
|
+
data: {
|
|
72
|
+
typeName,
|
|
73
|
+
// The name the fix writes: an aliased import's local name.
|
|
74
|
+
functionName: cast?.localName ?? functionName,
|
|
75
|
+
},
|
|
76
|
+
fix:
|
|
77
|
+
cast === undefined
|
|
78
|
+
? null
|
|
79
|
+
: (fixer) => [
|
|
80
|
+
...cast.edits(fixer),
|
|
81
|
+
fixer.replaceText(
|
|
82
|
+
node,
|
|
83
|
+
`${cast.localName}(${getArgumentText(sourceCode, node.expression)})`,
|
|
84
|
+
),
|
|
85
|
+
],
|
|
86
|
+
});
|
|
112
87
|
}
|
|
113
88
|
},
|
|
114
89
|
};
|
|
@@ -60,24 +60,6 @@ describe('prefer-as-int', () => {
|
|
|
60
60
|
`,
|
|
61
61
|
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
62
62
|
},
|
|
63
|
-
{
|
|
64
|
-
name: 'replaces multiple as Int with asInt imports',
|
|
65
|
-
code: dedent`
|
|
66
|
-
const n = 1;
|
|
67
|
-
const result = n as Int;
|
|
68
|
-
const another = (n + 1) as Int;
|
|
69
|
-
`,
|
|
70
|
-
output: dedent`
|
|
71
|
-
import { asInt } from 'ts-data-forge';
|
|
72
|
-
const n = 1;
|
|
73
|
-
const result = asInt(n);
|
|
74
|
-
const another = asInt(n + 1);
|
|
75
|
-
`,
|
|
76
|
-
errors: [
|
|
77
|
-
{ messageId: 'useBrandedNumberCastFunction' },
|
|
78
|
-
{ messageId: 'useBrandedNumberCastFunction' },
|
|
79
|
-
],
|
|
80
|
-
},
|
|
81
63
|
// {
|
|
82
64
|
// name: 'replaces as Int with asInt import with another disabled line',
|
|
83
65
|
// code: dedent`
|
|
@@ -140,7 +122,7 @@ describe('prefer-as-int', () => {
|
|
|
140
122
|
|
|
141
123
|
...([
|
|
142
124
|
{
|
|
143
|
-
name: 'adds
|
|
125
|
+
name: 'adds the name to an existing ts-data-forge import',
|
|
144
126
|
code: dedent`
|
|
145
127
|
import { isNonNullObject } from 'ts-data-forge';
|
|
146
128
|
|
|
@@ -148,8 +130,7 @@ describe('prefer-as-int', () => {
|
|
|
148
130
|
const result = n as Int;
|
|
149
131
|
`,
|
|
150
132
|
output: dedent`
|
|
151
|
-
import { asInt } from 'ts-data-forge';
|
|
152
|
-
import { isNonNullObject } from 'ts-data-forge';
|
|
133
|
+
import { isNonNullObject, asInt } from 'ts-data-forge';
|
|
153
134
|
|
|
154
135
|
const n = 1;
|
|
155
136
|
const result = asInt(n);
|
|
@@ -157,7 +138,7 @@ describe('prefer-as-int', () => {
|
|
|
157
138
|
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
158
139
|
},
|
|
159
140
|
{
|
|
160
|
-
name: 'adds
|
|
141
|
+
name: 'adds the name to an existing ts-data-forge import (with other imports)',
|
|
161
142
|
code: dedent`
|
|
162
143
|
import { noop } from './noop.mjs';
|
|
163
144
|
import { isNonNullObject } from 'ts-data-forge';
|
|
@@ -166,9 +147,8 @@ describe('prefer-as-int', () => {
|
|
|
166
147
|
const result = n as Int;
|
|
167
148
|
`,
|
|
168
149
|
output: dedent`
|
|
169
|
-
import { asInt } from 'ts-data-forge';
|
|
170
150
|
import { noop } from './noop.mjs';
|
|
171
|
-
import { isNonNullObject } from 'ts-data-forge';
|
|
151
|
+
import { isNonNullObject, asInt } from 'ts-data-forge';
|
|
172
152
|
|
|
173
153
|
const n = 1;
|
|
174
154
|
const result = asInt(n);
|
|
@@ -183,6 +163,32 @@ describe('prefer-as-int', () => {
|
|
|
183
163
|
tester.run('prefer-as-int (other types)', preferAsInt, {
|
|
184
164
|
valid: [],
|
|
185
165
|
invalid: [
|
|
166
|
+
{
|
|
167
|
+
name: 'replaces multiple as Int with asInt imports',
|
|
168
|
+
code: dedent`
|
|
169
|
+
const n = 1;
|
|
170
|
+
const result = n as Int;
|
|
171
|
+
const another = (n + 1) as Int;
|
|
172
|
+
`,
|
|
173
|
+
output: [
|
|
174
|
+
dedent`
|
|
175
|
+
import { asInt } from 'ts-data-forge';
|
|
176
|
+
const n = 1;
|
|
177
|
+
const result = asInt(n);
|
|
178
|
+
const another = (n + 1) as Int;
|
|
179
|
+
`,
|
|
180
|
+
dedent`
|
|
181
|
+
import { asInt } from 'ts-data-forge';
|
|
182
|
+
const n = 1;
|
|
183
|
+
const result = asInt(n);
|
|
184
|
+
const another = asInt(n + 1);
|
|
185
|
+
`,
|
|
186
|
+
],
|
|
187
|
+
errors: [
|
|
188
|
+
{ messageId: 'useBrandedNumberCastFunction' },
|
|
189
|
+
{ messageId: 'useBrandedNumberCastFunction' },
|
|
190
|
+
],
|
|
191
|
+
},
|
|
186
192
|
{
|
|
187
193
|
name: 'replaces as Uint32 with asUint32',
|
|
188
194
|
code: dedent`
|
|
@@ -239,17 +245,14 @@ describe('prefer-as-int', () => {
|
|
|
239
245
|
const c = n as SafeInt;
|
|
240
246
|
`,
|
|
241
247
|
dedent`
|
|
242
|
-
import { asUint32 } from 'ts-data-forge';
|
|
243
|
-
import { asInt } from 'ts-data-forge';
|
|
248
|
+
import { asInt, asUint32 } from 'ts-data-forge';
|
|
244
249
|
const n = 1;
|
|
245
250
|
const a = asInt(n);
|
|
246
251
|
const b = asUint32(n);
|
|
247
252
|
const c = n as SafeInt;
|
|
248
253
|
`,
|
|
249
254
|
dedent`
|
|
250
|
-
import { asSafeInt } from 'ts-data-forge';
|
|
251
|
-
import { asUint32 } from 'ts-data-forge';
|
|
252
|
-
import { asInt } from 'ts-data-forge';
|
|
255
|
+
import { asInt, asUint32, asSafeInt } from 'ts-data-forge';
|
|
253
256
|
const n = 1;
|
|
254
257
|
const a = asInt(n);
|
|
255
258
|
const b = asUint32(n);
|
|
@@ -316,3 +319,50 @@ describe('prefer-as-int with parenthesized operands', () => {
|
|
|
316
319
|
],
|
|
317
320
|
});
|
|
318
321
|
}, 20000);
|
|
322
|
+
|
|
323
|
+
describe('prefer-as-int and the import it adds', () => {
|
|
324
|
+
tester.run('prefer-as-int', preferAsInt, {
|
|
325
|
+
valid: [],
|
|
326
|
+
invalid: [
|
|
327
|
+
{
|
|
328
|
+
name: 'writes the local name of an aliased import',
|
|
329
|
+
code: dedent`
|
|
330
|
+
import { asInt as toInt } from 'ts-data-forge';
|
|
331
|
+
const n = 1;
|
|
332
|
+
const result = n as Int;
|
|
333
|
+
`,
|
|
334
|
+
output: dedent`
|
|
335
|
+
import { asInt as toInt } from 'ts-data-forge';
|
|
336
|
+
const n = 1;
|
|
337
|
+
const result = toInt(n);
|
|
338
|
+
`,
|
|
339
|
+
errors: [
|
|
340
|
+
{
|
|
341
|
+
messageId: 'useBrandedNumberCastFunction',
|
|
342
|
+
data: { typeName: 'Int', functionName: 'toInt' },
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'no fix when an import type binds the name',
|
|
348
|
+
code: dedent`
|
|
349
|
+
import type { asInt } from 'ts-data-forge';
|
|
350
|
+
const n = 1;
|
|
351
|
+
const result = n as Int;
|
|
352
|
+
`,
|
|
353
|
+
output: null,
|
|
354
|
+
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'no fix where a local binding takes the name',
|
|
358
|
+
code: dedent`
|
|
359
|
+
const asInt = (x: number): number => x;
|
|
360
|
+
const n = 1;
|
|
361
|
+
const result = n as Int;
|
|
362
|
+
`,
|
|
363
|
+
output: null,
|
|
364
|
+
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
}, 20000);
|