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
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
6
|
import { Arr } from 'ts-data-forge';
|
|
7
7
|
import * as ts from 'typescript';
|
|
8
|
+
import { skipTypeWrappers, typeWrapperLayers } from './ast-utils.mjs';
|
|
8
9
|
import {
|
|
9
10
|
buildImportFixes,
|
|
10
11
|
getImportedLocalName,
|
|
@@ -79,18 +80,29 @@ export const preferArrUniq: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
79
80
|
): void => {
|
|
80
81
|
const arrayExpression = getSetSourceArray(setNode);
|
|
81
82
|
|
|
82
|
-
if (arrayExpression === undefined)
|
|
83
|
+
if (arrayExpression === undefined) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
83
86
|
|
|
84
|
-
if (!isGlobal(sourceCode, node, 'Set'))
|
|
87
|
+
if (!isGlobal(sourceCode, node, 'Set')) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
// At every layer of type wrappers (see typeWrapperLayers).
|
|
92
|
+
const isPrimitiveArray = typeWrapperLayers(arrayExpression).every(
|
|
93
|
+
(layer) => {
|
|
94
|
+
const tsNode = getTsNode(layer);
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
return (
|
|
97
|
+
tsNode !== undefined &&
|
|
98
|
+
isPrimitiveArrayType(checker, checker.getTypeAtLocation(tsNode))
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
);
|
|
89
102
|
|
|
90
|
-
if (
|
|
91
|
-
!isPrimitiveArrayType(checker, checker.getTypeAtLocation(arrayTsNode))
|
|
92
|
-
)
|
|
103
|
+
if (!isPrimitiveArray) {
|
|
93
104
|
return;
|
|
105
|
+
}
|
|
94
106
|
|
|
95
107
|
mut_matches.push({
|
|
96
108
|
node,
|
|
@@ -117,17 +129,23 @@ export const preferArrUniq: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
117
129
|
return;
|
|
118
130
|
}
|
|
119
131
|
|
|
120
|
-
if (!isGlobal(sourceCode, node, 'Array'))
|
|
132
|
+
if (!isGlobal(sourceCode, node, 'Array')) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
121
135
|
|
|
122
136
|
check(node, node.arguments[0]);
|
|
123
137
|
},
|
|
124
138
|
ArrayExpression: (node) => {
|
|
125
139
|
// [...new Set(xs)]
|
|
126
|
-
if (!Arr.isFixedLengthTuple(1, node.elements))
|
|
140
|
+
if (!Arr.isFixedLengthTuple(1, node.elements)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
127
143
|
|
|
128
144
|
const element = node.elements[0];
|
|
129
145
|
|
|
130
|
-
if (element?.type !== AST_NODE_TYPES.SpreadElement)
|
|
146
|
+
if (element?.type !== AST_NODE_TYPES.SpreadElement) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
131
149
|
|
|
132
150
|
check(node, element.argument);
|
|
133
151
|
},
|
|
@@ -208,8 +226,11 @@ const mutatingMethods: ReadonlySet<string> = new Set([
|
|
|
208
226
|
*/
|
|
209
227
|
const getSetSourceArray = (
|
|
210
228
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
211
|
-
|
|
229
|
+
wrapped: TSESTree.Node,
|
|
212
230
|
): TSESTree.Expression | undefined => {
|
|
231
|
+
// `new Set(xs) as ReadonlySet<string>` is still `new Set(xs)`.
|
|
232
|
+
const node = skipTypeWrappers(wrapped);
|
|
233
|
+
|
|
213
234
|
if (
|
|
214
235
|
node.type !== AST_NODE_TYPES.NewExpression ||
|
|
215
236
|
node.callee.type !== AST_NODE_TYPES.Identifier ||
|
|
@@ -246,11 +267,10 @@ const findVariable = (
|
|
|
246
267
|
): TSESLint.Scope.Variable | undefined => {
|
|
247
268
|
const variable = scope.set.get(name);
|
|
248
269
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return findVariable(scope.upper, name);
|
|
270
|
+
return (
|
|
271
|
+
variable ??
|
|
272
|
+
(scope.upper === null ? undefined : findVariable(scope.upper, name))
|
|
273
|
+
);
|
|
254
274
|
};
|
|
255
275
|
|
|
256
276
|
/**
|
|
@@ -296,11 +316,9 @@ const isPrimitiveType = (
|
|
|
296
316
|
): boolean => {
|
|
297
317
|
const resolved = resolveConstraint(checker, type);
|
|
298
318
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return (resolved.flags & primitiveTypeFlags) !== 0;
|
|
319
|
+
return resolved.isUnion()
|
|
320
|
+
? resolved.types.every((t) => isPrimitiveType(checker, t))
|
|
321
|
+
: (resolved.flags & primitiveTypeFlags) !== 0;
|
|
304
322
|
};
|
|
305
323
|
|
|
306
324
|
const primitiveTypeFlags =
|
|
@@ -338,13 +356,17 @@ const needsMutableResult = (
|
|
|
338
356
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
339
357
|
node: TSESTree.Node,
|
|
340
358
|
): boolean => {
|
|
341
|
-
|
|
359
|
+
const outer = outermostTypePreservingWrapper(node);
|
|
342
360
|
|
|
343
|
-
|
|
361
|
+
if (isUsedAsMutable(checker, getTsNode, outer)) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const parent = outer.parent;
|
|
344
366
|
|
|
345
367
|
if (
|
|
346
368
|
parent?.type !== AST_NODE_TYPES.VariableDeclarator ||
|
|
347
|
-
parent.init !==
|
|
369
|
+
parent.init !== outer ||
|
|
348
370
|
parent.id.type !== AST_NODE_TYPES.Identifier ||
|
|
349
371
|
parent.id.typeAnnotation !== undefined
|
|
350
372
|
) {
|
|
@@ -357,11 +379,35 @@ const needsMutableResult = (
|
|
|
357
379
|
variable?.references.some(
|
|
358
380
|
(reference) =>
|
|
359
381
|
reference.init !== true &&
|
|
360
|
-
isUsedAsMutable(
|
|
382
|
+
isUsedAsMutable(
|
|
383
|
+
checker,
|
|
384
|
+
getTsNode,
|
|
385
|
+
outermostTypePreservingWrapper(reference.identifier),
|
|
386
|
+
),
|
|
361
387
|
) ?? false
|
|
362
388
|
);
|
|
363
389
|
};
|
|
364
390
|
|
|
391
|
+
/**
|
|
392
|
+
* `node`, or the outermost `!` / `satisfies` around it. Neither changes the
|
|
393
|
+
* type the value is used at, so `Array.from(new Set(xs))!.push(4)` and
|
|
394
|
+
* `ys!.push(4)` still need a mutable array. An `as` does change it, and a
|
|
395
|
+
* readonly array may be cast to a mutable one, so the walk stops there.
|
|
396
|
+
*/
|
|
397
|
+
const outermostTypePreservingWrapper = (
|
|
398
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
399
|
+
node: TSESTree.Node,
|
|
400
|
+
): TSESTree.Node => {
|
|
401
|
+
const { parent } = node;
|
|
402
|
+
|
|
403
|
+
return parent !== undefined &&
|
|
404
|
+
(parent.type === AST_NODE_TYPES.TSNonNullExpression ||
|
|
405
|
+
parent.type === AST_NODE_TYPES.TSSatisfiesExpression) &&
|
|
406
|
+
parent.expression === node
|
|
407
|
+
? outermostTypePreservingWrapper(parent)
|
|
408
|
+
: node;
|
|
409
|
+
};
|
|
410
|
+
|
|
365
411
|
const isUsedAsMutable = (
|
|
366
412
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
367
413
|
checker: ts.TypeChecker,
|
|
@@ -401,9 +447,13 @@ const isUsedAsMutable = (
|
|
|
401
447
|
// whose declared type is a mutable array.
|
|
402
448
|
const tsNode = getTsNode(node);
|
|
403
449
|
|
|
404
|
-
if (tsNode === undefined)
|
|
450
|
+
if (tsNode === undefined) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
405
453
|
|
|
406
|
-
if (!ts.isExpression(tsNode))
|
|
454
|
+
if (!ts.isExpression(tsNode)) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
407
457
|
|
|
408
458
|
const expectedType =
|
|
409
459
|
getDeclaredParameterType(checker, tsNode) ??
|
|
@@ -436,7 +486,9 @@ const getDeclaredParameterType = (
|
|
|
436
486
|
|
|
437
487
|
const index = call.arguments?.indexOf(argument) ?? -1;
|
|
438
488
|
|
|
439
|
-
if (index === -1)
|
|
489
|
+
if (index === -1) {
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
440
492
|
|
|
441
493
|
const declaration = checker.getResolvedSignature(call)?.getDeclaration();
|
|
442
494
|
|
|
@@ -444,11 +496,9 @@ const getDeclaredParameterType = (
|
|
|
444
496
|
|
|
445
497
|
// A rest parameter's declared type is the array of arguments, not the type of
|
|
446
498
|
// one; the contextual type is the better answer there.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return checker.getTypeAtLocation(parameter);
|
|
499
|
+
return parameter === undefined || parameter.dotDotDotToken !== undefined
|
|
500
|
+
? undefined
|
|
501
|
+
: checker.getTypeAtLocation(parameter);
|
|
452
502
|
};
|
|
453
503
|
|
|
454
504
|
/**
|
|
@@ -453,3 +453,74 @@ describe('prefer-arr-uniq', () => {
|
|
|
453
453
|
],
|
|
454
454
|
});
|
|
455
455
|
});
|
|
456
|
+
|
|
457
|
+
describe('prefer-arr-uniq through type wrappers', () => {
|
|
458
|
+
tester.run('prefer-arr-uniq', preferArrUniq, {
|
|
459
|
+
valid: [],
|
|
460
|
+
invalid: [
|
|
461
|
+
{
|
|
462
|
+
name: 'a Set wrapped in `as` or `satisfies`',
|
|
463
|
+
code: dedent`
|
|
464
|
+
declare const xs: readonly string[];
|
|
465
|
+
const a = Array.from(new Set(xs) as ReadonlySet<string>);
|
|
466
|
+
const b = [...(new Set(xs) satisfies ReadonlySet<string>)];
|
|
467
|
+
`,
|
|
468
|
+
output: dedent`
|
|
469
|
+
import { Arr } from 'ts-data-forge';
|
|
470
|
+
declare const xs: readonly string[];
|
|
471
|
+
const a = Arr.uniq(xs);
|
|
472
|
+
const b = Arr.uniq(xs);
|
|
473
|
+
`,
|
|
474
|
+
errors: [{ messageId: 'useArrUniq' }, { messageId: 'useArrUniq' }],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: 'only suggests when the result is mutated through `!`',
|
|
478
|
+
code: dedent`
|
|
479
|
+
const xs = [3, 1, 3];
|
|
480
|
+
Array.from(new Set(xs))!.push(4);
|
|
481
|
+
`,
|
|
482
|
+
output: null,
|
|
483
|
+
errors: [
|
|
484
|
+
{
|
|
485
|
+
messageId: 'useArrUniq',
|
|
486
|
+
suggestions: [
|
|
487
|
+
{
|
|
488
|
+
messageId: 'suggestArrUniq',
|
|
489
|
+
output: dedent`
|
|
490
|
+
import { Arr } from 'ts-data-forge';
|
|
491
|
+
const xs = [3, 1, 3];
|
|
492
|
+
Arr.uniq(xs)!.push(4);
|
|
493
|
+
`,
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: 'only suggests when a variable initialized through `!` is mutated',
|
|
501
|
+
code: dedent`
|
|
502
|
+
const xs = [3, 1, 3];
|
|
503
|
+
const ys = Array.from(new Set(xs))!;
|
|
504
|
+
ys.push(4);
|
|
505
|
+
`,
|
|
506
|
+
output: null,
|
|
507
|
+
errors: [
|
|
508
|
+
{
|
|
509
|
+
messageId: 'useArrUniq',
|
|
510
|
+
suggestions: [
|
|
511
|
+
{
|
|
512
|
+
messageId: 'suggestArrUniq',
|
|
513
|
+
output: dedent`
|
|
514
|
+
import { Arr } from 'ts-data-forge';
|
|
515
|
+
const xs = [3, 1, 3];
|
|
516
|
+
const ys = Arr.uniq(xs)!;
|
|
517
|
+
ys.push(4);
|
|
518
|
+
`,
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
});
|
|
526
|
+
});
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
-
import {
|
|
6
|
+
import type { DeepReadonly } from 'ts-type-forge';
|
|
7
|
+
import { toArgumentText } from './ast-utils.mjs';
|
|
7
8
|
import { brandedNumberTypeNameToFunctionName } from './branded-number-types.mjs';
|
|
8
9
|
import {
|
|
9
10
|
buildImportFixes,
|
|
@@ -38,47 +39,43 @@ export const preferAsInt: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
38
39
|
const tsDataForgeImport = getTsDataForgeImport(program);
|
|
39
40
|
|
|
40
41
|
const mut_nodesToFix: {
|
|
41
|
-
node: TSESTree.TSAsExpression;
|
|
42
|
+
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion;
|
|
42
43
|
typeName: string;
|
|
43
44
|
functionName: string;
|
|
44
45
|
}[] = [];
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
// `x as Int` and `<Int>x` are the same assertion.
|
|
48
|
+
const collect = (
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
50
|
+
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
|
|
51
|
+
): void => {
|
|
52
|
+
const typeInfo = getBrandedNumberTypeInfo(node.typeAnnotation);
|
|
53
|
+
|
|
54
|
+
if (typeInfo === undefined) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
mut_nodesToFix.push({
|
|
59
|
+
node,
|
|
60
|
+
typeName: typeInfo.typeName,
|
|
61
|
+
functionName: typeInfo.functionName,
|
|
62
|
+
});
|
|
63
|
+
};
|
|
51
64
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
functionName: typeInfo.functionName,
|
|
56
|
-
});
|
|
57
|
-
},
|
|
65
|
+
return {
|
|
66
|
+
TSAsExpression: collect,
|
|
67
|
+
TSTypeAssertion: collect,
|
|
58
68
|
'Program:exit': () => {
|
|
59
69
|
const namedImports = getNamedImports(tsDataForgeImport);
|
|
60
70
|
|
|
61
71
|
// Group nodes by function name to handle imports efficiently
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
typeName: string;
|
|
67
|
-
functionName: string;
|
|
68
|
-
}>[]
|
|
69
|
-
>();
|
|
70
|
-
|
|
71
|
-
for (const nodeInfo of mut_nodesToFix) {
|
|
72
|
-
const mut_nodes =
|
|
73
|
-
mut_functionNameToNodes.get(nodeInfo.functionName) ?? [];
|
|
74
|
-
|
|
75
|
-
mut_nodes.push(nodeInfo);
|
|
76
|
-
|
|
77
|
-
mut_functionNameToNodes.set(nodeInfo.functionName, mut_nodes);
|
|
78
|
-
}
|
|
72
|
+
const functionNameToNodes = Map.groupBy(
|
|
73
|
+
mut_nodesToFix,
|
|
74
|
+
(nodeInfo) => nodeInfo.functionName,
|
|
75
|
+
);
|
|
79
76
|
|
|
80
77
|
// Process each group
|
|
81
|
-
for (const [functionName, nodes] of
|
|
78
|
+
for (const [functionName, nodes] of functionNameToNodes) {
|
|
82
79
|
const hasImport = namedImports.includes(functionName);
|
|
83
80
|
|
|
84
81
|
for (const [index, { node, typeName }] of nodes.entries()) {
|
|
@@ -90,7 +87,7 @@ export const preferAsInt: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
90
87
|
functionName,
|
|
91
88
|
},
|
|
92
89
|
fix: (fixer) => {
|
|
93
|
-
const replacement = `${functionName}(${
|
|
90
|
+
const replacement = `${functionName}(${toArgumentText(node.expression, sourceCode)})`;
|
|
94
91
|
|
|
95
92
|
// Add import only for the first node of this function and only if not already imported
|
|
96
93
|
const importFixes =
|
|
@@ -114,18 +111,21 @@ export const preferAsInt: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
114
111
|
const getBrandedNumberTypeInfo = (
|
|
115
112
|
typeAnnotation: DeepReadonly<TSESTree.TypeNode>,
|
|
116
113
|
): Readonly<{ typeName: string; functionName: string }> | undefined => {
|
|
117
|
-
if (typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference)
|
|
114
|
+
if (typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
118
117
|
|
|
119
|
-
if (typeAnnotation.typeName.type !== AST_NODE_TYPES.Identifier)
|
|
118
|
+
if (typeAnnotation.typeName.type !== AST_NODE_TYPES.Identifier) {
|
|
120
119
|
return undefined;
|
|
120
|
+
}
|
|
121
121
|
|
|
122
122
|
const typeName = typeAnnotation.typeName.name;
|
|
123
123
|
|
|
124
|
-
if (!brandedNumberTypeNameToFunctionName.has(typeName))
|
|
124
|
+
if (!brandedNumberTypeNameToFunctionName.has(typeName)) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
125
127
|
|
|
126
128
|
const functionName = brandedNumberTypeNameToFunctionName.get(typeName);
|
|
127
129
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return { typeName, functionName };
|
|
130
|
+
return functionName === undefined ? undefined : { typeName, functionName };
|
|
131
131
|
};
|
|
@@ -265,3 +265,54 @@ describe('prefer-as-int', () => {
|
|
|
265
265
|
],
|
|
266
266
|
});
|
|
267
267
|
}, 20000);
|
|
268
|
+
|
|
269
|
+
describe('prefer-as-int through type wrappers', () => {
|
|
270
|
+
tester.run('prefer-as-int', preferAsInt, {
|
|
271
|
+
valid: [],
|
|
272
|
+
invalid: [
|
|
273
|
+
{
|
|
274
|
+
name: 'an angle-bracket assertion',
|
|
275
|
+
code: dedent`
|
|
276
|
+
import { asInt } from 'ts-data-forge';
|
|
277
|
+
declare const n: number;
|
|
278
|
+
const a = <Int>n;
|
|
279
|
+
`,
|
|
280
|
+
output: dedent`
|
|
281
|
+
import { asInt } from 'ts-data-forge';
|
|
282
|
+
declare const n: number;
|
|
283
|
+
const a = asInt(n);
|
|
284
|
+
`,
|
|
285
|
+
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
});
|
|
289
|
+
}, 20000);
|
|
290
|
+
|
|
291
|
+
describe('prefer-as-int with parenthesized operands', () => {
|
|
292
|
+
tester.run('prefer-as-int', preferAsInt, {
|
|
293
|
+
valid: [],
|
|
294
|
+
invalid: [
|
|
295
|
+
{
|
|
296
|
+
name: 'a sequence operand keeps its parentheses',
|
|
297
|
+
code: dedent`
|
|
298
|
+
import { asInt } from 'ts-data-forge';
|
|
299
|
+
declare const log: () => void;
|
|
300
|
+
declare const n: number;
|
|
301
|
+
const a = (log(), n) as Int;
|
|
302
|
+
const b = <Int>(log(), n);
|
|
303
|
+
`,
|
|
304
|
+
output: dedent`
|
|
305
|
+
import { asInt } from 'ts-data-forge';
|
|
306
|
+
declare const log: () => void;
|
|
307
|
+
declare const n: number;
|
|
308
|
+
const a = asInt((log(), n));
|
|
309
|
+
const b = asInt((log(), n));
|
|
310
|
+
`,
|
|
311
|
+
errors: [
|
|
312
|
+
{ messageId: 'useBrandedNumberCastFunction' },
|
|
313
|
+
{ messageId: 'useBrandedNumberCastFunction' },
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
}, 20000);
|