eslint-plugin-ts-data-forge 0.8.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -19
- package/dist/plugin.d.mts +2 -0
- 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 +16 -1
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rule-types.mjs.map +1 -1
- package/dist/rules/ast-utils.d.mts +21 -2
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +51 -4
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/constants.d.mts +2 -0
- package/dist/rules/constants.d.mts.map +1 -1
- package/dist/rules/constants.mjs +9 -0
- package/dist/rules/constants.mjs.map +1 -1
- package/dist/rules/mutable-result.d.mts +10 -0
- package/dist/rules/mutable-result.d.mts.map +1 -0
- package/dist/rules/mutable-result.mjs +145 -0
- package/dist/rules/mutable-result.mjs.map +1 -0
- package/dist/rules/prefer-arr-generate.d.mts +57 -0
- package/dist/rules/prefer-arr-generate.d.mts.map +1 -0
- package/dist/rules/prefer-arr-generate.mjs +588 -0
- package/dist/rules/prefer-arr-generate.mjs.map +1 -0
- package/dist/rules/prefer-arr-uniq.d.mts.map +1 -1
- package/dist/rules/prefer-arr-uniq.mjs +2 -149
- package/dist/rules/prefer-arr-uniq.mjs.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.mjs +47 -28
- package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
- package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
- package/dist/rules/prefer-is-non-null-object.mjs +110 -40
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.mjs +102 -45
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-range-for-loop.d.mts.map +1 -1
- package/dist/rules/prefer-range-for-loop.mjs +97 -16
- package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
- package/dist/rules/rules.d.mts +1 -0
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +2 -0
- package/dist/rules/rules.mjs.map +1 -1
- package/package.json +11 -11
- package/src/plugin.mts +1 -0
- package/src/rule-types.mts +17 -1
- package/src/rules/ast-utils.mts +72 -5
- package/src/rules/constants.mts +12 -0
- package/src/rules/mutable-result.mts +204 -0
- package/src/rules/prefer-arr-generate.mts +918 -0
- package/src/rules/prefer-arr-generate.test.mts +967 -0
- package/src/rules/prefer-arr-is-bounded-length-array.test.mts +19 -0
- package/src/rules/prefer-arr-is-max-length-array.test.mts +8 -0
- package/src/rules/prefer-arr-is-min-length-array.test.mts +8 -0
- package/src/rules/prefer-arr-uniq.mts +2 -207
- package/src/rules/prefer-canonical-array-slicing.mts +57 -40
- package/src/rules/prefer-canonical-array-slicing.test.mts +55 -0
- package/src/rules/prefer-is-non-null-object.mts +160 -57
- package/src/rules/prefer-is-non-null-object.test.mts +138 -0
- package/src/rules/prefer-is-record-and-has-key.mts +148 -67
- package/src/rules/prefer-is-record-and-has-key.test.mts +118 -23
- package/src/rules/prefer-range-for-loop.mts +142 -21
- package/src/rules/prefer-range-for-loop.test.mts +127 -5
- package/src/rules/rules.mts +2 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AST_NODE_TYPES,
|
|
3
|
+
ASTUtils,
|
|
3
4
|
type TSESLint,
|
|
4
5
|
type TSESTree,
|
|
5
6
|
} from '@typescript-eslint/utils';
|
|
@@ -42,12 +43,47 @@ export const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
42
43
|
TS_DATA_FORGE_MODULE,
|
|
43
44
|
);
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Whether `node` is the same value on every iteration: a literal, or a
|
|
48
|
+
* variable that is never reassigned anywhere (not only in the loop, since
|
|
49
|
+
* a function the loop calls could reassign it). The loop reads its end and
|
|
50
|
+
* its step on every iteration, and `range` reads them once.
|
|
51
|
+
*/
|
|
52
|
+
const isStableBound = (
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
54
|
+
wrapped: TSESTree.Node,
|
|
55
|
+
): boolean => {
|
|
56
|
+
const node = skipTypeWrappers(wrapped);
|
|
57
|
+
|
|
58
|
+
if (node.type === AST_NODE_TYPES.Literal) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (node.type !== AST_NODE_TYPES.Identifier) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const variable = ASTUtils.findVariable(
|
|
67
|
+
sourceCode.getScope(node),
|
|
68
|
+
node.name,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
variable !== null &&
|
|
73
|
+
variable.defs.length > 0 &&
|
|
74
|
+
variable.references.every(
|
|
75
|
+
(reference) => !reference.isWrite() || reference.init === true,
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
45
80
|
const mut_nodesToFix: {
|
|
46
81
|
node: TSESTree.ForStatement;
|
|
47
82
|
varName: string;
|
|
48
83
|
begin: string;
|
|
49
84
|
end: string;
|
|
50
85
|
step?: string;
|
|
86
|
+
fixable: boolean;
|
|
51
87
|
}[] = [];
|
|
52
88
|
|
|
53
89
|
return {
|
|
@@ -74,6 +110,11 @@ export const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
74
110
|
|
|
75
111
|
const beginExpr = declaration.init;
|
|
76
112
|
|
|
113
|
+
// `range` takes safe integers only.
|
|
114
|
+
if (isNonIntegerNumberLiteral(beginExpr)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
77
118
|
// Check test: i < end. The loop variable and the step are read
|
|
78
119
|
// through type wrappers: `(i satisfies number) < n` tests `i`, and
|
|
79
120
|
// `i += 0 as number` still steps by 0.
|
|
@@ -126,19 +167,35 @@ export const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
126
167
|
const stepNum = stepValue.value;
|
|
127
168
|
|
|
128
169
|
// Only support positive integer steps
|
|
129
|
-
if (
|
|
170
|
+
if (
|
|
171
|
+
typeof stepNum === 'number' &&
|
|
172
|
+
Number.isSafeInteger(stepNum) &&
|
|
173
|
+
stepNum > 0
|
|
174
|
+
) {
|
|
130
175
|
mut_step = stepNum === 1 ? undefined : stepNum.toString();
|
|
131
176
|
} else {
|
|
132
177
|
return;
|
|
133
178
|
}
|
|
179
|
+
} else if (isNegatedLiteral(stepValue)) {
|
|
180
|
+
// `i += -1` under `i < end` never gets there, and `range` with a
|
|
181
|
+
// negative step yields nothing: no fix that type-checks would
|
|
182
|
+
// keep that.
|
|
183
|
+
return;
|
|
134
184
|
} else {
|
|
135
|
-
// Variable step like i += step
|
|
185
|
+
// Variable step like i += step. Its type is left to the type
|
|
186
|
+
// checker: a step that is not a non-zero safe integer makes the
|
|
187
|
+
// fixed `range(...)` call fail to type-check.
|
|
136
188
|
mut_step = getArgumentText(sourceCode, node.update.right);
|
|
137
189
|
}
|
|
138
190
|
} else {
|
|
139
191
|
return;
|
|
140
192
|
}
|
|
141
193
|
|
|
194
|
+
// `const i` would not take an assignment anywhere but the update.
|
|
195
|
+
if (isAssignedOutsideUpdate(sourceCode, declaration, node.update)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
142
199
|
const beginText = getArgumentText(sourceCode, beginExpr);
|
|
143
200
|
|
|
144
201
|
const endText = getArgumentText(sourceCode, endExpr);
|
|
@@ -149,6 +206,10 @@ export const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
149
206
|
begin: beginText,
|
|
150
207
|
end: endText,
|
|
151
208
|
step: mut_step,
|
|
209
|
+
fixable:
|
|
210
|
+
isStableBound(endExpr) &&
|
|
211
|
+
(node.update.type !== AST_NODE_TYPES.AssignmentExpression ||
|
|
212
|
+
isStableBound(node.update.right)),
|
|
152
213
|
});
|
|
153
214
|
},
|
|
154
215
|
'Program:exit': () => {
|
|
@@ -156,10 +217,11 @@ export const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
156
217
|
|
|
157
218
|
const hasRangeImport = namedImports.includes('range');
|
|
158
219
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
220
|
+
const importCarrier = mut_nodesToFix.find(({ fixable }) => fixable);
|
|
221
|
+
|
|
222
|
+
for (const entry of mut_nodesToFix) {
|
|
223
|
+
const { node, varName, begin, end, step } = entry;
|
|
224
|
+
|
|
163
225
|
context.report({
|
|
164
226
|
node: node.init ?? node,
|
|
165
227
|
messageId: 'useRangeForLoop',
|
|
@@ -168,25 +230,33 @@ export const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
168
230
|
begin,
|
|
169
231
|
end,
|
|
170
232
|
},
|
|
171
|
-
fix:
|
|
172
|
-
|
|
233
|
+
fix: entry.fixable
|
|
234
|
+
? (fixer) => {
|
|
235
|
+
const bodyText = sourceCode.getText(node.body);
|
|
173
236
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
237
|
+
const rangeArgs =
|
|
238
|
+
step === undefined
|
|
239
|
+
? `${begin}, ${end}`
|
|
240
|
+
: `${begin}, ${end}, ${step}`;
|
|
178
241
|
|
|
179
|
-
|
|
242
|
+
const replacement = `for (const ${varName} of range(${rangeArgs})) ${bodyText}`;
|
|
180
243
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
244
|
+
// The import fix lives on the first fixable report:
|
|
245
|
+
// `insertTextBefore(program, …)` produces overlapping ranges
|
|
246
|
+
// otherwise.
|
|
247
|
+
const importFixes =
|
|
248
|
+
entry === importCarrier && !hasRangeImport
|
|
249
|
+
? buildNamedImportFix(
|
|
250
|
+
fixer,
|
|
251
|
+
program,
|
|
252
|
+
TS_DATA_FORGE_MODULE,
|
|
253
|
+
['range'],
|
|
254
|
+
)
|
|
255
|
+
: [];
|
|
187
256
|
|
|
188
|
-
|
|
189
|
-
|
|
257
|
+
return [...importFixes, fixer.replaceText(node, replacement)];
|
|
258
|
+
}
|
|
259
|
+
: undefined,
|
|
190
260
|
});
|
|
191
261
|
}
|
|
192
262
|
},
|
|
@@ -194,3 +264,54 @@ export const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
194
264
|
},
|
|
195
265
|
defaultOptions: [],
|
|
196
266
|
} as const;
|
|
267
|
+
|
|
268
|
+
const isNegatedLiteral = (
|
|
269
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
270
|
+
node: TSESTree.Node,
|
|
271
|
+
): boolean =>
|
|
272
|
+
node.type === AST_NODE_TYPES.UnaryExpression &&
|
|
273
|
+
node.operator === '-' &&
|
|
274
|
+
skipTypeWrappers(node.argument).type === AST_NODE_TYPES.Literal;
|
|
275
|
+
|
|
276
|
+
const isNonIntegerNumberLiteral = (
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
278
|
+
wrapped: TSESTree.Node,
|
|
279
|
+
): boolean => {
|
|
280
|
+
const node = skipTypeWrappers(wrapped);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
node.type === AST_NODE_TYPES.Literal &&
|
|
284
|
+
typeof node.value === 'number' &&
|
|
285
|
+
!Number.isSafeInteger(node.value)
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Whether the loop variable declared by `declaration` is written anywhere but
|
|
291
|
+
* its initializer and the loop's `update`.
|
|
292
|
+
*/
|
|
293
|
+
const isAssignedOutsideUpdate = (
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
295
|
+
sourceCode: TSESLint.SourceCode,
|
|
296
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
297
|
+
declaration: TSESTree.VariableDeclarator,
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
299
|
+
update: TSESTree.Expression,
|
|
300
|
+
): boolean =>
|
|
301
|
+
sourceCode
|
|
302
|
+
.getDeclaredVariables(declaration)
|
|
303
|
+
.flatMap((variable) => variable.references)
|
|
304
|
+
.some(
|
|
305
|
+
(reference) =>
|
|
306
|
+
reference.isWrite() &&
|
|
307
|
+
reference.init !== true &&
|
|
308
|
+
!isWithin(reference.identifier, update),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const isWithin = (
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
313
|
+
node: TSESTree.Node,
|
|
314
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
315
|
+
container: TSESTree.Node,
|
|
316
|
+
): boolean =>
|
|
317
|
+
container.range[0] <= node.range[0] && node.range[1] <= container.range[1];
|
|
@@ -205,22 +205,20 @@ describe('prefer-range-for-loop with parenthesized operands', () => {
|
|
|
205
205
|
valid: [],
|
|
206
206
|
invalid: [
|
|
207
207
|
{
|
|
208
|
-
name: 'sequence
|
|
208
|
+
name: 'a sequence begin keeps its parentheses',
|
|
209
209
|
code: dedent`
|
|
210
210
|
import { range } from 'ts-data-forge';
|
|
211
211
|
declare const n: number;
|
|
212
|
-
declare const step: number;
|
|
213
212
|
declare const log: () => void;
|
|
214
|
-
for (let i = (log(), 0); i <
|
|
213
|
+
for (let i = (log(), 0); i < n; ++i) {
|
|
215
214
|
console.log(i);
|
|
216
215
|
}
|
|
217
216
|
`,
|
|
218
217
|
output: dedent`
|
|
219
218
|
import { range } from 'ts-data-forge';
|
|
220
219
|
declare const n: number;
|
|
221
|
-
declare const step: number;
|
|
222
220
|
declare const log: () => void;
|
|
223
|
-
for (const i of range((log(), 0),
|
|
221
|
+
for (const i of range((log(), 0), n)) {
|
|
224
222
|
console.log(i);
|
|
225
223
|
}
|
|
226
224
|
`,
|
|
@@ -229,3 +227,127 @@ describe('prefer-range-for-loop with parenthesized operands', () => {
|
|
|
229
227
|
],
|
|
230
228
|
});
|
|
231
229
|
}, 20000);
|
|
230
|
+
|
|
231
|
+
describe('prefer-range-for-loop where the rewrite would change behavior', () => {
|
|
232
|
+
tester.run('prefer-range-for-loop', preferRangeForLoop, {
|
|
233
|
+
valid: [
|
|
234
|
+
{
|
|
235
|
+
// `const i` could not be assigned.
|
|
236
|
+
name: 'the loop variable assigned in the body',
|
|
237
|
+
code: dedent`
|
|
238
|
+
declare const n: number;
|
|
239
|
+
declare const skip: boolean;
|
|
240
|
+
for (let i = 0; i < n; ++i) {
|
|
241
|
+
if (skip) i += 1;
|
|
242
|
+
}
|
|
243
|
+
for (let j = 0; j < n; ++j) {
|
|
244
|
+
j = n;
|
|
245
|
+
}
|
|
246
|
+
`,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
// `range` takes safe integers only.
|
|
250
|
+
name: 'a step or a begin that is not an integer',
|
|
251
|
+
code: dedent`
|
|
252
|
+
declare const n: number;
|
|
253
|
+
for (let i = 0; i < n; i += 0.5) {
|
|
254
|
+
console.log(i);
|
|
255
|
+
}
|
|
256
|
+
for (let j = 0.5; j < n; ++j) {
|
|
257
|
+
console.log(j);
|
|
258
|
+
}
|
|
259
|
+
`,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
// `i < n` with a negative step never ends; `range` yields nothing.
|
|
263
|
+
name: 'a negative literal step',
|
|
264
|
+
code: dedent`
|
|
265
|
+
declare const n: number;
|
|
266
|
+
for (let i = 0; i < n; i += -1) {
|
|
267
|
+
console.log(i);
|
|
268
|
+
}
|
|
269
|
+
`,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
invalid: [
|
|
273
|
+
{
|
|
274
|
+
// The loop tests its end on every iteration; `range` reads it once.
|
|
275
|
+
name: 'an end that is not a literal or a never-reassigned variable gets no fix',
|
|
276
|
+
code: dedent`
|
|
277
|
+
declare const xs: number[];
|
|
278
|
+
declare const getN: () => number;
|
|
279
|
+
let m = 3;
|
|
280
|
+
for (let i = 0; i < xs.length; ++i) {
|
|
281
|
+
xs.pop();
|
|
282
|
+
}
|
|
283
|
+
for (let j = 0; j < getN(); ++j) {
|
|
284
|
+
console.log(j);
|
|
285
|
+
}
|
|
286
|
+
for (let k = 0; k < m; ++k) {
|
|
287
|
+
m -= 1;
|
|
288
|
+
}
|
|
289
|
+
`,
|
|
290
|
+
output: null,
|
|
291
|
+
errors: [
|
|
292
|
+
{ messageId: 'useRangeForLoop' },
|
|
293
|
+
{ messageId: 'useRangeForLoop' },
|
|
294
|
+
{ messageId: 'useRangeForLoop' },
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
// The loop adds the step it reads on each iteration; `range` reads it
|
|
299
|
+
// once.
|
|
300
|
+
name: 'a variable step that is reassigned gets no fix',
|
|
301
|
+
code: dedent`
|
|
302
|
+
declare const n: number;
|
|
303
|
+
let step = 1;
|
|
304
|
+
for (let i = 0; i < n; i += step) {
|
|
305
|
+
step *= 2;
|
|
306
|
+
}
|
|
307
|
+
`,
|
|
308
|
+
output: null,
|
|
309
|
+
errors: [{ messageId: 'useRangeForLoop' }],
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
// The type checker judges it: a `number` step makes the fixed call
|
|
313
|
+
// fail to type-check.
|
|
314
|
+
name: 'a variable step is fixed without asking its type',
|
|
315
|
+
code: dedent`
|
|
316
|
+
declare const n: number;
|
|
317
|
+
declare const step: number;
|
|
318
|
+
for (let i = 0; i < n; i += step) {
|
|
319
|
+
console.log(i);
|
|
320
|
+
}
|
|
321
|
+
`,
|
|
322
|
+
output: dedent`
|
|
323
|
+
import { range } from 'ts-data-forge';
|
|
324
|
+
declare const n: number;
|
|
325
|
+
declare const step: number;
|
|
326
|
+
for (const i of range(0, n, step)) {
|
|
327
|
+
console.log(i);
|
|
328
|
+
}
|
|
329
|
+
`,
|
|
330
|
+
errors: [{ messageId: 'useRangeForLoop' }],
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'a parameter that is never reassigned is a fixed end',
|
|
334
|
+
code: dedent`
|
|
335
|
+
const f = (n: number): void => {
|
|
336
|
+
for (let i = 0; i < n; ++i) {
|
|
337
|
+
console.log(i);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
`,
|
|
341
|
+
output: dedent`
|
|
342
|
+
import { range } from 'ts-data-forge';
|
|
343
|
+
const f = (n: number): void => {
|
|
344
|
+
for (const i of range(0, n)) {
|
|
345
|
+
console.log(i);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
`,
|
|
349
|
+
errors: [{ messageId: 'useRangeForLoop' }],
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
});
|
|
353
|
+
}, 20000);
|
package/src/rules/rules.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ESLintPlugin } from '../types.mjs';
|
|
2
2
|
import { noSideEffectImport } from './no-side-effect-import.mjs';
|
|
3
3
|
import { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';
|
|
4
|
+
import { preferArrGenerate } from './prefer-arr-generate.mjs';
|
|
4
5
|
import { preferArrIsArray } from './prefer-arr-is-array.mjs';
|
|
5
6
|
import { preferArrScan } from './prefer-arr-scan.mjs';
|
|
6
7
|
import { preferArrSum } from './prefer-arr-sum.mjs';
|
|
@@ -21,6 +22,7 @@ export const tsDataForgeRules = {
|
|
|
21
22
|
'prefer-canonical-array-slicing': preferCanonicalArraySlicing,
|
|
22
23
|
'prefer-canonical-length-guard': preferCanonicalLengthGuard,
|
|
23
24
|
'prefer-canonical-length-cast': preferCanonicalLengthCast,
|
|
25
|
+
'prefer-arr-generate': preferArrGenerate,
|
|
24
26
|
'prefer-arr-is-array': preferArrIsArray,
|
|
25
27
|
'prefer-arr-scan': preferArrScan,
|
|
26
28
|
'prefer-arr-sum': preferArrSum,
|