eslint-plugin-ts-data-forge 0.10.5 → 0.10.7

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 (107) hide show
  1. package/README.md +3 -3
  2. package/dist/plugin.d.mts +2 -2
  3. package/dist/plugin.d.mts.map +1 -1
  4. package/dist/plugin.mjs +1 -0
  5. package/dist/plugin.mjs.map +1 -1
  6. package/dist/rule-types.d.mts +5 -5
  7. package/dist/rules/ast-utils.d.mts +8 -9
  8. package/dist/rules/ast-utils.d.mts.map +1 -1
  9. package/dist/rules/ast-utils.mjs +3 -4
  10. package/dist/rules/ast-utils.mjs.map +1 -1
  11. package/dist/rules/import-binding.d.mts +38 -0
  12. package/dist/rules/import-binding.d.mts.map +1 -0
  13. package/dist/rules/import-binding.mjs +53 -0
  14. package/dist/rules/import-binding.mjs.map +1 -0
  15. package/dist/rules/mutable-result.mjs +37 -37
  16. package/dist/rules/mutable-result.mjs.map +1 -1
  17. package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
  18. package/dist/rules/no-unnecessary-type-guard.mjs +146 -143
  19. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
  20. package/dist/rules/prefer-arr-generate.d.mts +2 -2
  21. package/dist/rules/prefer-arr-generate.d.mts.map +1 -1
  22. package/dist/rules/prefer-arr-generate.mjs +146 -136
  23. package/dist/rules/prefer-arr-generate.mjs.map +1 -1
  24. package/dist/rules/prefer-arr-is-bounded-length-array.d.mts.map +1 -1
  25. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +14 -16
  26. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  27. package/dist/rules/prefer-arr-is-fixed-length-array.d.mts.map +1 -1
  28. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +15 -17
  29. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  30. package/dist/rules/prefer-arr-is-max-length-array.d.mts.map +1 -1
  31. package/dist/rules/prefer-arr-is-max-length-array.mjs +14 -16
  32. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  33. package/dist/rules/prefer-arr-is-min-length-array.d.mts.map +1 -1
  34. package/dist/rules/prefer-arr-is-min-length-array.mjs +14 -16
  35. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  36. package/dist/rules/prefer-arr-is-non-empty.d.mts.map +1 -1
  37. package/dist/rules/prefer-arr-is-non-empty.mjs +14 -16
  38. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
  39. package/dist/rules/prefer-arr-scan.d.mts.map +1 -1
  40. package/dist/rules/prefer-arr-scan.mjs +9 -18
  41. package/dist/rules/prefer-arr-scan.mjs.map +1 -1
  42. package/dist/rules/prefer-arr-sum.d.mts.map +1 -1
  43. package/dist/rules/prefer-arr-sum.mjs +16 -21
  44. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  45. package/dist/rules/prefer-arr-uniq.d.mts.map +1 -1
  46. package/dist/rules/prefer-arr-uniq.mjs +38 -43
  47. package/dist/rules/prefer-arr-uniq.mjs.map +1 -1
  48. package/dist/rules/prefer-as-int.d.mts.map +1 -1
  49. package/dist/rules/prefer-as-int.mjs +19 -26
  50. package/dist/rules/prefer-as-int.mjs.map +1 -1
  51. package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -1
  52. package/dist/rules/prefer-canonical-array-slicing.mjs +49 -16
  53. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  54. package/dist/rules/prefer-canonical-length-cast.mjs +15 -15
  55. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
  56. package/dist/rules/prefer-num-safe-parse-float.d.mts +1 -1
  57. package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -1
  58. package/dist/rules/prefer-num-safe-parse-int.d.mts +1 -1
  59. package/dist/rules/prefer-num-safe-parse-int.d.mts.map +1 -1
  60. package/dist/rules/prefer-obj-over-entries-round-trip.d.mts.map +1 -1
  61. package/dist/rules/prefer-obj-over-entries-round-trip.mjs +100 -99
  62. package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
  63. package/dist/rules/prefer-pipe-map-nullable.d.mts +2 -2
  64. package/dist/rules/prefer-pipe-map-nullable.d.mts.map +1 -1
  65. package/dist/rules/prefer-pipe-map-nullable.mjs +2 -4
  66. package/dist/rules/prefer-pipe-map-nullable.mjs.map +1 -1
  67. package/dist/rules/prefer-range-for-loop.d.mts.map +1 -1
  68. package/dist/rules/prefer-range-for-loop.mjs +18 -24
  69. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  70. package/dist/rules/rules.d.mts +2 -2
  71. package/package.json +6 -6
  72. package/src/plugin.mts +4 -3
  73. package/src/rule-types.mts +5 -5
  74. package/src/rules/ast-utils.mts +14 -15
  75. package/src/rules/import-binding.mts +94 -0
  76. package/src/rules/mutable-result.mts +56 -56
  77. package/src/rules/no-unnecessary-type-guard.mts +203 -207
  78. package/src/rules/no-unnecessary-type-guard.test.mts +62 -2
  79. package/src/rules/prefer-arr-generate.mts +222 -214
  80. package/src/rules/prefer-arr-generate.test.mts +490 -133
  81. package/src/rules/prefer-arr-is-bounded-length-array.mts +22 -33
  82. package/src/rules/prefer-arr-is-fixed-length-array.mts +26 -38
  83. package/src/rules/prefer-arr-is-fixed-length-array.test.mts +16 -7
  84. package/src/rules/prefer-arr-is-max-length-array.mts +23 -36
  85. package/src/rules/prefer-arr-is-max-length-array.test.mts +16 -7
  86. package/src/rules/prefer-arr-is-min-length-array.mts +23 -36
  87. package/src/rules/prefer-arr-is-min-length-array.test.mts +16 -7
  88. package/src/rules/prefer-arr-is-non-empty.mts +17 -34
  89. package/src/rules/prefer-arr-is-non-empty.test.mts +110 -0
  90. package/src/rules/prefer-arr-scan.mts +17 -42
  91. package/src/rules/prefer-arr-scan.test.mts +55 -0
  92. package/src/rules/prefer-arr-sum.mts +22 -36
  93. package/src/rules/prefer-arr-uniq.mts +51 -62
  94. package/src/rules/prefer-arr-uniq.test.mts +46 -20
  95. package/src/rules/prefer-as-int.mts +27 -52
  96. package/src/rules/prefer-as-int.test.mts +79 -29
  97. package/src/rules/prefer-canonical-array-slicing.mts +101 -40
  98. package/src/rules/prefer-canonical-array-slicing.test.mts +133 -34
  99. package/src/rules/prefer-canonical-length-cast.mts +29 -29
  100. package/src/rules/prefer-canonical-length-guard.mts +2 -2
  101. package/src/rules/prefer-num-safe-parse-float.test.mts +255 -94
  102. package/src/rules/prefer-num-safe-parse-int.test.mts +212 -76
  103. package/src/rules/prefer-obj-over-entries-round-trip.mts +152 -170
  104. package/src/rules/prefer-obj-over-entries-round-trip.test.mts +48 -6
  105. package/src/rules/prefer-pipe-map-nullable.mts +17 -19
  106. package/src/rules/prefer-range-for-loop.mts +37 -61
  107. package/src/rules/prefer-range-for-loop.test.mts +2 -2
@@ -572,12 +572,20 @@ describe('prefer-canonical-array-slicing', () => {
572
572
  const ys = xs.slice(1);
573
573
  const zs = xs.slice(0, -1);
574
574
  `,
575
- output: dedent`
576
- import { Arr } from 'ts-data-forge';
577
- declare const xs: readonly number[];
578
- const ys = Arr.tail(xs);
579
- const zs = Arr.butLast(xs);
580
- `,
575
+ output: [
576
+ dedent`
577
+ import { Arr } from 'ts-data-forge';
578
+ declare const xs: readonly number[];
579
+ const ys = Arr.tail(xs);
580
+ const zs = xs.slice(0, -1);
581
+ `,
582
+ dedent`
583
+ import { Arr } from 'ts-data-forge';
584
+ declare const xs: readonly number[];
585
+ const ys = Arr.tail(xs);
586
+ const zs = Arr.butLast(xs);
587
+ `,
588
+ ],
581
589
  errors: [
582
590
  { messageId: 'preferCanonicalArraySlicing' },
583
591
  { messageId: 'preferCanonicalArraySlicing' },
@@ -629,13 +637,22 @@ describe('prefer-canonical-array-slicing through type wrappers', () => {
629
637
  const b = xs.slice(0 satisfies number, -1 as number);
630
638
  const c = xs.toSpliced(2 satisfies number);
631
639
  `,
632
- output: dedent`
633
- import { Arr } from 'ts-data-forge';
634
- declare const xs: readonly number[];
635
- const a = Arr.tail(xs);
636
- const b = Arr.butLast(xs);
637
- const c = Arr.take(xs, 2);
638
- `,
640
+ output: [
641
+ dedent`
642
+ import { Arr } from 'ts-data-forge';
643
+ declare const xs: readonly number[];
644
+ const a = Arr.tail(xs);
645
+ const b = xs.slice(0 satisfies number, -1 as number);
646
+ const c = xs.toSpliced(2 satisfies number);
647
+ `,
648
+ dedent`
649
+ import { Arr } from 'ts-data-forge';
650
+ declare const xs: readonly number[];
651
+ const a = Arr.tail(xs);
652
+ const b = Arr.butLast(xs);
653
+ const c = Arr.take(xs, 2);
654
+ `,
655
+ ],
639
656
  errors: [
640
657
  { messageId: 'preferCanonicalArraySlicing' },
641
658
  { messageId: 'preferCanonicalArraySlicing' },
@@ -663,13 +680,22 @@ describe('prefer-canonical-array-slicing through type wrappers', () => {
663
680
  const ys = (xs satisfies readonly number[]).slice(1);
664
681
  const zs = [...xs!, v!];
665
682
  `,
666
- output: dedent`
667
- import { Arr } from 'ts-data-forge';
668
- declare const xs: readonly number[];
669
- declare const v: number | undefined;
670
- const ys = Arr.tail(xs satisfies readonly number[]);
671
- const zs = Arr.toPushed(xs!, v!);
672
- `,
683
+ output: [
684
+ dedent`
685
+ import { Arr } from 'ts-data-forge';
686
+ declare const xs: readonly number[];
687
+ declare const v: number | undefined;
688
+ const ys = Arr.tail(xs satisfies readonly number[]);
689
+ const zs = [...xs!, v!];
690
+ `,
691
+ dedent`
692
+ import { Arr } from 'ts-data-forge';
693
+ declare const xs: readonly number[];
694
+ declare const v: number | undefined;
695
+ const ys = Arr.tail(xs satisfies readonly number[]);
696
+ const zs = Arr.toPushed(xs!, v!);
697
+ `,
698
+ ],
673
699
  errors: [
674
700
  { messageId: 'preferCanonicalArraySlicing' },
675
701
  { messageId: 'preferCanonicalArraySlicing' },
@@ -697,14 +723,24 @@ describe('prefer-canonical-array-slicing through type wrappers', () => {
697
723
  const c = xs.filter((_, i) => i < (xs.length satisfies number) - 2);
698
724
  const d = xs.toSpliced(0, xs!.length - 2);
699
725
  `,
700
- output: dedent`
701
- import { Arr } from 'ts-data-forge';
702
- declare const xs: readonly number[];
703
- const a = Arr.butLast(xs);
704
- const b = Arr.butLast(xs);
705
- const c = Arr.skipLast(xs, 2);
706
- const d = Arr.takeLast(xs, 2);
707
- `,
726
+ output: [
727
+ dedent`
728
+ import { Arr } from 'ts-data-forge';
729
+ declare const xs: readonly number[];
730
+ const a = Arr.butLast(xs);
731
+ const b = xs.slice(0, (xs as readonly number[]).length - 1);
732
+ const c = xs.filter((_, i) => i < (xs.length satisfies number) - 2);
733
+ const d = xs.toSpliced(0, xs!.length - 2);
734
+ `,
735
+ dedent`
736
+ import { Arr } from 'ts-data-forge';
737
+ declare const xs: readonly number[];
738
+ const a = Arr.butLast(xs);
739
+ const b = Arr.butLast(xs);
740
+ const c = Arr.skipLast(xs, 2);
741
+ const d = Arr.takeLast(xs, 2);
742
+ `,
743
+ ],
708
744
  errors: [
709
745
  { messageId: 'preferCanonicalArraySlicing' },
710
746
  { messageId: 'preferCanonicalArraySlicing' },
@@ -719,12 +755,20 @@ describe('prefer-canonical-array-slicing through type wrappers', () => {
719
755
  const a = (xs satisfies readonly number[]).slice(0, xs.length - 1);
720
756
  const b = xs.toSpliced((xs.length as number), 0, 1);
721
757
  `,
722
- output: dedent`
723
- import { Arr } from 'ts-data-forge';
724
- declare const xs: readonly number[];
725
- const a = Arr.butLast(xs satisfies readonly number[]);
726
- const b = Arr.toPushed(xs, 1);
727
- `,
758
+ output: [
759
+ dedent`
760
+ import { Arr } from 'ts-data-forge';
761
+ declare const xs: readonly number[];
762
+ const a = Arr.butLast(xs satisfies readonly number[]);
763
+ const b = xs.toSpliced((xs.length as number), 0, 1);
764
+ `,
765
+ dedent`
766
+ import { Arr } from 'ts-data-forge';
767
+ declare const xs: readonly number[];
768
+ const a = Arr.butLast(xs satisfies readonly number[]);
769
+ const b = Arr.toPushed(xs, 1);
770
+ `,
771
+ ],
728
772
  errors: [
729
773
  { messageId: 'preferCanonicalArraySlicing' },
730
774
  { messageId: 'preferCanonicalArraySlicing' },
@@ -733,3 +777,58 @@ describe('prefer-canonical-array-slicing through type wrappers', () => {
733
777
  ],
734
778
  });
735
779
  });
780
+
781
+ describe('prefer-canonical-array-slicing and the import it adds', () => {
782
+ tester.run('prefer-canonical-array-slicing', preferCanonicalArraySlicing, {
783
+ valid: [],
784
+ invalid: [
785
+ {
786
+ name: 'writes through a namespace import, which needs no edit',
787
+ code: dedent`
788
+ import * as tdf from 'ts-data-forge';
789
+ declare const xs: readonly number[];
790
+ const ys = xs.slice(1);
791
+ `,
792
+ output: dedent`
793
+ import * as tdf from 'ts-data-forge';
794
+ declare const xs: readonly number[];
795
+ const ys = tdf.Arr.tail(xs);
796
+ `,
797
+ errors: [{ messageId: 'preferCanonicalArraySlicing' }],
798
+ },
799
+ {
800
+ name: 'joins an existing named import from the module',
801
+ code: dedent`
802
+ import { asInt } from 'ts-data-forge';
803
+ declare const xs: readonly number[];
804
+ const ys = xs.slice(1);
805
+ `,
806
+ output: dedent`
807
+ import { asInt, Arr } from 'ts-data-forge';
808
+ declare const xs: readonly number[];
809
+ const ys = Arr.tail(xs);
810
+ `,
811
+ errors: [{ messageId: 'preferCanonicalArraySlicing' }],
812
+ },
813
+ {
814
+ name: 'no fix when an import type binds the name',
815
+ code: dedent`
816
+ import type { Arr } from 'ts-data-forge';
817
+ declare const xs: readonly number[];
818
+ const ys = xs.slice(1);
819
+ `,
820
+ output: null,
821
+ errors: [{ messageId: 'preferCanonicalArraySlicing' }],
822
+ },
823
+ {
824
+ name: 'no fix where a local binding takes the name',
825
+ code: dedent`
826
+ declare const xs: readonly number[];
827
+ const f = (Arr: unknown) => xs.slice(1);
828
+ `,
829
+ output: null,
830
+ errors: [{ messageId: 'preferCanonicalArraySlicing' }],
831
+ },
832
+ ],
833
+ });
834
+ });
@@ -218,6 +218,24 @@ const getCastName = (
218
218
  : undefined;
219
219
  };
220
220
 
221
+ /**
222
+ * A non-negative integer literal's value, or `undefined` for anything else.
223
+ * Read through type wrappers: `1 as const` is still the bound `1`.
224
+ */
225
+ const getLengthLiteral = (
226
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
227
+ wrapped: TSESTree.Node,
228
+ ): number | undefined => {
229
+ const node = skipTypeWrappers(wrapped);
230
+
231
+ return node.type === AST_NODE_TYPES.Literal &&
232
+ typeof node.value === 'number' &&
233
+ Number.isSafeInteger(node.value) &&
234
+ node.value >= 0
235
+ ? node.value
236
+ : undefined;
237
+ };
238
+
221
239
  /**
222
240
  * Whether the call is `cast(...bounds, array)` with every bound written as the
223
241
  * exact numeric literal the rewrite requires.
@@ -270,10 +288,10 @@ const matchesBounds = (
270
288
  /**
271
289
  * Removes the length arguments the rewrite drops. Each removal runs from the
272
290
  * first token after the delimiter before the dropped argument (the call's `(`
273
- * or the previous comma) up to the first token after the comma that follows
274
- * it. Parentheses the source put around the dropped argument, the comma,
275
- * whitespace and any comments in between go with it; parentheses around the
276
- * next argument stay. The ranges never overlap.
291
+ * or the previous comma) up to the first token after the comma that follows it.
292
+ * Parentheses the source put around the dropped argument, the comma, whitespace
293
+ * and any comments in between go with it; parentheses around the next argument
294
+ * stay. The ranges never overlap.
277
295
  */
278
296
  const dropBoundsFix = (
279
297
  fixer: TSESLint.RuleFixer,
@@ -285,13 +303,13 @@ const dropBoundsFix = (
285
303
  rewrite: CastRewrite,
286
304
  ): readonly TSESLint.RuleFix[] =>
287
305
  rewrite.bounds.flatMap((_, index) => {
288
- // Read before the `includes` below, not after. The strict standard
289
- // library types `includes` as `searchElement is T` — that is what lets a
290
- // literal union narrow a wider value — but the predicate applies to the
291
- // false branch too, and here `keep` is `readonly number[]` against a
292
- // `number`, so `index` narrows to `never` past the guard and `index + 1`
293
- // stops type-checking. A false `includes` means "not in this array", not
294
- // "not of this type".
306
+ // Read before the `includes` below, not after. The strict standard library
307
+ // types `includes` as `searchElement is T` — that is what lets a literal
308
+ // union narrow a wider value — but the predicate applies to the false
309
+ // branch too, and here `keep` is `readonly number[]` against a `number`, so
310
+ // `index` narrows to `never` past the guard and `index + 1` stops
311
+ // type-checking. A false `includes` means "not in this array", not "not of
312
+ // this type".
295
313
  const dropped = node.arguments[index];
296
314
 
297
315
  const previous = node.arguments[index - 1];
@@ -319,21 +337,3 @@ const dropBoundsFix = (
319
337
  ? []
320
338
  : [fixer.removeRange([start.range[0], end.range[0]])];
321
339
  });
322
-
323
- /**
324
- * A non-negative integer literal's value, or `undefined` for anything else.
325
- * Read through type wrappers: `1 as const` is still the bound `1`.
326
- */
327
- const getLengthLiteral = (
328
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
329
- wrapped: TSESTree.Node,
330
- ): number | undefined => {
331
- const node = skipTypeWrappers(wrapped);
332
-
333
- return node.type === AST_NODE_TYPES.Literal &&
334
- typeof node.value === 'number' &&
335
- Number.isSafeInteger(node.value) &&
336
- node.value >= 0
337
- ? node.value
338
- : undefined;
339
- };
@@ -59,8 +59,8 @@ const MESSAGES = {
59
59
  } as const satisfies ReadonlyRecord<MessageIds, string>;
60
60
 
61
61
  /**
62
- * Length guards whose bound makes them redundant with a named degenerate
63
- * guard, keyed by guard name.
62
+ * Length guards whose bound makes them redundant with a named degenerate guard,
63
+ * keyed by guard name.
64
64
  *
65
65
  * Every entry stays inside its own family rather than crossing the
66
66
  * branded/structural divide: