eslint-plugin-functype 1.4.0 → 2.0.1

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.
Files changed (55) hide show
  1. package/dist/cli/list-rules.d.ts +1 -1
  2. package/dist/cli/list-rules.js +15 -239
  3. package/dist/cli/list-rules.js.map +1 -1
  4. package/dist/configs/recommended.d.ts +15 -0
  5. package/dist/configs/recommended.js +2 -0
  6. package/dist/configs/recommended.js.map +1 -0
  7. package/dist/configs/strict.d.ts +15 -0
  8. package/dist/configs/strict.js +2 -0
  9. package/dist/configs/strict.js.map +1 -0
  10. package/dist/dependency-validator-BBxa9-7D.js +4 -0
  11. package/dist/dependency-validator-BBxa9-7D.js.map +1 -0
  12. package/dist/index.d.ts +20 -17
  13. package/dist/index.js +1 -1364
  14. package/dist/index.js.map +1 -1
  15. package/dist/rules/index.d.ts +24 -32
  16. package/dist/rules/index.js +1 -1357
  17. package/dist/rules/index.js.map +1 -1
  18. package/dist/rules/no-get-unsafe.d.ts +7 -0
  19. package/dist/rules/no-get-unsafe.js +2 -0
  20. package/dist/rules/no-get-unsafe.js.map +1 -0
  21. package/dist/rules/no-imperative-loops.d.ts +7 -0
  22. package/dist/rules/no-imperative-loops.js +2 -0
  23. package/dist/rules/no-imperative-loops.js.map +1 -0
  24. package/dist/rules/prefer-do-notation.d.ts +7 -0
  25. package/dist/rules/prefer-do-notation.js +5 -0
  26. package/dist/rules/prefer-do-notation.js.map +1 -0
  27. package/dist/rules/prefer-either.d.ts +7 -0
  28. package/dist/rules/prefer-either.js +2 -0
  29. package/dist/rules/prefer-either.js.map +1 -0
  30. package/dist/rules/prefer-flatmap.d.ts +7 -0
  31. package/dist/rules/prefer-flatmap.js +2 -0
  32. package/dist/rules/prefer-flatmap.js.map +1 -0
  33. package/dist/rules/prefer-fold.d.ts +7 -0
  34. package/dist/rules/prefer-fold.js +2 -0
  35. package/dist/rules/prefer-fold.js.map +1 -0
  36. package/dist/rules/prefer-list.d.ts +7 -0
  37. package/dist/rules/prefer-list.js +2 -0
  38. package/dist/rules/prefer-list.js.map +1 -0
  39. package/dist/rules/prefer-map.d.ts +7 -0
  40. package/dist/rules/prefer-map.js +2 -0
  41. package/dist/rules/prefer-map.js.map +1 -0
  42. package/dist/rules/prefer-option.d.ts +7 -0
  43. package/dist/rules/prefer-option.js +2 -0
  44. package/dist/rules/prefer-option.js.map +1 -0
  45. package/dist/types/ast.d.ts +12 -0
  46. package/dist/types/ast.js +1 -0
  47. package/dist/utils/dependency-validator.d.ts +13 -11
  48. package/dist/utils/dependency-validator.js +1 -109
  49. package/dist/utils/functype-detection.d.ts +69 -0
  50. package/dist/utils/functype-detection.js +2 -0
  51. package/dist/utils/functype-detection.js.map +1 -0
  52. package/package.json +48 -52
  53. package/LICENSE +0 -21
  54. package/README.md +0 -325
  55. package/dist/utils/dependency-validator.js.map +0 -1
@@ -1,1358 +1,2 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var __create = Object.create;
6
- var __defProp = Object.defineProperty;
7
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
- var __getOwnPropNames = Object.getOwnPropertyNames;
9
- var __getProtoOf = Object.getPrototypeOf;
10
- var __hasOwnProp = Object.prototype.hasOwnProperty;
11
- var __esm = (fn, res) => function __init() {
12
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
- };
14
- var __commonJS = (cb, mod) => function __require() {
15
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
- };
17
- var __copyProps = (to, from, except, desc) => {
18
- if (from && typeof from === "object" || typeof from === "function") {
19
- for (let key of __getOwnPropNames(from))
20
- if (!__hasOwnProp.call(to, key) && key !== except)
21
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
- }
23
- return to;
24
- };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
- // If the importer is in node compatibility mode or this is not an ESM
27
- // file that has been converted to a CommonJS file using a Babel-
28
- // compatible transform (i.e. "__esModule" has not been set), then set
29
- // "default" to the CommonJS "module.exports" for node compatibility.
30
- !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
- mod
32
- ));
33
-
34
- // src/utils/functype-detection.ts
35
- function getFunctypeImports(context) {
36
- const types = /* @__PURE__ */ new Set();
37
- const functions = /* @__PURE__ */ new Set();
38
- const all = /* @__PURE__ */ new Set();
39
- const sourceCode = context.sourceCode;
40
- const program = sourceCode.ast;
41
- for (const node of program.body) {
42
- if (node.type === "ImportDeclaration" && node.source.type === "Literal" && node.source.value === "functype") {
43
- if (node.specifiers) {
44
- for (const spec of node.specifiers) {
45
- if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") {
46
- const name = spec.imported.name;
47
- all.add(name);
48
- if (["Option", "Either", "List", "LazyList", "Task", "Try"].includes(name)) {
49
- types.add(name);
50
- } else if (["Do", "DoAsync", "$"].includes(name)) {
51
- functions.add(name);
52
- } else {
53
- functions.add(name);
54
- }
55
- } else if (spec.type === "ImportDefaultSpecifier") {
56
- all.add("default");
57
- functions.add("default");
58
- } else if (spec.type === "ImportNamespaceSpecifier") {
59
- all.add("*");
60
- types.add("*");
61
- functions.add("*");
62
- }
63
- }
64
- }
65
- }
66
- }
67
- return { types, functions, all };
68
- }
69
- function getFunctypeImportsLegacy(context) {
70
- return getFunctypeImports(context).all;
71
- }
72
- function isFunctypeType(node, functypeImports) {
73
- if (!node) return false;
74
- const types = functypeImports instanceof Set ? functypeImports : functypeImports.types || functypeImports.all;
75
- if (node.type === "TSTypeReference" && node.typeName?.type === "Identifier") {
76
- const typeName = node.typeName.name;
77
- return types.has(typeName) || ["Option", "Either", "List", "LazyList", "Task", "Try"].includes(typeName);
78
- }
79
- return false;
80
- }
81
- function isFunctypeCall(node, functypeImports) {
82
- if (!node || node.type !== "CallExpression") return false;
83
- const callee = node.callee;
84
- if (callee.type === "MemberExpression" && callee.object.type === "Identifier") {
85
- const objectName = callee.object.name;
86
- const methodName = callee.property?.name;
87
- if (functypeImports.has(objectName)) return true;
88
- if (objectName === "Option" && ["some", "none", "of"].includes(methodName) || objectName === "Either" && ["left", "right", "of"].includes(methodName) || objectName === "List" && ["of", "from", "empty"].includes(methodName)) {
89
- return true;
90
- }
91
- }
92
- if (callee.type === "MemberExpression") {
93
- const methodName = callee.property?.name;
94
- if ([
95
- "map",
96
- "flatMap",
97
- "filter",
98
- "fold",
99
- "foldLeft",
100
- "foldRight",
101
- "getOrElse",
102
- "orElse",
103
- "isEmpty",
104
- "nonEmpty",
105
- "isDefined",
106
- "isSome",
107
- "isNone",
108
- "isLeft",
109
- "isRight",
110
- "toArray"
111
- ].includes(methodName)) {
112
- return true;
113
- }
114
- }
115
- return false;
116
- }
117
- function isAlreadyUsingFunctype(node, functypeImports) {
118
- let parent = node.parent;
119
- while (parent) {
120
- if (isFunctypeCall(parent, functypeImports) || isFunctypeType(parent, functypeImports)) {
121
- return true;
122
- }
123
- parent = parent.parent;
124
- }
125
- return false;
126
- }
127
- function isChainedMethodCall(node, methodName) {
128
- if (!node || node.type !== "CallExpression") return false;
129
- const callee = node.callee;
130
- if (callee.type === "MemberExpression" && callee.property.type === "Identifier" && callee.property.name === methodName) {
131
- return true;
132
- }
133
- return false;
134
- }
135
- var init_functype_detection = __esm({
136
- "src/utils/functype-detection.ts"() {
137
- }
138
- });
139
-
140
- // src/rules/prefer-option.ts
141
- var require_prefer_option = __commonJS({
142
- "src/rules/prefer-option.ts"(exports, module) {
143
- init_functype_detection();
144
- var rule2 = {
145
- meta: {
146
- type: "suggestion",
147
- docs: {
148
- description: "Prefer Option<T> over nullable types (T | null | undefined)",
149
- category: "Stylistic Issues",
150
- recommended: true
151
- },
152
- schema: [
153
- {
154
- type: "object",
155
- properties: {
156
- allowNullableIntersections: {
157
- type: "boolean",
158
- default: false
159
- }
160
- },
161
- additionalProperties: false
162
- }
163
- ],
164
- messages: {
165
- preferOption: "Prefer Option<{{type}}> over nullable type '{{nullable}}'",
166
- preferOptionReturn: "Prefer Option<{{type}}> as return type over nullable '{{nullable}}'"
167
- }
168
- },
169
- create(context) {
170
- const functypeImports = getFunctypeImportsLegacy(context);
171
- return {
172
- TSUnionType(node) {
173
- if (!node.types || node.types.length < 2) return;
174
- const hasNull = node.types.some(
175
- (type) => type.type === "TSNullKeyword" || type.type === "TSUndefinedKeyword"
176
- );
177
- if (!hasNull) return;
178
- const nonNullTypes = node.types.filter(
179
- (type) => type.type !== "TSNullKeyword" && type.type !== "TSUndefinedKeyword"
180
- );
181
- if (nonNullTypes.length === 1) {
182
- const nonNullType = nonNullTypes[0];
183
- if (isFunctypeType(nonNullType, functypeImports)) return;
184
- if (isAlreadyUsingFunctype(node, functypeImports)) return;
185
- const sourceCode = context.sourceCode;
186
- const nonNullTypeText = sourceCode.getText(nonNullType);
187
- const fullType = sourceCode.getText(node);
188
- if (nonNullTypeText.startsWith("Option<")) return;
189
- context.report({
190
- node,
191
- messageId: "preferOption",
192
- data: {
193
- type: nonNullTypeText,
194
- nullable: fullType
195
- }
196
- });
197
- }
198
- }
199
- };
200
- }
201
- };
202
- module.exports = rule2;
203
- }
204
- });
205
-
206
- // src/rules/prefer-either.ts
207
- var require_prefer_either = __commonJS({
208
- "src/rules/prefer-either.ts"(exports, module) {
209
- var rule2 = {
210
- meta: {
211
- type: "suggestion",
212
- docs: {
213
- description: "Prefer Either<E, T> over try/catch blocks and throw statements",
214
- category: "Best Practices",
215
- recommended: true
216
- },
217
- schema: [
218
- {
219
- type: "object",
220
- properties: {
221
- allowThrowInTests: {
222
- type: "boolean",
223
- default: true
224
- }
225
- },
226
- additionalProperties: false
227
- }
228
- ],
229
- messages: {
230
- preferEitherOverTryCatch: "Prefer Either<Error, T> over try/catch block",
231
- preferEitherOverThrow: "Prefer Either.left(error) over throw statement",
232
- preferEitherReturn: "Consider returning Either<Error, {{type}}> instead of throwing"
233
- }
234
- },
235
- create(context) {
236
- const options = context.options[0] || {};
237
- const allowThrowInTests = options.allowThrowInTests !== false;
238
- function isInTestFile() {
239
- const filename = context.getFilename();
240
- return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
241
- }
242
- function hasThrowStatementsOutsideCatch(node) {
243
- if (!node) return false;
244
- if (node.type === "ThrowStatement") {
245
- let parent = node.parent;
246
- while (parent) {
247
- if (parent.type === "CatchClause") return false;
248
- parent = parent.parent;
249
- }
250
- return true;
251
- }
252
- if (node.type === "CatchClause") return false;
253
- for (const key in node) {
254
- if (key === "parent") continue;
255
- const child = node[key];
256
- if (Array.isArray(child)) {
257
- for (const item of child) {
258
- if (item && typeof item === "object" && hasThrowStatementsOutsideCatch(item)) {
259
- return true;
260
- }
261
- }
262
- } else if (child && typeof child === "object" && hasThrowStatementsOutsideCatch(child)) {
263
- return true;
264
- }
265
- }
266
- return false;
267
- }
268
- return {
269
- TryStatement(node) {
270
- if (allowThrowInTests && isInTestFile()) return;
271
- if (node.handler && node.handler.body) {
272
- const catchBody = node.handler.body.body;
273
- const hasRethrow = catchBody.some((stmt) => stmt.type === "ThrowStatement");
274
- if (hasRethrow) return;
275
- }
276
- context.report({
277
- node,
278
- messageId: "preferEitherOverTryCatch"
279
- });
280
- },
281
- ThrowStatement(node) {
282
- if (allowThrowInTests && isInTestFile()) return;
283
- let parent = node.parent;
284
- while (parent) {
285
- if (parent.type === "CatchClause") return;
286
- parent = parent.parent;
287
- }
288
- context.report({
289
- node,
290
- messageId: "preferEitherOverThrow"
291
- });
292
- },
293
- FunctionDeclaration(node) {
294
- if (allowThrowInTests && isInTestFile()) return;
295
- if (!node.body) return;
296
- const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body);
297
- if (hasThrowsNotInCatch) {
298
- const returnType = node.returnType?.typeAnnotation;
299
- if (returnType) {
300
- const sourceCode = context.sourceCode;
301
- const returnTypeText = sourceCode.getText(returnType);
302
- if (!returnTypeText.includes("Either")) {
303
- context.report({
304
- node: node.id || node,
305
- messageId: "preferEitherReturn",
306
- data: { type: returnTypeText }
307
- });
308
- }
309
- }
310
- }
311
- }
312
- };
313
- }
314
- };
315
- module.exports = rule2;
316
- }
317
- });
318
-
319
- // src/rules/prefer-list.ts
320
- var require_prefer_list = __commonJS({
321
- "src/rules/prefer-list.ts"(exports, module) {
322
- init_functype_detection();
323
- var rule2 = {
324
- meta: {
325
- type: "suggestion",
326
- docs: {
327
- description: "Prefer List<T> over native arrays for immutable collections",
328
- category: "Stylistic Issues",
329
- recommended: true
330
- },
331
- schema: [
332
- {
333
- type: "object",
334
- properties: {
335
- allowArraysInTests: {
336
- type: "boolean",
337
- default: true
338
- },
339
- allowReadonlyArrays: {
340
- type: "boolean",
341
- default: true
342
- }
343
- },
344
- additionalProperties: false
345
- }
346
- ],
347
- messages: {
348
- preferList: "Prefer List<{{type}}> over array type {{arrayType}}",
349
- preferListLiteral: "Prefer List.of(...) or List.from([...]) over array literal"
350
- }
351
- },
352
- create(context) {
353
- const options = context.options[0] || {};
354
- const allowArraysInTests = options.allowArraysInTests !== false;
355
- const allowReadonlyArrays = options.allowReadonlyArrays !== false;
356
- const functypeImports = getFunctypeImportsLegacy(context);
357
- function isInTestFile() {
358
- const filename = context.getFilename();
359
- return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
360
- }
361
- function findTypeParameter(node, sourceCode) {
362
- function findInNode(n) {
363
- if (n.type === "TSTypeParameterInstantiation" && n.params && n.params[0]) {
364
- return sourceCode.getText(n.params[0]);
365
- }
366
- for (const key in n) {
367
- if (key === "parent") continue;
368
- const child = n[key];
369
- if (Array.isArray(child)) {
370
- for (const item of child) {
371
- if (item && typeof item === "object" && item.type) {
372
- const result = findInNode(item);
373
- if (result) return result;
374
- }
375
- }
376
- } else if (child && typeof child === "object" && child.type) {
377
- const result = findInNode(child);
378
- if (result) return result;
379
- }
380
- }
381
- return null;
382
- }
383
- return findInNode(node);
384
- }
385
- return {
386
- TSArrayType(node) {
387
- if (allowArraysInTests && isInTestFile()) return;
388
- const sourceCode = context.sourceCode;
389
- const elementType = sourceCode.getText(node.elementType);
390
- const fullType = sourceCode.getText(node);
391
- context.report({
392
- node,
393
- messageId: "preferList",
394
- data: {
395
- type: elementType,
396
- arrayType: fullType
397
- }
398
- });
399
- },
400
- TSTypeReference(node) {
401
- if (allowArraysInTests && isInTestFile()) return;
402
- const sourceCode = context.sourceCode;
403
- let typeName = "";
404
- if (node.typeName && node.typeName.type === "Identifier") {
405
- typeName = node.typeName.name;
406
- } else if (node.typeName) {
407
- typeName = sourceCode.getText(node.typeName);
408
- } else {
409
- return;
410
- }
411
- if (typeName === "Array") {
412
- const typeParam = findTypeParameter(node, sourceCode);
413
- const fullType = sourceCode.getText(node);
414
- if (allowReadonlyArrays && fullType.startsWith("readonly")) return;
415
- context.report({
416
- node,
417
- messageId: "preferList",
418
- data: {
419
- type: typeParam || "T",
420
- arrayType: fullType
421
- }
422
- });
423
- }
424
- if (typeName === "ReadonlyArray") {
425
- const typeParam = findTypeParameter(node, sourceCode);
426
- const fullType = sourceCode.getText(node);
427
- context.report({
428
- node,
429
- messageId: "preferList",
430
- data: {
431
- type: typeParam || "T",
432
- arrayType: fullType
433
- }
434
- });
435
- }
436
- },
437
- ArrayExpression(node) {
438
- if (allowArraysInTests && isInTestFile()) return;
439
- if (node.elements.length === 0) return;
440
- let parent = node.parent;
441
- if (parent && isFunctypeCall(parent, functypeImports)) {
442
- return;
443
- }
444
- 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)) {
445
- return;
446
- }
447
- parent = node.parent;
448
- while (parent) {
449
- if (parent.type === "ArrayExpression") {
450
- return;
451
- }
452
- parent = parent.parent;
453
- }
454
- let hasTypeAnnotation = false;
455
- parent = node.parent;
456
- while (parent) {
457
- if (parent.type === "VariableDeclarator" && parent.id?.typeAnnotation) {
458
- hasTypeAnnotation = true;
459
- break;
460
- }
461
- if (parent.type === "TSTypeAnnotation") {
462
- hasTypeAnnotation = true;
463
- break;
464
- }
465
- parent = parent.parent;
466
- }
467
- if (hasTypeAnnotation) return;
468
- context.report({
469
- node,
470
- messageId: "preferListLiteral"
471
- });
472
- }
473
- };
474
- }
475
- };
476
- module.exports = rule2;
477
- }
478
- });
479
-
480
- // src/rules/no-get-unsafe.ts
481
- var require_no_get_unsafe = __commonJS({
482
- "src/rules/no-get-unsafe.ts"(exports, module) {
483
- var rule2 = {
484
- meta: {
485
- type: "problem",
486
- docs: {
487
- description: "Avoid unsafe .get() calls on Option, Either, and other monadic types",
488
- category: "Possible Errors",
489
- recommended: true
490
- },
491
- fixable: "code",
492
- schema: [
493
- {
494
- type: "object",
495
- properties: {
496
- allowInTests: {
497
- type: "boolean",
498
- default: true
499
- },
500
- unsafeMethods: {
501
- type: "array",
502
- items: { type: "string" },
503
- default: ["get", "getOrThrow", "unwrap", "expect"]
504
- }
505
- },
506
- additionalProperties: false
507
- }
508
- ],
509
- messages: {
510
- noUnsafeGet: "Avoid unsafe .{{method}}() call. Use .fold(), .map(), or .getOrElse() instead",
511
- noUnsafeGetSuggestion: "Consider using .getOrElse(defaultValue) or .fold() for safe access"
512
- }
513
- },
514
- create(context) {
515
- const options = context.options[0] || {};
516
- const allowInTests = options.allowInTests !== false;
517
- const unsafeMethods = options.unsafeMethods || ["get", "getOrThrow", "unwrap", "expect"];
518
- function isInTestFile() {
519
- const filename = context.getFilename();
520
- return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
521
- }
522
- function isMonadicType(node) {
523
- if (!node) return false;
524
- const sourceCode = context.sourceCode;
525
- const text = sourceCode.getText(node);
526
- if (/\b(Option|Either|Maybe|Result|Some|None|Left|Right)\b/.test(text)) {
527
- return true;
528
- }
529
- if (/\.(map|flatMap|filter|fold)\s*\(/.test(text)) {
530
- return true;
531
- }
532
- if (/\b(option|either|maybe|result|some|none|left|right)\w*\b/i.test(text)) {
533
- return true;
534
- }
535
- if (node.type === "Identifier") {
536
- const varName = node.name.toLowerCase();
537
- if (/(option|either|maybe|result|some|none|left|right|opt)/.test(varName)) {
538
- return true;
539
- }
540
- }
541
- if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.object) {
542
- return isMonadicType(node.callee.object);
543
- }
544
- return false;
545
- }
546
- return {
547
- CallExpression(node) {
548
- if (allowInTests && isInTestFile()) return;
549
- if (node.callee.type !== "MemberExpression") return;
550
- const property = node.callee.property;
551
- if (!property || property.type !== "Identifier") return;
552
- const methodName = property.name;
553
- if (!unsafeMethods.includes(methodName)) return;
554
- if (isMonadicType(node.callee.object)) {
555
- context.report({
556
- node,
557
- messageId: "noUnsafeGet",
558
- data: { method: methodName },
559
- fix(fixer) {
560
- const sourceCode = context.sourceCode;
561
- const objectText = sourceCode.getText(node.callee.object);
562
- let replacement;
563
- if (methodName === "get") {
564
- replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`;
565
- } else if (methodName === "getOrThrow") {
566
- replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`;
567
- } else if (methodName === "unwrap") {
568
- replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`;
569
- } else if (methodName === "expect") {
570
- replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`;
571
- } else {
572
- replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`;
573
- }
574
- return fixer.replaceText(node, replacement);
575
- }
576
- });
577
- }
578
- }
579
- };
580
- }
581
- };
582
- module.exports = rule2;
583
- }
584
- });
585
-
586
- // src/rules/prefer-fold.ts
587
- var require_prefer_fold = __commonJS({
588
- "src/rules/prefer-fold.ts"(exports, module) {
589
- var rule2 = {
590
- meta: {
591
- type: "suggestion",
592
- docs: {
593
- description: "Prefer .fold() over if/else chains when working with monadic types",
594
- category: "Best Practices",
595
- recommended: true
596
- },
597
- fixable: "code",
598
- schema: [
599
- {
600
- type: "object",
601
- properties: {
602
- minComplexity: {
603
- type: "integer",
604
- minimum: 1,
605
- default: 2
606
- }
607
- },
608
- additionalProperties: false
609
- }
610
- ],
611
- messages: {
612
- preferFold: "Prefer .fold() over if/else when working with {{type}} types",
613
- preferFoldTernary: "Consider using .fold() instead of ternary operator for {{type}}"
614
- }
615
- },
616
- create(context) {
617
- const options = context.options[0] || {};
618
- const minComplexity = options.minComplexity || 2;
619
- function extractBodyForFold(node, sourceCode) {
620
- if (node.type === "BlockStatement") {
621
- const statements = node.body;
622
- if (statements.length === 1 && statements[0].type === "ReturnStatement") {
623
- return sourceCode.getText(statements[0].argument);
624
- } else {
625
- return sourceCode.getText(node).slice(1, -1).trim();
626
- }
627
- } else {
628
- return sourceCode.getText(node);
629
- }
630
- }
631
- function generateFoldFromIf(node) {
632
- const sourceCode = context.sourceCode;
633
- if (node.type !== "IfStatement") return null;
634
- const test = node.test;
635
- const consequent = node.consequent;
636
- const alternate = node.alternate;
637
- if (!consequent || !alternate) return null;
638
- let monadicObj = null;
639
- let isNegated = false;
640
- if (test.type === "CallExpression" && test.callee.type === "MemberExpression") {
641
- const methodName = test.callee.property.name;
642
- monadicObj = sourceCode.getText(test.callee.object);
643
- if (methodName === "isSome" || methodName === "isRight" || methodName === "isSuccess") {
644
- isNegated = false;
645
- } else if (methodName === "isNone" || methodName === "isEmpty" || methodName === "isLeft" || methodName === "isFailure") {
646
- isNegated = true;
647
- }
648
- }
649
- if (!monadicObj) return null;
650
- if (consequent.type === "BlockStatement") {
651
- if (consequent.body.length !== 1 || consequent.body[0].type !== "ReturnStatement") {
652
- return null;
653
- }
654
- }
655
- if (alternate.type === "BlockStatement") {
656
- if (alternate.body.length !== 1 || alternate.body[0].type !== "ReturnStatement") {
657
- return null;
658
- }
659
- } else if (alternate.type === "IfStatement") {
660
- return null;
661
- }
662
- const thenBody = extractBodyForFold(consequent, sourceCode);
663
- const elseBody = extractBodyForFold(alternate, sourceCode);
664
- if (isNegated) {
665
- return `${monadicObj}.fold(() => ${thenBody}, () => ${elseBody})`;
666
- } else {
667
- return `${monadicObj}.fold(() => ${elseBody}, (value) => ${thenBody})`;
668
- }
669
- }
670
- function generateFoldFromTernary(node) {
671
- const sourceCode = context.sourceCode;
672
- if (node.type !== "ConditionalExpression") return null;
673
- const test = node.test;
674
- const consequent = node.consequent;
675
- const alternate = node.alternate;
676
- let monadicObj = null;
677
- let isNegated = false;
678
- if (test.type === "CallExpression" && test.callee.type === "MemberExpression") {
679
- const methodName = test.callee.property.name;
680
- monadicObj = sourceCode.getText(test.callee.object);
681
- if (methodName === "isSome" || methodName === "isRight" || methodName === "isSuccess") {
682
- isNegated = false;
683
- } else if (methodName === "isNone" || methodName === "isEmpty" || methodName === "isLeft" || methodName === "isFailure") {
684
- isNegated = true;
685
- }
686
- }
687
- if (!monadicObj) return null;
688
- const thenExpr = sourceCode.getText(consequent);
689
- const elseExpr = sourceCode.getText(alternate);
690
- if (isNegated) {
691
- return `${monadicObj}.fold(() => ${thenExpr}, () => ${elseExpr})`;
692
- } else {
693
- return `${monadicObj}.fold(() => ${elseExpr}, (value) => ${thenExpr})`;
694
- }
695
- }
696
- function shouldAutoFix(node) {
697
- if (node.type === "CallExpression" && node.callee.type === "MemberExpression") {
698
- const methodName = node.callee.property.name;
699
- return ["isSome", "isNone", "isEmpty", "isRight", "isLeft", "isSuccess", "isFailure"].includes(methodName);
700
- }
701
- return false;
702
- }
703
- function isMonadicCheck(node) {
704
- const sourceCode = context.sourceCode;
705
- const text = sourceCode.getText(node);
706
- if (/\.(isSome|isNone|isEmpty|isDefined)\s*\(\s*\)/.test(text)) {
707
- return { isMonadic: true, type: "Option" };
708
- }
709
- if (/\.(isLeft|isRight)\s*\(\s*\)/.test(text)) {
710
- return { isMonadic: true, type: "Either" };
711
- }
712
- if (/\.(isSuccess|isFailure)\s*\(\s*\)/.test(text)) {
713
- return { isMonadic: true, type: "Result" };
714
- }
715
- if (node.type === "BinaryExpression") {
716
- 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))) {
717
- return { isMonadic: true, type: "Option" };
718
- }
719
- if (node.operator === "==" || node.operator === "!=" || node.operator === "===" || node.operator === "!==") {
720
- const leftIsUndefined = node.left.type === "Identifier" && node.left.name === "undefined" || node.left.type === "Literal" && node.left.value === void 0;
721
- const rightIsUndefined = node.right.type === "Identifier" && node.right.name === "undefined" || node.right.type === "Literal" && node.right.value === void 0;
722
- if (leftIsUndefined || rightIsUndefined) {
723
- return { isMonadic: true, type: "Option" };
724
- }
725
- }
726
- }
727
- return { isMonadic: false, type: "" };
728
- }
729
- function analyzeIfStatement(node) {
730
- const test = node.test;
731
- const monadicInfo = isMonadicCheck(test);
732
- if (!monadicInfo.isMonadic) return;
733
- if (node.parent && node.parent.type === "IfStatement") return;
734
- let complexity = 1;
735
- let current = node;
736
- while (current.alternate) {
737
- complexity++;
738
- if (current.alternate.type === "IfStatement") {
739
- current = current.alternate;
740
- } else {
741
- break;
742
- }
743
- }
744
- if (complexity >= minComplexity) {
745
- context.report({
746
- node,
747
- messageId: "preferFold",
748
- data: { type: monadicInfo.type },
749
- fix(fixer) {
750
- if (!shouldAutoFix(node.test)) {
751
- return null;
752
- }
753
- const replacement = generateFoldFromIf(node);
754
- if (replacement) {
755
- return fixer.replaceText(node, replacement);
756
- }
757
- return null;
758
- }
759
- });
760
- }
761
- }
762
- return {
763
- IfStatement(node) {
764
- analyzeIfStatement(node);
765
- },
766
- ConditionalExpression(node) {
767
- const monadicInfo = isMonadicCheck(node.test);
768
- if (monadicInfo.isMonadic) {
769
- context.report({
770
- node,
771
- messageId: "preferFoldTernary",
772
- data: { type: monadicInfo.type },
773
- fix(fixer) {
774
- if (!shouldAutoFix(node.test)) {
775
- return null;
776
- }
777
- const replacement = generateFoldFromTernary(node);
778
- if (replacement) {
779
- return fixer.replaceText(node, replacement);
780
- }
781
- return null;
782
- }
783
- });
784
- }
785
- }
786
- };
787
- }
788
- };
789
- module.exports = rule2;
790
- }
791
- });
792
-
793
- // src/rules/prefer-map.ts
794
- var require_prefer_map = __commonJS({
795
- "src/rules/prefer-map.ts"(exports, module) {
796
- var rule2 = {
797
- meta: {
798
- type: "suggestion",
799
- docs: {
800
- description: "Prefer .map() over manual transformations and imperative patterns",
801
- category: "Best Practices",
802
- recommended: true
803
- },
804
- fixable: "code",
805
- schema: [
806
- {
807
- type: "object",
808
- properties: {
809
- checkArrayMethods: {
810
- type: "boolean",
811
- default: true
812
- },
813
- checkForLoops: {
814
- type: "boolean",
815
- default: true
816
- }
817
- },
818
- additionalProperties: false
819
- }
820
- ],
821
- messages: {
822
- preferMapOverLoop: "Prefer .map() over for loop for transforming {{collection}}",
823
- preferMapOverPush: "Prefer .map() over manual .push() in loop",
824
- preferMapChain: "Consider using .map() for transformation instead of manual property access"
825
- }
826
- },
827
- create(context) {
828
- const options = context.options[0] || {};
829
- const checkArrayMethods = options.checkArrayMethods !== false;
830
- const checkForLoops = options.checkForLoops !== false;
831
- function isForEachToMapSafe(node) {
832
- if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression" || node.callee.property.name !== "forEach") {
833
- return false;
834
- }
835
- const callback = node.arguments[0];
836
- if (callback && (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression")) {
837
- const body = callback.body;
838
- return body.type === "MemberExpression";
839
- }
840
- return false;
841
- }
842
- function isTransformationLoop(node) {
843
- if (!node.body || node.body.type !== "BlockStatement") return false;
844
- const statements = node.body.body;
845
- if (statements.length === 0) return false;
846
- return statements.some((stmt) => {
847
- if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression") {
848
- const call = stmt.expression;
849
- return call.callee.type === "MemberExpression" && call.callee.property.name === "push";
850
- }
851
- return false;
852
- });
853
- }
854
- function isSimplePropertyAccess(node) {
855
- if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.property.name === "forEach") {
856
- const callback = node.arguments[0];
857
- if (callback && (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression")) {
858
- const body = callback.body;
859
- if (body.type === "MemberExpression") {
860
- return true;
861
- }
862
- }
863
- }
864
- return false;
865
- }
866
- return {
867
- ForStatement(node) {
868
- if (!checkForLoops) return;
869
- if (isTransformationLoop(node)) {
870
- context.report({
871
- node,
872
- messageId: "preferMapOverLoop",
873
- data: { collection: "array" }
874
- });
875
- }
876
- },
877
- ForInStatement(node) {
878
- if (!checkForLoops) return;
879
- if (isTransformationLoop(node)) {
880
- context.report({
881
- node,
882
- messageId: "preferMapOverLoop",
883
- data: { collection: "object" }
884
- });
885
- }
886
- },
887
- ForOfStatement(node) {
888
- if (!checkForLoops) return;
889
- if (isTransformationLoop(node)) {
890
- context.report({
891
- node,
892
- messageId: "preferMapOverLoop",
893
- data: { collection: "iterable" }
894
- });
895
- }
896
- },
897
- CallExpression(node) {
898
- if (!checkArrayMethods) return;
899
- if (node.callee.type === "MemberExpression" && node.callee.property.name === "forEach") {
900
- if (isSimplePropertyAccess(node)) {
901
- context.report({
902
- node,
903
- messageId: "preferMapChain",
904
- fix(fixer) {
905
- if (!isForEachToMapSafe(node)) {
906
- return null;
907
- }
908
- const sourceCode = context.sourceCode;
909
- const text = sourceCode.getText(node);
910
- const fixedText = text.replace(/\.forEach\s*\(/g, ".map(");
911
- return fixer.replaceText(node, fixedText);
912
- }
913
- });
914
- }
915
- }
916
- if (node.callee.type === "MemberExpression" && node.callee.property.name === "push") {
917
- let parent = node.parent;
918
- while (parent) {
919
- if (parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && (parent.callee.property.name === "forEach" || parent.callee.property.name === "for")) {
920
- context.report({
921
- node,
922
- messageId: "preferMapOverPush"
923
- });
924
- break;
925
- }
926
- parent = parent.parent;
927
- }
928
- }
929
- }
930
- };
931
- }
932
- };
933
- module.exports = rule2;
934
- }
935
- });
936
-
937
- // src/rules/prefer-flatmap.ts
938
- var require_prefer_flatmap = __commonJS({
939
- "src/rules/prefer-flatmap.ts"(exports, module) {
940
- var rule2 = {
941
- meta: {
942
- type: "suggestion",
943
- docs: {
944
- description: "Prefer .flatMap() over .map().flat() and nested transformations",
945
- category: "Best Practices",
946
- recommended: true
947
- },
948
- fixable: "code",
949
- schema: [
950
- {
951
- type: "object",
952
- properties: {
953
- checkNestedMaps: {
954
- type: "boolean",
955
- default: true
956
- }
957
- },
958
- additionalProperties: false
959
- }
960
- ],
961
- messages: {
962
- preferFlatMapOverMapFlat: "Use .flatMap() instead of .map().flat()",
963
- preferFlatMapNested: "Consider .flatMap() for nested transformations that return arrays",
964
- preferFlatMapChain: "Use .flatMap() instead of chained .map() operations that flatten results"
965
- }
966
- },
967
- create(context) {
968
- const options = context.options[0] || {};
969
- const checkNestedMaps = options.checkNestedMaps !== false;
970
- function isMapFollowedByFlat(node) {
971
- if (node.type !== "CallExpression") return false;
972
- const callee = node.callee;
973
- if (callee.type !== "MemberExpression") return false;
974
- if (callee.property.name === "flat") {
975
- const object = callee.object;
976
- if (object.type === "CallExpression" && object.callee.type === "MemberExpression" && object.callee.property.name === "map") {
977
- return true;
978
- }
979
- }
980
- return false;
981
- }
982
- function returnsArray(functionNode) {
983
- if (!functionNode || !functionNode.body) return false;
984
- if (functionNode.body.type === "ArrayExpression") {
985
- return true;
986
- }
987
- if (functionNode.body.type === "CallExpression") {
988
- const call = functionNode.body;
989
- if (call.callee.type === "MemberExpression") {
990
- const methodName = call.callee.property.name;
991
- if (["map", "filter", "slice", "concat", "split"].includes(methodName)) {
992
- return true;
993
- }
994
- }
995
- }
996
- if (functionNode.body.type === "BlockStatement") {
997
- const statements = functionNode.body.body;
998
- for (const stmt of statements) {
999
- if (stmt.type === "ReturnStatement" && stmt.argument) {
1000
- if (stmt.argument.type === "ArrayExpression") {
1001
- return true;
1002
- }
1003
- if (stmt.argument.type === "CallExpression") {
1004
- const call = stmt.argument;
1005
- if (call.callee.type === "MemberExpression") {
1006
- const methodName = call.callee.property.name;
1007
- if (["map", "filter", "slice", "concat", "split"].includes(methodName)) {
1008
- return true;
1009
- }
1010
- }
1011
- }
1012
- }
1013
- }
1014
- }
1015
- return false;
1016
- }
1017
- function isNestedMapReturningArrays(node) {
1018
- if (node.type !== "CallExpression") return false;
1019
- const callee = node.callee;
1020
- if (callee.type !== "MemberExpression") return false;
1021
- if (callee.property.name === "map") {
1022
- const callback = node.arguments[0];
1023
- if (callback && (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression")) {
1024
- return returnsArray(callback);
1025
- }
1026
- }
1027
- return false;
1028
- }
1029
- return {
1030
- CallExpression(node) {
1031
- if (isMapFollowedByFlat(node)) {
1032
- const sourceCode = context.sourceCode;
1033
- context.report({
1034
- node,
1035
- messageId: "preferFlatMapOverMapFlat",
1036
- fix(fixer) {
1037
- const mapCall = node.callee.object;
1038
- const mapCallText = sourceCode.getText(mapCall);
1039
- const flatMapText = mapCallText.replace(/\.map\s*\(/, ".flatMap(");
1040
- return fixer.replaceText(node, flatMapText);
1041
- }
1042
- });
1043
- return;
1044
- }
1045
- if (node.callee.type === "MemberExpression" && node.callee.property.name === "map") {
1046
- const object = node.callee.object;
1047
- if (object.type === "CallExpression" && object.callee.type === "MemberExpression" && object.callee.property.name === "map") {
1048
- const firstMapCallback = object.arguments[0];
1049
- if (firstMapCallback && returnsArray(firstMapCallback)) {
1050
- context.report({
1051
- node: object,
1052
- // Report on the first map call
1053
- messageId: "preferFlatMapChain"
1054
- });
1055
- return;
1056
- }
1057
- }
1058
- }
1059
- if (checkNestedMaps && isNestedMapReturningArrays(node)) {
1060
- if (node.parent && node.parent.type === "MemberExpression" && node.parent.parent && node.parent.parent.type === "CallExpression" && node.parent.property.name === "flat") {
1061
- return;
1062
- }
1063
- const object = node.callee?.object;
1064
- if (object?.type === "CallExpression" && object.callee?.type === "MemberExpression" && object.callee?.property?.name === "map") {
1065
- return;
1066
- }
1067
- if (node.parent?.type === "MemberExpression" && node.parent.parent?.type === "CallExpression" && node.parent.parent.callee?.property?.name === "map") {
1068
- return;
1069
- }
1070
- context.report({
1071
- node,
1072
- messageId: "preferFlatMapNested"
1073
- });
1074
- }
1075
- }
1076
- };
1077
- }
1078
- };
1079
- module.exports = rule2;
1080
- }
1081
- });
1082
-
1083
- // src/rules/no-imperative-loops.ts
1084
- var require_no_imperative_loops = __commonJS({
1085
- "src/rules/no-imperative-loops.ts"(exports, module) {
1086
- var rule2 = {
1087
- meta: {
1088
- type: "suggestion",
1089
- docs: {
1090
- description: "Prefer functional iteration methods over imperative for loops",
1091
- category: "Best Practices",
1092
- recommended: true
1093
- },
1094
- schema: [
1095
- {
1096
- type: "object",
1097
- properties: {
1098
- allowForIndexAccess: {
1099
- type: "boolean",
1100
- default: false
1101
- },
1102
- allowWhileLoops: {
1103
- type: "boolean",
1104
- default: false
1105
- },
1106
- allowInTests: {
1107
- type: "boolean",
1108
- default: true
1109
- }
1110
- },
1111
- additionalProperties: false
1112
- }
1113
- ],
1114
- messages: {
1115
- noForLoop: "Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops",
1116
- noForInLoop: "Prefer Object.keys().forEach() or functional methods over for..in loops",
1117
- noForOfLoop: "Prefer .forEach() or .map() over for..of loops",
1118
- noWhileLoop: "Prefer functional iteration or recursion over while loops",
1119
- noDoWhileLoop: "Prefer functional iteration or recursion over do..while loops",
1120
- suggestFunctional: "Consider: {{suggestion}}"
1121
- }
1122
- },
1123
- create(context) {
1124
- const options = context.options[0] || {};
1125
- const allowForIndexAccess = options.allowForIndexAccess || false;
1126
- const allowWhileLoops = options.allowWhileLoops || false;
1127
- const allowInTests = options.allowInTests !== false;
1128
- function isInTestFile() {
1129
- const filename = context.getFilename();
1130
- return /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(filename) || filename.includes("__tests__") || filename.includes("/test/") || filename.includes("/tests/");
1131
- }
1132
- function needsIndexAccess(node) {
1133
- if (!node.body || node.body.type !== "BlockStatement") return false;
1134
- const sourceCode = context.sourceCode;
1135
- const bodyText = sourceCode.getText(node.body);
1136
- return /\[\s*\w+\s*\]/.test(bodyText) && node.init && node.init.type === "VariableDeclaration";
1137
- }
1138
- return {
1139
- ForStatement(node) {
1140
- if (allowInTests && isInTestFile()) return;
1141
- if (allowForIndexAccess && needsIndexAccess(node)) return;
1142
- context.report({
1143
- node,
1144
- messageId: "noForLoop"
1145
- });
1146
- },
1147
- ForInStatement(node) {
1148
- if (allowInTests && isInTestFile()) return;
1149
- context.report({
1150
- node,
1151
- messageId: "noForInLoop"
1152
- });
1153
- },
1154
- ForOfStatement(node) {
1155
- if (allowInTests && isInTestFile()) return;
1156
- context.report({
1157
- node,
1158
- messageId: "noForOfLoop"
1159
- });
1160
- },
1161
- WhileStatement(node) {
1162
- if (allowWhileLoops) return;
1163
- if (allowInTests && isInTestFile()) return;
1164
- context.report({
1165
- node,
1166
- messageId: "noWhileLoop"
1167
- });
1168
- },
1169
- DoWhileStatement(node) {
1170
- if (allowWhileLoops) return;
1171
- if (allowInTests && isInTestFile()) return;
1172
- context.report({
1173
- node,
1174
- messageId: "noDoWhileLoop"
1175
- });
1176
- }
1177
- };
1178
- }
1179
- };
1180
- module.exports = rule2;
1181
- }
1182
- });
1183
-
1184
- // src/rules/index.ts
1185
- var import_prefer_option = __toESM(require_prefer_option());
1186
- var import_prefer_either = __toESM(require_prefer_either());
1187
- var import_prefer_list = __toESM(require_prefer_list());
1188
- var import_no_get_unsafe = __toESM(require_no_get_unsafe());
1189
- var import_prefer_fold = __toESM(require_prefer_fold());
1190
- var import_prefer_map = __toESM(require_prefer_map());
1191
- var import_prefer_flatmap = __toESM(require_prefer_flatmap());
1192
- var import_no_imperative_loops = __toESM(require_no_imperative_loops());
1193
-
1194
- // src/rules/prefer-do-notation.ts
1195
- init_functype_detection();
1196
- var rule = {
1197
- meta: {
1198
- type: "suggestion",
1199
- docs: {
1200
- description: "Prefer Do notation for complex monadic compositions and nested operations",
1201
- category: "Best Practices",
1202
- recommended: true
1203
- },
1204
- fixable: "code",
1205
- messages: {
1206
- preferDoForNestedChecks: "Prefer Do notation for nested null/undefined checks instead of logical AND chains",
1207
- preferDoForChainedMethods: "Prefer Do notation for complex flatMap chains ({{count}} levels deep)",
1208
- preferDoForMixedMonads: "Consider Do notation when mixing Option, Either, and Try operations",
1209
- preferDoAsyncForChainedTasks: "Prefer DoAsync notation for chained async operations"
1210
- },
1211
- schema: [
1212
- {
1213
- type: "object",
1214
- properties: {
1215
- minChainDepth: {
1216
- type: "integer",
1217
- minimum: 2,
1218
- default: 3
1219
- },
1220
- detectMixedMonads: {
1221
- type: "boolean",
1222
- default: true
1223
- }
1224
- },
1225
- additionalProperties: false
1226
- }
1227
- ]
1228
- },
1229
- create(context) {
1230
- const options = context.options[0] || {};
1231
- const minChainDepth = options.minChainDepth || 3;
1232
- const detectMixedMonads = options.detectMixedMonads !== false;
1233
- const functypeImports = getFunctypeImports(context);
1234
- const hasDoImport = functypeImports.functions.has("Do") || functypeImports.functions.has("DoAsync");
1235
- function checkNestedNullChecks(node) {
1236
- if (node.operator !== "&&") return;
1237
- let depth = 0;
1238
- let current = node;
1239
- while (current.type === "LogicalExpression" && current.operator === "&&") {
1240
- if (current.right.type === "MemberExpression" || current.right.type === "LogicalExpression" && current.right.operator === "&&") {
1241
- depth++;
1242
- }
1243
- current = current.left;
1244
- }
1245
- if (depth >= 2 && hasNestedPropertyAccess(node)) {
1246
- context.report({
1247
- node,
1248
- messageId: "preferDoForNestedChecks",
1249
- fix: hasDoImport ? (fixer) => fixNestedChecks(fixer, node) : void 0
1250
- });
1251
- }
1252
- }
1253
- function hasNestedPropertyAccess(node) {
1254
- const text = context.sourceCode.getText(node);
1255
- const nestedPattern = /\w+(\.\w+){2,}/g;
1256
- const matches = text.match(nestedPattern);
1257
- return matches !== null && matches.length > 0;
1258
- }
1259
- function checkChainedMethods(node) {
1260
- if (!isChainedMethodCall(node, "flatMap")) return;
1261
- const chainDepth = getChainDepth(node);
1262
- if (chainDepth >= minChainDepth) {
1263
- context.report({
1264
- node,
1265
- messageId: "preferDoForChainedMethods",
1266
- data: { count: chainDepth.toString() },
1267
- fix: hasDoImport ? (fixer) => fixChainedMethods(fixer, node) : void 0
1268
- });
1269
- }
1270
- }
1271
- function getChainDepth(node) {
1272
- let depth = 1;
1273
- let current = node;
1274
- while (current.callee.type === "MemberExpression" && current.callee.object.type === "CallExpression") {
1275
- const method = current.callee.property;
1276
- if (method.type === "Identifier" && ["flatMap", "map", "filter", "fold"].includes(method.name)) {
1277
- depth++;
1278
- }
1279
- current = current.callee.object;
1280
- }
1281
- return depth;
1282
- }
1283
- function fixNestedChecks(fixer, node) {
1284
- const text = context.sourceCode.getText(node);
1285
- const match = text.match(/(\w+)(\.\w+)+/);
1286
- if (match) {
1287
- const baseVar = match[1];
1288
- const chain = match[0];
1289
- const doNotation = `Do(function* () {
1290
- const obj = yield* $(Option(${baseVar}))
1291
- return yield* $(Option(obj${chain.substring(baseVar.length)}))
1292
- })`;
1293
- return fixer.replaceText(node, doNotation);
1294
- }
1295
- return fixer.replaceText(node, `/* TODO: Convert to Do notation */ ${text}`);
1296
- }
1297
- function fixChainedMethods(fixer, node) {
1298
- const text = context.sourceCode.getText(node);
1299
- return fixer.replaceText(node, `/* TODO: Consider Do notation for complex chains */ ${text}`);
1300
- }
1301
- function checkMixedMonads(node) {
1302
- if (!detectMixedMonads) return;
1303
- const text = context.sourceCode.getText(node);
1304
- const monadTypes = ["Option", "Either", "Try", "Task"];
1305
- const foundMonads = monadTypes.filter((type) => text.includes(type));
1306
- if (foundMonads.length >= 2) {
1307
- context.report({
1308
- node,
1309
- messageId: "preferDoForMixedMonads"
1310
- });
1311
- }
1312
- }
1313
- return {
1314
- LogicalExpression(node) {
1315
- checkNestedNullChecks(node);
1316
- },
1317
- CallExpression(node) {
1318
- checkChainedMethods(node);
1319
- checkMixedMonads(node);
1320
- }
1321
- };
1322
- }
1323
- };
1324
- var prefer_do_notation_default = rule;
1325
-
1326
- // src/rules/index.ts
1327
- var rules_default = {
1328
- "prefer-option": import_prefer_option.default,
1329
- "prefer-either": import_prefer_either.default,
1330
- "prefer-list": import_prefer_list.default,
1331
- "no-get-unsafe": import_no_get_unsafe.default,
1332
- "prefer-fold": import_prefer_fold.default,
1333
- "prefer-map": import_prefer_map.default,
1334
- "prefer-flatmap": import_prefer_flatmap.default,
1335
- "no-imperative-loops": import_no_imperative_loops.default,
1336
- "prefer-do-notation": prefer_do_notation_default
1337
- };
1338
- var export_noGetUnsafe = import_no_get_unsafe.default;
1339
- var export_noImperativeLoops = import_no_imperative_loops.default;
1340
- var export_preferEither = import_prefer_either.default;
1341
- var export_preferFlatmap = import_prefer_flatmap.default;
1342
- var export_preferFold = import_prefer_fold.default;
1343
- var export_preferList = import_prefer_list.default;
1344
- var export_preferMap = import_prefer_map.default;
1345
- var export_preferOption = import_prefer_option.default;
1346
-
1347
- exports.default = rules_default;
1348
- exports.noGetUnsafe = export_noGetUnsafe;
1349
- exports.noImperativeLoops = export_noImperativeLoops;
1350
- exports.preferDoNotation = prefer_do_notation_default;
1351
- exports.preferEither = export_preferEither;
1352
- exports.preferFlatmap = export_preferFlatmap;
1353
- exports.preferFold = export_preferFold;
1354
- exports.preferList = export_preferList;
1355
- exports.preferMap = export_preferMap;
1356
- exports.preferOption = export_preferOption;
1357
- //# sourceMappingURL=index.js.map
1
+ import e from"./no-get-unsafe.js";import t from"./no-imperative-loops.js";import n from"./prefer-do-notation.js";import r from"./prefer-either.js";import i from"./prefer-flatmap.js";import a from"./prefer-fold.js";import o from"./prefer-list.js";import s from"./prefer-map.js";import c from"./prefer-option.js";var l={"prefer-option":c,"prefer-either":r,"prefer-list":o,"no-get-unsafe":e,"prefer-fold":a,"prefer-map":s,"prefer-flatmap":i,"no-imperative-loops":t,"prefer-do-notation":n};export{l as default,e as noGetUnsafe,t as noImperativeLoops,n as preferDoNotation,r as preferEither,i as preferFlatmap,a as preferFold,o as preferList,s as preferMap,c as preferOption};
1358
2
  //# sourceMappingURL=index.js.map