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.
Files changed (93) hide show
  1. package/dist/rules/ast-utils.d.mts +5 -5
  2. package/dist/rules/ast-utils.d.mts.map +1 -1
  3. package/dist/rules/ast-utils.mjs +3 -3
  4. package/dist/rules/ast-utils.mjs.map +1 -1
  5. package/dist/rules/import-binding.d.mts +38 -0
  6. package/dist/rules/import-binding.d.mts.map +1 -0
  7. package/dist/rules/import-binding.mjs +53 -0
  8. package/dist/rules/import-binding.mjs.map +1 -0
  9. package/dist/rules/mutable-result.mjs +37 -37
  10. package/dist/rules/mutable-result.mjs.map +1 -1
  11. package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
  12. package/dist/rules/no-unnecessary-type-guard.mjs +146 -143
  13. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
  14. package/dist/rules/prefer-arr-generate.d.mts.map +1 -1
  15. package/dist/rules/prefer-arr-generate.mjs +129 -131
  16. package/dist/rules/prefer-arr-generate.mjs.map +1 -1
  17. package/dist/rules/prefer-arr-is-bounded-length-array.d.mts.map +1 -1
  18. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +14 -16
  19. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  20. package/dist/rules/prefer-arr-is-fixed-length-array.d.mts.map +1 -1
  21. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +15 -17
  22. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  23. package/dist/rules/prefer-arr-is-max-length-array.d.mts.map +1 -1
  24. package/dist/rules/prefer-arr-is-max-length-array.mjs +14 -16
  25. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  26. package/dist/rules/prefer-arr-is-min-length-array.d.mts.map +1 -1
  27. package/dist/rules/prefer-arr-is-min-length-array.mjs +14 -16
  28. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  29. package/dist/rules/prefer-arr-is-non-empty.d.mts.map +1 -1
  30. package/dist/rules/prefer-arr-is-non-empty.mjs +14 -16
  31. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
  32. package/dist/rules/prefer-arr-scan.d.mts.map +1 -1
  33. package/dist/rules/prefer-arr-scan.mjs +9 -18
  34. package/dist/rules/prefer-arr-scan.mjs.map +1 -1
  35. package/dist/rules/prefer-arr-sum.d.mts.map +1 -1
  36. package/dist/rules/prefer-arr-sum.mjs +16 -21
  37. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  38. package/dist/rules/prefer-arr-uniq.d.mts.map +1 -1
  39. package/dist/rules/prefer-arr-uniq.mjs +38 -43
  40. package/dist/rules/prefer-arr-uniq.mjs.map +1 -1
  41. package/dist/rules/prefer-as-int.d.mts.map +1 -1
  42. package/dist/rules/prefer-as-int.mjs +19 -26
  43. package/dist/rules/prefer-as-int.mjs.map +1 -1
  44. package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -1
  45. package/dist/rules/prefer-canonical-array-slicing.mjs +49 -15
  46. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  47. package/dist/rules/prefer-canonical-length-cast.mjs +15 -15
  48. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
  49. package/dist/rules/prefer-obj-over-entries-round-trip.d.mts.map +1 -1
  50. package/dist/rules/prefer-obj-over-entries-round-trip.mjs +100 -99
  51. package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
  52. package/dist/rules/prefer-pipe-map-nullable.d.mts.map +1 -1
  53. package/dist/rules/prefer-pipe-map-nullable.mjs +2 -4
  54. package/dist/rules/prefer-pipe-map-nullable.mjs.map +1 -1
  55. package/dist/rules/prefer-range-for-loop.d.mts.map +1 -1
  56. package/dist/rules/prefer-range-for-loop.mjs +18 -24
  57. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  58. package/package.json +5 -5
  59. package/src/rules/ast-utils.mts +11 -11
  60. package/src/rules/import-binding.mts +94 -0
  61. package/src/rules/mutable-result.mts +56 -56
  62. package/src/rules/no-unnecessary-type-guard.mts +199 -203
  63. package/src/rules/no-unnecessary-type-guard.test.mts +62 -2
  64. package/src/rules/prefer-arr-generate.mts +184 -194
  65. package/src/rules/prefer-arr-generate.test.mts +262 -134
  66. package/src/rules/prefer-arr-is-array.test.mts +87 -5
  67. package/src/rules/prefer-arr-is-bounded-length-array.mts +22 -33
  68. package/src/rules/prefer-arr-is-fixed-length-array.mts +24 -36
  69. package/src/rules/prefer-arr-is-fixed-length-array.test.mts +16 -7
  70. package/src/rules/prefer-arr-is-max-length-array.mts +21 -34
  71. package/src/rules/prefer-arr-is-max-length-array.test.mts +16 -7
  72. package/src/rules/prefer-arr-is-min-length-array.mts +21 -34
  73. package/src/rules/prefer-arr-is-min-length-array.test.mts +16 -7
  74. package/src/rules/prefer-arr-is-non-empty.mts +17 -34
  75. package/src/rules/prefer-arr-is-non-empty.test.mts +110 -0
  76. package/src/rules/prefer-arr-scan.mts +11 -36
  77. package/src/rules/prefer-arr-scan.test.mts +55 -0
  78. package/src/rules/prefer-arr-sum.mts +19 -33
  79. package/src/rules/prefer-arr-uniq.mts +49 -60
  80. package/src/rules/prefer-arr-uniq.test.mts +46 -20
  81. package/src/rules/prefer-as-int.mts +27 -52
  82. package/src/rules/prefer-as-int.test.mts +79 -29
  83. package/src/rules/prefer-canonical-array-slicing.mts +86 -24
  84. package/src/rules/prefer-canonical-array-slicing.test.mts +133 -34
  85. package/src/rules/prefer-canonical-length-cast.mts +18 -18
  86. package/src/rules/prefer-is-non-null-object.test.mts +62 -37
  87. package/src/rules/prefer-is-record-and-has-key.test.mts +32 -17
  88. package/src/rules/prefer-num-safe-parse-float.test.mts +45 -21
  89. package/src/rules/prefer-num-safe-parse-int.test.mts +29 -14
  90. package/src/rules/prefer-obj-over-entries-round-trip.mts +152 -170
  91. package/src/rules/prefer-obj-over-entries-round-trip.test.mts +48 -6
  92. package/src/rules/prefer-pipe-map-nullable.mts +6 -8
  93. package/src/rules/prefer-range-for-loop.mts +31 -55
@@ -1,17 +1,11 @@
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
+ import { getArgumentText, skipTypeWrappers } from 'eslint-rule-forge';
9
3
  import {
10
4
  asLengthAccess,
11
5
  isArrayOrTupleExpression,
12
6
  isIntegerLiteralOrConstant,
13
7
  } from './ast-utils.mjs';
14
- import { TS_DATA_FORGE_MODULE } from './constants.mjs';
8
+ import { tsDataForgeValueAt } from './import-binding.mjs';
15
9
 
16
10
  type Options = readonly [];
17
11
 
@@ -39,13 +33,6 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
39
33
  create: (context) => {
40
34
  const sourceCode = context.sourceCode;
41
35
 
42
- const program = sourceCode.ast;
43
-
44
- const tsDataForgeImports = getImportDeclarations(
45
- program,
46
- TS_DATA_FORGE_MODULE,
47
- );
48
-
49
36
  const services = context.sourceCode.parserServices;
50
37
 
51
38
  const mut_nodesToFix: {
@@ -99,20 +86,22 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
99
86
  });
100
87
  },
101
88
  'Program:exit': () => {
102
- const namedImports = getNamedImports(tsDataForgeImports);
89
+ const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
103
90
 
104
- const hasArrImport = namedImports.includes('Arr');
105
-
106
- for (const [
107
- index,
108
- { node, arrayExpression, lengthExpression, isNegated },
109
- ] of mut_nodesToFix.entries()) {
91
+ for (const {
92
+ node,
93
+ arrayExpression,
94
+ lengthExpression,
95
+ isNegated,
96
+ } of mut_nodesToFix) {
110
97
  const arrayText = getArgumentText(sourceCode, arrayExpression);
111
98
 
112
99
  const lengthText = sourceCode.getText(lengthExpression);
113
100
 
114
101
  const originalText = sourceCode.getText(node);
115
102
 
103
+ const arr = arrAt(node);
104
+
116
105
  context.report({
117
106
  node,
118
107
  messageId: 'useIsFixedLengthArray',
@@ -121,20 +110,19 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
121
110
  arrayName: arrayText,
122
111
  length: lengthText,
123
112
  },
124
- fix: (fixer) => {
125
- const baseCall = `Arr.isFixedLengthArray(${lengthText}, ${arrayText})`;
126
-
127
- const replacement = isNegated ? `!${baseCall}` : baseCall;
128
-
129
- const importFixes =
130
- index === 0 && !hasArrImport
131
- ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
132
- 'Arr',
133
- ])
134
- : [];
135
-
136
- return [...importFixes, fixer.replaceText(node, replacement)];
137
- },
113
+ fix:
114
+ arr === undefined
115
+ ? null
116
+ : (fixer) => {
117
+ const baseCall = `${arr.localName}.isFixedLengthArray(${lengthText}, ${arrayText})`;
118
+
119
+ const replacement = isNegated ? `!${baseCall}` : baseCall;
120
+
121
+ return [
122
+ ...arr.edits(fixer),
123
+ fixer.replaceText(node, replacement),
124
+ ];
125
+ },
138
126
  });
139
127
  }
140
128
  },
@@ -158,13 +158,22 @@ describe('prefer-arr-is-fixed-length-array', () => {
158
158
  const ok1 = xs.length === 3;
159
159
  const ok2 = ys.length === 2;
160
160
  `,
161
- output: dedent`
162
- import { Arr } from 'ts-data-forge';
163
- const xs = [1, 2, 3];
164
- const ys = [4, 5];
165
- const ok1 = Arr.isFixedLengthArray(3, xs);
166
- const ok2 = Arr.isFixedLengthArray(2, ys);
167
- `,
161
+ output: [
162
+ dedent`
163
+ import { Arr } from 'ts-data-forge';
164
+ const xs = [1, 2, 3];
165
+ const ys = [4, 5];
166
+ const ok1 = Arr.isFixedLengthArray(3, xs);
167
+ const ok2 = ys.length === 2;
168
+ `,
169
+ dedent`
170
+ import { Arr } from 'ts-data-forge';
171
+ const xs = [1, 2, 3];
172
+ const ys = [4, 5];
173
+ const ok1 = Arr.isFixedLengthArray(3, xs);
174
+ const ok2 = Arr.isFixedLengthArray(2, ys);
175
+ `,
176
+ ],
168
177
  errors: [
169
178
  { messageId: 'useIsFixedLengthArray' },
170
179
  { messageId: 'useIsFixedLengthArray' },
@@ -1,18 +1,12 @@
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
+ import { getArgumentText, skipTypeWrappers } from 'eslint-rule-forge';
9
3
  import {
10
4
  asLengthAccess,
11
5
  isArrayOrTupleExpression,
12
6
  isIntegerLiteralOrConstant,
13
7
  isPartOfBoundedLengthCheck,
14
8
  } from './ast-utils.mjs';
15
- import { TS_DATA_FORGE_MODULE } from './constants.mjs';
9
+ import { tsDataForgeValueAt } from './import-binding.mjs';
16
10
 
17
11
  type Options = readonly [];
18
12
 
@@ -40,13 +34,6 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
40
34
  create: (context) => {
41
35
  const sourceCode = context.sourceCode;
42
36
 
43
- const program = sourceCode.ast;
44
-
45
- const tsDataForgeImports = getImportDeclarations(
46
- program,
47
- TS_DATA_FORGE_MODULE,
48
- );
49
-
50
37
  const services = context.sourceCode.parserServices;
51
38
 
52
39
  const mut_nodesToFix: {
@@ -104,20 +91,21 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
104
91
  });
105
92
  },
106
93
  'Program:exit': () => {
107
- const namedImports = getNamedImports(tsDataForgeImports);
94
+ const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
108
95
 
109
- const hasArrImport = namedImports.includes('Arr');
110
-
111
- for (const [
112
- index,
113
- { node, arrayExpression, lengthExpression },
114
- ] of mut_nodesToFix.entries()) {
96
+ for (const {
97
+ node,
98
+ arrayExpression,
99
+ lengthExpression,
100
+ } of mut_nodesToFix) {
115
101
  const arrayText = getArgumentText(sourceCode, arrayExpression);
116
102
 
117
103
  const lengthText = sourceCode.getText(lengthExpression);
118
104
 
119
105
  const originalText = sourceCode.getText(node);
120
106
 
107
+ const arr = arrAt(node);
108
+
121
109
  context.report({
122
110
  node,
123
111
  messageId: 'useIsMaxLengthArray',
@@ -126,18 +114,17 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
126
114
  arrayName: arrayText,
127
115
  length: lengthText,
128
116
  },
129
- fix: (fixer) => {
130
- const replacement = `Arr.isMaxLengthArray(${lengthText}, ${arrayText})`;
131
-
132
- const importFixes =
133
- index === 0 && !hasArrImport
134
- ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
135
- 'Arr',
136
- ])
137
- : [];
138
-
139
- return [...importFixes, fixer.replaceText(node, replacement)];
140
- },
117
+ fix:
118
+ arr === undefined
119
+ ? null
120
+ : (fixer) => {
121
+ const replacement = `${arr.localName}.isMaxLengthArray(${lengthText}, ${arrayText})`;
122
+
123
+ return [
124
+ ...arr.edits(fixer),
125
+ fixer.replaceText(node, replacement),
126
+ ];
127
+ },
141
128
  });
142
129
  }
143
130
  },
@@ -171,13 +171,22 @@ describe('prefer-arr-is-max-length-array', () => {
171
171
  const ok1 = xs.length <= 2;
172
172
  const ok2 = ys.length <= 1;
173
173
  `,
174
- output: dedent`
175
- import { Arr } from 'ts-data-forge';
176
- const xs = [1, 2, 3];
177
- const ys = [4, 5];
178
- const ok1 = Arr.isMaxLengthArray(2, xs);
179
- const ok2 = Arr.isMaxLengthArray(1, ys);
180
- `,
174
+ output: [
175
+ dedent`
176
+ import { Arr } from 'ts-data-forge';
177
+ const xs = [1, 2, 3];
178
+ const ys = [4, 5];
179
+ const ok1 = Arr.isMaxLengthArray(2, xs);
180
+ const ok2 = ys.length <= 1;
181
+ `,
182
+ dedent`
183
+ import { Arr } from 'ts-data-forge';
184
+ const xs = [1, 2, 3];
185
+ const ys = [4, 5];
186
+ const ok1 = Arr.isMaxLengthArray(2, xs);
187
+ const ok2 = Arr.isMaxLengthArray(1, ys);
188
+ `,
189
+ ],
181
190
  errors: [
182
191
  { messageId: 'useIsMaxLengthArray' },
183
192
  { messageId: 'useIsMaxLengthArray' },
@@ -1,18 +1,12 @@
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
+ import { getArgumentText, skipTypeWrappers } from 'eslint-rule-forge';
9
3
  import {
10
4
  asLengthAccess,
11
5
  isArrayOrTupleExpression,
12
6
  isIntegerLiteralOrConstant,
13
7
  isPartOfBoundedLengthCheck,
14
8
  } from './ast-utils.mjs';
15
- import { TS_DATA_FORGE_MODULE } from './constants.mjs';
9
+ import { tsDataForgeValueAt } from './import-binding.mjs';
16
10
 
17
11
  type Options = readonly [];
18
12
 
@@ -40,13 +34,6 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
40
34
  create: (context) => {
41
35
  const sourceCode = context.sourceCode;
42
36
 
43
- const program = sourceCode.ast;
44
-
45
- const tsDataForgeImports = getImportDeclarations(
46
- program,
47
- TS_DATA_FORGE_MODULE,
48
- );
49
-
50
37
  const services = context.sourceCode.parserServices;
51
38
 
52
39
  const mut_nodesToFix: {
@@ -104,20 +91,21 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
104
91
  });
105
92
  },
106
93
  'Program:exit': () => {
107
- const namedImports = getNamedImports(tsDataForgeImports);
94
+ const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
108
95
 
109
- const hasArrImport = namedImports.includes('Arr');
110
-
111
- for (const [
112
- index,
113
- { node, arrayExpression, lengthExpression },
114
- ] of mut_nodesToFix.entries()) {
96
+ for (const {
97
+ node,
98
+ arrayExpression,
99
+ lengthExpression,
100
+ } of mut_nodesToFix) {
115
101
  const arrayText = getArgumentText(sourceCode, arrayExpression);
116
102
 
117
103
  const lengthText = sourceCode.getText(lengthExpression);
118
104
 
119
105
  const originalText = sourceCode.getText(node);
120
106
 
107
+ const arr = arrAt(node);
108
+
121
109
  context.report({
122
110
  node,
123
111
  messageId: 'useIsMinLengthArray',
@@ -126,18 +114,17 @@ export const preferArrIsMinLengthArray: TSESLint.RuleModule<
126
114
  arrayName: arrayText,
127
115
  length: lengthText,
128
116
  },
129
- fix: (fixer) => {
130
- const replacement = `Arr.isMinLengthArray(${lengthText}, ${arrayText})`;
131
-
132
- const importFixes =
133
- index === 0 && !hasArrImport
134
- ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
135
- 'Arr',
136
- ])
137
- : [];
138
-
139
- return [...importFixes, fixer.replaceText(node, replacement)];
140
- },
117
+ fix:
118
+ arr === undefined
119
+ ? null
120
+ : (fixer) => {
121
+ const replacement = `${arr.localName}.isMinLengthArray(${lengthText}, ${arrayText})`;
122
+
123
+ return [
124
+ ...arr.edits(fixer),
125
+ fixer.replaceText(node, replacement),
126
+ ];
127
+ },
141
128
  });
142
129
  }
143
130
  },
@@ -172,13 +172,22 @@ describe('prefer-arr-is-min-length-array', () => {
172
172
  const ok1 = xs.length >= 2;
173
173
  const ok2 = ys.length >= 1;
174
174
  `,
175
- output: dedent`
176
- import { Arr } from 'ts-data-forge';
177
- const xs = [1, 2, 3];
178
- const ys = [4, 5];
179
- const ok1 = Arr.isMinLengthArray(2, xs);
180
- const ok2 = Arr.isMinLengthArray(1, ys);
181
- `,
175
+ output: [
176
+ dedent`
177
+ import { Arr } from 'ts-data-forge';
178
+ const xs = [1, 2, 3];
179
+ const ys = [4, 5];
180
+ const ok1 = Arr.isMinLengthArray(2, xs);
181
+ const ok2 = ys.length >= 1;
182
+ `,
183
+ dedent`
184
+ import { Arr } from 'ts-data-forge';
185
+ const xs = [1, 2, 3];
186
+ const ys = [4, 5];
187
+ const ok1 = Arr.isMinLengthArray(2, xs);
188
+ const ok2 = Arr.isMinLengthArray(1, ys);
189
+ `,
190
+ ],
182
191
  errors: [
183
192
  { messageId: 'useIsMinLengthArray' },
184
193
  { messageId: 'useIsMinLengthArray' },
@@ -3,15 +3,9 @@ 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
- skipTypeWrappers,
12
- } from 'eslint-rule-forge';
6
+ import { getArgumentText, skipTypeWrappers } from 'eslint-rule-forge';
13
7
  import { asLengthAccess, isArrayOrTupleExpression } from './ast-utils.mjs';
14
- import { TS_DATA_FORGE_MODULE } from './constants.mjs';
8
+ import { tsDataForgeValueAt } from './import-binding.mjs';
15
9
 
16
10
  type Options = readonly [];
17
11
 
@@ -36,13 +30,6 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
36
30
  create: (context) => {
37
31
  const sourceCode = context.sourceCode;
38
32
 
39
- const program = sourceCode.ast;
40
-
41
- const tsDataForgeImports = getImportDeclarations(
42
- program,
43
- TS_DATA_FORGE_MODULE,
44
- );
45
-
46
33
  const services = context.sourceCode.parserServices;
47
34
 
48
35
  const mut_nodesToFix: {
@@ -90,18 +77,15 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
90
77
  });
91
78
  },
92
79
  'Program:exit': () => {
93
- const namedImports = getNamedImports(tsDataForgeImports);
94
-
95
- const hasArrImport = namedImports.includes('Arr');
80
+ const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
96
81
 
97
- for (const [
98
- index,
99
- { node, arrayExpression },
100
- ] of mut_nodesToFix.entries()) {
82
+ for (const { node, arrayExpression } of mut_nodesToFix) {
101
83
  const arrayText = getArgumentText(sourceCode, arrayExpression);
102
84
 
103
85
  const originalText = sourceCode.getText(node);
104
86
 
87
+ const arr = arrAt(node);
88
+
105
89
  context.report({
106
90
  node,
107
91
  messageId: 'useIsNonEmpty',
@@ -109,18 +93,17 @@ export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
109
93
  original: originalText,
110
94
  arrayName: arrayText,
111
95
  },
112
- fix: (fixer) => {
113
- const replacement = `Arr.isNonEmpty(${arrayText})`;
114
-
115
- const importFixes =
116
- index === 0 && !hasArrImport
117
- ? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, [
118
- 'Arr',
119
- ])
120
- : [];
121
-
122
- return [...importFixes, fixer.replaceText(node, replacement)];
123
- },
96
+ fix:
97
+ arr === undefined
98
+ ? null
99
+ : (fixer) => {
100
+ const replacement = `${arr.localName}.isNonEmpty(${arrayText})`;
101
+
102
+ return [
103
+ ...arr.edits(fixer),
104
+ fixer.replaceText(node, replacement),
105
+ ];
106
+ },
124
107
  });
125
108
  }
126
109
  },
@@ -167,3 +167,113 @@ describe('prefer-arr-is-non-empty with parenthesized operands', () => {
167
167
  ],
168
168
  });
169
169
  }, 20000);
170
+
171
+ describe('prefer-arr-is-non-empty and the import it adds', () => {
172
+ tester.run('prefer-arr-is-non-empty', preferArrIsNonEmpty, {
173
+ valid: [],
174
+ invalid: [
175
+ {
176
+ name: 'a leading directive stays first',
177
+ code: dedent`
178
+ 'use client';
179
+ declare const xs: readonly number[];
180
+ const ok = xs.length > 0;
181
+ `,
182
+ output: dedent`
183
+ 'use client';
184
+ import { Arr } from 'ts-data-forge';
185
+ declare const xs: readonly number[];
186
+ const ok = Arr.isNonEmpty(xs);
187
+ `,
188
+ errors: [{ messageId: 'useIsNonEmpty' }],
189
+ },
190
+ {
191
+ name: 'joins an existing named import from the module',
192
+ code: dedent`
193
+ import { asInt } from 'ts-data-forge';
194
+ declare const xs: readonly number[];
195
+ const ok = xs.length > 0;
196
+ `,
197
+ output: dedent`
198
+ import { asInt, Arr } from 'ts-data-forge';
199
+ declare const xs: readonly number[];
200
+ const ok = Arr.isNonEmpty(xs);
201
+ `,
202
+ errors: [{ messageId: 'useIsNonEmpty' }],
203
+ },
204
+ {
205
+ name: 'turns a type-only specifier into a value import',
206
+ code: dedent`
207
+ import { type Arr } from 'ts-data-forge';
208
+ declare const xs: readonly number[];
209
+ const ok = xs.length > 0;
210
+ `,
211
+ output: dedent`
212
+ import { Arr } from 'ts-data-forge';
213
+ declare const xs: readonly number[];
214
+ const ok = Arr.isNonEmpty(xs);
215
+ `,
216
+ errors: [{ messageId: 'useIsNonEmpty' }],
217
+ },
218
+ {
219
+ name: 'writes the local name of an aliased import',
220
+ code: dedent`
221
+ import { Arr as A } from 'ts-data-forge';
222
+ declare const xs: readonly number[];
223
+ const ok = xs.length > 0;
224
+ `,
225
+ output: dedent`
226
+ import { Arr as A } from 'ts-data-forge';
227
+ declare const xs: readonly number[];
228
+ const ok = A.isNonEmpty(xs);
229
+ `,
230
+ errors: [{ messageId: 'useIsNonEmpty' }],
231
+ },
232
+ {
233
+ name: 'no fix when an import type binds the name',
234
+ code: dedent`
235
+ import type { Arr } from 'ts-data-forge';
236
+ declare const xs: readonly number[];
237
+ const ok = xs.length > 0;
238
+ `,
239
+ output: null,
240
+ errors: [{ messageId: 'useIsNonEmpty' }],
241
+ },
242
+ {
243
+ name: 'no fix where a local binding takes the name',
244
+ code: dedent`
245
+ declare const xs: readonly number[];
246
+ const f = (Arr: unknown) => xs.length > 0;
247
+ `,
248
+ output: null,
249
+ errors: [{ messageId: 'useIsNonEmpty' }],
250
+ },
251
+ {
252
+ name: 'each fix adds the import it needs, one per pass',
253
+ code: dedent`
254
+ declare const xs: readonly number[];
255
+ const a = xs.length > 0;
256
+ const b = 0 < xs.length;
257
+ `,
258
+ output: [
259
+ dedent`
260
+ import { Arr } from 'ts-data-forge';
261
+ declare const xs: readonly number[];
262
+ const a = Arr.isNonEmpty(xs);
263
+ const b = 0 < xs.length;
264
+ `,
265
+ dedent`
266
+ import { Arr } from 'ts-data-forge';
267
+ declare const xs: readonly number[];
268
+ const a = Arr.isNonEmpty(xs);
269
+ const b = Arr.isNonEmpty(xs);
270
+ `,
271
+ ],
272
+ errors: [
273
+ { messageId: 'useIsNonEmpty' },
274
+ { messageId: 'useIsNonEmpty' },
275
+ ],
276
+ },
277
+ ],
278
+ });
279
+ }, 20000);
@@ -4,17 +4,14 @@ 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 } from 'ts-data-forge';
15
12
  import type { FixedLengthTuple } from 'ts-type-forge';
16
13
  import * as ts from 'typescript';
17
- import { TS_DATA_FORGE_MODULE } from './constants.mjs';
14
+ import { tsDataForgeValueAt } from './import-binding.mjs';
18
15
 
19
16
  type Options = readonly [];
20
17
 
@@ -47,8 +44,9 @@ type SliceCall = Readonly<{
47
44
  type AccumulationCandidate = Readonly<{
48
45
  call: TSESTree.CallExpression;
49
46
  /**
50
- * What the call is replaced with, or `undefined` when the rewrite could not
51
- * be derived and the report carries no fix.
47
+ * What the call is replaced with, after the name `Arr` is bound to
48
+ * (`scan(xs, f, init)`), or `undefined` when the rewrite could not be
49
+ * derived and the report carries no fix.
52
50
  */
53
51
  replacement: string | undefined;
54
52
  }>;
@@ -180,13 +178,6 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
180
178
  create: (context) => {
181
179
  const sourceCode = context.sourceCode;
182
180
 
183
- const program = sourceCode.ast;
184
-
185
- const tsDataForgeImports = getImportDeclarations(
186
- program,
187
- TS_DATA_FORGE_MODULE,
188
- );
189
-
190
181
  const services = sourceCode.parserServices;
191
182
 
192
183
  const checker = services?.program?.getTypeChecker();
@@ -363,7 +354,7 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
363
354
  ? `(${elementText})`
364
355
  : elementText;
365
356
 
366
- return `Arr.scan(${arrayText}, (${parameterText}) => ${bodyText}, ${getArgumentText(sourceCode, seed)})`;
357
+ return `scan(${arrayText}, (${parameterText}) => ${bodyText}, ${getArgumentText(sourceCode, seed)})`;
367
358
  };
368
359
 
369
360
  /**
@@ -498,36 +489,20 @@ export const preferArrScan: TSESLint.RuleModule<MessageIds, Options> = {
498
489
  // been seen by the time its `map` is judged, whichever order the traversal
499
490
  // reached them in.
500
491
  'Program:exit': () => {
501
- const hasArrImport =
502
- getNamedImports(tsDataForgeImports).includes('Arr');
503
-
504
- // The import is inserted once per pass: two fixes writing the same
505
- // insertion point would otherwise produce it twice.
506
- const mut_importNeededBy = hasArrImport
507
- ? undefined
508
- : mut_accumulations.find(
509
- (accumulation) => accumulation.replacement !== undefined,
510
- )?.call;
492
+ const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
511
493
 
512
- for (const accumulation of mut_accumulations) {
513
- const { call, replacement } = accumulation;
494
+ for (const { call, replacement } of mut_accumulations) {
495
+ const arr = arrAt(call);
514
496
 
515
497
  context.report({
516
498
  node: call,
517
499
  messageId: 'preferArrScanAccumulate',
518
500
  fix:
519
- replacement === undefined
501
+ replacement === undefined || arr === undefined
520
502
  ? undefined
521
503
  : (fixer) => [
522
- ...(call === mut_importNeededBy
523
- ? buildNamedImportFix(
524
- fixer,
525
- program,
526
- TS_DATA_FORGE_MODULE,
527
- ['Arr'],
528
- )
529
- : []),
530
- fixer.replaceText(call, replacement),
504
+ ...arr.edits(fixer),
505
+ fixer.replaceText(call, `${arr.localName}.${replacement}`),
531
506
  ],
532
507
  });
533
508
  }