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.
- package/README.md +3 -3
- package/dist/plugin.d.mts +2 -2
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +5 -5
- package/dist/rules/ast-utils.d.mts +8 -9
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +3 -4
- 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 +2 -2
- package/dist/rules/prefer-arr-generate.d.mts.map +1 -1
- package/dist/rules/prefer-arr-generate.mjs +146 -136
- 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 -16
- 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-num-safe-parse-float.d.mts +1 -1
- package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -1
- package/dist/rules/prefer-num-safe-parse-int.d.mts +1 -1
- package/dist/rules/prefer-num-safe-parse-int.d.mts.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 +2 -2
- 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/dist/rules/rules.d.mts +2 -2
- package/package.json +6 -6
- package/src/plugin.mts +4 -3
- package/src/rule-types.mts +5 -5
- package/src/rules/ast-utils.mts +14 -15
- package/src/rules/import-binding.mts +94 -0
- package/src/rules/mutable-result.mts +56 -56
- package/src/rules/no-unnecessary-type-guard.mts +203 -207
- package/src/rules/no-unnecessary-type-guard.test.mts +62 -2
- package/src/rules/prefer-arr-generate.mts +222 -214
- package/src/rules/prefer-arr-generate.test.mts +490 -133
- package/src/rules/prefer-arr-is-bounded-length-array.mts +22 -33
- package/src/rules/prefer-arr-is-fixed-length-array.mts +26 -38
- package/src/rules/prefer-arr-is-fixed-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-max-length-array.mts +23 -36
- package/src/rules/prefer-arr-is-max-length-array.test.mts +16 -7
- package/src/rules/prefer-arr-is-min-length-array.mts +23 -36
- 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 +17 -42
- package/src/rules/prefer-arr-scan.test.mts +55 -0
- package/src/rules/prefer-arr-sum.mts +22 -36
- package/src/rules/prefer-arr-uniq.mts +51 -62
- 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 +101 -40
- package/src/rules/prefer-canonical-array-slicing.test.mts +133 -34
- package/src/rules/prefer-canonical-length-cast.mts +29 -29
- package/src/rules/prefer-canonical-length-guard.mts +2 -2
- package/src/rules/prefer-num-safe-parse-float.test.mts +255 -94
- package/src/rules/prefer-num-safe-parse-int.test.mts +212 -76
- 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 +17 -19
- package/src/rules/prefer-range-for-loop.mts +37 -61
- package/src/rules/prefer-range-for-loop.test.mts +2 -2
|
@@ -3,15 +3,10 @@ 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
|
-
} from 'eslint-rule-forge';
|
|
6
|
+
import { getArgumentText } from 'eslint-rule-forge';
|
|
12
7
|
import type { DeepReadonly } from 'ts-type-forge';
|
|
13
8
|
import { brandedNumberTypeNameToFunctionName } from './branded-number-types.mjs';
|
|
14
|
-
import {
|
|
9
|
+
import { tsDataForgeValueAtByName } from './import-binding.mjs';
|
|
15
10
|
|
|
16
11
|
type Options = readonly [];
|
|
17
12
|
|
|
@@ -36,13 +31,6 @@ export const preferAsInt: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
36
31
|
create: (context) => {
|
|
37
32
|
const sourceCode = context.sourceCode;
|
|
38
33
|
|
|
39
|
-
const program = sourceCode.ast;
|
|
40
|
-
|
|
41
|
-
const tsDataForgeImports = getImportDeclarations(
|
|
42
|
-
program,
|
|
43
|
-
TS_DATA_FORGE_MODULE,
|
|
44
|
-
);
|
|
45
|
-
|
|
46
34
|
const mut_nodesToFix: {
|
|
47
35
|
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion;
|
|
48
36
|
typeName: string;
|
|
@@ -71,44 +59,31 @@ export const preferAsInt: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
71
59
|
TSAsExpression: collect,
|
|
72
60
|
TSTypeAssertion: collect,
|
|
73
61
|
'Program:exit': () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
index === 0 && !hasImport
|
|
100
|
-
? buildNamedImportFix(
|
|
101
|
-
fixer,
|
|
102
|
-
program,
|
|
103
|
-
TS_DATA_FORGE_MODULE,
|
|
104
|
-
[functionName],
|
|
105
|
-
)
|
|
106
|
-
: [];
|
|
107
|
-
|
|
108
|
-
return [...importFixes, fixer.replaceText(node, replacement)];
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
}
|
|
62
|
+
// One binding per cast function, however many casts use it.
|
|
63
|
+
const castAt = tsDataForgeValueAtByName(sourceCode);
|
|
64
|
+
|
|
65
|
+
for (const { node, typeName, functionName } of mut_nodesToFix) {
|
|
66
|
+
const cast = castAt(functionName)(node);
|
|
67
|
+
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'useBrandedNumberCastFunction',
|
|
71
|
+
data: {
|
|
72
|
+
typeName,
|
|
73
|
+
// The name the fix writes: an aliased import's local name.
|
|
74
|
+
functionName: cast?.localName ?? functionName,
|
|
75
|
+
},
|
|
76
|
+
fix:
|
|
77
|
+
cast === undefined
|
|
78
|
+
? null
|
|
79
|
+
: (fixer) => [
|
|
80
|
+
...cast.edits(fixer),
|
|
81
|
+
fixer.replaceText(
|
|
82
|
+
node,
|
|
83
|
+
`${cast.localName}(${getArgumentText(sourceCode, node.expression)})`,
|
|
84
|
+
),
|
|
85
|
+
],
|
|
86
|
+
});
|
|
112
87
|
}
|
|
113
88
|
},
|
|
114
89
|
};
|
|
@@ -60,24 +60,6 @@ describe('prefer-as-int', () => {
|
|
|
60
60
|
`,
|
|
61
61
|
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
62
62
|
},
|
|
63
|
-
{
|
|
64
|
-
name: 'replaces multiple as Int with asInt imports',
|
|
65
|
-
code: dedent`
|
|
66
|
-
const n = 1;
|
|
67
|
-
const result = n as Int;
|
|
68
|
-
const another = (n + 1) as Int;
|
|
69
|
-
`,
|
|
70
|
-
output: dedent`
|
|
71
|
-
import { asInt } from 'ts-data-forge';
|
|
72
|
-
const n = 1;
|
|
73
|
-
const result = asInt(n);
|
|
74
|
-
const another = asInt(n + 1);
|
|
75
|
-
`,
|
|
76
|
-
errors: [
|
|
77
|
-
{ messageId: 'useBrandedNumberCastFunction' },
|
|
78
|
-
{ messageId: 'useBrandedNumberCastFunction' },
|
|
79
|
-
],
|
|
80
|
-
},
|
|
81
63
|
// {
|
|
82
64
|
// name: 'replaces as Int with asInt import with another disabled line',
|
|
83
65
|
// code: dedent`
|
|
@@ -140,7 +122,7 @@ describe('prefer-as-int', () => {
|
|
|
140
122
|
|
|
141
123
|
...([
|
|
142
124
|
{
|
|
143
|
-
name: 'adds
|
|
125
|
+
name: 'adds the name to an existing ts-data-forge import',
|
|
144
126
|
code: dedent`
|
|
145
127
|
import { isNonNullObject } from 'ts-data-forge';
|
|
146
128
|
|
|
@@ -148,8 +130,7 @@ describe('prefer-as-int', () => {
|
|
|
148
130
|
const result = n as Int;
|
|
149
131
|
`,
|
|
150
132
|
output: dedent`
|
|
151
|
-
import { asInt } from 'ts-data-forge';
|
|
152
|
-
import { isNonNullObject } from 'ts-data-forge';
|
|
133
|
+
import { isNonNullObject, asInt } from 'ts-data-forge';
|
|
153
134
|
|
|
154
135
|
const n = 1;
|
|
155
136
|
const result = asInt(n);
|
|
@@ -157,7 +138,7 @@ describe('prefer-as-int', () => {
|
|
|
157
138
|
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
158
139
|
},
|
|
159
140
|
{
|
|
160
|
-
name: 'adds
|
|
141
|
+
name: 'adds the name to an existing ts-data-forge import (with other imports)',
|
|
161
142
|
code: dedent`
|
|
162
143
|
import { noop } from './noop.mjs';
|
|
163
144
|
import { isNonNullObject } from 'ts-data-forge';
|
|
@@ -166,9 +147,8 @@ describe('prefer-as-int', () => {
|
|
|
166
147
|
const result = n as Int;
|
|
167
148
|
`,
|
|
168
149
|
output: dedent`
|
|
169
|
-
import { asInt } from 'ts-data-forge';
|
|
170
150
|
import { noop } from './noop.mjs';
|
|
171
|
-
import { isNonNullObject } from 'ts-data-forge';
|
|
151
|
+
import { isNonNullObject, asInt } from 'ts-data-forge';
|
|
172
152
|
|
|
173
153
|
const n = 1;
|
|
174
154
|
const result = asInt(n);
|
|
@@ -183,6 +163,32 @@ describe('prefer-as-int', () => {
|
|
|
183
163
|
tester.run('prefer-as-int (other types)', preferAsInt, {
|
|
184
164
|
valid: [],
|
|
185
165
|
invalid: [
|
|
166
|
+
{
|
|
167
|
+
name: 'replaces multiple as Int with asInt imports',
|
|
168
|
+
code: dedent`
|
|
169
|
+
const n = 1;
|
|
170
|
+
const result = n as Int;
|
|
171
|
+
const another = (n + 1) as Int;
|
|
172
|
+
`,
|
|
173
|
+
output: [
|
|
174
|
+
dedent`
|
|
175
|
+
import { asInt } from 'ts-data-forge';
|
|
176
|
+
const n = 1;
|
|
177
|
+
const result = asInt(n);
|
|
178
|
+
const another = (n + 1) as Int;
|
|
179
|
+
`,
|
|
180
|
+
dedent`
|
|
181
|
+
import { asInt } from 'ts-data-forge';
|
|
182
|
+
const n = 1;
|
|
183
|
+
const result = asInt(n);
|
|
184
|
+
const another = asInt(n + 1);
|
|
185
|
+
`,
|
|
186
|
+
],
|
|
187
|
+
errors: [
|
|
188
|
+
{ messageId: 'useBrandedNumberCastFunction' },
|
|
189
|
+
{ messageId: 'useBrandedNumberCastFunction' },
|
|
190
|
+
],
|
|
191
|
+
},
|
|
186
192
|
{
|
|
187
193
|
name: 'replaces as Uint32 with asUint32',
|
|
188
194
|
code: dedent`
|
|
@@ -239,17 +245,14 @@ describe('prefer-as-int', () => {
|
|
|
239
245
|
const c = n as SafeInt;
|
|
240
246
|
`,
|
|
241
247
|
dedent`
|
|
242
|
-
import { asUint32 } from 'ts-data-forge';
|
|
243
|
-
import { asInt } from 'ts-data-forge';
|
|
248
|
+
import { asInt, asUint32 } from 'ts-data-forge';
|
|
244
249
|
const n = 1;
|
|
245
250
|
const a = asInt(n);
|
|
246
251
|
const b = asUint32(n);
|
|
247
252
|
const c = n as SafeInt;
|
|
248
253
|
`,
|
|
249
254
|
dedent`
|
|
250
|
-
import { asSafeInt } from 'ts-data-forge';
|
|
251
|
-
import { asUint32 } from 'ts-data-forge';
|
|
252
|
-
import { asInt } from 'ts-data-forge';
|
|
255
|
+
import { asInt, asUint32, asSafeInt } from 'ts-data-forge';
|
|
253
256
|
const n = 1;
|
|
254
257
|
const a = asInt(n);
|
|
255
258
|
const b = asUint32(n);
|
|
@@ -316,3 +319,50 @@ describe('prefer-as-int with parenthesized operands', () => {
|
|
|
316
319
|
],
|
|
317
320
|
});
|
|
318
321
|
}, 20000);
|
|
322
|
+
|
|
323
|
+
describe('prefer-as-int and the import it adds', () => {
|
|
324
|
+
tester.run('prefer-as-int', preferAsInt, {
|
|
325
|
+
valid: [],
|
|
326
|
+
invalid: [
|
|
327
|
+
{
|
|
328
|
+
name: 'writes the local name of an aliased import',
|
|
329
|
+
code: dedent`
|
|
330
|
+
import { asInt as toInt } from 'ts-data-forge';
|
|
331
|
+
const n = 1;
|
|
332
|
+
const result = n as Int;
|
|
333
|
+
`,
|
|
334
|
+
output: dedent`
|
|
335
|
+
import { asInt as toInt } from 'ts-data-forge';
|
|
336
|
+
const n = 1;
|
|
337
|
+
const result = toInt(n);
|
|
338
|
+
`,
|
|
339
|
+
errors: [
|
|
340
|
+
{
|
|
341
|
+
messageId: 'useBrandedNumberCastFunction',
|
|
342
|
+
data: { typeName: 'Int', functionName: 'toInt' },
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'no fix when an import type binds the name',
|
|
348
|
+
code: dedent`
|
|
349
|
+
import type { asInt } from 'ts-data-forge';
|
|
350
|
+
const n = 1;
|
|
351
|
+
const result = n as Int;
|
|
352
|
+
`,
|
|
353
|
+
output: null,
|
|
354
|
+
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'no fix where a local binding takes the name',
|
|
358
|
+
code: dedent`
|
|
359
|
+
const asInt = (x: number): number => x;
|
|
360
|
+
const n = 1;
|
|
361
|
+
const result = n as Int;
|
|
362
|
+
`,
|
|
363
|
+
output: null,
|
|
364
|
+
errors: [{ messageId: 'useBrandedNumberCastFunction' }],
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
}, 20000);
|
|
@@ -5,11 +5,14 @@ import {
|
|
|
5
5
|
type TSESTree,
|
|
6
6
|
} from '@typescript-eslint/utils';
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
bindValueImports,
|
|
9
9
|
getImportDeclarations,
|
|
10
10
|
getImportedLocalName,
|
|
11
|
+
hasForeignBinding,
|
|
12
|
+
importedNameOf,
|
|
11
13
|
skipTypeWrappers,
|
|
12
14
|
typeWrapperLayers,
|
|
15
|
+
type ValueImportBinding,
|
|
13
16
|
} from 'eslint-rule-forge';
|
|
14
17
|
import type { ReadonlyRecord } from 'ts-type-forge';
|
|
15
18
|
import * as ts from 'typescript';
|
|
@@ -84,8 +87,8 @@ const asNegatedPositiveIntegerLiteral = (
|
|
|
84
87
|
|
|
85
88
|
/**
|
|
86
89
|
* Whether re-reading the expression is free of side effects (a plain variable
|
|
87
|
-
* or a non-computed property chain, type wrappers aside). Required for
|
|
88
|
-
*
|
|
90
|
+
* or a non-computed property chain, type wrappers aside). Required for patterns
|
|
91
|
+
* that reference the receiver twice, such as
|
|
89
92
|
* `xs.filter((_, i) => i < xs.length - 1)`.
|
|
90
93
|
*/
|
|
91
94
|
const isSimpleReference = (wrapped: TSESTree.Node): boolean => {
|
|
@@ -210,10 +213,16 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
210
213
|
const checker = parserServices.program.getTypeChecker();
|
|
211
214
|
|
|
212
215
|
/**
|
|
213
|
-
* The in-scope way to reference the `Arr` namespace
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
+
* The in-scope way to reference the `Arr` namespace, the name the reference
|
|
217
|
+
* starts with, and the edits that bring it into scope: a value
|
|
218
|
+
* import of `Arr` (possibly aliased), a namespace import member, or `Arr`
|
|
219
|
+
* bound by {@link bindValueImports}, which adds it to an existing import or
|
|
220
|
+
* drops `type` from one. `undefined` when none can: `import type { Arr }`
|
|
221
|
+
* with no namespace import.
|
|
216
222
|
*/
|
|
223
|
+
const arrTarget = resolveArrTarget(sourceCode, tsDataForgeImports);
|
|
224
|
+
|
|
225
|
+
/** How a call already in the file names `Arr`, to recognize it. */
|
|
217
226
|
const arrLocalName = getImportedLocalName(tsDataForgeImports, 'Arr');
|
|
218
227
|
|
|
219
228
|
const namespaceLocalName = tsDataForgeImports
|
|
@@ -223,16 +232,11 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
223
232
|
specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,
|
|
224
233
|
)?.local.name;
|
|
225
234
|
|
|
226
|
-
const arrRef =
|
|
227
|
-
arrLocalName ??
|
|
228
|
-
(namespaceLocalName === undefined ? 'Arr' : `${namespaceLocalName}.Arr`);
|
|
229
|
-
|
|
230
|
-
const needsArrImport =
|
|
231
|
-
arrLocalName === undefined && namespaceLocalName === undefined;
|
|
235
|
+
const arrRef = arrTarget?.ref ?? 'Arr';
|
|
232
236
|
|
|
233
237
|
/**
|
|
234
|
-
* Deferred (non-concrete) types are reduced to their base constraint
|
|
235
|
-
*
|
|
238
|
+
* Deferred (non-concrete) types are reduced to their base constraint before
|
|
239
|
+
* checking; `any` / `unknown` are conservatively not array-like.
|
|
236
240
|
*/
|
|
237
241
|
const isArrayOrTupleType = (type: ts.Type): boolean => {
|
|
238
242
|
if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {
|
|
@@ -394,8 +398,8 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
394
398
|
|
|
395
399
|
/**
|
|
396
400
|
* Matches an index-only filter callback (`(_, i) => <comparison>`) and
|
|
397
|
-
* classifies the comparison. Both a leading index (`i >= 1`) and a
|
|
398
|
-
*
|
|
401
|
+
* classifies the comparison. Both a leading index (`i >= 1`) and a trailing
|
|
402
|
+
* index (`1 <= i`) operand order are accepted.
|
|
399
403
|
*/
|
|
400
404
|
const classifyFilterCallback = (
|
|
401
405
|
callback: TSESTree.CallExpressionArgument,
|
|
@@ -467,10 +471,10 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
467
471
|
return undefined;
|
|
468
472
|
}
|
|
469
473
|
|
|
470
|
-
// The element parameter must not participate in the comparison (only
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
+
// The element parameter must not participate in the comparison (only the
|
|
475
|
+
// index side and a receiver-independent bound are recognized, so reaching
|
|
476
|
+
// here with an element reference is impossible for literals, but
|
|
477
|
+
// `xs.length - N` needs a stable receiver).
|
|
474
478
|
const n = asNonNegativeIntegerLiteral(other);
|
|
475
479
|
|
|
476
480
|
if (n !== undefined) {
|
|
@@ -720,8 +724,8 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
720
724
|
return;
|
|
721
725
|
}
|
|
722
726
|
|
|
723
|
-
// The receiver must be an array or tuple (not a string / typed array
|
|
724
|
-
//
|
|
727
|
+
// The receiver must be an array or tuple (not a string / typed array /
|
|
728
|
+
// other object that happens to share the method name).
|
|
725
729
|
if (!holdsThroughWrappers(receiver, isArrayOrTupleType)) {
|
|
726
730
|
return;
|
|
727
731
|
}
|
|
@@ -794,8 +798,8 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
794
798
|
return;
|
|
795
799
|
}
|
|
796
800
|
|
|
797
|
-
// `xs.concat(item)` appends only when `item` can never be an
|
|
798
|
-
//
|
|
801
|
+
// `xs.concat(item)` appends only when `item` can never be an array
|
|
802
|
+
// (otherwise `concat` would flatten it).
|
|
799
803
|
if (holdsThroughWrappers(arg, isDefinitelyNotArrayType)) {
|
|
800
804
|
report(
|
|
801
805
|
node,
|
|
@@ -835,8 +839,7 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
835
839
|
: [undefined, undefined, undefined];
|
|
836
840
|
|
|
837
841
|
// `spread`, `item`, and `fn` are set together (all from the same
|
|
838
|
-
// tuple), so checking `spread` also guarantees the others are
|
|
839
|
-
// defined.
|
|
842
|
+
// tuple), so checking `spread` also guarantees the others are defined.
|
|
840
843
|
if (spread === undefined) {
|
|
841
844
|
return;
|
|
842
845
|
}
|
|
@@ -855,27 +858,85 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
855
858
|
},
|
|
856
859
|
|
|
857
860
|
'Program:exit': () => {
|
|
858
|
-
|
|
861
|
+
// Every fix brings `Arr` into scope itself, so that one applied alone
|
|
862
|
+
// (an editor's quick fix) is complete.
|
|
863
|
+
for (const nodeInfo of mut_nodesToFix) {
|
|
864
|
+
const writable =
|
|
865
|
+
arrTarget !== undefined &&
|
|
866
|
+
!hasForeignBinding(
|
|
867
|
+
sourceCode,
|
|
868
|
+
nodeInfo.node,
|
|
869
|
+
arrTarget.boundName,
|
|
870
|
+
TS_DATA_FORGE_MODULE,
|
|
871
|
+
);
|
|
872
|
+
|
|
859
873
|
context.report({
|
|
860
874
|
node: nodeInfo.node,
|
|
861
875
|
messageId: 'preferCanonicalArraySlicing',
|
|
862
876
|
data: { replacement: nodeInfo.replacement },
|
|
863
|
-
fix:
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
: [];
|
|
870
|
-
|
|
871
|
-
return [
|
|
872
|
-
...importFixes,
|
|
873
|
-
fixer.replaceText(nodeInfo.node, nodeInfo.replacement),
|
|
874
|
-
];
|
|
875
|
-
},
|
|
877
|
+
fix: writable
|
|
878
|
+
? (fixer) => [
|
|
879
|
+
...arrTarget.edits(fixer),
|
|
880
|
+
fixer.replaceText(nodeInfo.node, nodeInfo.replacement),
|
|
881
|
+
]
|
|
882
|
+
: null,
|
|
876
883
|
});
|
|
877
884
|
}
|
|
878
885
|
},
|
|
879
886
|
};
|
|
880
887
|
},
|
|
881
888
|
} as const;
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* How `Arr` is written in this file; see where it is called. A value import of
|
|
892
|
+
* `Arr` comes first, then a namespace import, and only then an edit.
|
|
893
|
+
*/
|
|
894
|
+
const resolveArrTarget = (
|
|
895
|
+
sourceCode: TSESLint.SourceCode,
|
|
896
|
+
tsDataForgeImports: readonly TSESTree.ImportDeclaration[],
|
|
897
|
+
):
|
|
898
|
+
| Readonly<{
|
|
899
|
+
ref: string;
|
|
900
|
+
boundName: string;
|
|
901
|
+
edits: ValueImportBinding['edits'];
|
|
902
|
+
}>
|
|
903
|
+
| undefined => {
|
|
904
|
+
const valueSpecifiers = tsDataForgeImports
|
|
905
|
+
.filter((declaration) => declaration.importKind !== 'type')
|
|
906
|
+
.flatMap((declaration) => declaration.specifiers);
|
|
907
|
+
|
|
908
|
+
const arrImport = valueSpecifiers.find(
|
|
909
|
+
(specifier) =>
|
|
910
|
+
specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
911
|
+
specifier.importKind !== 'type' &&
|
|
912
|
+
importedNameOf(specifier) === 'Arr',
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
if (arrImport !== undefined) {
|
|
916
|
+
return {
|
|
917
|
+
ref: arrImport.local.name,
|
|
918
|
+
boundName: arrImport.local.name,
|
|
919
|
+
edits: () => [],
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const namespace = valueSpecifiers.find(
|
|
924
|
+
(specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
if (namespace !== undefined) {
|
|
928
|
+
return {
|
|
929
|
+
ref: `${namespace.local.name}.Arr`,
|
|
930
|
+
boundName: namespace.local.name,
|
|
931
|
+
edits: () => [],
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const binding = bindValueImports(sourceCode, TS_DATA_FORGE_MODULE, ['Arr']);
|
|
936
|
+
|
|
937
|
+
const localName = binding?.localNames[0];
|
|
938
|
+
|
|
939
|
+
return binding === undefined || localName === undefined
|
|
940
|
+
? undefined
|
|
941
|
+
: { ref: localName, boundName: localName, edits: binding.edits };
|
|
942
|
+
};
|