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
package/dist/index.js CHANGED
@@ -1,1076 +1,2 @@
1
- 'use strict';
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