eslint-plugin-ts-data-forge 0.1.7 → 0.2.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.
- package/README.md +47 -19
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/plugin.d.mts +44 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +34 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +3 -63
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.d.mts +6 -0
- package/dist/rules/prefer-canonical-length-guard.d.mts.map +1 -0
- package/dist/rules/prefer-canonical-length-guard.mjs +125 -0
- package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -0
- package/dist/rules/rules.d.mts +1 -5
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +2 -10
- package/dist/rules/rules.mjs.map +1 -1
- package/dist/types.d.mts +1 -0
- package/dist/types.d.mts.map +1 -1
- package/package.json +6 -2
- package/src/index.mts +1 -1
- package/src/plugin.mts +46 -3
- package/src/plugin.test.mts +24 -0
- package/src/rule-types.mts +3 -67
- package/src/rules/prefer-canonical-length-guard.mts +231 -0
- package/src/rules/prefer-canonical-length-guard.test.mts +307 -0
- package/src/rules/rules.mts +2 -10
- package/src/types.mts +2 -0
package/src/plugin.mts
CHANGED
|
@@ -1,9 +1,52 @@
|
|
|
1
1
|
import { tsDataForgeRules } from './rules/index.mjs';
|
|
2
|
-
import { type ESLintPlugin } from './types.mjs';
|
|
2
|
+
import { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Every rule this plugin ships, at `error`.
|
|
6
|
+
*
|
|
7
|
+
* The `satisfies` clause below is keyed off {@link tsDataForgeRules}, so adding
|
|
8
|
+
* a rule without listing it here fails to type-check.
|
|
9
|
+
*/
|
|
10
|
+
const recommendedRules = {
|
|
11
|
+
'ts-data-forge/prefer-canonical-array-slicing': 'error',
|
|
12
|
+
'ts-data-forge/prefer-canonical-length-guard': 'error',
|
|
13
|
+
'ts-data-forge/prefer-arr-is-array': 'error',
|
|
14
|
+
'ts-data-forge/prefer-arr-sum': 'error',
|
|
15
|
+
'ts-data-forge/prefer-as-int': 'error',
|
|
16
|
+
'ts-data-forge/prefer-is-non-null-object': 'error',
|
|
17
|
+
'ts-data-forge/prefer-range-for-loop': 'error',
|
|
18
|
+
'ts-data-forge/prefer-is-record-and-has-key': 'error',
|
|
19
|
+
'ts-data-forge/prefer-num-safe-parse-int': 'error',
|
|
20
|
+
'ts-data-forge/prefer-num-safe-parse-float': 'error',
|
|
21
|
+
'ts-data-forge/no-unnecessary-type-guard': 'error',
|
|
22
|
+
'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',
|
|
23
|
+
} as const satisfies Readonly<
|
|
24
|
+
Record<`ts-data-forge/${keyof typeof tsDataForgeRules}`, 'error'>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
const recommendedConfig = {
|
|
28
|
+
name: 'ts-data-forge/recommended',
|
|
29
|
+
plugins: {
|
|
30
|
+
// Resolved lazily so that this config registers the *same* object that
|
|
31
|
+
// `eslintPluginTsDataForge` refers to. ESLint rejects a plugin name that
|
|
32
|
+
// maps to two different objects with `Cannot redefine plugin`, which is
|
|
33
|
+
// what a user combining this preset with their own
|
|
34
|
+
// `plugins: { 'ts-data-forge': eslintPluginTsDataForge }` entry would hit
|
|
35
|
+
// if the preset embedded a separate copy of the plugin.
|
|
36
|
+
get 'ts-data-forge'(): ESLintPlugin {
|
|
37
|
+
return eslintPluginTsDataForge;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
rules: recommendedRules,
|
|
41
|
+
} as const satisfies ESLintFlatConfig;
|
|
42
|
+
|
|
43
|
+
export const eslintPluginTsDataForge = {
|
|
5
44
|
meta: {
|
|
6
45
|
name: 'eslint-plugin-ts-data-forge',
|
|
7
46
|
},
|
|
8
47
|
rules: tsDataForgeRules,
|
|
9
|
-
|
|
48
|
+
configs: {
|
|
49
|
+
/** Enables every rule of this plugin at `error`. */
|
|
50
|
+
recommended: recommendedConfig,
|
|
51
|
+
},
|
|
52
|
+
} as const satisfies ESLintPlugin;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { eslintPluginTsDataForge } from './plugin.mjs';
|
|
2
|
+
import { tsDataForgeRules } from './rules/index.mjs';
|
|
3
|
+
|
|
4
|
+
describe('eslintPluginTsDataForge.configs.recommended', () => {
|
|
5
|
+
const recommended = eslintPluginTsDataForge.configs.recommended;
|
|
6
|
+
|
|
7
|
+
test('enables every rule of the plugin, and nothing else, at "error"', () => {
|
|
8
|
+
assert.deepStrictEqual(
|
|
9
|
+
recommended.rules,
|
|
10
|
+
Object.fromEntries(
|
|
11
|
+
Object.keys(tsDataForgeRules).map((name) => [
|
|
12
|
+
`ts-data-forge/${name}`,
|
|
13
|
+
'error',
|
|
14
|
+
]),
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('registers the exported plugin object itself', () => {
|
|
20
|
+
// Registering a *copy* would make `Cannot redefine plugin` errors possible
|
|
21
|
+
// for users who also list the plugin in their own `plugins` record.
|
|
22
|
+
expect(recommended.plugins['ts-data-forge']).toBe(eslintPluginTsDataForge);
|
|
23
|
+
});
|
|
24
|
+
});
|
package/src/rule-types.mts
CHANGED
|
@@ -43,7 +43,7 @@ namespace PreferCanonicalArraySlicing {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* @description
|
|
46
|
+
* @description Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and degenerate guards (e.g. `Arr.isFixedLengthTuple(xs, 0)`) become `Arr.isEmpty` / `Arr.isNonEmpty`.
|
|
47
47
|
*
|
|
48
48
|
* ```md
|
|
49
49
|
* | key | value |
|
|
@@ -53,52 +53,7 @@ namespace PreferCanonicalArraySlicing {
|
|
|
53
53
|
* | fixable | code |
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
|
-
namespace
|
|
57
|
-
export type RuleEntry = Linter.StringSeverity;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @description Replace `xs.length <= n` with `Arr.isMaxLengthArray(xs, n)` from ts-data-forge.
|
|
62
|
-
*
|
|
63
|
-
* ```md
|
|
64
|
-
* | key | value |
|
|
65
|
-
* | :--------- | :--------- |
|
|
66
|
-
* | type | suggestion |
|
|
67
|
-
* | deprecated | false |
|
|
68
|
-
* | fixable | code |
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
namespace PreferArrIsMaxLengthArray {
|
|
72
|
-
export type RuleEntry = Linter.StringSeverity;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @description Replace `xs.length >= min && xs.length <= max` with `Arr.isBoundedLengthArray(xs, min, max)` from ts-data-forge.
|
|
77
|
-
*
|
|
78
|
-
* ```md
|
|
79
|
-
* | key | value |
|
|
80
|
-
* | :--------- | :--------- |
|
|
81
|
-
* | type | suggestion |
|
|
82
|
-
* | deprecated | false |
|
|
83
|
-
* | fixable | code |
|
|
84
|
-
* ```
|
|
85
|
-
*/
|
|
86
|
-
namespace PreferArrIsBoundedLengthArray {
|
|
87
|
-
export type RuleEntry = Linter.StringSeverity;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @description Replace `xs.length === n` with `Arr.isFixedLengthArray(xs, n)` from ts-data-forge.
|
|
92
|
-
*
|
|
93
|
-
* ```md
|
|
94
|
-
* | key | value |
|
|
95
|
-
* | :--------- | :--------- |
|
|
96
|
-
* | type | suggestion |
|
|
97
|
-
* | deprecated | false |
|
|
98
|
-
* | fixable | code |
|
|
99
|
-
* ```
|
|
100
|
-
*/
|
|
101
|
-
namespace PreferArrIsFixedLengthArray {
|
|
56
|
+
namespace PreferCanonicalLengthGuard {
|
|
102
57
|
export type RuleEntry = Linter.StringSeverity;
|
|
103
58
|
}
|
|
104
59
|
|
|
@@ -117,21 +72,6 @@ namespace PreferArrIsArray {
|
|
|
117
72
|
export type RuleEntry = Linter.StringSeverity;
|
|
118
73
|
}
|
|
119
74
|
|
|
120
|
-
/**
|
|
121
|
-
* @description Replace `xs.length > 0` with `Arr.isNonEmpty(xs)` from ts-data-forge.
|
|
122
|
-
*
|
|
123
|
-
* ```md
|
|
124
|
-
* | key | value |
|
|
125
|
-
* | :--------- | :--------- |
|
|
126
|
-
* | type | suggestion |
|
|
127
|
-
* | deprecated | false |
|
|
128
|
-
* | fixable | code |
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
|
-
namespace PreferArrIsNonEmpty {
|
|
132
|
-
export type RuleEntry = Linter.StringSeverity;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
75
|
/**
|
|
136
76
|
* @description Replace `xs.reduce((a, b) => a + b, 0)` with `Arr.sum(xs)` or `Arr.sumBy(xs, fn)` from ts-data-forge.
|
|
137
77
|
*
|
|
@@ -297,12 +237,8 @@ namespace PreferComparisonOverNullishGuard {
|
|
|
297
237
|
|
|
298
238
|
export type EslintTsDataForgeRules = Readonly<{
|
|
299
239
|
'ts-data-forge/prefer-canonical-array-slicing': PreferCanonicalArraySlicing.RuleEntry;
|
|
300
|
-
'ts-data-forge/prefer-
|
|
301
|
-
'ts-data-forge/prefer-arr-is-max-length-array': PreferArrIsMaxLengthArray.RuleEntry;
|
|
302
|
-
'ts-data-forge/prefer-arr-is-bounded-length-array': PreferArrIsBoundedLengthArray.RuleEntry;
|
|
303
|
-
'ts-data-forge/prefer-arr-is-fixed-length-array': PreferArrIsFixedLengthArray.RuleEntry;
|
|
240
|
+
'ts-data-forge/prefer-canonical-length-guard': PreferCanonicalLengthGuard.RuleEntry;
|
|
304
241
|
'ts-data-forge/prefer-arr-is-array': PreferArrIsArray.RuleEntry;
|
|
305
|
-
'ts-data-forge/prefer-arr-is-non-empty': PreferArrIsNonEmpty.RuleEntry;
|
|
306
242
|
'ts-data-forge/prefer-arr-sum': PreferArrSum.RuleEntry;
|
|
307
243
|
'ts-data-forge/prefer-as-int': PreferAsInt.RuleEntry;
|
|
308
244
|
'ts-data-forge/prefer-is-non-null-object': PreferIsNonNullObject.RuleEntry;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
type TSESLint,
|
|
4
|
+
type TSESTree,
|
|
5
|
+
} from '@typescript-eslint/utils';
|
|
6
|
+
import { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';
|
|
7
|
+
import { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';
|
|
8
|
+
import { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';
|
|
9
|
+
import { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';
|
|
10
|
+
import { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';
|
|
11
|
+
import { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';
|
|
12
|
+
|
|
13
|
+
type Options = readonly [];
|
|
14
|
+
|
|
15
|
+
type MessageIds =
|
|
16
|
+
| 'useCanonicalGuard'
|
|
17
|
+
| 'useIsBoundedLengthArray'
|
|
18
|
+
| 'useIsFixedLengthArray'
|
|
19
|
+
| 'useIsMaxLengthArray'
|
|
20
|
+
| 'useIsMinLengthArray'
|
|
21
|
+
| 'useIsNonEmpty';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The `xs.length <op> n` → `Arr.is*` rules folded into this one.
|
|
25
|
+
*
|
|
26
|
+
* Their implementations are reused as-is rather than rewritten: this rule
|
|
27
|
+
* merges their visitors, so every case they already cover (type-aware array
|
|
28
|
+
* checks, `Arr` import insertion, the `&&`-bounded-range special case) keeps
|
|
29
|
+
* behaving exactly as before, and their co-located tests keep exercising them
|
|
30
|
+
* directly.
|
|
31
|
+
*/
|
|
32
|
+
const COMPARISON_RULES = [
|
|
33
|
+
preferArrIsNonEmpty,
|
|
34
|
+
preferArrIsMinLengthArray,
|
|
35
|
+
preferArrIsMaxLengthArray,
|
|
36
|
+
preferArrIsBoundedLengthArray,
|
|
37
|
+
preferArrIsFixedLengthArray,
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
const MESSAGES = {
|
|
41
|
+
useCanonicalGuard:
|
|
42
|
+
'Replace `{{arrName}}.{{guard}}({{boundsText}})` with `{{arrName}}.{{replacement}}(...)`: the bound makes the two guards equivalent.',
|
|
43
|
+
useIsNonEmpty: preferArrIsNonEmpty.meta.messages.useIsNonEmpty,
|
|
44
|
+
useIsMinLengthArray:
|
|
45
|
+
preferArrIsMinLengthArray.meta.messages.useIsMinLengthArray,
|
|
46
|
+
useIsMaxLengthArray:
|
|
47
|
+
preferArrIsMaxLengthArray.meta.messages.useIsMaxLengthArray,
|
|
48
|
+
useIsBoundedLengthArray:
|
|
49
|
+
preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,
|
|
50
|
+
useIsFixedLengthArray:
|
|
51
|
+
preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray,
|
|
52
|
+
} as const satisfies Record<MessageIds, string>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Length guards whose bound makes them redundant with `Arr.isEmpty` /
|
|
56
|
+
* `Arr.isNonEmpty`, keyed by guard name.
|
|
57
|
+
*
|
|
58
|
+
* `isEmpty` narrows to `FixedLengthArray<0, E> & Xs` and `isNonEmpty` to
|
|
59
|
+
* `MinLengthArray<1, E> & Xs`, so:
|
|
60
|
+
*
|
|
61
|
+
* - The `*Array` entries are **type-identical** — `isFixedLengthArray(xs, 0)`
|
|
62
|
+
* and `isMinLengthArray(xs, 1)` produce exactly those types.
|
|
63
|
+
* - The `*Tuple` entries **narrow**: they resolve to the structural
|
|
64
|
+
* `readonly []` / `readonly [E, ...E[]]`, and the canonical guards add the
|
|
65
|
+
* brand on top. The result stays assignable everywhere the old type was, so
|
|
66
|
+
* the rewrite is safe, but it is a strengthening rather than a pure rename.
|
|
67
|
+
*/
|
|
68
|
+
const GUARD_REWRITES = [
|
|
69
|
+
{ guard: 'isFixedLengthArray', bounds: [0], replacement: 'isEmpty' },
|
|
70
|
+
{ guard: 'isFixedLengthTuple', bounds: [0], replacement: 'isEmpty' },
|
|
71
|
+
{ guard: 'isMaxLengthTuple', bounds: [0], replacement: 'isEmpty' },
|
|
72
|
+
{ guard: 'isMaxLengthArray', bounds: [0], replacement: 'isEmpty' },
|
|
73
|
+
{ guard: 'isBoundedLengthTuple', bounds: [0, 0], replacement: 'isEmpty' },
|
|
74
|
+
{ guard: 'isBoundedLengthArray', bounds: [0, 0], replacement: 'isEmpty' },
|
|
75
|
+
{ guard: 'isMinLengthArray', bounds: [1], replacement: 'isNonEmpty' },
|
|
76
|
+
{ guard: 'isMinLengthTuple', bounds: [1], replacement: 'isNonEmpty' },
|
|
77
|
+
] as const satisfies readonly Readonly<{
|
|
78
|
+
guard: string;
|
|
79
|
+
bounds: readonly number[];
|
|
80
|
+
replacement: string;
|
|
81
|
+
}>[];
|
|
82
|
+
|
|
83
|
+
export const preferCanonicalLengthGuard: TSESLint.RuleModule<
|
|
84
|
+
MessageIds,
|
|
85
|
+
Options
|
|
86
|
+
> = {
|
|
87
|
+
meta: {
|
|
88
|
+
type: 'suggestion',
|
|
89
|
+
docs: {
|
|
90
|
+
description:
|
|
91
|
+
'Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and degenerate guards (e.g. `Arr.isFixedLengthTuple(xs, 0)`) become `Arr.isEmpty` / `Arr.isNonEmpty`.',
|
|
92
|
+
},
|
|
93
|
+
fixable: 'code',
|
|
94
|
+
schema: [],
|
|
95
|
+
messages: MESSAGES,
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
create: (context) => {
|
|
99
|
+
const sourceCode = context.sourceCode;
|
|
100
|
+
|
|
101
|
+
const arrLocalName = getImportedLocalName(
|
|
102
|
+
getTsDataForgeImport(sourceCode.ast),
|
|
103
|
+
'Arr',
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const comparisonVisitors = COMPARISON_RULES.map((rule) =>
|
|
107
|
+
rule.create(context),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (arrLocalName === undefined) return mergeVisitors(comparisonVisitors);
|
|
111
|
+
|
|
112
|
+
return mergeVisitors([
|
|
113
|
+
...comparisonVisitors,
|
|
114
|
+
{
|
|
115
|
+
CallExpression: (node) => {
|
|
116
|
+
const guardName = getGuardName(node, arrLocalName);
|
|
117
|
+
|
|
118
|
+
if (guardName === undefined) return;
|
|
119
|
+
|
|
120
|
+
const rewrite = GUARD_REWRITES.find(
|
|
121
|
+
(entry) => entry.guard === guardName,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (rewrite === undefined || !matchesBounds(node, rewrite.bounds)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const [array] = node.arguments;
|
|
129
|
+
|
|
130
|
+
if (array === undefined) return;
|
|
131
|
+
|
|
132
|
+
const arrayText = sourceCode.getText(array);
|
|
133
|
+
|
|
134
|
+
context.report({
|
|
135
|
+
node,
|
|
136
|
+
messageId: 'useCanonicalGuard',
|
|
137
|
+
data: {
|
|
138
|
+
arrName: arrLocalName,
|
|
139
|
+
guard: guardName,
|
|
140
|
+
boundsText: ['…', ...rewrite.bounds.map(String)].join(', '),
|
|
141
|
+
replacement: rewrite.replacement,
|
|
142
|
+
},
|
|
143
|
+
fix: (fixer) =>
|
|
144
|
+
fixer.replaceText(
|
|
145
|
+
node,
|
|
146
|
+
`${arrLocalName}.${rewrite.replacement}(${arrayText})`,
|
|
147
|
+
),
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
},
|
|
153
|
+
defaultOptions: [],
|
|
154
|
+
} as const;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Combines several rule listeners into one, calling every handler registered
|
|
158
|
+
* for a given selector in order.
|
|
159
|
+
*/
|
|
160
|
+
const mergeVisitors = (
|
|
161
|
+
// `RuleListener` holds mutable AST handler signatures, so it is not deeply
|
|
162
|
+
// readonly.
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
164
|
+
visitors: readonly TSESLint.RuleListener[],
|
|
165
|
+
): TSESLint.RuleListener => {
|
|
166
|
+
const mut_merged: Record<string, ((node: never) => void)[]> = {};
|
|
167
|
+
|
|
168
|
+
for (const visitor of visitors) {
|
|
169
|
+
for (const [selector, handler] of Object.entries(visitor)) {
|
|
170
|
+
if (typeof handler !== 'function') continue;
|
|
171
|
+
|
|
172
|
+
const mut_handlers = mut_merged[selector] ?? [];
|
|
173
|
+
|
|
174
|
+
mut_handlers.push(handler);
|
|
175
|
+
|
|
176
|
+
mut_merged[selector] = mut_handlers;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return Object.fromEntries(
|
|
181
|
+
Object.entries(mut_merged).map(([selector, handlers]) => [
|
|
182
|
+
selector,
|
|
183
|
+
(node: never) => {
|
|
184
|
+
for (const handler of handlers) {
|
|
185
|
+
handler(node);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
]),
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** The `Arr.<guard>` method name of `node`, or `undefined` for other calls. */
|
|
193
|
+
const getGuardName = (
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
195
|
+
node: TSESTree.CallExpression,
|
|
196
|
+
arrLocalName: string,
|
|
197
|
+
): string | undefined => {
|
|
198
|
+
const { callee } = node;
|
|
199
|
+
|
|
200
|
+
return callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
201
|
+
!callee.computed &&
|
|
202
|
+
callee.object.type === AST_NODE_TYPES.Identifier &&
|
|
203
|
+
callee.object.name === arrLocalName &&
|
|
204
|
+
callee.property.type === AST_NODE_TYPES.Identifier
|
|
205
|
+
? callee.property.name
|
|
206
|
+
: undefined;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Whether the call is `guard(array, ...bounds)` with every bound written as the
|
|
211
|
+
* exact numeric literal the rewrite requires.
|
|
212
|
+
*/
|
|
213
|
+
const matchesBounds = (
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
215
|
+
node: TSESTree.CallExpression,
|
|
216
|
+
bounds: readonly number[],
|
|
217
|
+
): boolean => {
|
|
218
|
+
const args = node.arguments;
|
|
219
|
+
|
|
220
|
+
if (args.length !== bounds.length + 1 || node.typeArguments !== undefined) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return args.every(
|
|
225
|
+
(arg, index) =>
|
|
226
|
+
arg.type !== AST_NODE_TYPES.SpreadElement &&
|
|
227
|
+
(index === 0 ||
|
|
228
|
+
(arg.type === AST_NODE_TYPES.Literal &&
|
|
229
|
+
arg.value === bounds[index - 1])),
|
|
230
|
+
);
|
|
231
|
+
};
|