eslint-plugin-ts-data-forge 0.10.5 → 0.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rules/ast-utils.d.mts +5 -5
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +3 -3
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/import-binding.d.mts +38 -0
- package/dist/rules/import-binding.d.mts.map +1 -0
- package/dist/rules/import-binding.mjs +53 -0
- package/dist/rules/import-binding.mjs.map +1 -0
- package/dist/rules/mutable-result.mjs +37 -37
- package/dist/rules/mutable-result.mjs.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.mjs +146 -143
- package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
- package/dist/rules/prefer-arr-generate.d.mts.map +1 -1
- package/dist/rules/prefer-arr-generate.mjs +129 -131
- package/dist/rules/prefer-arr-generate.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs +14 -16
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs +15 -17
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.mjs +14 -16
- package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.mjs +14 -16
- package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-non-empty.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-non-empty.mjs +14 -16
- package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
- package/dist/rules/prefer-arr-scan.d.mts.map +1 -1
- package/dist/rules/prefer-arr-scan.mjs +9 -18
- package/dist/rules/prefer-arr-scan.mjs.map +1 -1
- package/dist/rules/prefer-arr-sum.d.mts.map +1 -1
- package/dist/rules/prefer-arr-sum.mjs +16 -21
- package/dist/rules/prefer-arr-sum.mjs.map +1 -1
- package/dist/rules/prefer-arr-uniq.d.mts.map +1 -1
- package/dist/rules/prefer-arr-uniq.mjs +38 -43
- package/dist/rules/prefer-arr-uniq.mjs.map +1 -1
- package/dist/rules/prefer-as-int.d.mts.map +1 -1
- package/dist/rules/prefer-as-int.mjs +19 -26
- package/dist/rules/prefer-as-int.mjs.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.mjs +49 -15
- package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-cast.mjs +15 -15
- package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
- package/dist/rules/prefer-obj-over-entries-round-trip.d.mts.map +1 -1
- package/dist/rules/prefer-obj-over-entries-round-trip.mjs +100 -99
- package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
- package/dist/rules/prefer-pipe-map-nullable.d.mts.map +1 -1
- package/dist/rules/prefer-pipe-map-nullable.mjs +2 -4
- package/dist/rules/prefer-pipe-map-nullable.mjs.map +1 -1
- package/dist/rules/prefer-range-for-loop.d.mts.map +1 -1
- package/dist/rules/prefer-range-for-loop.mjs +18 -24
- package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
- package/package.json +4 -4
- package/src/rules/ast-utils.mts +11 -11
- package/src/rules/import-binding.mts +94 -0
- package/src/rules/mutable-result.mts +56 -56
- package/src/rules/no-unnecessary-type-guard.mts +199 -203
- package/src/rules/no-unnecessary-type-guard.test.mts +62 -2
- package/src/rules/prefer-arr-generate.mts +184 -194
- package/src/rules/prefer-arr-generate.test.mts +262 -134
- package/src/rules/prefer-arr-is-bounded-length-array.mts +22 -33
- package/src/rules/prefer-arr-is-fixed-length-array.mts +24 -36
- package/src/rules/prefer-arr-is-fixed-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-max-length-array.mts +21 -34
- package/src/rules/prefer-arr-is-max-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-min-length-array.mts +21 -34
- package/src/rules/prefer-arr-is-min-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-non-empty.mts +17 -34
- package/src/rules/prefer-arr-is-non-empty.test.mts +110 -0
- package/src/rules/prefer-arr-scan.mts +11 -36
- package/src/rules/prefer-arr-scan.test.mts +55 -0
- package/src/rules/prefer-arr-sum.mts +19 -33
- package/src/rules/prefer-arr-uniq.mts +49 -60
- package/src/rules/prefer-arr-uniq.test.mts +46 -20
- package/src/rules/prefer-as-int.mts +27 -52
- package/src/rules/prefer-as-int.test.mts +79 -29
- package/src/rules/prefer-canonical-array-slicing.mts +86 -24
- package/src/rules/prefer-canonical-array-slicing.test.mts +133 -34
- package/src/rules/prefer-canonical-length-cast.mts +18 -18
- package/src/rules/prefer-obj-over-entries-round-trip.mts +152 -170
- package/src/rules/prefer-obj-over-entries-round-trip.test.mts +48 -6
- package/src/rules/prefer-pipe-map-nullable.mts +6 -8
- package/src/rules/prefer-range-for-loop.mts +31 -55
|
@@ -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:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 {
|
|
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
|
|
94
|
+
const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
|
|
108
95
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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 {
|
|
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
|
|
94
|
+
const arrAt = tsDataForgeValueAt(sourceCode, 'Arr');
|
|
108
95
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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 {
|
|
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
|
|
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:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 {
|
|
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,
|
|
51
|
-
*
|
|
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 `
|
|
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
|
|
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
|
|
513
|
-
const
|
|
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
|
-
...(
|
|
523
|
-
|
|
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
|
}
|
|
@@ -442,3 +442,58 @@ describe('prefer-arr-scan with parenthesized operands', () => {
|
|
|
442
442
|
],
|
|
443
443
|
});
|
|
444
444
|
});
|
|
445
|
+
|
|
446
|
+
describe('prefer-arr-scan and the import it adds', () => {
|
|
447
|
+
tester.run('prefer-arr-scan', preferArrScan, {
|
|
448
|
+
valid: [],
|
|
449
|
+
invalid: [
|
|
450
|
+
{
|
|
451
|
+
name: 'writes the local name of an aliased import',
|
|
452
|
+
code: dedent`
|
|
453
|
+
import { Arr as A } from 'ts-data-forge';
|
|
454
|
+
declare const xs: readonly number[];
|
|
455
|
+
const totals = xs.reduce<readonly number[]>(
|
|
456
|
+
(acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
|
|
457
|
+
[0],
|
|
458
|
+
);
|
|
459
|
+
`,
|
|
460
|
+
output: dedent`
|
|
461
|
+
import { Arr as A } from 'ts-data-forge';
|
|
462
|
+
declare const xs: readonly number[];
|
|
463
|
+
const totals = A.scan(xs, (acc, x) => (acc) + x, 0);
|
|
464
|
+
`,
|
|
465
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'turns a type-only specifier into a value import',
|
|
469
|
+
code: dedent`
|
|
470
|
+
import { type Arr } from 'ts-data-forge';
|
|
471
|
+
declare const xs: readonly number[];
|
|
472
|
+
const totals = xs.reduce<readonly number[]>(
|
|
473
|
+
(acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
|
|
474
|
+
[0],
|
|
475
|
+
);
|
|
476
|
+
`,
|
|
477
|
+
output: dedent`
|
|
478
|
+
import { Arr } from 'ts-data-forge';
|
|
479
|
+
declare const xs: readonly number[];
|
|
480
|
+
const totals = Arr.scan(xs, (acc, x) => (acc) + x, 0);
|
|
481
|
+
`,
|
|
482
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'no fix when an import type binds the name',
|
|
486
|
+
code: dedent`
|
|
487
|
+
import type { Arr } from 'ts-data-forge';
|
|
488
|
+
declare const xs: readonly number[];
|
|
489
|
+
const totals = xs.reduce<readonly number[]>(
|
|
490
|
+
(acc, x) => [...acc, (acc.at(-1) ?? 0) + x],
|
|
491
|
+
[0],
|
|
492
|
+
);
|
|
493
|
+
`,
|
|
494
|
+
output: null,
|
|
495
|
+
errors: [{ messageId: 'preferArrScanAccumulate' }],
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
});
|
|
499
|
+
});
|