eslint-plugin-ts-data-forge 0.8.5 → 0.9.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.
Files changed (135) hide show
  1. package/README.md +20 -19
  2. package/dist/plugin.d.mts +2 -0
  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 +16 -1
  7. package/dist/rule-types.d.mts.map +1 -1
  8. package/dist/rule-types.mjs.map +1 -1
  9. package/dist/rules/ast-utils.d.mts +21 -34
  10. package/dist/rules/ast-utils.d.mts.map +1 -1
  11. package/dist/rules/ast-utils.mjs +53 -58
  12. package/dist/rules/ast-utils.mjs.map +1 -1
  13. package/dist/rules/constants.d.mts +2 -0
  14. package/dist/rules/constants.d.mts.map +1 -1
  15. package/dist/rules/constants.mjs +9 -0
  16. package/dist/rules/constants.mjs.map +1 -1
  17. package/dist/rules/mutable-result.d.mts +10 -0
  18. package/dist/rules/mutable-result.d.mts.map +1 -0
  19. package/dist/rules/mutable-result.mjs +145 -0
  20. package/dist/rules/mutable-result.mjs.map +1 -0
  21. package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
  22. package/dist/rules/no-unnecessary-type-guard.mjs +6 -6
  23. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
  24. package/dist/rules/prefer-arr-generate.d.mts +57 -0
  25. package/dist/rules/prefer-arr-generate.d.mts.map +1 -0
  26. package/dist/rules/prefer-arr-generate.mjs +588 -0
  27. package/dist/rules/prefer-arr-generate.mjs.map +1 -0
  28. package/dist/rules/prefer-arr-is-array.d.mts.map +1 -1
  29. package/dist/rules/prefer-arr-is-array.mjs +7 -5
  30. package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
  31. package/dist/rules/prefer-arr-is-bounded-length-array.d.mts.map +1 -1
  32. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +9 -6
  33. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  34. package/dist/rules/prefer-arr-is-fixed-length-array.d.mts.map +1 -1
  35. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +9 -6
  36. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  37. package/dist/rules/prefer-arr-is-max-length-array.d.mts.map +1 -1
  38. package/dist/rules/prefer-arr-is-max-length-array.mjs +9 -6
  39. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  40. package/dist/rules/prefer-arr-is-min-length-array.d.mts.map +1 -1
  41. package/dist/rules/prefer-arr-is-min-length-array.mjs +9 -6
  42. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  43. package/dist/rules/prefer-arr-is-non-empty.d.mts.map +1 -1
  44. package/dist/rules/prefer-arr-is-non-empty.mjs +9 -6
  45. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
  46. package/dist/rules/prefer-arr-scan.d.mts.map +1 -1
  47. package/dist/rules/prefer-arr-scan.mjs +8 -11
  48. package/dist/rules/prefer-arr-scan.mjs.map +1 -1
  49. package/dist/rules/prefer-arr-sum.d.mts.map +1 -1
  50. package/dist/rules/prefer-arr-sum.mjs +8 -6
  51. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  52. package/dist/rules/prefer-arr-uniq.d.mts.map +1 -1
  53. package/dist/rules/prefer-arr-uniq.mjs +12 -166
  54. package/dist/rules/prefer-arr-uniq.mjs.map +1 -1
  55. package/dist/rules/prefer-as-int.d.mts.map +1 -1
  56. package/dist/rules/prefer-as-int.mjs +6 -8
  57. package/dist/rules/prefer-as-int.mjs.map +1 -1
  58. package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -1
  59. package/dist/rules/prefer-canonical-array-slicing.mjs +57 -34
  60. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  61. package/dist/rules/prefer-canonical-length-cast.d.mts.map +1 -1
  62. package/dist/rules/prefer-canonical-length-cast.mjs +3 -3
  63. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
  64. package/dist/rules/prefer-canonical-length-guard.d.mts.map +1 -1
  65. package/dist/rules/prefer-canonical-length-guard.mjs +4 -4
  66. package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
  67. package/dist/rules/prefer-comparison-over-nullish-guard.d.mts.map +1 -1
  68. package/dist/rules/prefer-comparison-over-nullish-guard.mjs +3 -2
  69. package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
  70. package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
  71. package/dist/rules/prefer-is-non-null-object.mjs +113 -43
  72. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  73. package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
  74. package/dist/rules/prefer-is-record-and-has-key.mjs +105 -48
  75. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  76. package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -1
  77. package/dist/rules/prefer-num-safe-parse-float.mjs +6 -6
  78. package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
  79. package/dist/rules/prefer-num-safe-parse-int.d.mts.map +1 -1
  80. package/dist/rules/prefer-num-safe-parse-int.mjs +6 -6
  81. package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
  82. package/dist/rules/prefer-obj-over-entries-round-trip.d.mts.map +1 -1
  83. package/dist/rules/prefer-obj-over-entries-round-trip.mjs +9 -12
  84. package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
  85. package/dist/rules/prefer-range-for-loop.d.mts.map +1 -1
  86. package/dist/rules/prefer-range-for-loop.mjs +104 -23
  87. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  88. package/dist/rules/rules.d.mts +1 -0
  89. package/dist/rules/rules.d.mts.map +1 -1
  90. package/dist/rules/rules.mjs +2 -0
  91. package/dist/rules/rules.mjs.map +1 -1
  92. package/package.json +11 -10
  93. package/src/plugin.mts +1 -0
  94. package/src/rule-types.mts +17 -1
  95. package/src/rules/ast-utils.mts +73 -80
  96. package/src/rules/constants.mts +12 -0
  97. package/src/rules/mutable-result.mts +204 -0
  98. package/src/rules/no-unnecessary-type-guard.mts +14 -10
  99. package/src/rules/prefer-arr-generate.mts +918 -0
  100. package/src/rules/prefer-arr-generate.test.mts +967 -0
  101. package/src/rules/prefer-arr-is-array.mts +14 -7
  102. package/src/rules/prefer-arr-is-array.test.mts +14 -0
  103. package/src/rules/prefer-arr-is-bounded-length-array.mts +16 -10
  104. package/src/rules/prefer-arr-is-bounded-length-array.test.mts +19 -0
  105. package/src/rules/prefer-arr-is-fixed-length-array.mts +17 -11
  106. package/src/rules/prefer-arr-is-max-length-array.mts +17 -11
  107. package/src/rules/prefer-arr-is-max-length-array.test.mts +8 -0
  108. package/src/rules/prefer-arr-is-min-length-array.mts +17 -11
  109. package/src/rules/prefer-arr-is-min-length-array.test.mts +8 -0
  110. package/src/rules/prefer-arr-is-non-empty.mts +16 -13
  111. package/src/rules/prefer-arr-scan.mts +24 -19
  112. package/src/rules/prefer-arr-sum.mts +18 -14
  113. package/src/rules/prefer-arr-uniq.mts +25 -234
  114. package/src/rules/prefer-as-int.mts +19 -12
  115. package/src/rules/prefer-canonical-array-slicing.mts +79 -52
  116. package/src/rules/prefer-canonical-array-slicing.test.mts +55 -0
  117. package/src/rules/prefer-canonical-length-cast.mts +7 -3
  118. package/src/rules/prefer-canonical-length-guard.mts +9 -4
  119. package/src/rules/prefer-comparison-over-nullish-guard.mts +3 -2
  120. package/src/rules/prefer-comparison-over-nullish-guard.test.mts +16 -0
  121. package/src/rules/prefer-is-non-null-object.mts +171 -64
  122. package/src/rules/prefer-is-non-null-object.test.mts +138 -0
  123. package/src/rules/prefer-is-record-and-has-key.mts +159 -74
  124. package/src/rules/prefer-is-record-and-has-key.test.mts +118 -23
  125. package/src/rules/prefer-num-safe-parse-float.mts +15 -10
  126. package/src/rules/prefer-num-safe-parse-int.mts +15 -13
  127. package/src/rules/prefer-obj-over-entries-round-trip.mts +19 -22
  128. package/src/rules/prefer-range-for-loop.mts +157 -31
  129. package/src/rules/prefer-range-for-loop.test.mts +127 -5
  130. package/src/rules/rules.mts +2 -0
  131. package/dist/rules/import-utils.d.mts +0 -30
  132. package/dist/rules/import-utils.d.mts.map +0 -1
  133. package/dist/rules/import-utils.mjs +0 -98
  134. package/dist/rules/import-utils.mjs.map +0 -1
  135. package/src/rules/import-utils.mts +0 -175
@@ -3,12 +3,13 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { skipTypeWrappers } from './ast-utils.mjs';
7
6
  import {
8
- buildImportFixes,
7
+ buildNamedImportFix,
8
+ getImportDeclarations,
9
9
  getNamedImports,
10
- getTsDataForgeImport,
11
- } from './import-utils.mjs';
10
+ skipTypeWrappers,
11
+ } from 'eslint-rule-forge';
12
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
12
13
 
13
14
  type Options = readonly [];
14
15
 
@@ -34,7 +35,10 @@ export const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {
34
35
 
35
36
  const program = sourceCode.ast;
36
37
 
37
- const tsDataForgeImport = getTsDataForgeImport(program);
38
+ const tsDataForgeImports = getImportDeclarations(
39
+ program,
40
+ TS_DATA_FORGE_MODULE,
41
+ );
38
42
 
39
43
  const mut_nodesToFix: TSESTree.CallExpression[] = [];
40
44
 
@@ -48,7 +52,8 @@ export const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {
48
52
  },
49
53
  'Program:exit': () => {
50
54
  // Check if Arr is already imported
51
- const hasArrImport = getNamedImports(tsDataForgeImport).includes('Arr');
55
+ const hasArrImport =
56
+ getNamedImports(tsDataForgeImports).includes('Arr');
52
57
 
53
58
  // Note: We add import only for the first node to avoid conflicts when
54
59
  // multiple fixes try to insert at the same position. This means if the
@@ -70,7 +75,9 @@ export const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {
70
75
  // Add import only for the first node and only if not already imported
71
76
  const importFixes =
72
77
  index === 0 && !hasArrImport
73
- ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
78
+ ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
79
+ 'Arr',
80
+ ])
74
81
  : [];
75
82
 
76
83
  return [...importFixes, fixer.replaceText(callee, 'Arr.isArray')];
@@ -33,6 +33,20 @@ ruleTester.run('prefer-arr-is-array', preferArrIsArray, {
33
33
  `,
34
34
  errors: [{ messageId: 'useArrIsArray' }],
35
35
  },
36
+ {
37
+ name: 'Arr imported by a second ts-data-forge declaration is not imported again',
38
+ code: dedent`
39
+ import type { Optional } from 'ts-data-forge';
40
+ import { Arr } from 'ts-data-forge';
41
+ const result = Array.isArray(value);
42
+ `,
43
+ output: dedent`
44
+ import type { Optional } from 'ts-data-forge';
45
+ import { Arr } from 'ts-data-forge';
46
+ const result = Arr.isArray(value);
47
+ `,
48
+ errors: [{ messageId: 'useArrIsArray' }],
49
+ },
36
50
  {
37
51
  name: 'Replace Array.isArray with Arr.isArray when Arr is already imported',
38
52
  code: dedent`
@@ -1,15 +1,16 @@
1
1
  import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ import {
3
+ buildNamedImportFix,
4
+ getArgumentText,
5
+ getImportDeclarations,
6
+ getNamedImports,
7
+ } from 'eslint-rule-forge';
2
8
  import {
3
9
  isArrayOrTupleExpression,
4
10
  isSameArrayText,
5
11
  parseLengthComparison,
6
- toArgumentText,
7
12
  } from './ast-utils.mjs';
8
- import {
9
- buildImportFixes,
10
- getNamedImports,
11
- getTsDataForgeImport,
12
- } from './import-utils.mjs';
13
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
13
14
 
14
15
  type Options = readonly [];
15
16
 
@@ -38,7 +39,10 @@ export const preferArrIsBoundedLengthArray: TSESLint.RuleModule<
38
39
 
39
40
  const program = sourceCode.ast;
40
41
 
41
- const tsDataForgeImport = getTsDataForgeImport(program);
42
+ const tsDataForgeImports = getImportDeclarations(
43
+ program,
44
+ TS_DATA_FORGE_MODULE,
45
+ );
42
46
 
43
47
  const services = context.sourceCode.parserServices;
44
48
 
@@ -95,7 +99,7 @@ export const preferArrIsBoundedLengthArray: TSESLint.RuleModule<
95
99
  });
96
100
  },
97
101
  'Program:exit': () => {
98
- const namedImports = getNamedImports(tsDataForgeImport);
102
+ const namedImports = getNamedImports(tsDataForgeImports);
99
103
 
100
104
  const hasArrImport = namedImports.includes('Arr');
101
105
 
@@ -103,7 +107,7 @@ export const preferArrIsBoundedLengthArray: TSESLint.RuleModule<
103
107
  index,
104
108
  { node, arrayExpression, minExpression, maxExpression },
105
109
  ] of mut_nodesToFix.entries()) {
106
- const arrayText = toArgumentText(arrayExpression, sourceCode);
110
+ const arrayText = getArgumentText(sourceCode, arrayExpression);
107
111
 
108
112
  const minText = sourceCode.getText(minExpression);
109
113
 
@@ -125,7 +129,9 @@ export const preferArrIsBoundedLengthArray: TSESLint.RuleModule<
125
129
 
126
130
  const importFixes =
127
131
  index === 0 && !hasArrImport
128
- ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
132
+ ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
133
+ 'Arr',
134
+ ])
129
135
  : [];
130
136
 
131
137
  return [...importFixes, fixer.replaceText(node, replacement)];
@@ -188,6 +188,25 @@ describe('prefer-arr-is-bounded-length-array through type wrappers', () => {
188
188
  { messageId: 'useIsBoundedLengthArray' },
189
189
  ],
190
190
  },
191
+ {
192
+ name: 'either comparison wrapped as a whole',
193
+ code: dedent`
194
+ import { Arr } from 'ts-data-forge';
195
+ declare const xs: readonly number[];
196
+ const a = (xs.length >= 1 satisfies boolean) && xs.length <= 3;
197
+ const b = xs.length >= 1 && (xs.length <= 3 as boolean);
198
+ `,
199
+ output: dedent`
200
+ import { Arr } from 'ts-data-forge';
201
+ declare const xs: readonly number[];
202
+ const a = Arr.isBoundedLengthArray(1, 3, xs);
203
+ const b = Arr.isBoundedLengthArray(1, 3, xs);
204
+ `,
205
+ errors: [
206
+ { messageId: 'useIsBoundedLengthArray' },
207
+ { messageId: 'useIsBoundedLengthArray' },
208
+ ],
209
+ },
191
210
  ],
192
211
  },
193
212
  );
@@ -1,16 +1,17 @@
1
1
  import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ import {
3
+ buildNamedImportFix,
4
+ getArgumentText,
5
+ getImportDeclarations,
6
+ getNamedImports,
7
+ skipTypeWrappers,
8
+ } from 'eslint-rule-forge';
2
9
  import {
3
10
  asLengthAccess,
4
11
  isArrayOrTupleExpression,
5
12
  isIntegerLiteralOrConstant,
6
- skipTypeWrappers,
7
- toArgumentText,
8
13
  } from './ast-utils.mjs';
9
- import {
10
- buildImportFixes,
11
- getNamedImports,
12
- getTsDataForgeImport,
13
- } from './import-utils.mjs';
14
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
14
15
 
15
16
  type Options = readonly [];
16
17
 
@@ -39,7 +40,10 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
39
40
 
40
41
  const program = sourceCode.ast;
41
42
 
42
- const tsDataForgeImport = getTsDataForgeImport(program);
43
+ const tsDataForgeImports = getImportDeclarations(
44
+ program,
45
+ TS_DATA_FORGE_MODULE,
46
+ );
43
47
 
44
48
  const services = context.sourceCode.parserServices;
45
49
 
@@ -94,7 +98,7 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
94
98
  });
95
99
  },
96
100
  'Program:exit': () => {
97
- const namedImports = getNamedImports(tsDataForgeImport);
101
+ const namedImports = getNamedImports(tsDataForgeImports);
98
102
 
99
103
  const hasArrImport = namedImports.includes('Arr');
100
104
 
@@ -102,7 +106,7 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
102
106
  index,
103
107
  { node, arrayExpression, lengthExpression, isNegated },
104
108
  ] of mut_nodesToFix.entries()) {
105
- const arrayText = toArgumentText(arrayExpression, sourceCode);
109
+ const arrayText = getArgumentText(sourceCode, arrayExpression);
106
110
 
107
111
  const lengthText = sourceCode.getText(lengthExpression);
108
112
 
@@ -123,7 +127,9 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
123
127
 
124
128
  const importFixes =
125
129
  index === 0 && !hasArrImport
126
- ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
130
+ ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
131
+ 'Arr',
132
+ ])
127
133
  : [];
128
134
 
129
135
  return [...importFixes, fixer.replaceText(node, replacement)];
@@ -1,17 +1,18 @@
1
1
  import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ import {
3
+ buildNamedImportFix,
4
+ getArgumentText,
5
+ getImportDeclarations,
6
+ getNamedImports,
7
+ skipTypeWrappers,
8
+ } from 'eslint-rule-forge';
2
9
  import {
3
10
  asLengthAccess,
4
11
  isArrayOrTupleExpression,
5
12
  isIntegerLiteralOrConstant,
6
13
  isPartOfBoundedLengthCheck,
7
- skipTypeWrappers,
8
- toArgumentText,
9
14
  } from './ast-utils.mjs';
10
- import {
11
- buildImportFixes,
12
- getNamedImports,
13
- getTsDataForgeImport,
14
- } from './import-utils.mjs';
15
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
15
16
 
16
17
  type Options = readonly [];
17
18
 
@@ -40,7 +41,10 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
40
41
 
41
42
  const program = sourceCode.ast;
42
43
 
43
- const tsDataForgeImport = getTsDataForgeImport(program);
44
+ const tsDataForgeImports = getImportDeclarations(
45
+ program,
46
+ TS_DATA_FORGE_MODULE,
47
+ );
44
48
 
45
49
  const services = context.sourceCode.parserServices;
46
50
 
@@ -99,7 +103,7 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
99
103
  });
100
104
  },
101
105
  'Program:exit': () => {
102
- const namedImports = getNamedImports(tsDataForgeImport);
106
+ const namedImports = getNamedImports(tsDataForgeImports);
103
107
 
104
108
  const hasArrImport = namedImports.includes('Arr');
105
109
 
@@ -107,7 +111,7 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
107
111
  index,
108
112
  { node, arrayExpression, lengthExpression },
109
113
  ] of mut_nodesToFix.entries()) {
110
- const arrayText = toArgumentText(arrayExpression, sourceCode);
114
+ const arrayText = getArgumentText(sourceCode, arrayExpression);
111
115
 
112
116
  const lengthText = sourceCode.getText(lengthExpression);
113
117
 
@@ -126,7 +130,9 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
126
130
 
127
131
  const importFixes =
128
132
  index === 0 && !hasArrImport
129
- ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
133
+ ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
134
+ 'Arr',
135
+ ])
130
136
  : [];
131
137
 
132
138
  return [...importFixes, fixer.replaceText(node, replacement)];
@@ -205,6 +205,14 @@ describe('prefer-arr-is-max-length-array through type wrappers', () => {
205
205
  const ok = xs!.length >= 1 && xs.length <= 3;
206
206
  `,
207
207
  },
208
+ {
209
+ name: 'one half of a bounded pair, either half wrapped as a whole',
210
+ code: dedent`
211
+ declare const xs: readonly number[];
212
+ const a = (xs.length >= 1 satisfies boolean) && xs.length <= 3;
213
+ const b = xs.length >= 1 && (xs.length <= 3 as boolean);
214
+ `,
215
+ },
208
216
  ],
209
217
  invalid: [
210
218
  {
@@ -1,17 +1,18 @@
1
1
  import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
+ import {
3
+ buildNamedImportFix,
4
+ getArgumentText,
5
+ getImportDeclarations,
6
+ getNamedImports,
7
+ skipTypeWrappers,
8
+ } from 'eslint-rule-forge';
2
9
  import {
3
10
  asLengthAccess,
4
11
  isArrayOrTupleExpression,
5
12
  isIntegerLiteralOrConstant,
6
13
  isPartOfBoundedLengthCheck,
7
- skipTypeWrappers,
8
- toArgumentText,
9
14
  } from './ast-utils.mjs';
10
- import {
11
- buildImportFixes,
12
- getNamedImports,
13
- getTsDataForgeImport,
14
- } from './import-utils.mjs';
15
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
15
16
 
16
17
  type Options = readonly [];
17
18
 
@@ -40,7 +41,10 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
40
41
 
41
42
  const program = sourceCode.ast;
42
43
 
43
- const tsDataForgeImport = getTsDataForgeImport(program);
44
+ const tsDataForgeImports = getImportDeclarations(
45
+ program,
46
+ TS_DATA_FORGE_MODULE,
47
+ );
44
48
 
45
49
  const services = context.sourceCode.parserServices;
46
50
 
@@ -99,7 +103,7 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
99
103
  });
100
104
  },
101
105
  'Program:exit': () => {
102
- const namedImports = getNamedImports(tsDataForgeImport);
106
+ const namedImports = getNamedImports(tsDataForgeImports);
103
107
 
104
108
  const hasArrImport = namedImports.includes('Arr');
105
109
 
@@ -107,7 +111,7 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
107
111
  index,
108
112
  { node, arrayExpression, lengthExpression },
109
113
  ] of mut_nodesToFix.entries()) {
110
- const arrayText = toArgumentText(arrayExpression, sourceCode);
114
+ const arrayText = getArgumentText(sourceCode, arrayExpression);
111
115
 
112
116
  const lengthText = sourceCode.getText(lengthExpression);
113
117
 
@@ -126,7 +130,9 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
126
130
 
127
131
  const importFixes =
128
132
  index === 0 && !hasArrImport
129
- ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
133
+ ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
134
+ 'Arr',
135
+ ])
130
136
  : [];
131
137
 
132
138
  return [...importFixes, fixer.replaceText(node, replacement)];
@@ -219,6 +219,14 @@ describe('prefer-arr-is-min-length-array through type wrappers', () => {
219
219
  const ok = xs.length >= 1 && (xs as readonly number[]).length <= 3;
220
220
  `,
221
221
  },
222
+ {
223
+ name: 'one half of a bounded pair, either half wrapped as a whole',
224
+ code: dedent`
225
+ declare const xs: readonly number[];
226
+ const a = (xs.length >= 1 satisfies boolean) && xs.length <= 3;
227
+ const b = xs.length >= 1 && (xs.length <= 3 as boolean);
228
+ `,
229
+ },
222
230
  ],
223
231
  invalid: [
224
232
  {
@@ -4,16 +4,14 @@ import {
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
6
  import {
7
- asLengthAccess,
8
- isArrayOrTupleExpression,
9
- skipTypeWrappers,
10
- toArgumentText,
11
- } from './ast-utils.mjs';
12
- import {
13
- buildImportFixes,
7
+ buildNamedImportFix,
8
+ getArgumentText,
9
+ getImportDeclarations,
14
10
  getNamedImports,
15
- getTsDataForgeImport,
16
- } from './import-utils.mjs';
11
+ skipTypeWrappers,
12
+ } from 'eslint-rule-forge';
13
+ import { asLengthAccess, isArrayOrTupleExpression } from './ast-utils.mjs';
14
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
17
15
 
18
16
  type Options = readonly [];
19
17
 
@@ -39,7 +37,10 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
39
37
 
40
38
  const program = sourceCode.ast;
41
39
 
42
- const tsDataForgeImport = getTsDataForgeImport(program);
40
+ const tsDataForgeImports = getImportDeclarations(
41
+ program,
42
+ TS_DATA_FORGE_MODULE,
43
+ );
43
44
 
44
45
  const services = context.sourceCode.parserServices;
45
46
 
@@ -88,7 +89,7 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
88
89
  });
89
90
  },
90
91
  'Program:exit': () => {
91
- const namedImports = getNamedImports(tsDataForgeImport);
92
+ const namedImports = getNamedImports(tsDataForgeImports);
92
93
 
93
94
  const hasArrImport = namedImports.includes('Arr');
94
95
 
@@ -96,7 +97,7 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
96
97
  index,
97
98
  { node, arrayExpression },
98
99
  ] of mut_nodesToFix.entries()) {
99
- const arrayText = toArgumentText(arrayExpression, sourceCode);
100
+ const arrayText = getArgumentText(sourceCode, arrayExpression);
100
101
 
101
102
  const originalText = sourceCode.getText(node);
102
103
 
@@ -112,7 +113,9 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
112
113
 
113
114
  const importFixes =
114
115
  index === 0 && !hasArrImport
115
- ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
116
+ ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
117
+ 'Arr',
118
+ ])
116
119
  : [];
117
120
 
118
121
  return [...importFixes, fixer.replaceText(node, replacement)];
@@ -3,19 +3,18 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { Arr } from 'ts-data-forge';
7
- import type { FixedLengthTuple } from 'ts-type-forge';
8
- import * as ts from 'typescript';
9
6
  import {
7
+ buildNamedImportFix,
8
+ getArgumentText,
9
+ getImportDeclarations,
10
+ getNamedImports,
10
11
  skipTypeWrappers,
11
- toArgumentText,
12
12
  typeWrapperLayers,
13
- } from './ast-utils.mjs';
14
- import {
15
- buildImportFixes,
16
- getNamedImports,
17
- getTsDataForgeImport,
18
- } from './import-utils.mjs';
13
+ } from 'eslint-rule-forge';
14
+ import { Arr } from 'ts-data-forge';
15
+ import type { FixedLengthTuple } from 'ts-type-forge';
16
+ import * as ts from 'typescript';
17
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
19
18
 
20
19
  type Options = readonly [];
21
20
 
@@ -179,7 +178,10 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
179
178
 
180
179
  const program = sourceCode.ast;
181
180
 
182
- const tsDataForgeImport = getTsDataForgeImport(program);
181
+ const tsDataForgeImports = getImportDeclarations(
182
+ program,
183
+ TS_DATA_FORGE_MODULE,
184
+ );
183
185
 
184
186
  const services = sourceCode.parserServices;
185
187
 
@@ -343,10 +345,9 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
343
345
  mut_lastReads,
344
346
  );
345
347
 
346
- const arrayText = toArgumentText(
347
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
348
+ const arrayText = getArgumentText(
349
+ sourceCode, // eslint-disable-next-line total-functions/no-unsafe-type-assertion
348
350
  (call.callee as TSESTree.MemberExpression).object,
349
- sourceCode,
350
351
  );
351
352
 
352
353
  // A node's text never includes the parentheses the source put around it:
@@ -358,7 +359,7 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
358
359
  ? `(${elementText})`
359
360
  : elementText;
360
361
 
361
- return `Arr.scan(${arrayText}, (${parameterText}) => ${bodyText}, ${toArgumentText(seed, sourceCode)})`;
362
+ return `Arr.scan(${arrayText}, (${parameterText}) => ${bodyText}, ${getArgumentText(sourceCode, seed)})`;
362
363
  };
363
364
 
364
365
  /**
@@ -493,7 +494,8 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
493
494
  // been seen by the time its `map` is judged, whichever order the traversal
494
495
  // reached them in.
495
496
  'Program:exit': () => {
496
- const hasArrImport = getNamedImports(tsDataForgeImport).includes('Arr');
497
+ const hasArrImport =
498
+ getNamedImports(tsDataForgeImports).includes('Arr');
497
499
 
498
500
  // The import is inserted once per pass: two fixes writing the same
499
501
  // insertion point would otherwise produce it twice.
@@ -514,9 +516,12 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
514
516
  ? undefined
515
517
  : (fixer) => [
516
518
  ...(call === mut_importNeededBy
517
- ? buildImportFixes(fixer, program, tsDataForgeImport, [
518
- 'Arr',
519
- ])
519
+ ? buildNamedImportFix(
520
+ fixer,
521
+ program,
522
+ TS_DATA_FORGE_MODULE,
523
+ ['Arr'],
524
+ )
520
525
  : []),
521
526
  fixer.replaceText(call, replacement),
522
527
  ],
@@ -3,18 +3,17 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { Arr, pipe } from 'ts-data-forge';
7
- import type { TypeReference } from 'typescript';
8
6
  import {
7
+ buildNamedImportFix,
8
+ getArgumentText,
9
+ getImportDeclarations,
10
+ getNamedImports,
9
11
  skipTypeWrappers,
10
- toArgumentText,
11
12
  typeWrapperLayers,
12
- } from './ast-utils.mjs';
13
- import {
14
- buildImportFixes,
15
- getNamedImports,
16
- getTsDataForgeImport,
17
- } from './import-utils.mjs';
13
+ } from 'eslint-rule-forge';
14
+ import { Arr, pipe } from 'ts-data-forge';
15
+ import type { TypeReference } from 'typescript';
16
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
18
17
 
19
18
  type Options = readonly [];
20
19
 
@@ -41,7 +40,10 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
41
40
 
42
41
  const program = sourceCode.ast;
43
42
 
44
- const tsDataForgeImport = getTsDataForgeImport(program);
43
+ const tsDataForgeImports = getImportDeclarations(
44
+ program,
45
+ TS_DATA_FORGE_MODULE,
46
+ );
45
47
 
46
48
  const services = context.sourceCode.parserServices;
47
49
 
@@ -264,14 +266,14 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
264
266
  }
265
267
  },
266
268
  'Program:exit': () => {
267
- const namedImports = getNamedImports(tsDataForgeImport);
269
+ const namedImports = getNamedImports(tsDataForgeImports);
268
270
 
269
271
  const hasArrImport = namedImports.includes('Arr');
270
272
 
271
273
  for (const [index, nodeInfo] of mut_nodesToFix.entries()) {
272
- const arrayText = toArgumentText(
273
- nodeInfo.arrayExpression,
274
+ const arrayText = getArgumentText(
274
275
  sourceCode,
276
+ nodeInfo.arrayExpression,
275
277
  );
276
278
 
277
279
  context.report({
@@ -289,7 +291,9 @@ export const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {
289
291
 
290
292
  const importFixes =
291
293
  index === 0 && !hasArrImport
292
- ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
294
+ ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
295
+ 'Arr',
296
+ ])
293
297
  : [];
294
298
 
295
299
  return [