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