eslint-plugin-functype 1.2.0 → 2.0.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 +86 -44
- package/dist/chunk-BlXvk904.js +1 -0
- package/dist/cli/list-rules.d.ts +1 -1
- package/dist/cli/list-rules.js +15 -239
- package/dist/cli/list-rules.js.map +1 -1
- package/dist/index.d.ts +19 -16
- package/dist/index.js +1 -1075
- package/dist/index.js.map +1 -1
- package/dist/rules/index.d.ts +24 -29
- package/dist/rules/index.js +1 -1071
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/no-get-unsafe.d.ts +7 -0
- package/dist/rules/no-get-unsafe.js +2 -0
- package/dist/rules/no-get-unsafe.js.map +1 -0
- package/dist/rules/no-imperative-loops.d.ts +7 -0
- package/dist/rules/no-imperative-loops.js +2 -0
- package/dist/rules/no-imperative-loops.js.map +1 -0
- package/dist/rules/prefer-do-notation.d.ts +7 -0
- package/dist/rules/prefer-do-notation.js +5 -0
- package/dist/rules/prefer-do-notation.js.map +1 -0
- package/dist/rules/prefer-either.d.ts +7 -0
- package/dist/rules/prefer-either.js +2 -0
- package/dist/rules/prefer-either.js.map +1 -0
- package/dist/rules/prefer-flatmap.d.ts +7 -0
- package/dist/rules/prefer-flatmap.js +2 -0
- package/dist/rules/prefer-flatmap.js.map +1 -0
- package/dist/rules/prefer-fold.d.ts +7 -0
- package/dist/rules/prefer-fold.js +2 -0
- package/dist/rules/prefer-fold.js.map +1 -0
- package/dist/rules/prefer-list.d.ts +7 -0
- package/dist/rules/prefer-list.js +2 -0
- package/dist/rules/prefer-list.js.map +1 -0
- package/dist/rules/prefer-map.d.ts +7 -0
- package/dist/rules/prefer-map.js +2 -0
- package/dist/rules/prefer-map.js.map +1 -0
- package/dist/rules/prefer-option.d.ts +7 -0
- package/dist/rules/prefer-option.js +2 -0
- package/dist/rules/prefer-option.js.map +1 -0
- package/dist/types/ast.d.ts +12 -0
- package/dist/types/ast.js +1 -0
- package/dist/utils/dependency-validator.d.ts +13 -11
- package/dist/utils/dependency-validator.js +3 -108
- package/dist/utils/dependency-validator.js.map +1 -1
- package/dist/utils/functype-detection.d.ts +69 -0
- package/dist/utils/functype-detection.js +2 -0
- package/dist/utils/functype-detection.js.map +1 -0
- package/package.json +37 -34
package/dist/index.js
CHANGED
|
@@ -1,1076 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __esm = (fn, res) => function __init() {
|
|
10
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
-
};
|
|
12
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
13
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
14
|
-
};
|
|
15
|
-
var __copyProps = (to, from, except, desc) => {
|
|
16
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
-
for (let key of __getOwnPropNames(from))
|
|
18
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
-
}
|
|
21
|
-
return to;
|
|
22
|
-
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
-
!mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
-
mod
|
|
30
|
-
));
|
|
31
|
-
|
|
32
|
-
// src/utils/functype-detection.ts
|
|
33
|
-
function getFunctypeImports(context) {
|
|
34
|
-
const imports = /* @__PURE__ */ new Set();
|
|
35
|
-
const sourceCode = context.sourceCode;
|
|
36
|
-
const program = sourceCode.ast;
|
|
37
|
-
for (const node of program.body) {
|
|
38
|
-
if (node.type === "ImportDeclaration" && node.source.type === "Literal" && node.source.value === "functype") {
|
|
39
|
-
if (node.specifiers) {
|
|
40
|
-
for (const spec of node.specifiers) {
|
|
41
|
-
if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") {
|
|
42
|
-
imports.add(spec.imported.name);
|
|
43
|
-
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
44
|
-
imports.add("default");
|
|
45
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
46
|
-
imports.add("*");
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return imports;
|
|
53
|
-
}
|
|
54
|
-
function isFunctypeType(node, functypeImports) {
|
|
55
|
-
if (!node) return false;
|
|
56
|
-
if (node.type === "TSTypeReference" && node.typeName?.type === "Identifier") {
|
|
57
|
-
const typeName = node.typeName.name;
|
|
58
|
-
return functypeImports.has(typeName) || ["Option", "Either", "List", "LazyList", "Task"].includes(typeName);
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
function isFunctypeCall(node, functypeImports) {
|
|
63
|
-
if (!node || node.type !== "CallExpression") return false;
|
|
64
|
-
const callee = node.callee;
|
|
65
|
-
if (callee.type === "MemberExpression" && callee.object.type === "Identifier") {
|
|
66
|
-
const objectName = callee.object.name;
|
|
67
|
-
const methodName = callee.property?.name;
|
|
68
|
-
if (functypeImports.has(objectName)) return true;
|
|
69
|
-
if (objectName === "Option" && ["some", "none", "of"].includes(methodName) || objectName === "Either" && ["left", "right", "of"].includes(methodName) || objectName === "List" && ["of", "from", "empty"].includes(methodName)) {
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (callee.type === "MemberExpression") {
|
|
74
|
-
const methodName = callee.property?.name;
|
|
75
|
-
if ([
|
|
76
|
-
"map",
|
|
77
|
-
"flatMap",
|
|
78
|
-
"filter",
|
|
79
|
-
"fold",
|
|
80
|
-
"foldLeft",
|
|
81
|
-
"foldRight",
|
|
82
|
-
"getOrElse",
|
|
83
|
-
"orElse",
|
|
84
|
-
"isEmpty",
|
|
85
|
-
"nonEmpty",
|
|
86
|
-
"isDefined",
|
|
87
|
-
"isSome",
|
|
88
|
-
"isNone",
|
|
89
|
-
"isLeft",
|
|
90
|
-
"isRight",
|
|
91
|
-
"toArray"
|
|
92
|
-
].includes(methodName)) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
function isAlreadyUsingFunctype(node, functypeImports) {
|
|
99
|
-
let parent = node.parent;
|
|
100
|
-
while (parent) {
|
|
101
|
-
if (isFunctypeCall(parent, functypeImports) || isFunctypeType(parent, functypeImports)) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
parent = parent.parent;
|
|
105
|
-
}
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
var init_functype_detection = __esm({
|
|
109
|
-
"src/utils/functype-detection.ts"() {
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// src/rules/prefer-option.ts
|
|
114
|
-
var require_prefer_option = __commonJS({
|
|
115
|
-
"src/rules/prefer-option.ts"(exports, module) {
|
|
116
|
-
init_functype_detection();
|
|
117
|
-
var rule = {
|
|
118
|
-
meta: {
|
|
119
|
-
type: "suggestion",
|
|
120
|
-
docs: {
|
|
121
|
-
description: "Prefer Option<T> over nullable types (T | null | undefined)",
|
|
122
|
-
category: "Stylistic Issues",
|
|
123
|
-
recommended: true
|
|
124
|
-
},
|
|
125
|
-
fixable: "code",
|
|
126
|
-
schema: [
|
|
127
|
-
{
|
|
128
|
-
type: "object",
|
|
129
|
-
properties: {
|
|
130
|
-
allowNullableIntersections: {
|
|
131
|
-
type: "boolean",
|
|
132
|
-
default: false
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
additionalProperties: false
|
|
136
|
-
}
|
|
137
|
-
],
|
|
138
|
-
messages: {
|
|
139
|
-
preferOption: "Prefer Option<{{type}}> over nullable type '{{nullable}}'",
|
|
140
|
-
preferOptionReturn: "Prefer Option<{{type}}> as return type over nullable '{{nullable}}'"
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
create(context) {
|
|
144
|
-
const functypeImports = getFunctypeImports(context);
|
|
145
|
-
return {
|
|
146
|
-
TSUnionType(node) {
|
|
147
|
-
if (!node.types || node.types.length < 2) return;
|
|
148
|
-
const hasNull = node.types.some(
|
|
149
|
-
(type) => type.type === "TSNullKeyword" || type.type === "TSUndefinedKeyword"
|
|
150
|
-
);
|
|
151
|
-
if (!hasNull) return;
|
|
152
|
-
const nonNullTypes = node.types.filter(
|
|
153
|
-
(type) => type.type !== "TSNullKeyword" && type.type !== "TSUndefinedKeyword"
|
|
154
|
-
);
|
|
155
|
-
if (nonNullTypes.length === 1) {
|
|
156
|
-
const nonNullType = nonNullTypes[0];
|
|
157
|
-
if (isFunctypeType(nonNullType, functypeImports)) return;
|
|
158
|
-
if (isAlreadyUsingFunctype(node, functypeImports)) return;
|
|
159
|
-
const sourceCode = context.sourceCode;
|
|
160
|
-
const nonNullTypeText = sourceCode.getText(nonNullType);
|
|
161
|
-
const fullType = sourceCode.getText(node);
|
|
162
|
-
if (nonNullTypeText.startsWith("Option<")) return;
|
|
163
|
-
context.report({
|
|
164
|
-
node,
|
|
165
|
-
messageId: "preferOption",
|
|
166
|
-
data: {
|
|
167
|
-
type: nonNullTypeText,
|
|
168
|
-
nullable: fullType
|
|
169
|
-
},
|
|
170
|
-
fix(fixer) {
|
|
171
|
-
return fixer.replaceText(node, `Option<${nonNullTypeText}>`);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
module.exports = rule;
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// src/rules/prefer-either.ts
|
|
184
|
-
var require_prefer_either = __commonJS({
|
|
185
|
-
"src/rules/prefer-either.ts"(exports, module) {
|
|
186
|
-
var rule = {
|
|
187
|
-
meta: {
|
|
188
|
-
type: "suggestion",
|
|
189
|
-
docs: {
|
|
190
|
-
description: "Prefer Either<E, T> over try/catch blocks and throw statements",
|
|
191
|
-
category: "Best Practices",
|
|
192
|
-
recommended: true
|
|
193
|
-
},
|
|
194
|
-
schema: [
|
|
195
|
-
{
|
|
196
|
-
type: "object",
|
|
197
|
-
properties: {
|
|
198
|
-
allowThrowInTests: {
|
|
199
|
-
type: "boolean",
|
|
200
|
-
default: true
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
additionalProperties: false
|
|
204
|
-
}
|
|
205
|
-
],
|
|
206
|
-
messages: {
|
|
207
|
-
preferEitherOverTryCatch: "Prefer Either<Error, T> over try/catch block",
|
|
208
|
-
preferEitherOverThrow: "Prefer Either.left(error) over throw statement",
|
|
209
|
-
preferEitherReturn: "Consider returning Either<Error, {{type}}> instead of throwing"
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
create(context) {
|
|
213
|
-
const options = context.options[0] || {};
|
|
214
|
-
const allowThrowInTests = options.allowThrowInTests !== false;
|
|
215
|
-
function isInTestFile() {
|
|
216
|
-
const filename = context.getFilename();
|
|
217
|
-
return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
|
|
218
|
-
}
|
|
219
|
-
function hasThrowStatementsOutsideCatch(node) {
|
|
220
|
-
if (!node) return false;
|
|
221
|
-
if (node.type === "ThrowStatement") {
|
|
222
|
-
let parent = node.parent;
|
|
223
|
-
while (parent) {
|
|
224
|
-
if (parent.type === "CatchClause") return false;
|
|
225
|
-
parent = parent.parent;
|
|
226
|
-
}
|
|
227
|
-
return true;
|
|
228
|
-
}
|
|
229
|
-
if (node.type === "CatchClause") return false;
|
|
230
|
-
for (const key in node) {
|
|
231
|
-
if (key === "parent") continue;
|
|
232
|
-
const child = node[key];
|
|
233
|
-
if (Array.isArray(child)) {
|
|
234
|
-
for (const item of child) {
|
|
235
|
-
if (item && typeof item === "object" && hasThrowStatementsOutsideCatch(item)) {
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} else if (child && typeof child === "object" && hasThrowStatementsOutsideCatch(child)) {
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
return {
|
|
246
|
-
TryStatement(node) {
|
|
247
|
-
if (allowThrowInTests && isInTestFile()) return;
|
|
248
|
-
if (node.handler && node.handler.body) {
|
|
249
|
-
const catchBody = node.handler.body.body;
|
|
250
|
-
const hasRethrow = catchBody.some((stmt) => stmt.type === "ThrowStatement");
|
|
251
|
-
if (hasRethrow) return;
|
|
252
|
-
}
|
|
253
|
-
context.report({
|
|
254
|
-
node,
|
|
255
|
-
messageId: "preferEitherOverTryCatch"
|
|
256
|
-
});
|
|
257
|
-
},
|
|
258
|
-
ThrowStatement(node) {
|
|
259
|
-
if (allowThrowInTests && isInTestFile()) return;
|
|
260
|
-
let parent = node.parent;
|
|
261
|
-
while (parent) {
|
|
262
|
-
if (parent.type === "CatchClause") return;
|
|
263
|
-
parent = parent.parent;
|
|
264
|
-
}
|
|
265
|
-
context.report({
|
|
266
|
-
node,
|
|
267
|
-
messageId: "preferEitherOverThrow"
|
|
268
|
-
});
|
|
269
|
-
},
|
|
270
|
-
FunctionDeclaration(node) {
|
|
271
|
-
if (allowThrowInTests && isInTestFile()) return;
|
|
272
|
-
if (!node.body) return;
|
|
273
|
-
const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body);
|
|
274
|
-
if (hasThrowsNotInCatch) {
|
|
275
|
-
const returnType = node.returnType?.typeAnnotation;
|
|
276
|
-
if (returnType) {
|
|
277
|
-
const sourceCode = context.sourceCode;
|
|
278
|
-
const returnTypeText = sourceCode.getText(returnType);
|
|
279
|
-
if (!returnTypeText.includes("Either")) {
|
|
280
|
-
context.report({
|
|
281
|
-
node: node.id || node,
|
|
282
|
-
messageId: "preferEitherReturn",
|
|
283
|
-
data: { type: returnTypeText }
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
module.exports = rule;
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// src/rules/prefer-list.ts
|
|
297
|
-
var require_prefer_list = __commonJS({
|
|
298
|
-
"src/rules/prefer-list.ts"(exports, module) {
|
|
299
|
-
init_functype_detection();
|
|
300
|
-
var rule = {
|
|
301
|
-
meta: {
|
|
302
|
-
type: "suggestion",
|
|
303
|
-
docs: {
|
|
304
|
-
description: "Prefer List<T> over native arrays for immutable collections",
|
|
305
|
-
category: "Stylistic Issues",
|
|
306
|
-
recommended: true
|
|
307
|
-
},
|
|
308
|
-
fixable: "code",
|
|
309
|
-
schema: [
|
|
310
|
-
{
|
|
311
|
-
type: "object",
|
|
312
|
-
properties: {
|
|
313
|
-
allowArraysInTests: {
|
|
314
|
-
type: "boolean",
|
|
315
|
-
default: true
|
|
316
|
-
},
|
|
317
|
-
allowReadonlyArrays: {
|
|
318
|
-
type: "boolean",
|
|
319
|
-
default: true
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
additionalProperties: false
|
|
323
|
-
}
|
|
324
|
-
],
|
|
325
|
-
messages: {
|
|
326
|
-
preferList: "Prefer List<{{type}}> over array type {{arrayType}}",
|
|
327
|
-
preferListLiteral: "Prefer List.of(...) or List.from([...]) over array literal"
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
create(context) {
|
|
331
|
-
const options = context.options[0] || {};
|
|
332
|
-
const allowArraysInTests = options.allowArraysInTests !== false;
|
|
333
|
-
const allowReadonlyArrays = options.allowReadonlyArrays !== false;
|
|
334
|
-
const functypeImports = getFunctypeImports(context);
|
|
335
|
-
function isInTestFile() {
|
|
336
|
-
const filename = context.getFilename();
|
|
337
|
-
return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
|
|
338
|
-
}
|
|
339
|
-
function findTypeParameter(node, sourceCode) {
|
|
340
|
-
function findInNode(n) {
|
|
341
|
-
if (n.type === "TSTypeParameterInstantiation" && n.params && n.params[0]) {
|
|
342
|
-
return sourceCode.getText(n.params[0]);
|
|
343
|
-
}
|
|
344
|
-
for (const key in n) {
|
|
345
|
-
if (key === "parent") continue;
|
|
346
|
-
const child = n[key];
|
|
347
|
-
if (Array.isArray(child)) {
|
|
348
|
-
for (const item of child) {
|
|
349
|
-
if (item && typeof item === "object" && item.type) {
|
|
350
|
-
const result = findInNode(item);
|
|
351
|
-
if (result) return result;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
} else if (child && typeof child === "object" && child.type) {
|
|
355
|
-
const result = findInNode(child);
|
|
356
|
-
if (result) return result;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
return findInNode(node);
|
|
362
|
-
}
|
|
363
|
-
return {
|
|
364
|
-
TSArrayType(node) {
|
|
365
|
-
if (allowArraysInTests && isInTestFile()) return;
|
|
366
|
-
const sourceCode = context.sourceCode;
|
|
367
|
-
const elementType = sourceCode.getText(node.elementType);
|
|
368
|
-
const fullType = sourceCode.getText(node);
|
|
369
|
-
context.report({
|
|
370
|
-
node,
|
|
371
|
-
messageId: "preferList",
|
|
372
|
-
data: {
|
|
373
|
-
type: elementType,
|
|
374
|
-
arrayType: fullType
|
|
375
|
-
},
|
|
376
|
-
fix(fixer) {
|
|
377
|
-
return fixer.replaceText(node, `List<${elementType}>`);
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
},
|
|
381
|
-
TSTypeReference(node) {
|
|
382
|
-
if (allowArraysInTests && isInTestFile()) return;
|
|
383
|
-
const sourceCode = context.sourceCode;
|
|
384
|
-
let typeName = "";
|
|
385
|
-
if (node.typeName && node.typeName.type === "Identifier") {
|
|
386
|
-
typeName = node.typeName.name;
|
|
387
|
-
} else if (node.typeName) {
|
|
388
|
-
typeName = sourceCode.getText(node.typeName);
|
|
389
|
-
} else {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
if (typeName === "Array") {
|
|
393
|
-
const typeParam = findTypeParameter(node, sourceCode);
|
|
394
|
-
const fullType = sourceCode.getText(node);
|
|
395
|
-
if (allowReadonlyArrays && fullType.startsWith("readonly")) return;
|
|
396
|
-
context.report({
|
|
397
|
-
node,
|
|
398
|
-
messageId: "preferList",
|
|
399
|
-
data: {
|
|
400
|
-
type: typeParam || "T",
|
|
401
|
-
arrayType: fullType
|
|
402
|
-
},
|
|
403
|
-
fix(fixer) {
|
|
404
|
-
return fixer.replaceText(node, `List<${typeParam || "T"}>`);
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
if (typeName === "ReadonlyArray") {
|
|
409
|
-
const typeParam = findTypeParameter(node, sourceCode);
|
|
410
|
-
const fullType = sourceCode.getText(node);
|
|
411
|
-
context.report({
|
|
412
|
-
node,
|
|
413
|
-
messageId: "preferList",
|
|
414
|
-
data: {
|
|
415
|
-
type: typeParam || "T",
|
|
416
|
-
arrayType: fullType
|
|
417
|
-
},
|
|
418
|
-
fix(fixer) {
|
|
419
|
-
return fixer.replaceText(node, `List<${typeParam || "T"}>`);
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
},
|
|
424
|
-
ArrayExpression(node) {
|
|
425
|
-
if (allowArraysInTests && isInTestFile()) return;
|
|
426
|
-
if (node.elements.length === 0) return;
|
|
427
|
-
let parent = node.parent;
|
|
428
|
-
if (parent && isFunctypeCall(parent, functypeImports)) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
if (parent && parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && parent.callee.object.type === "Identifier" && parent.callee.object.name === "List" && ["from", "of"].includes(parent.callee.property.name)) {
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
parent = node.parent;
|
|
435
|
-
while (parent) {
|
|
436
|
-
if (parent.type === "ArrayExpression") {
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
parent = parent.parent;
|
|
440
|
-
}
|
|
441
|
-
let hasTypeAnnotation = false;
|
|
442
|
-
parent = node.parent;
|
|
443
|
-
while (parent) {
|
|
444
|
-
if (parent.type === "VariableDeclarator" && parent.id?.typeAnnotation) {
|
|
445
|
-
hasTypeAnnotation = true;
|
|
446
|
-
break;
|
|
447
|
-
}
|
|
448
|
-
if (parent.type === "TSTypeAnnotation") {
|
|
449
|
-
hasTypeAnnotation = true;
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
parent = parent.parent;
|
|
453
|
-
}
|
|
454
|
-
if (hasTypeAnnotation) return;
|
|
455
|
-
context.report({
|
|
456
|
-
node,
|
|
457
|
-
messageId: "preferListLiteral",
|
|
458
|
-
fix(fixer) {
|
|
459
|
-
const sourceCode = context.sourceCode;
|
|
460
|
-
const elements = sourceCode.getText(node);
|
|
461
|
-
return fixer.replaceText(node, `List.from(${elements})`);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
module.exports = rule;
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// src/rules/no-get-unsafe.ts
|
|
473
|
-
var require_no_get_unsafe = __commonJS({
|
|
474
|
-
"src/rules/no-get-unsafe.ts"(exports, module) {
|
|
475
|
-
var rule = {
|
|
476
|
-
meta: {
|
|
477
|
-
type: "problem",
|
|
478
|
-
docs: {
|
|
479
|
-
description: "Avoid unsafe .get() calls on Option, Either, and other monadic types",
|
|
480
|
-
category: "Possible Errors",
|
|
481
|
-
recommended: true
|
|
482
|
-
},
|
|
483
|
-
schema: [
|
|
484
|
-
{
|
|
485
|
-
type: "object",
|
|
486
|
-
properties: {
|
|
487
|
-
allowInTests: {
|
|
488
|
-
type: "boolean",
|
|
489
|
-
default: true
|
|
490
|
-
},
|
|
491
|
-
unsafeMethods: {
|
|
492
|
-
type: "array",
|
|
493
|
-
items: { type: "string" },
|
|
494
|
-
default: ["get", "getOrThrow", "unwrap", "expect"]
|
|
495
|
-
}
|
|
496
|
-
},
|
|
497
|
-
additionalProperties: false
|
|
498
|
-
}
|
|
499
|
-
],
|
|
500
|
-
messages: {
|
|
501
|
-
noUnsafeGet: "Avoid unsafe .{{method}}() call. Use .fold(), .map(), or .getOrElse() instead",
|
|
502
|
-
noUnsafeGetSuggestion: "Consider using .getOrElse(defaultValue) or .fold() for safe access"
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
create(context) {
|
|
506
|
-
const options = context.options[0] || {};
|
|
507
|
-
const allowInTests = options.allowInTests !== false;
|
|
508
|
-
const unsafeMethods = options.unsafeMethods || ["get", "getOrThrow", "unwrap", "expect"];
|
|
509
|
-
function isInTestFile() {
|
|
510
|
-
const filename = context.getFilename();
|
|
511
|
-
return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
|
|
512
|
-
}
|
|
513
|
-
function isMonadicType(node) {
|
|
514
|
-
if (!node) return false;
|
|
515
|
-
const sourceCode = context.sourceCode;
|
|
516
|
-
const text = sourceCode.getText(node);
|
|
517
|
-
if (/\b(Option|Either|Maybe|Result|Some|None|Left|Right)\b/.test(text)) {
|
|
518
|
-
return true;
|
|
519
|
-
}
|
|
520
|
-
if (/\.(map|flatMap|filter|fold)\s*\(/.test(text)) {
|
|
521
|
-
return true;
|
|
522
|
-
}
|
|
523
|
-
if (/\b(option|either|maybe|result|some|none|left|right)\w*\b/i.test(text)) {
|
|
524
|
-
return true;
|
|
525
|
-
}
|
|
526
|
-
if (node.type === "Identifier") {
|
|
527
|
-
const varName = node.name.toLowerCase();
|
|
528
|
-
if (/(option|either|maybe|result|some|none|left|right|opt)/.test(varName)) {
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.object) {
|
|
533
|
-
return isMonadicType(node.callee.object);
|
|
534
|
-
}
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
CallExpression(node) {
|
|
539
|
-
if (allowInTests && isInTestFile()) return;
|
|
540
|
-
if (node.callee.type !== "MemberExpression") return;
|
|
541
|
-
const property = node.callee.property;
|
|
542
|
-
if (!property || property.type !== "Identifier") return;
|
|
543
|
-
const methodName = property.name;
|
|
544
|
-
if (!unsafeMethods.includes(methodName)) return;
|
|
545
|
-
if (isMonadicType(node.callee.object)) {
|
|
546
|
-
context.report({
|
|
547
|
-
node,
|
|
548
|
-
messageId: "noUnsafeGet",
|
|
549
|
-
data: { method: methodName }
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
module.exports = rule;
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
// src/rules/prefer-fold.ts
|
|
561
|
-
var require_prefer_fold = __commonJS({
|
|
562
|
-
"src/rules/prefer-fold.ts"(exports, module) {
|
|
563
|
-
var rule = {
|
|
564
|
-
meta: {
|
|
565
|
-
type: "suggestion",
|
|
566
|
-
docs: {
|
|
567
|
-
description: "Prefer .fold() over if/else chains when working with monadic types",
|
|
568
|
-
category: "Best Practices",
|
|
569
|
-
recommended: true
|
|
570
|
-
},
|
|
571
|
-
schema: [
|
|
572
|
-
{
|
|
573
|
-
type: "object",
|
|
574
|
-
properties: {
|
|
575
|
-
minComplexity: {
|
|
576
|
-
type: "integer",
|
|
577
|
-
minimum: 1,
|
|
578
|
-
default: 2
|
|
579
|
-
}
|
|
580
|
-
},
|
|
581
|
-
additionalProperties: false
|
|
582
|
-
}
|
|
583
|
-
],
|
|
584
|
-
messages: {
|
|
585
|
-
preferFold: "Prefer .fold() over if/else when working with {{type}} types",
|
|
586
|
-
preferFoldTernary: "Consider using .fold() instead of ternary operator for {{type}}"
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
create(context) {
|
|
590
|
-
const options = context.options[0] || {};
|
|
591
|
-
const minComplexity = options.minComplexity || 2;
|
|
592
|
-
function isMonadicCheck(node) {
|
|
593
|
-
const sourceCode = context.sourceCode;
|
|
594
|
-
const text = sourceCode.getText(node);
|
|
595
|
-
if (/\.(isSome|isNone|isEmpty|isDefined)\s*\(\s*\)/.test(text)) {
|
|
596
|
-
return { isMonadic: true, type: "Option" };
|
|
597
|
-
}
|
|
598
|
-
if (/\.(isLeft|isRight)\s*\(\s*\)/.test(text)) {
|
|
599
|
-
return { isMonadic: true, type: "Either" };
|
|
600
|
-
}
|
|
601
|
-
if (/\.(isSuccess|isFailure)\s*\(\s*\)/.test(text)) {
|
|
602
|
-
return { isMonadic: true, type: "Result" };
|
|
603
|
-
}
|
|
604
|
-
if (node.type === "BinaryExpression") {
|
|
605
|
-
if ((node.operator === "===" || node.operator === "!==") && (node.left.type === "Literal" && (node.left.value === null || node.left.value === void 0) || node.right.type === "Literal" && (node.right.value === null || node.right.value === void 0))) {
|
|
606
|
-
return { isMonadic: true, type: "Option" };
|
|
607
|
-
}
|
|
608
|
-
if (node.operator === "==" || node.operator === "!=" || node.operator === "===" || node.operator === "!==") {
|
|
609
|
-
const leftIsUndefined = node.left.type === "Identifier" && node.left.name === "undefined" || node.left.type === "Literal" && node.left.value === void 0;
|
|
610
|
-
const rightIsUndefined = node.right.type === "Identifier" && node.right.name === "undefined" || node.right.type === "Literal" && node.right.value === void 0;
|
|
611
|
-
if (leftIsUndefined || rightIsUndefined) {
|
|
612
|
-
return { isMonadic: true, type: "Option" };
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
return { isMonadic: false, type: "" };
|
|
617
|
-
}
|
|
618
|
-
function analyzeIfStatement(node) {
|
|
619
|
-
const test = node.test;
|
|
620
|
-
const monadicInfo = isMonadicCheck(test);
|
|
621
|
-
if (!monadicInfo.isMonadic) return;
|
|
622
|
-
if (node.parent && node.parent.type === "IfStatement") return;
|
|
623
|
-
let complexity = 1;
|
|
624
|
-
let current = node;
|
|
625
|
-
while (current.alternate) {
|
|
626
|
-
complexity++;
|
|
627
|
-
if (current.alternate.type === "IfStatement") {
|
|
628
|
-
current = current.alternate;
|
|
629
|
-
} else {
|
|
630
|
-
break;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
if (complexity >= minComplexity) {
|
|
634
|
-
context.report({
|
|
635
|
-
node,
|
|
636
|
-
messageId: "preferFold",
|
|
637
|
-
data: { type: monadicInfo.type }
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
return {
|
|
642
|
-
IfStatement(node) {
|
|
643
|
-
analyzeIfStatement(node);
|
|
644
|
-
},
|
|
645
|
-
ConditionalExpression(node) {
|
|
646
|
-
const monadicInfo = isMonadicCheck(node.test);
|
|
647
|
-
if (monadicInfo.isMonadic) {
|
|
648
|
-
context.report({
|
|
649
|
-
node,
|
|
650
|
-
messageId: "preferFoldTernary",
|
|
651
|
-
data: { type: monadicInfo.type }
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
|
-
module.exports = rule;
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
// src/rules/prefer-map.ts
|
|
663
|
-
var require_prefer_map = __commonJS({
|
|
664
|
-
"src/rules/prefer-map.ts"(exports, module) {
|
|
665
|
-
var rule = {
|
|
666
|
-
meta: {
|
|
667
|
-
type: "suggestion",
|
|
668
|
-
docs: {
|
|
669
|
-
description: "Prefer .map() over manual transformations and imperative patterns",
|
|
670
|
-
category: "Best Practices",
|
|
671
|
-
recommended: true
|
|
672
|
-
},
|
|
673
|
-
schema: [
|
|
674
|
-
{
|
|
675
|
-
type: "object",
|
|
676
|
-
properties: {
|
|
677
|
-
checkArrayMethods: {
|
|
678
|
-
type: "boolean",
|
|
679
|
-
default: true
|
|
680
|
-
},
|
|
681
|
-
checkForLoops: {
|
|
682
|
-
type: "boolean",
|
|
683
|
-
default: true
|
|
684
|
-
}
|
|
685
|
-
},
|
|
686
|
-
additionalProperties: false
|
|
687
|
-
}
|
|
688
|
-
],
|
|
689
|
-
messages: {
|
|
690
|
-
preferMapOverLoop: "Prefer .map() over for loop for transforming {{collection}}",
|
|
691
|
-
preferMapOverPush: "Prefer .map() over manual .push() in loop",
|
|
692
|
-
preferMapChain: "Consider using .map() for transformation instead of manual property access"
|
|
693
|
-
}
|
|
694
|
-
},
|
|
695
|
-
create(context) {
|
|
696
|
-
const options = context.options[0] || {};
|
|
697
|
-
const checkArrayMethods = options.checkArrayMethods !== false;
|
|
698
|
-
const checkForLoops = options.checkForLoops !== false;
|
|
699
|
-
function isTransformationLoop(node) {
|
|
700
|
-
if (!node.body || node.body.type !== "BlockStatement") return false;
|
|
701
|
-
const statements = node.body.body;
|
|
702
|
-
if (statements.length === 0) return false;
|
|
703
|
-
return statements.some((stmt) => {
|
|
704
|
-
if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression") {
|
|
705
|
-
const call = stmt.expression;
|
|
706
|
-
return call.callee.type === "MemberExpression" && call.callee.property.name === "push";
|
|
707
|
-
}
|
|
708
|
-
return false;
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
function isSimplePropertyAccess(node) {
|
|
712
|
-
if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.property.name === "forEach") {
|
|
713
|
-
const callback = node.arguments[0];
|
|
714
|
-
if (callback && (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression")) {
|
|
715
|
-
const body = callback.body;
|
|
716
|
-
if (body.type === "MemberExpression") {
|
|
717
|
-
return true;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
return false;
|
|
722
|
-
}
|
|
723
|
-
return {
|
|
724
|
-
ForStatement(node) {
|
|
725
|
-
if (!checkForLoops) return;
|
|
726
|
-
if (isTransformationLoop(node)) {
|
|
727
|
-
context.report({
|
|
728
|
-
node,
|
|
729
|
-
messageId: "preferMapOverLoop",
|
|
730
|
-
data: { collection: "array" }
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
},
|
|
734
|
-
ForInStatement(node) {
|
|
735
|
-
if (!checkForLoops) return;
|
|
736
|
-
if (isTransformationLoop(node)) {
|
|
737
|
-
context.report({
|
|
738
|
-
node,
|
|
739
|
-
messageId: "preferMapOverLoop",
|
|
740
|
-
data: { collection: "object" }
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
},
|
|
744
|
-
ForOfStatement(node) {
|
|
745
|
-
if (!checkForLoops) return;
|
|
746
|
-
if (isTransformationLoop(node)) {
|
|
747
|
-
context.report({
|
|
748
|
-
node,
|
|
749
|
-
messageId: "preferMapOverLoop",
|
|
750
|
-
data: { collection: "iterable" }
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
},
|
|
754
|
-
CallExpression(node) {
|
|
755
|
-
if (!checkArrayMethods) return;
|
|
756
|
-
if (node.callee.type === "MemberExpression" && node.callee.property.name === "forEach") {
|
|
757
|
-
if (isSimplePropertyAccess(node)) {
|
|
758
|
-
context.report({
|
|
759
|
-
node,
|
|
760
|
-
messageId: "preferMapChain"
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
if (node.callee.type === "MemberExpression" && node.callee.property.name === "push") {
|
|
765
|
-
let parent = node.parent;
|
|
766
|
-
while (parent) {
|
|
767
|
-
if (parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && (parent.callee.property.name === "forEach" || parent.callee.property.name === "for")) {
|
|
768
|
-
context.report({
|
|
769
|
-
node,
|
|
770
|
-
messageId: "preferMapOverPush"
|
|
771
|
-
});
|
|
772
|
-
break;
|
|
773
|
-
}
|
|
774
|
-
parent = parent.parent;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
};
|
|
781
|
-
module.exports = rule;
|
|
782
|
-
}
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
// src/rules/prefer-flatmap.ts
|
|
786
|
-
var require_prefer_flatmap = __commonJS({
|
|
787
|
-
"src/rules/prefer-flatmap.ts"(exports, module) {
|
|
788
|
-
var rule = {
|
|
789
|
-
meta: {
|
|
790
|
-
type: "suggestion",
|
|
791
|
-
docs: {
|
|
792
|
-
description: "Prefer .flatMap() over .map().flat() and nested transformations",
|
|
793
|
-
category: "Best Practices",
|
|
794
|
-
recommended: true
|
|
795
|
-
},
|
|
796
|
-
fixable: "code",
|
|
797
|
-
schema: [
|
|
798
|
-
{
|
|
799
|
-
type: "object",
|
|
800
|
-
properties: {
|
|
801
|
-
checkNestedMaps: {
|
|
802
|
-
type: "boolean",
|
|
803
|
-
default: true
|
|
804
|
-
}
|
|
805
|
-
},
|
|
806
|
-
additionalProperties: false
|
|
807
|
-
}
|
|
808
|
-
],
|
|
809
|
-
messages: {
|
|
810
|
-
preferFlatMapOverMapFlat: "Use .flatMap() instead of .map().flat()",
|
|
811
|
-
preferFlatMapNested: "Consider .flatMap() for nested transformations that return arrays",
|
|
812
|
-
preferFlatMapChain: "Use .flatMap() instead of chained .map() operations that flatten results"
|
|
813
|
-
}
|
|
814
|
-
},
|
|
815
|
-
create(context) {
|
|
816
|
-
const options = context.options[0] || {};
|
|
817
|
-
const checkNestedMaps = options.checkNestedMaps !== false;
|
|
818
|
-
function isMapFollowedByFlat(node) {
|
|
819
|
-
if (node.type !== "CallExpression") return false;
|
|
820
|
-
const callee = node.callee;
|
|
821
|
-
if (callee.type !== "MemberExpression") return false;
|
|
822
|
-
if (callee.property.name === "flat") {
|
|
823
|
-
const object = callee.object;
|
|
824
|
-
if (object.type === "CallExpression" && object.callee.type === "MemberExpression" && object.callee.property.name === "map") {
|
|
825
|
-
return true;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
return false;
|
|
829
|
-
}
|
|
830
|
-
function returnsArray(functionNode) {
|
|
831
|
-
if (!functionNode || !functionNode.body) return false;
|
|
832
|
-
if (functionNode.body.type === "ArrayExpression") {
|
|
833
|
-
return true;
|
|
834
|
-
}
|
|
835
|
-
if (functionNode.body.type === "CallExpression") {
|
|
836
|
-
const call = functionNode.body;
|
|
837
|
-
if (call.callee.type === "MemberExpression") {
|
|
838
|
-
const methodName = call.callee.property.name;
|
|
839
|
-
if (["map", "filter", "slice", "concat", "split"].includes(methodName)) {
|
|
840
|
-
return true;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
if (functionNode.body.type === "BlockStatement") {
|
|
845
|
-
const statements = functionNode.body.body;
|
|
846
|
-
for (const stmt of statements) {
|
|
847
|
-
if (stmt.type === "ReturnStatement" && stmt.argument) {
|
|
848
|
-
if (stmt.argument.type === "ArrayExpression") {
|
|
849
|
-
return true;
|
|
850
|
-
}
|
|
851
|
-
if (stmt.argument.type === "CallExpression") {
|
|
852
|
-
const call = stmt.argument;
|
|
853
|
-
if (call.callee.type === "MemberExpression") {
|
|
854
|
-
const methodName = call.callee.property.name;
|
|
855
|
-
if (["map", "filter", "slice", "concat", "split"].includes(methodName)) {
|
|
856
|
-
return true;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
return false;
|
|
864
|
-
}
|
|
865
|
-
function isNestedMapReturningArrays(node) {
|
|
866
|
-
if (node.type !== "CallExpression") return false;
|
|
867
|
-
const callee = node.callee;
|
|
868
|
-
if (callee.type !== "MemberExpression") return false;
|
|
869
|
-
if (callee.property.name === "map") {
|
|
870
|
-
const callback = node.arguments[0];
|
|
871
|
-
if (callback && (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression")) {
|
|
872
|
-
return returnsArray(callback);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
return false;
|
|
876
|
-
}
|
|
877
|
-
return {
|
|
878
|
-
CallExpression(node) {
|
|
879
|
-
if (isMapFollowedByFlat(node)) {
|
|
880
|
-
const sourceCode = context.sourceCode;
|
|
881
|
-
context.report({
|
|
882
|
-
node,
|
|
883
|
-
messageId: "preferFlatMapOverMapFlat",
|
|
884
|
-
fix(fixer) {
|
|
885
|
-
const mapCall = node.callee.object;
|
|
886
|
-
const mapCallText = sourceCode.getText(mapCall);
|
|
887
|
-
const flatMapText = mapCallText.replace(/\.map\s*\(/, ".flatMap(");
|
|
888
|
-
return fixer.replaceText(node, flatMapText);
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
if (node.callee.type === "MemberExpression" && node.callee.property.name === "map") {
|
|
894
|
-
const object = node.callee.object;
|
|
895
|
-
if (object.type === "CallExpression" && object.callee.type === "MemberExpression" && object.callee.property.name === "map") {
|
|
896
|
-
const firstMapCallback = object.arguments[0];
|
|
897
|
-
if (firstMapCallback && returnsArray(firstMapCallback)) {
|
|
898
|
-
context.report({
|
|
899
|
-
node: object,
|
|
900
|
-
// Report on the first map call
|
|
901
|
-
messageId: "preferFlatMapChain"
|
|
902
|
-
});
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
if (checkNestedMaps && isNestedMapReturningArrays(node)) {
|
|
908
|
-
if (node.parent && node.parent.type === "MemberExpression" && node.parent.parent && node.parent.parent.type === "CallExpression" && node.parent.property.name === "flat") {
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const object = node.callee?.object;
|
|
912
|
-
if (object?.type === "CallExpression" && object.callee?.type === "MemberExpression" && object.callee?.property?.name === "map") {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
if (node.parent?.type === "MemberExpression" && node.parent.parent?.type === "CallExpression" && node.parent.parent.callee?.property?.name === "map") {
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
context.report({
|
|
919
|
-
node,
|
|
920
|
-
messageId: "preferFlatMapNested"
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
};
|
|
927
|
-
module.exports = rule;
|
|
928
|
-
}
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
// src/rules/no-imperative-loops.ts
|
|
932
|
-
var require_no_imperative_loops = __commonJS({
|
|
933
|
-
"src/rules/no-imperative-loops.ts"(exports, module) {
|
|
934
|
-
var rule = {
|
|
935
|
-
meta: {
|
|
936
|
-
type: "suggestion",
|
|
937
|
-
docs: {
|
|
938
|
-
description: "Prefer functional iteration methods over imperative for loops",
|
|
939
|
-
category: "Best Practices",
|
|
940
|
-
recommended: true
|
|
941
|
-
},
|
|
942
|
-
schema: [
|
|
943
|
-
{
|
|
944
|
-
type: "object",
|
|
945
|
-
properties: {
|
|
946
|
-
allowForIndexAccess: {
|
|
947
|
-
type: "boolean",
|
|
948
|
-
default: false
|
|
949
|
-
},
|
|
950
|
-
allowWhileLoops: {
|
|
951
|
-
type: "boolean",
|
|
952
|
-
default: false
|
|
953
|
-
},
|
|
954
|
-
allowInTests: {
|
|
955
|
-
type: "boolean",
|
|
956
|
-
default: true
|
|
957
|
-
}
|
|
958
|
-
},
|
|
959
|
-
additionalProperties: false
|
|
960
|
-
}
|
|
961
|
-
],
|
|
962
|
-
messages: {
|
|
963
|
-
noForLoop: "Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops",
|
|
964
|
-
noForInLoop: "Prefer Object.keys().forEach() or functional methods over for..in loops",
|
|
965
|
-
noForOfLoop: "Prefer .forEach() or .map() over for..of loops",
|
|
966
|
-
noWhileLoop: "Prefer functional iteration or recursion over while loops",
|
|
967
|
-
noDoWhileLoop: "Prefer functional iteration or recursion over do..while loops",
|
|
968
|
-
suggestFunctional: "Consider: {{suggestion}}"
|
|
969
|
-
}
|
|
970
|
-
},
|
|
971
|
-
create(context) {
|
|
972
|
-
const options = context.options[0] || {};
|
|
973
|
-
const allowForIndexAccess = options.allowForIndexAccess || false;
|
|
974
|
-
const allowWhileLoops = options.allowWhileLoops || false;
|
|
975
|
-
const allowInTests = options.allowInTests !== false;
|
|
976
|
-
function isInTestFile() {
|
|
977
|
-
const filename = context.getFilename();
|
|
978
|
-
return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
|
|
979
|
-
}
|
|
980
|
-
function needsIndexAccess(node) {
|
|
981
|
-
if (!node.body || node.body.type !== "BlockStatement") return false;
|
|
982
|
-
const sourceCode = context.sourceCode;
|
|
983
|
-
const bodyText = sourceCode.getText(node.body);
|
|
984
|
-
return /\[\s*\w+\s*\]/.test(bodyText) && node.init && node.init.type === "VariableDeclaration";
|
|
985
|
-
}
|
|
986
|
-
return {
|
|
987
|
-
ForStatement(node) {
|
|
988
|
-
if (allowInTests && isInTestFile()) return;
|
|
989
|
-
if (allowForIndexAccess && needsIndexAccess(node)) return;
|
|
990
|
-
context.report({
|
|
991
|
-
node,
|
|
992
|
-
messageId: "noForLoop"
|
|
993
|
-
});
|
|
994
|
-
},
|
|
995
|
-
ForInStatement(node) {
|
|
996
|
-
if (allowInTests && isInTestFile()) return;
|
|
997
|
-
context.report({
|
|
998
|
-
node,
|
|
999
|
-
messageId: "noForInLoop"
|
|
1000
|
-
});
|
|
1001
|
-
},
|
|
1002
|
-
ForOfStatement(node) {
|
|
1003
|
-
if (allowInTests && isInTestFile()) return;
|
|
1004
|
-
context.report({
|
|
1005
|
-
node,
|
|
1006
|
-
messageId: "noForOfLoop"
|
|
1007
|
-
});
|
|
1008
|
-
},
|
|
1009
|
-
WhileStatement(node) {
|
|
1010
|
-
if (allowWhileLoops) return;
|
|
1011
|
-
if (allowInTests && isInTestFile()) return;
|
|
1012
|
-
context.report({
|
|
1013
|
-
node,
|
|
1014
|
-
messageId: "noWhileLoop"
|
|
1015
|
-
});
|
|
1016
|
-
},
|
|
1017
|
-
DoWhileStatement(node) {
|
|
1018
|
-
if (allowWhileLoops) return;
|
|
1019
|
-
if (allowInTests && isInTestFile()) return;
|
|
1020
|
-
context.report({
|
|
1021
|
-
node,
|
|
1022
|
-
messageId: "noDoWhileLoop"
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
};
|
|
1028
|
-
module.exports = rule;
|
|
1029
|
-
}
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
// src/rules/index.ts
|
|
1033
|
-
var import_prefer_option, import_prefer_either, import_prefer_list, import_no_get_unsafe, import_prefer_fold, import_prefer_map, import_prefer_flatmap, import_no_imperative_loops, rules_default;
|
|
1034
|
-
var init_rules = __esm({
|
|
1035
|
-
"src/rules/index.ts"() {
|
|
1036
|
-
import_prefer_option = __toESM(require_prefer_option());
|
|
1037
|
-
import_prefer_either = __toESM(require_prefer_either());
|
|
1038
|
-
import_prefer_list = __toESM(require_prefer_list());
|
|
1039
|
-
import_no_get_unsafe = __toESM(require_no_get_unsafe());
|
|
1040
|
-
import_prefer_fold = __toESM(require_prefer_fold());
|
|
1041
|
-
import_prefer_map = __toESM(require_prefer_map());
|
|
1042
|
-
import_prefer_flatmap = __toESM(require_prefer_flatmap());
|
|
1043
|
-
import_no_imperative_loops = __toESM(require_no_imperative_loops());
|
|
1044
|
-
rules_default = {
|
|
1045
|
-
"prefer-option": import_prefer_option.default,
|
|
1046
|
-
"prefer-either": import_prefer_either.default,
|
|
1047
|
-
"prefer-list": import_prefer_list.default,
|
|
1048
|
-
"no-get-unsafe": import_no_get_unsafe.default,
|
|
1049
|
-
"prefer-fold": import_prefer_fold.default,
|
|
1050
|
-
"prefer-map": import_prefer_map.default,
|
|
1051
|
-
"prefer-flatmap": import_prefer_flatmap.default,
|
|
1052
|
-
"no-imperative-loops": import_no_imperative_loops.default
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
// src/index.ts
|
|
1058
|
-
var require_src = __commonJS({
|
|
1059
|
-
"src/index.ts"(exports, module) {
|
|
1060
|
-
init_rules();
|
|
1061
|
-
var plugin = {
|
|
1062
|
-
rules: rules_default,
|
|
1063
|
-
// Meta information
|
|
1064
|
-
meta: {
|
|
1065
|
-
name: "eslint-plugin-functype",
|
|
1066
|
-
version: "2.0.0"
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
module.exports = plugin;
|
|
1070
|
-
}
|
|
1071
|
-
});
|
|
1072
|
-
var index = require_src();
|
|
1073
|
-
|
|
1074
|
-
module.exports = index;
|
|
1075
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import e from"./rules/index.js";const t={rules:e,meta:{name:`eslint-plugin-functype`,version:`2.0.0`}};export{t as default};
|
|
1076
2
|
//# sourceMappingURL=index.js.map
|