eslint-plugin-functype 2.103.1 → 2.104.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 (44) hide show
  1. package/README.md +2 -1
  2. package/dist/cli/list-rules.js +3 -3
  3. package/dist/cli/list-rules.js.map +1 -1
  4. package/dist/configs/recommended.d.ts +1 -0
  5. package/dist/configs/recommended.js +1 -1
  6. package/dist/configs/recommended.js.map +1 -1
  7. package/dist/configs/strict.d.ts +1 -0
  8. package/dist/configs/strict.js +1 -1
  9. package/dist/configs/strict.js.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/rules/index.d.ts +3 -1
  12. package/dist/rules/index.js +1 -1
  13. package/dist/rules/index.js.map +1 -1
  14. package/dist/rules/no-imperative-loops.js +1 -1
  15. package/dist/rules/no-imperative-loops.js.map +1 -1
  16. package/dist/rules/prefer-do-notation.js +2 -2
  17. package/dist/rules/prefer-do-notation.js.map +1 -1
  18. package/dist/rules/prefer-either.js +1 -1
  19. package/dist/rules/prefer-either.js.map +1 -1
  20. package/dist/rules/prefer-flatmap.js +1 -1
  21. package/dist/rules/prefer-flatmap.js.map +1 -1
  22. package/dist/rules/prefer-fold.js +1 -1
  23. package/dist/rules/prefer-fold.js.map +1 -1
  24. package/dist/rules/prefer-functype-map.js +1 -1
  25. package/dist/rules/prefer-functype-map.js.map +1 -1
  26. package/dist/rules/prefer-functype-set.js +1 -1
  27. package/dist/rules/prefer-functype-set.js.map +1 -1
  28. package/dist/rules/prefer-list.js +1 -1
  29. package/dist/rules/prefer-list.js.map +1 -1
  30. package/dist/rules/prefer-map.js +1 -1
  31. package/dist/rules/prefer-map.js.map +1 -1
  32. package/dist/rules/prefer-try.d.ts +7 -0
  33. package/dist/rules/prefer-try.js +2 -0
  34. package/dist/rules/prefer-try.js.map +1 -0
  35. package/dist/utils/dependency-validator.js +4 -1
  36. package/dist/utils/dependency-validator.js.map +1 -0
  37. package/dist/utils/functype-detection.d.ts +0 -3
  38. package/dist/utils/functype-detection.js +1 -1
  39. package/dist/utils/functype-detection.js.map +1 -1
  40. package/dist/utils/import-fixer.js +1 -1
  41. package/dist/utils/import-fixer.js.map +1 -1
  42. package/package.json +7 -7
  43. package/dist/dependency-validator-Cnms4306.js +0 -4
  44. package/dist/dependency-validator-Cnms4306.js.map +0 -1
package/README.md CHANGED
@@ -30,7 +30,8 @@ export default [functype.configs.strict]
30
30
  | Rule | Recommended | Strict | Description |
31
31
  | ------------------------------ | :---------: | :----: | -------------------------------------------------------- |
32
32
  | `functype/prefer-option` | warn | error | Use `Option`/`Some`/`None` instead of `null`/`undefined` |
33
- | `functype/prefer-either` | warn | error | Use `Either`/`Right`/`Left` instead of throwing |
33
+ | `functype/prefer-either` | warn | error | Use `Either`/`Left`/`Right` for typed domain errors |
34
+ | `functype/prefer-try` | warn | error | Prefer `Try(() => …)` for computations that may throw |
34
35
  | `functype/prefer-fold` | warn | warn | Use `fold`/`match` instead of manual unwrapping |
35
36
  | `functype/prefer-map` | warn | warn | Use `map` instead of manual option/either checks |
36
37
  | `functype/prefer-flatmap` | warn | warn | Use `flatMap` instead of nested `map` |
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{r as e}from"../dependency-validator-Cnms4306.js";import t from"../index.js";const n={reset:`\x1B[0m`,bright:`\x1B[1m`,red:`\x1B[31m`,green:`\x1B[32m`,yellow:`\x1B[33m`,blue:`\x1B[34m`,magenta:`\x1B[35m`,cyan:`\x1B[36m`};function r(e,t){return n[t]+e+n.reset}function i(e){console.log(r(`
2
+ import e from"../index.js";import{validatePeerDependencies as t}from"../utils/dependency-validator.js";const n={reset:`\x1B[0m`,bright:`\x1B[1m`,red:`\x1B[31m`,green:`\x1B[32m`,yellow:`\x1B[33m`,blue:`\x1B[34m`,magenta:`\x1B[35m`,cyan:`\x1B[36m`};function r(e,t){return n[t]+e+n.reset}function i(e){console.log(r(`
3
3
  šŸ” Dependency Status Check:`,`bright`)),console.log(r(`=`.repeat(40),`blue`)),e.available.length>0&&(console.log(r(`
4
4
  āœ… Available:`,`green`)),e.available.forEach(e=>{let t=e.required?`šŸ”§`:`šŸ”Œ`;console.log(` ${t} ${r(e.name,`green`)} - ${e.description}`)})),e.missing.length>0&&(console.log(r(`
5
5
  āŒ Missing:`,`red`)),e.missing.forEach(e=>{let t=e.required?`āš ļø `:`šŸ’”`,n=e.required?`red`:`yellow`;console.log(` ${t} ${r(e.name,n)} - ${e.description}`)}),e.installCommand&&(console.log(r(`
@@ -7,8 +7,8 @@ import{r as e}from"../dependency-validator-Cnms4306.js";import t from"../index.j
7
7
  āš ļø Warnings:`,`yellow`)),e.warnings.forEach(e=>console.log(` ${e}`)));let t=e.isValid?`āœ… Ready to use`:`āŒ Configuration will fail`,n=e.isValid?`green`:`red`;console.log(r(`\n${t}`,n))}async function a(){let n=process.argv.slice(2),a=n.includes(`--help`)||n.includes(`-h`),s=n.includes(`--usage`)||n.includes(`-u`),c=n.includes(`--check-deps`)||n.includes(`--check`);if(a){console.log(r(`šŸ“‹ ESLint Plugin Functype - Custom Rules`,`bright`)),console.log(`
8
8
  Usage: pnpm run list-rules [options]`),console.log(`
9
9
  Options:`),console.log(` --verbose, -v Show rule descriptions and schemas`),console.log(` --usage, -u Show usage examples`),console.log(` --check-deps Check peer dependency status`),console.log(` --help, -h Show this help message`),console.log(`
10
- This command lists all custom rules provided by the functype plugin.`);return}if(c){console.log(r(`šŸ”§ ESLint Plugin Functype - Dependency Check`,`bright`));let t=e();i(t),t.isValid||process.exit(1);return}console.log(r(`šŸ”§ ESLint Plugin Functype - Custom Rules`,`bright`)),t.rules||(console.error(r(`āŒ No rules found in plugin.`,`red`)),process.exit(1)),console.log(r(`
11
- šŸ“¦ Available Custom Rules:`,`bright`)),console.log(r(`=`.repeat(40),`blue`));let l=Object.keys(t.rules);l.forEach(e=>{let n=t.rules[e],i=`functype/${e}`;if(console.log(`\n${r(`ā—`,`green`)} ${r(i,`bright`)}`),n.meta?.docs?.description&&console.log(` ${r(`Description:`,`cyan`)} ${n.meta.docs.description}`),n.meta?.type){let e=n.meta.type===`problem`?`red`:n.meta.type===`suggestion`?`yellow`:`blue`;console.log(` ${r(`Type:`,`cyan`)} ${r(n.meta.type,e)}`)}n.meta?.fixable&&console.log(` ${r(`Fixable:`,`cyan`)} ${r(`Yes`,`green`)}`),s&&console.log(` ${r(`Usage:`,`cyan`)} "${i}": "error"`)}),console.log(r(`\nšŸ“Š Summary: ${l.length} custom rules available`,`bright`)),s&&o(),console.log(r(`
10
+ This command lists all custom rules provided by the functype plugin.`);return}if(c){console.log(r(`šŸ”§ ESLint Plugin Functype - Dependency Check`,`bright`));let e=t();i(e),e.isValid||process.exit(1);return}console.log(r(`šŸ”§ ESLint Plugin Functype - Custom Rules`,`bright`)),e.rules||(console.error(r(`āŒ No rules found in plugin.`,`red`)),process.exit(1)),console.log(r(`
11
+ šŸ“¦ Available Custom Rules:`,`bright`)),console.log(r(`=`.repeat(40),`blue`));let l=Object.keys(e.rules);l.forEach(t=>{let n=e.rules[t],i=`functype/${t}`;if(console.log(`\n${r(`ā—`,`green`)} ${r(i,`bright`)}`),n.meta?.docs?.description&&console.log(` ${r(`Description:`,`cyan`)} ${n.meta.docs.description}`),n.meta?.type){let e=n.meta.type===`problem`?`red`:n.meta.type===`suggestion`?`yellow`:`blue`;console.log(` ${r(`Type:`,`cyan`)} ${r(n.meta.type,e)}`)}n.meta?.fixable&&console.log(` ${r(`Fixable:`,`cyan`)} ${r(`Yes`,`green`)}`),s&&console.log(` ${r(`Usage:`,`cyan`)} "${i}": "error"`)}),console.log(r(`\nšŸ“Š Summary: ${l.length} custom rules available`,`bright`)),s&&o(),console.log(r(`
12
12
  šŸ’” Tips:`,`bright`)),console.log(`• Use --verbose to see detailed rule information`),console.log(`• Use --usage to see configuration examples`),console.log(`• All rules are prefixed with "functype/"`),console.log(`• Consider using eslint-config-functype for pre-configured setup`),console.log(r(`
13
13
  šŸ”— Links:`,`bright`)),console.log(`• Documentation: https://github.com/jordanburke/eslint-plugin-functype`),console.log(`• Configuration Bundle: https://github.com/jordanburke/eslint-config-functype`),console.log(`• Functype Library: https://github.com/jordanburke/functype`)}function o(){console.log(r(`
14
14
  šŸ’” Usage Examples:`,`bright`)),console.log(r(`=`.repeat(30),`blue`)),console.log(`
@@ -1 +1 @@
1
- {"version":3,"file":"list-rules.js","names":[],"sources":["../../src/cli/list-rules.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport plugin from \"../index.js\"\nimport { validatePeerDependencies, type ValidationResult } from \"../utils/dependency-validator.js\"\n\n// Colors for console output\nconst colors = {\n reset: \"\\x1b[0m\",\n bright: \"\\x1b[1m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n} as const\n\nfunction colorize(text: string, color: keyof typeof colors): string {\n return colors[color] + text + colors.reset\n}\n\n// Removed unused utility functions - they were for the old config-based approach\n\nfunction printDependencyStatus(result: ValidationResult): void {\n console.log(colorize(\"\\nšŸ” Dependency Status Check:\", \"bright\"))\n console.log(colorize(\"=\".repeat(40), \"blue\"))\n\n // Show available dependencies\n if (result.available.length > 0) {\n console.log(colorize(\"\\nāœ… Available:\", \"green\"))\n result.available.forEach((dep) => {\n const icon = dep.required ? \"šŸ”§\" : \"šŸ”Œ\"\n console.log(` ${icon} ${colorize(dep.name, \"green\")} - ${dep.description}`)\n })\n }\n\n // Show missing dependencies\n if (result.missing.length > 0) {\n console.log(colorize(\"\\nāŒ Missing:\", \"red\"))\n result.missing.forEach((dep) => {\n const icon = dep.required ? \"āš ļø \" : \"šŸ’”\"\n const color = dep.required ? \"red\" : \"yellow\"\n console.log(` ${icon} ${colorize(dep.name, color)} - ${dep.description}`)\n })\n\n if (result.installCommand) {\n console.log(colorize(\"\\nšŸ“¦ Install missing dependencies:\", \"bright\"))\n console.log(` ${colorize(result.installCommand, \"cyan\")}`)\n }\n }\n\n // Show warnings\n if (result.warnings.length > 0) {\n console.log(colorize(\"\\nāš ļø Warnings:\", \"yellow\"))\n result.warnings.forEach((warning) => console.log(` ${warning}`))\n }\n\n // Overall status\n const status = result.isValid ? \"āœ… Ready to use\" : \"āŒ Configuration will fail\"\n const statusColor = result.isValid ? \"green\" : \"red\"\n console.log(colorize(`\\n${status}`, statusColor))\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2)\n const showHelp = args.includes(\"--help\") || args.includes(\"-h\")\n const showUsage = args.includes(\"--usage\") || args.includes(\"-u\")\n const checkDeps = args.includes(\"--check-deps\") || args.includes(\"--check\")\n\n if (showHelp) {\n console.log(colorize(\"šŸ“‹ ESLint Plugin Functype - Custom Rules\", \"bright\"))\n console.log(\"\\nUsage: pnpm run list-rules [options]\")\n console.log(\"\\nOptions:\")\n console.log(\" --verbose, -v Show rule descriptions and schemas\")\n console.log(\" --usage, -u Show usage examples\")\n console.log(\" --check-deps Check peer dependency status\")\n console.log(\" --help, -h Show this help message\")\n console.log(\"\\nThis command lists all custom rules provided by the functype plugin.\")\n return\n }\n\n // Handle dependency check\n if (checkDeps) {\n console.log(colorize(\"šŸ”§ ESLint Plugin Functype - Dependency Check\", \"bright\"))\n const result = validatePeerDependencies()\n printDependencyStatus(result)\n\n if (!result.isValid) {\n process.exit(1)\n }\n return\n }\n\n console.log(colorize(\"šŸ”§ ESLint Plugin Functype - Custom Rules\", \"bright\"))\n\n if (!plugin.rules) {\n console.error(colorize(\"āŒ No rules found in plugin.\", \"red\"))\n process.exit(1)\n }\n\n console.log(colorize(\"\\nšŸ“¦ Available Custom Rules:\", \"bright\"))\n console.log(colorize(\"=\".repeat(40), \"blue\"))\n\n const rules = Object.keys(plugin.rules)\n\n rules.forEach((ruleName) => {\n const rule = plugin.rules[ruleName]\n const fullName = `functype/${ruleName}`\n\n console.log(`\\n${colorize(\"ā—\", \"green\")} ${colorize(fullName, \"bright\")}`)\n\n if (rule.meta?.docs?.description) {\n console.log(` ${colorize(\"Description:\", \"cyan\")} ${rule.meta.docs.description}`)\n }\n\n if (rule.meta?.type) {\n const typeColor = rule.meta.type === \"problem\" ? \"red\" : rule.meta.type === \"suggestion\" ? \"yellow\" : \"blue\"\n console.log(` ${colorize(\"Type:\", \"cyan\")} ${colorize(rule.meta.type, typeColor)}`)\n }\n\n if (rule.meta?.fixable) {\n console.log(` ${colorize(\"Fixable:\", \"cyan\")} ${colorize(\"Yes\", \"green\")}`)\n }\n\n if (showUsage) {\n console.log(` ${colorize(\"Usage:\", \"cyan\")} \"${fullName}\": \"error\"`)\n }\n })\n\n console.log(colorize(`\\nšŸ“Š Summary: ${rules.length} custom rules available`, \"bright\"))\n\n if (showUsage) {\n printCustomUsageInfo()\n }\n\n console.log(colorize(\"\\nšŸ’” Tips:\", \"bright\"))\n console.log(\"• Use --verbose to see detailed rule information\")\n console.log(\"• Use --usage to see configuration examples\")\n console.log('• All rules are prefixed with \"functype/\"')\n console.log(\"• Consider using eslint-config-functype for pre-configured setup\")\n\n console.log(colorize(\"\\nšŸ”— Links:\", \"bright\"))\n console.log(\"• Documentation: https://github.com/jordanburke/eslint-plugin-functype\")\n console.log(\"• Configuration Bundle: https://github.com/jordanburke/eslint-config-functype\")\n console.log(\"• Functype Library: https://github.com/jordanburke/functype\")\n}\n\nfunction printCustomUsageInfo(): void {\n console.log(colorize(\"\\nšŸ’” Usage Examples:\", \"bright\"))\n console.log(colorize(\"=\".repeat(30), \"blue\"))\n console.log(\"\\n\" + colorize(\"ESLint 9+ (flat config):\", \"green\"))\n console.log(' import functypePlugin from \"eslint-plugin-functype\"')\n console.log(\" export default [\")\n console.log(\" {\")\n console.log(\" plugins: { functype: functypePlugin },\")\n console.log(\" rules: {\")\n console.log(' \"functype/prefer-option\": \"error\",')\n console.log(' \"functype/prefer-either\": \"error\",')\n console.log(' \"functype/no-get-unsafe\": \"error\",')\n console.log(\" }\")\n console.log(\" }\")\n console.log(\" ]\")\n console.log(\"\\n\" + colorize(\"With eslint-config-functype (recommended):\", \"green\"))\n console.log(' import functypeConfig from \"eslint-config-functype\"')\n console.log(\" export default [functypeConfig.recommended]\")\n}\n\n// Run the CLI\nmain().catch((error) => {\n console.error(colorize(\"āŒ Unexpected error:\", \"red\"), error)\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"list-rules.js","names":[],"sources":["../../src/cli/list-rules.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport plugin from \"../index.js\"\nimport { validatePeerDependencies, type ValidationResult } from \"../utils/dependency-validator.js\"\n\n// Colors for console output\nconst colors = {\n reset: \"\\x1b[0m\",\n bright: \"\\x1b[1m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n} as const\n\nfunction colorize(text: string, color: keyof typeof colors): string {\n return colors[color] + text + colors.reset\n}\n\n// Removed unused utility functions - they were for the old config-based approach\n\nfunction printDependencyStatus(result: ValidationResult): void {\n console.log(colorize(\"\\nšŸ” Dependency Status Check:\", \"bright\"))\n console.log(colorize(\"=\".repeat(40), \"blue\"))\n\n // Show available dependencies\n if (result.available.length > 0) {\n console.log(colorize(\"\\nāœ… Available:\", \"green\"))\n result.available.forEach((dep) => {\n const icon = dep.required ? \"šŸ”§\" : \"šŸ”Œ\"\n console.log(` ${icon} ${colorize(dep.name, \"green\")} - ${dep.description}`)\n })\n }\n\n // Show missing dependencies\n if (result.missing.length > 0) {\n console.log(colorize(\"\\nāŒ Missing:\", \"red\"))\n result.missing.forEach((dep) => {\n const icon = dep.required ? \"āš ļø \" : \"šŸ’”\"\n const color = dep.required ? \"red\" : \"yellow\"\n console.log(` ${icon} ${colorize(dep.name, color)} - ${dep.description}`)\n })\n\n if (result.installCommand) {\n console.log(colorize(\"\\nšŸ“¦ Install missing dependencies:\", \"bright\"))\n console.log(` ${colorize(result.installCommand, \"cyan\")}`)\n }\n }\n\n // Show warnings\n if (result.warnings.length > 0) {\n console.log(colorize(\"\\nāš ļø Warnings:\", \"yellow\"))\n result.warnings.forEach((warning) => console.log(` ${warning}`))\n }\n\n // Overall status\n const status = result.isValid ? \"āœ… Ready to use\" : \"āŒ Configuration will fail\"\n const statusColor = result.isValid ? \"green\" : \"red\"\n console.log(colorize(`\\n${status}`, statusColor))\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2)\n const showHelp = args.includes(\"--help\") || args.includes(\"-h\")\n const showUsage = args.includes(\"--usage\") || args.includes(\"-u\")\n const checkDeps = args.includes(\"--check-deps\") || args.includes(\"--check\")\n\n if (showHelp) {\n console.log(colorize(\"šŸ“‹ ESLint Plugin Functype - Custom Rules\", \"bright\"))\n console.log(\"\\nUsage: pnpm run list-rules [options]\")\n console.log(\"\\nOptions:\")\n console.log(\" --verbose, -v Show rule descriptions and schemas\")\n console.log(\" --usage, -u Show usage examples\")\n console.log(\" --check-deps Check peer dependency status\")\n console.log(\" --help, -h Show this help message\")\n console.log(\"\\nThis command lists all custom rules provided by the functype plugin.\")\n return\n }\n\n // Handle dependency check\n if (checkDeps) {\n console.log(colorize(\"šŸ”§ ESLint Plugin Functype - Dependency Check\", \"bright\"))\n const result = validatePeerDependencies()\n printDependencyStatus(result)\n\n if (!result.isValid) {\n process.exit(1)\n }\n return\n }\n\n console.log(colorize(\"šŸ”§ ESLint Plugin Functype - Custom Rules\", \"bright\"))\n\n if (!plugin.rules) {\n console.error(colorize(\"āŒ No rules found in plugin.\", \"red\"))\n process.exit(1)\n }\n\n console.log(colorize(\"\\nšŸ“¦ Available Custom Rules:\", \"bright\"))\n console.log(colorize(\"=\".repeat(40), \"blue\"))\n\n const rules = Object.keys(plugin.rules)\n\n rules.forEach((ruleName) => {\n const rule = plugin.rules[ruleName]\n const fullName = `functype/${ruleName}`\n\n console.log(`\\n${colorize(\"ā—\", \"green\")} ${colorize(fullName, \"bright\")}`)\n\n if (rule.meta?.docs?.description) {\n console.log(` ${colorize(\"Description:\", \"cyan\")} ${rule.meta.docs.description}`)\n }\n\n if (rule.meta?.type) {\n const typeColor = rule.meta.type === \"problem\" ? \"red\" : rule.meta.type === \"suggestion\" ? \"yellow\" : \"blue\"\n console.log(` ${colorize(\"Type:\", \"cyan\")} ${colorize(rule.meta.type, typeColor)}`)\n }\n\n if (rule.meta?.fixable) {\n console.log(` ${colorize(\"Fixable:\", \"cyan\")} ${colorize(\"Yes\", \"green\")}`)\n }\n\n if (showUsage) {\n console.log(` ${colorize(\"Usage:\", \"cyan\")} \"${fullName}\": \"error\"`)\n }\n })\n\n console.log(colorize(`\\nšŸ“Š Summary: ${rules.length} custom rules available`, \"bright\"))\n\n if (showUsage) {\n printCustomUsageInfo()\n }\n\n console.log(colorize(\"\\nšŸ’” Tips:\", \"bright\"))\n console.log(\"• Use --verbose to see detailed rule information\")\n console.log(\"• Use --usage to see configuration examples\")\n console.log('• All rules are prefixed with \"functype/\"')\n console.log(\"• Consider using eslint-config-functype for pre-configured setup\")\n\n console.log(colorize(\"\\nšŸ”— Links:\", \"bright\"))\n console.log(\"• Documentation: https://github.com/jordanburke/eslint-plugin-functype\")\n console.log(\"• Configuration Bundle: https://github.com/jordanburke/eslint-config-functype\")\n console.log(\"• Functype Library: https://github.com/jordanburke/functype\")\n}\n\nfunction printCustomUsageInfo(): void {\n console.log(colorize(\"\\nšŸ’” Usage Examples:\", \"bright\"))\n console.log(colorize(\"=\".repeat(30), \"blue\"))\n console.log(\"\\n\" + colorize(\"ESLint 9+ (flat config):\", \"green\"))\n console.log(' import functypePlugin from \"eslint-plugin-functype\"')\n console.log(\" export default [\")\n console.log(\" {\")\n console.log(\" plugins: { functype: functypePlugin },\")\n console.log(\" rules: {\")\n console.log(' \"functype/prefer-option\": \"error\",')\n console.log(' \"functype/prefer-either\": \"error\",')\n console.log(' \"functype/no-get-unsafe\": \"error\",')\n console.log(\" }\")\n console.log(\" }\")\n console.log(\" ]\")\n console.log(\"\\n\" + colorize(\"With eslint-config-functype (recommended):\", \"green\"))\n console.log(' import functypeConfig from \"eslint-config-functype\"')\n console.log(\" export default [functypeConfig.recommended]\")\n}\n\n// Run the CLI\nmain().catch((error) => {\n console.error(colorize(\"āŒ Unexpected error:\", \"red\"), error)\n process.exit(1)\n})\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ declare const recommendedRules: {
3
3
  "functype/no-let": string;
4
4
  "functype/prefer-option": string;
5
5
  "functype/prefer-either": string;
6
+ "functype/prefer-try": string;
6
7
  "functype/prefer-fold": string;
7
8
  "functype/prefer-map": string;
8
9
  "functype/prefer-flatmap": string;
@@ -1,2 +1,2 @@
1
- const e={"functype/no-let":`error`,"functype/prefer-option":`warn`,"functype/prefer-either":`warn`,"functype/prefer-fold":`warn`,"functype/prefer-map":`warn`,"functype/prefer-flatmap":`warn`,"functype/prefer-functype-map":`warn`,"functype/prefer-functype-set":`warn`,"functype/no-imperative-loops":`warn`,"functype/prefer-do-notation":`warn`,"functype/no-get-unsafe":`off`,"functype/prefer-list":`off`};export{e as default};
1
+ const e={"functype/no-let":`error`,"functype/prefer-option":`warn`,"functype/prefer-either":`warn`,"functype/prefer-try":`warn`,"functype/prefer-fold":`warn`,"functype/prefer-map":`warn`,"functype/prefer-flatmap":`warn`,"functype/prefer-functype-map":`warn`,"functype/prefer-functype-set":`warn`,"functype/no-imperative-loops":`warn`,"functype/prefer-do-notation":`warn`,"functype/no-get-unsafe":`off`,"functype/prefer-list":`off`};export{e as default};
2
2
  //# sourceMappingURL=recommended.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recommended.js","names":[],"sources":["../../src/configs/recommended.ts"],"sourcesContent":["const recommendedRules = {\n \"functype/no-let\": \"error\",\n \"functype/prefer-option\": \"warn\",\n \"functype/prefer-either\": \"warn\",\n \"functype/prefer-fold\": \"warn\",\n \"functype/prefer-map\": \"warn\",\n \"functype/prefer-flatmap\": \"warn\",\n \"functype/prefer-functype-map\": \"warn\",\n \"functype/prefer-functype-set\": \"warn\",\n \"functype/no-imperative-loops\": \"warn\",\n \"functype/prefer-do-notation\": \"warn\",\n \"functype/no-get-unsafe\": \"off\",\n \"functype/prefer-list\": \"off\",\n}\n\nexport default recommendedRules\n"],"mappings":"AAAA,MAAM,EAAmB,CACvB,kBAAmB,QACnB,yBAA0B,OAC1B,yBAA0B,OAC1B,uBAAwB,OACxB,sBAAuB,OACvB,0BAA2B,OAC3B,+BAAgC,OAChC,+BAAgC,OAChC,+BAAgC,OAChC,8BAA+B,OAC/B,yBAA0B,MAC1B,uBAAwB,KAC1B"}
1
+ {"version":3,"file":"recommended.js","names":[],"sources":["../../src/configs/recommended.ts"],"sourcesContent":["const recommendedRules = {\n \"functype/no-let\": \"error\",\n \"functype/prefer-option\": \"warn\",\n \"functype/prefer-either\": \"warn\",\n \"functype/prefer-try\": \"warn\",\n \"functype/prefer-fold\": \"warn\",\n \"functype/prefer-map\": \"warn\",\n \"functype/prefer-flatmap\": \"warn\",\n \"functype/prefer-functype-map\": \"warn\",\n \"functype/prefer-functype-set\": \"warn\",\n \"functype/no-imperative-loops\": \"warn\",\n \"functype/prefer-do-notation\": \"warn\",\n \"functype/no-get-unsafe\": \"off\",\n \"functype/prefer-list\": \"off\",\n}\n\nexport default recommendedRules\n"],"mappings":"AAAA,MAAM,EAAmB,CACvB,kBAAmB,QACnB,yBAA0B,OAC1B,yBAA0B,OAC1B,sBAAuB,OACvB,uBAAwB,OACxB,sBAAuB,OACvB,0BAA2B,OAC3B,+BAAgC,OAChC,+BAAgC,OAChC,+BAAgC,OAChC,8BAA+B,OAC/B,yBAA0B,MAC1B,uBAAwB,KAC1B"}
@@ -2,6 +2,7 @@
2
2
  declare const strictRules: {
3
3
  "functype/prefer-option": string;
4
4
  "functype/prefer-either": string;
5
+ "functype/prefer-try": string;
5
6
  "functype/no-get-unsafe": string;
6
7
  "functype/prefer-list": string;
7
8
  "functype/prefer-functype-map": string;
@@ -1,2 +1,2 @@
1
- import e from"./recommended.js";const t={...e,"functype/prefer-option":`error`,"functype/prefer-either":`error`,"functype/no-get-unsafe":`error`,"functype/prefer-list":`warn`,"functype/prefer-functype-map":`error`,"functype/prefer-functype-set":`error`,"functype/no-imperative-loops":`error`};export{t as default};
1
+ import e from"./recommended.js";const t={...e,"functype/prefer-option":`error`,"functype/prefer-either":`error`,"functype/prefer-try":`error`,"functype/no-get-unsafe":`error`,"functype/prefer-list":`warn`,"functype/prefer-functype-map":`error`,"functype/prefer-functype-set":`error`,"functype/no-imperative-loops":`error`};export{t as default};
2
2
  //# sourceMappingURL=strict.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"strict.js","names":[],"sources":["../../src/configs/strict.ts"],"sourcesContent":["import recommendedRules from \"./recommended\"\n\nconst strictRules = {\n ...recommendedRules,\n \"functype/prefer-option\": \"error\",\n \"functype/prefer-either\": \"error\",\n \"functype/no-get-unsafe\": \"error\",\n \"functype/prefer-list\": \"warn\",\n \"functype/prefer-functype-map\": \"error\",\n \"functype/prefer-functype-set\": \"error\",\n \"functype/no-imperative-loops\": \"error\",\n}\n\nexport default strictRules\n"],"mappings":"gCAEA,MAAM,EAAc,CAClB,GAAG,EACH,yBAA0B,QAC1B,yBAA0B,QAC1B,yBAA0B,QAC1B,uBAAwB,OACxB,+BAAgC,QAChC,+BAAgC,QAChC,+BAAgC,OAClC"}
1
+ {"version":3,"file":"strict.js","names":[],"sources":["../../src/configs/strict.ts"],"sourcesContent":["import recommendedRules from \"./recommended\"\n\nconst strictRules = {\n ...recommendedRules,\n \"functype/prefer-option\": \"error\",\n \"functype/prefer-either\": \"error\",\n \"functype/prefer-try\": \"error\",\n \"functype/no-get-unsafe\": \"error\",\n \"functype/prefer-list\": \"warn\",\n \"functype/prefer-functype-map\": \"error\",\n \"functype/prefer-functype-set\": \"error\",\n \"functype/no-imperative-loops\": \"error\",\n}\n\nexport default strictRules\n"],"mappings":"gCAEA,MAAM,EAAc,CAClB,GAAG,EACH,yBAA0B,QAC1B,yBAA0B,QAC1B,sBAAuB,QACvB,yBAA0B,QAC1B,uBAAwB,OACxB,+BAAgC,QAChC,+BAAgC,QAChC,+BAAgC,OAClC"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ declare const plugin: {
3
3
  rules: {
4
4
  "prefer-option": import("eslint").Rule.RuleModule;
5
5
  "prefer-either": import("eslint").Rule.RuleModule;
6
+ "prefer-try": import("eslint").Rule.RuleModule;
6
7
  "prefer-list": import("eslint").Rule.RuleModule;
7
8
  "no-get-unsafe": import("eslint").Rule.RuleModule;
8
9
  "no-let": import("eslint").Rule.RuleModule;
@@ -10,11 +10,13 @@ import rule$8 from "./prefer-functype-set.js";
10
10
  import rule$9 from "./prefer-list.js";
11
11
  import rule$10 from "./prefer-map.js";
12
12
  import rule$11 from "./prefer-option.js";
13
+ import rule$12 from "./prefer-try.js";
13
14
 
14
15
  //#region src/rules/index.d.ts
15
16
  declare const _default: {
16
17
  "prefer-option": import("eslint").Rule.RuleModule;
17
18
  "prefer-either": import("eslint").Rule.RuleModule;
19
+ "prefer-try": import("eslint").Rule.RuleModule;
18
20
  "prefer-list": import("eslint").Rule.RuleModule;
19
21
  "no-get-unsafe": import("eslint").Rule.RuleModule;
20
22
  "no-let": import("eslint").Rule.RuleModule;
@@ -27,5 +29,5 @@ declare const _default: {
27
29
  "prefer-do-notation": import("eslint").Rule.RuleModule;
28
30
  };
29
31
  //#endregion
30
- export { _default as default, rule as noGetUnsafe, rule$1 as noImperativeLoops, rule$2 as noLet, rule$3 as preferDoNotation, rule$4 as preferEither, rule$5 as preferFlatmap, rule$6 as preferFold, rule$7 as preferFunctypeMap, rule$8 as preferFunctypeSet, rule$9 as preferList, rule$10 as preferMap, rule$11 as preferOption };
32
+ export { _default as default, rule as noGetUnsafe, rule$1 as noImperativeLoops, rule$2 as noLet, rule$3 as preferDoNotation, rule$4 as preferEither, rule$5 as preferFlatmap, rule$6 as preferFold, rule$7 as preferFunctypeMap, rule$8 as preferFunctypeSet, rule$9 as preferList, rule$10 as preferMap, rule$11 as preferOption, rule$12 as preferTry };
31
33
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- import e from"./no-get-unsafe.js";import t from"./no-imperative-loops.js";import n from"./no-let.js";import r from"./prefer-do-notation.js";import i from"./prefer-either.js";import a from"./prefer-flatmap.js";import o from"./prefer-fold.js";import s from"./prefer-functype-map.js";import c from"./prefer-functype-set.js";import l from"./prefer-list.js";import u from"./prefer-map.js";import d from"./prefer-option.js";var f={"prefer-option":d,"prefer-either":i,"prefer-list":l,"no-get-unsafe":e,"no-let":n,"prefer-fold":o,"prefer-map":u,"prefer-flatmap":a,"prefer-functype-map":s,"prefer-functype-set":c,"no-imperative-loops":t,"prefer-do-notation":r};export{f as default,e as noGetUnsafe,t as noImperativeLoops,n as noLet,r as preferDoNotation,i as preferEither,a as preferFlatmap,o as preferFold,s as preferFunctypeMap,c as preferFunctypeSet,l as preferList,u as preferMap,d as preferOption};
1
+ import e from"./no-get-unsafe.js";import t from"./no-imperative-loops.js";import n from"./no-let.js";import r from"./prefer-do-notation.js";import i from"./prefer-either.js";import a from"./prefer-flatmap.js";import o from"./prefer-fold.js";import s from"./prefer-functype-map.js";import c from"./prefer-functype-set.js";import l from"./prefer-list.js";import u from"./prefer-map.js";import d from"./prefer-option.js";import f from"./prefer-try.js";var p={"prefer-option":d,"prefer-either":i,"prefer-try":f,"prefer-list":l,"no-get-unsafe":e,"no-let":n,"prefer-fold":o,"prefer-map":u,"prefer-flatmap":a,"prefer-functype-map":s,"prefer-functype-set":c,"no-imperative-loops":t,"prefer-do-notation":r};export{p as default,e as noGetUnsafe,t as noImperativeLoops,n as noLet,r as preferDoNotation,i as preferEither,a as preferFlatmap,o as preferFold,s as preferFunctypeMap,c as preferFunctypeSet,l as preferList,u as preferMap,d as preferOption,f as preferTry};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["preferOption","preferEither","preferList","noGetUnsafe","noLet","preferFold","preferMap","preferFlatmap","preferFunctypeMap","preferFunctypeSet","noImperativeLoops","preferDoNotation"],"sources":["../../src/rules/index.ts"],"sourcesContent":["import noGetUnsafe from \"./no-get-unsafe\"\nimport noImperativeLoops from \"./no-imperative-loops\"\nimport noLet from \"./no-let\"\nimport preferDoNotation from \"./prefer-do-notation\"\nimport preferEither from \"./prefer-either\"\nimport preferFlatmap from \"./prefer-flatmap\"\nimport preferFold from \"./prefer-fold\"\nimport preferFunctypeMap from \"./prefer-functype-map\"\nimport preferFunctypeSet from \"./prefer-functype-set\"\nimport preferList from \"./prefer-list\"\nimport preferMap from \"./prefer-map\"\nimport preferOption from \"./prefer-option\"\n\nexport {\n noGetUnsafe,\n noImperativeLoops,\n noLet,\n preferDoNotation,\n preferEither,\n preferFlatmap,\n preferFold,\n preferFunctypeMap,\n preferFunctypeSet,\n preferList,\n preferMap,\n preferOption,\n}\n\nexport default {\n \"prefer-option\": preferOption,\n \"prefer-either\": preferEither,\n \"prefer-list\": preferList,\n \"no-get-unsafe\": noGetUnsafe,\n \"no-let\": noLet,\n \"prefer-fold\": preferFold,\n \"prefer-map\": preferMap,\n \"prefer-flatmap\": preferFlatmap,\n \"prefer-functype-map\": preferFunctypeMap,\n \"prefer-functype-set\": preferFunctypeSet,\n \"no-imperative-loops\": noImperativeLoops,\n \"prefer-do-notation\": preferDoNotation,\n}\n"],"mappings":"kaA4BA,IAAA,EAAe,CACb,gBAAiBA,EACjB,gBAAiBC,EACjB,cAAeC,EACf,gBAAiBC,EACjB,SAAUC,EACV,cAAeC,EACf,aAAcC,EACd,iBAAkBC,EAClB,sBAAuBC,EACvB,sBAAuBC,EACvB,sBAAuBC,EACvB,qBAAsBC,CACxB"}
1
+ {"version":3,"file":"index.js","names":["preferOption","preferEither","preferTry","preferList","noGetUnsafe","noLet","preferFold","preferMap","preferFlatmap","preferFunctypeMap","preferFunctypeSet","noImperativeLoops","preferDoNotation"],"sources":["../../src/rules/index.ts"],"sourcesContent":["import noGetUnsafe from \"./no-get-unsafe\"\nimport noImperativeLoops from \"./no-imperative-loops\"\nimport noLet from \"./no-let\"\nimport preferDoNotation from \"./prefer-do-notation\"\nimport preferEither from \"./prefer-either\"\nimport preferFlatmap from \"./prefer-flatmap\"\nimport preferFold from \"./prefer-fold\"\nimport preferFunctypeMap from \"./prefer-functype-map\"\nimport preferFunctypeSet from \"./prefer-functype-set\"\nimport preferList from \"./prefer-list\"\nimport preferMap from \"./prefer-map\"\nimport preferOption from \"./prefer-option\"\nimport preferTry from \"./prefer-try\"\n\nexport {\n noGetUnsafe,\n noImperativeLoops,\n noLet,\n preferDoNotation,\n preferEither,\n preferFlatmap,\n preferFold,\n preferFunctypeMap,\n preferFunctypeSet,\n preferList,\n preferMap,\n preferOption,\n preferTry,\n}\n\nexport default {\n \"prefer-option\": preferOption,\n \"prefer-either\": preferEither,\n \"prefer-try\": preferTry,\n \"prefer-list\": preferList,\n \"no-get-unsafe\": noGetUnsafe,\n \"no-let\": noLet,\n \"prefer-fold\": preferFold,\n \"prefer-map\": preferMap,\n \"prefer-flatmap\": preferFlatmap,\n \"prefer-functype-map\": preferFunctypeMap,\n \"prefer-functype-set\": preferFunctypeSet,\n \"no-imperative-loops\": noImperativeLoops,\n \"prefer-do-notation\": preferDoNotation,\n}\n"],"mappings":"icA8BA,IAAA,EAAe,CACb,gBAAiBA,EACjB,gBAAiBC,EACjB,aAAcC,EACd,cAAeC,EACf,gBAAiBC,EACjB,SAAUC,EACV,cAAeC,EACf,aAAcC,EACd,iBAAkBC,EAClB,sBAAuBC,EACvB,sBAAuBC,EACvB,sBAAuBC,EACvB,qBAAsBC,CACxB"}
@@ -1,2 +1,2 @@
1
- const e={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer functional iteration methods over imperative for loops`,recommended:!0},schema:[{type:`object`,properties:{allowForIndexAccess:{type:`boolean`,default:!1},allowWhileLoops:{type:`boolean`,default:!1},allowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{noForLoop:`Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops`,noForInLoop:`Prefer Object.keys().forEach() or functional methods over for..in loops`,noForOfLoop:`Prefer .forEach() or .map() over for..of loops`,noWhileLoop:`Prefer functional iteration or recursion over while loops`,noDoWhileLoop:`Prefer functional iteration or recursion over do..while loops`,suggestForEach:`Replace with {{iterable}}.forEach(...)`,suggestObjectKeys:`Replace with Object.keys({{object}}).forEach(...)`}},create(e){let t=e.options[0]||{},n=t.allowForIndexAccess||!1,r=t.allowWhileLoops||!1,i=t.allowInTests!==!1;function a(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}function o(t){if(!t.body||t.body.type!==`BlockStatement`)return!1;let n=e.sourceCode.getText(t.body);return/\[\s*\w+\s*\]/.test(n)&&t.init&&t.init.type===`VariableDeclaration`}function s(e){return!e.body||e.body.type!==`BlockStatement`?!1:e.body.body.length===1}function c(e){let t=e.left;if(!t)return!1;if(t.type===`VariableDeclaration`&&t.declarations[0]){let e=t.declarations[0].id;return e.type===`ObjectPattern`||e.type===`ArrayPattern`}return!1}function l(t){let n=e.sourceCode.getText(t.body);return/\b(break|continue)\b/.test(n)}return{ForStatement(t){i&&a()||n&&o(t)||e.report({node:t,messageId:`noForLoop`})},ForInStatement(t){if(i&&a())return;let n=[];if(s(t)&&!c(t)&&!l(t)){let r=e.sourceCode,i=t.body.body[0];if(i&&i.type===`ExpressionStatement`){let e=r.getText(i),a=r.getText(t.right),o=t.left,s;s=o.type===`VariableDeclaration`&&o.declarations[0]?r.getText(o.declarations[0].id):r.getText(o),n.push({messageId:`suggestObjectKeys`,data:{object:a},fix(n){return n.replaceText(t,`Object.keys(${a}).forEach((${s}) => {\n ${e}\n})`)}})}}e.report({node:t,messageId:`noForInLoop`,suggest:n.length>0?n:void 0})},ForOfStatement(t){if(i&&a())return;let n=[];if(s(t)&&!c(t)&&!l(t)){let r=e.sourceCode,i=t.body.body[0];if(i&&i.type===`ExpressionStatement`){let e=r.getText(i),a=r.getText(t.right),o=t.left,s;s=o.type===`VariableDeclaration`&&o.declarations[0]?r.getText(o.declarations[0].id):r.getText(o),e.includes(`.push(`)||n.push({messageId:`suggestForEach`,data:{iterable:a},fix(n){return n.replaceText(t,`${a}.forEach((${s}) => {\n ${e}\n})`)}})}}e.report({node:t,messageId:`noForOfLoop`,suggest:n.length>0?n:void 0})},WhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noWhileLoop`})},DoWhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noDoWhileLoop`})}}}};export{e as default};
1
+ const e={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer functional iteration methods over imperative for loops`,recommended:!0},schema:[{type:`object`,properties:{allowForIndexAccess:{type:`boolean`,default:!1},allowWhileLoops:{type:`boolean`,default:!1},allowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{noForLoop:`Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops`,noForInLoop:`Prefer Object.keys().forEach() or functional methods over for..in loops`,noForOfLoop:`Prefer .forEach() or .map() over for..of loops`,noWhileLoop:`Prefer functional iteration or recursion over while loops`,noDoWhileLoop:`Prefer functional iteration or recursion over do..while loops`,suggestForEach:`Replace with {{iterable}}.forEach(...)`,suggestObjectKeys:`Replace with Object.keys({{object}}).forEach(...)`}},create(e){let t=e.options[0]||{},n=t.allowForIndexAccess||!1,r=t.allowWhileLoops||!1,i=t.allowInTests!==!1;function a(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}function o(t){if(!t.body||t.body.type!==`BlockStatement`)return!1;let n=e.sourceCode.getText(t.body);return/\[\s*\w+\s*\]/.test(n)&&t.init&&t.init.type===`VariableDeclaration`}function s(e){return!e.body||e.body.type!==`BlockStatement`?!1:e.body.body.length===1}function c(e){let t=e.left;if(!t)return!1;if(t.type===`VariableDeclaration`&&t.declarations[0]){let e=t.declarations[0].id;return e.type===`ObjectPattern`||e.type===`ArrayPattern`}return!1}function l(t){let n=e.sourceCode.getText(t.body);return/\b(break|continue)\b/.test(n)}return{ForStatement(t){i&&a()||n&&o(t)||e.report({node:t,messageId:`noForLoop`})},ForInStatement(t){if(i&&a())return;let n=[];if(s(t)&&!c(t)&&!l(t)){let r=e.sourceCode,i=t.body.body[0];if(i&&i.type===`ExpressionStatement`){let e=r.getText(i),a=r.getText(t.right),o=t.left,s=o.type===`VariableDeclaration`&&o.declarations[0]?r.getText(o.declarations[0].id):r.getText(o);n.push({messageId:`suggestObjectKeys`,data:{object:a},fix(n){return n.replaceText(t,`Object.keys(${a}).forEach((${s}) => {\n ${e}\n})`)}})}}e.report({node:t,messageId:`noForInLoop`,suggest:n.length>0?n:void 0})},ForOfStatement(t){if(i&&a())return;let n=[];if(s(t)&&!c(t)&&!l(t)){let r=e.sourceCode,i=t.body.body[0];if(i&&i.type===`ExpressionStatement`){let e=r.getText(i),a=r.getText(t.right),o=t.left,s=o.type===`VariableDeclaration`&&o.declarations[0]?r.getText(o.declarations[0].id):r.getText(o);e.includes(`.push(`)||n.push({messageId:`suggestForEach`,data:{iterable:a},fix(n){return n.replaceText(t,`${a}.forEach((${s}) => {\n ${e}\n})`)}})}}e.report({node:t,messageId:`noForOfLoop`,suggest:n.length>0?n:void 0})},WhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noWhileLoop`})},DoWhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noDoWhileLoop`})}}}};export{e as default};
2
2
  //# sourceMappingURL=no-imperative-loops.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"no-imperative-loops.js","names":[],"sources":["../../src/rules/no-imperative-loops.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer functional iteration methods over imperative for loops\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowForIndexAccess: {\n type: \"boolean\",\n default: false,\n },\n allowWhileLoops: {\n type: \"boolean\",\n default: false,\n },\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noForLoop: \"Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops\",\n noForInLoop: \"Prefer Object.keys().forEach() or functional methods over for..in loops\",\n noForOfLoop: \"Prefer .forEach() or .map() over for..of loops\",\n noWhileLoop: \"Prefer functional iteration or recursion over while loops\",\n noDoWhileLoop: \"Prefer functional iteration or recursion over do..while loops\",\n suggestForEach: \"Replace with {{iterable}}.forEach(...)\",\n suggestObjectKeys: \"Replace with Object.keys({{object}}).forEach(...)\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowForIndexAccess = options.allowForIndexAccess || false\n const allowWhileLoops = options.allowWhileLoops || false\n const allowInTests = options.allowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function needsIndexAccess(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n\n // Look for array index access patterns like arr[i]\n return /\\[\\s*\\w+\\s*\\]/.test(bodyText) && node.init && node.init.type === \"VariableDeclaration\"\n }\n\n function isSingleStatementBody(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n return node.body.body.length === 1\n }\n\n function hasDestructuringVariable(node: ASTNode): boolean {\n const left = node.left\n if (!left) return false\n if (left.type === \"VariableDeclaration\" && left.declarations[0]) {\n const id = left.declarations[0].id\n return id.type === \"ObjectPattern\" || id.type === \"ArrayPattern\"\n }\n return false\n }\n\n function bodyContainsBreakOrContinue(node: ASTNode): boolean {\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n return /\\b(break|continue)\\b/.test(bodyText)\n }\n\n return {\n ForStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Allow traditional for loops if they need index access and option is set\n if (allowForIndexAccess && needsIndexAccess(node)) return\n\n context.report({\n node,\n messageId: \"noForLoop\",\n })\n },\n\n ForInStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSingleStatementBody(node) && !hasDestructuringVariable(node) && !bodyContainsBreakOrContinue(node)) {\n const sourceCode = context.sourceCode\n const bodyStmt = node.body.body[0]\n\n if (bodyStmt && bodyStmt.type === \"ExpressionStatement\") {\n const stmtText = sourceCode.getText(bodyStmt)\n const objText = sourceCode.getText(node.right)\n\n const left = node.left\n let keyVar: string\n if (left.type === \"VariableDeclaration\" && left.declarations[0]) {\n keyVar = sourceCode.getText(left.declarations[0].id)\n } else {\n keyVar = sourceCode.getText(left)\n }\n\n suggest.push({\n messageId: \"suggestObjectKeys\",\n data: { object: objText },\n fix(fixer) {\n return fixer.replaceText(node, `Object.keys(${objText}).forEach((${keyVar}) => {\\n ${stmtText}\\n})`)\n },\n })\n }\n }\n\n context.report({\n node,\n messageId: \"noForInLoop\",\n suggest: suggest.length > 0 ? suggest : undefined,\n })\n },\n\n ForOfStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSingleStatementBody(node) && !hasDestructuringVariable(node) && !bodyContainsBreakOrContinue(node)) {\n const sourceCode = context.sourceCode\n const bodyStmt = node.body.body[0]\n\n if (bodyStmt && bodyStmt.type === \"ExpressionStatement\") {\n const stmtText = sourceCode.getText(bodyStmt)\n const iterableText = sourceCode.getText(node.right)\n\n const varDecl = node.left\n let varName: string\n if (varDecl.type === \"VariableDeclaration\" && varDecl.declarations[0]) {\n varName = sourceCode.getText(varDecl.declarations[0].id)\n } else {\n varName = sourceCode.getText(varDecl)\n }\n\n if (!stmtText.includes(\".push(\")) {\n suggest.push({\n messageId: \"suggestForEach\",\n data: { iterable: iterableText },\n fix(fixer) {\n return fixer.replaceText(node, `${iterableText}.forEach((${varName}) => {\\n ${stmtText}\\n})`)\n },\n })\n }\n }\n }\n\n context.report({\n node,\n messageId: \"noForOfLoop\",\n suggest: suggest.length > 0 ? suggest : undefined,\n })\n },\n\n WhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noWhileLoop\",\n })\n },\n\n DoWhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noDoWhileLoop\",\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
1
+ {"version":3,"file":"no-imperative-loops.js","names":[],"sources":["../../src/rules/no-imperative-loops.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer functional iteration methods over imperative for loops\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowForIndexAccess: {\n type: \"boolean\",\n default: false,\n },\n allowWhileLoops: {\n type: \"boolean\",\n default: false,\n },\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noForLoop: \"Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops\",\n noForInLoop: \"Prefer Object.keys().forEach() or functional methods over for..in loops\",\n noForOfLoop: \"Prefer .forEach() or .map() over for..of loops\",\n noWhileLoop: \"Prefer functional iteration or recursion over while loops\",\n noDoWhileLoop: \"Prefer functional iteration or recursion over do..while loops\",\n suggestForEach: \"Replace with {{iterable}}.forEach(...)\",\n suggestObjectKeys: \"Replace with Object.keys({{object}}).forEach(...)\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowForIndexAccess = options.allowForIndexAccess || false\n const allowWhileLoops = options.allowWhileLoops || false\n const allowInTests = options.allowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function needsIndexAccess(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n\n // Look for array index access patterns like arr[i]\n return /\\[\\s*\\w+\\s*\\]/.test(bodyText) && node.init && node.init.type === \"VariableDeclaration\"\n }\n\n function isSingleStatementBody(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n return node.body.body.length === 1\n }\n\n function hasDestructuringVariable(node: ASTNode): boolean {\n const left = node.left\n if (!left) return false\n if (left.type === \"VariableDeclaration\" && left.declarations[0]) {\n const id = left.declarations[0].id\n return id.type === \"ObjectPattern\" || id.type === \"ArrayPattern\"\n }\n return false\n }\n\n function bodyContainsBreakOrContinue(node: ASTNode): boolean {\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n return /\\b(break|continue)\\b/.test(bodyText)\n }\n\n return {\n ForStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Allow traditional for loops if they need index access and option is set\n if (allowForIndexAccess && needsIndexAccess(node)) return\n\n context.report({\n node,\n messageId: \"noForLoop\",\n })\n },\n\n ForInStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSingleStatementBody(node) && !hasDestructuringVariable(node) && !bodyContainsBreakOrContinue(node)) {\n const sourceCode = context.sourceCode\n const bodyStmt = node.body.body[0]\n\n if (bodyStmt && bodyStmt.type === \"ExpressionStatement\") {\n const stmtText = sourceCode.getText(bodyStmt)\n const objText = sourceCode.getText(node.right)\n\n const left = node.left\n const keyVar =\n left.type === \"VariableDeclaration\" && left.declarations[0]\n ? sourceCode.getText(left.declarations[0].id)\n : sourceCode.getText(left)\n\n suggest.push({\n messageId: \"suggestObjectKeys\",\n data: { object: objText },\n fix(fixer) {\n return fixer.replaceText(node, `Object.keys(${objText}).forEach((${keyVar}) => {\\n ${stmtText}\\n})`)\n },\n })\n }\n }\n\n context.report({\n node,\n messageId: \"noForInLoop\",\n suggest: suggest.length > 0 ? suggest : undefined,\n })\n },\n\n ForOfStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSingleStatementBody(node) && !hasDestructuringVariable(node) && !bodyContainsBreakOrContinue(node)) {\n const sourceCode = context.sourceCode\n const bodyStmt = node.body.body[0]\n\n if (bodyStmt && bodyStmt.type === \"ExpressionStatement\") {\n const stmtText = sourceCode.getText(bodyStmt)\n const iterableText = sourceCode.getText(node.right)\n\n const varDecl = node.left\n const varName =\n varDecl.type === \"VariableDeclaration\" && varDecl.declarations[0]\n ? sourceCode.getText(varDecl.declarations[0].id)\n : sourceCode.getText(varDecl)\n\n if (!stmtText.includes(\".push(\")) {\n suggest.push({\n messageId: \"suggestForEach\",\n data: { iterable: iterableText },\n fix(fixer) {\n return fixer.replaceText(node, `${iterableText}.forEach((${varName}) => {\\n ${stmtText}\\n})`)\n },\n })\n }\n }\n }\n\n context.report({\n node,\n messageId: \"noForOfLoop\",\n suggest: suggest.length > 0 ? suggest : undefined,\n })\n },\n\n WhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noWhileLoop\",\n })\n },\n\n DoWhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noDoWhileLoop\",\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,gEACb,YAAa,EACf,EACA,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,oBAAqB,CACnB,KAAM,UACN,QAAS,EACX,EACA,gBAAiB,CACf,KAAM,UACN,QAAS,EACX,EACA,aAAc,CACZ,KAAM,UACN,QAAS,EACX,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,UAAW,8EACX,YAAa,0EACb,YAAa,iDACb,YAAa,4DACb,cAAe,gEACf,eAAgB,yCAChB,kBAAmB,mDACrB,CACF,EAEA,OAAO,EAAS,CACd,IAAM,EAAU,EAAQ,QAAQ,IAAM,CAAC,EACjC,EAAsB,EAAQ,qBAAuB,GACrD,EAAkB,EAAQ,iBAAmB,GAC7C,EAAe,EAAQ,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,CAAQ,GAC/C,EAAS,SAAS,WAAW,GAC7B,EAAS,SAAS,QAAQ,GAC1B,EAAS,SAAS,SAAS,CAE/B,CAEA,SAAS,EAAiB,EAAwB,CAChD,GAAI,CAAC,EAAK,MAAQ,EAAK,KAAK,OAAS,iBAAkB,MAAO,GAG9D,IAAM,EADa,EAAQ,WACC,QAAQ,EAAK,IAAI,EAG7C,MAAO,gBAAgB,KAAK,CAAQ,GAAK,EAAK,MAAQ,EAAK,KAAK,OAAS,qBAC3E,CAEA,SAAS,EAAsB,EAAwB,CAErD,MADI,CAAC,EAAK,MAAQ,EAAK,KAAK,OAAS,iBAAyB,GACvD,EAAK,KAAK,KAAK,SAAW,CACnC,CAEA,SAAS,EAAyB,EAAwB,CACxD,IAAM,EAAO,EAAK,KAClB,GAAI,CAAC,EAAM,MAAO,GAClB,GAAI,EAAK,OAAS,uBAAyB,EAAK,aAAa,GAAI,CAC/D,IAAM,EAAK,EAAK,aAAa,EAAE,CAAC,GAChC,OAAO,EAAG,OAAS,iBAAmB,EAAG,OAAS,cACpD,CACA,MAAO,EACT,CAEA,SAAS,EAA4B,EAAwB,CAE3D,IAAM,EADa,EAAQ,WACC,QAAQ,EAAK,IAAI,EAC7C,MAAO,uBAAuB,KAAK,CAAQ,CAC7C,CAEA,MAAO,CACL,aAAa,EAAe,CACtB,GAAgB,EAAa,GAG7B,GAAuB,EAAiB,CAAI,GAEhD,EAAQ,OAAO,CACb,OACA,UAAW,WACb,CAAC,CACH,EAEA,eAAe,EAAe,CAC5B,GAAI,GAAgB,EAAa,EAAG,OAEpC,IAAM,EAA6C,CAAC,EAEpD,GAAI,EAAsB,CAAI,GAAK,CAAC,EAAyB,CAAI,GAAK,CAAC,EAA4B,CAAI,EAAG,CACxG,IAAM,EAAa,EAAQ,WACrB,EAAW,EAAK,KAAK,KAAK,GAEhC,GAAI,GAAY,EAAS,OAAS,sBAAuB,CACvD,IAAM,EAAW,EAAW,QAAQ,CAAQ,EACtC,EAAU,EAAW,QAAQ,EAAK,KAAK,EAEvC,EAAO,EAAK,KACZ,EACJ,EAAK,OAAS,uBAAyB,EAAK,aAAa,GACrD,EAAW,QAAQ,EAAK,aAAa,EAAE,CAAC,EAAE,EAC1C,EAAW,QAAQ,CAAI,EAE7B,EAAQ,KAAK,CACX,UAAW,oBACX,KAAM,CAAE,OAAQ,CAAQ,EACxB,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,eAAe,EAAQ,aAAa,EAAO,YAAY,EAAS,KAAK,CACtG,CACF,CAAC,CACH,CACF,CAEA,EAAQ,OAAO,CACb,OACA,UAAW,cACX,QAAS,EAAQ,OAAS,EAAI,EAAU,IAAA,EAC1C,CAAC,CACH,EAEA,eAAe,EAAe,CAC5B,GAAI,GAAgB,EAAa,EAAG,OAEpC,IAAM,EAA6C,CAAC,EAEpD,GAAI,EAAsB,CAAI,GAAK,CAAC,EAAyB,CAAI,GAAK,CAAC,EAA4B,CAAI,EAAG,CACxG,IAAM,EAAa,EAAQ,WACrB,EAAW,EAAK,KAAK,KAAK,GAEhC,GAAI,GAAY,EAAS,OAAS,sBAAuB,CACvD,IAAM,EAAW,EAAW,QAAQ,CAAQ,EACtC,EAAe,EAAW,QAAQ,EAAK,KAAK,EAE5C,EAAU,EAAK,KACf,EACJ,EAAQ,OAAS,uBAAyB,EAAQ,aAAa,GAC3D,EAAW,QAAQ,EAAQ,aAAa,EAAE,CAAC,EAAE,EAC7C,EAAW,QAAQ,CAAO,EAE3B,EAAS,SAAS,QAAQ,GAC7B,EAAQ,KAAK,CACX,UAAW,iBACX,KAAM,CAAE,SAAU,CAAa,EAC/B,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,GAAG,EAAa,YAAY,EAAQ,YAAY,EAAS,KAAK,CAC/F,CACF,CAAC,CAEL,CACF,CAEA,EAAQ,OAAO,CACb,OACA,UAAW,cACX,QAAS,EAAQ,OAAS,EAAI,EAAU,IAAA,EAC1C,CAAC,CACH,EAEA,eAAe,EAAe,CACxB,GACA,GAAgB,EAAa,GAEjC,EAAQ,OAAO,CACb,OACA,UAAW,aACb,CAAC,CACH,EAEA,iBAAiB,EAAe,CAC1B,GACA,GAAgB,EAAa,GAEjC,EAAQ,OAAO,CACb,OACA,UAAW,eACb,CAAC,CACH,CACF,CACF,CACF"}
@@ -1,5 +1,5 @@
1
- import{getFunctypeImports as e,isChainedMethodCall as t}from"../utils/functype-detection.js";const n={meta:{type:`suggestion`,docs:{description:`Prefer Do notation for complex monadic compositions and nested operations`,recommended:!0},fixable:`code`,messages:{preferDoForNestedChecks:`Prefer Do notation for nested null/undefined checks instead of logical AND chains`,preferDoForChainedMethods:`Prefer Do notation for complex flatMap chains ({{count}} levels deep)`,preferDoForMixedMonads:`Consider Do notation when mixing Option, Either, and Try operations`,preferDoAsyncForChainedTasks:`Prefer DoAsync notation for chained async operations`},schema:[{type:`object`,properties:{minChainDepth:{type:`integer`,minimum:2,default:3},detectMixedMonads:{type:`boolean`,default:!0}},additionalProperties:!1}]},create(n){let r=n.options[0]||{},i=r.minChainDepth||3,a=r.detectMixedMonads!==!1,o=e(n),s=o.functions.has(`Do`)||o.functions.has(`DoAsync`);function c(e){if(e.operator!==`&&`)return;let t=0,r=e;for(;r.type===`LogicalExpression`&&r.operator===`&&`;)(r.right.type===`MemberExpression`||r.right.type===`LogicalExpression`&&r.right.operator===`&&`)&&t++,r=r.left;t>=2&&l(e)&&n.report({node:e,messageId:`preferDoForNestedChecks`,fix:s?t=>f(t,e):void 0})}function l(e){let t=n.sourceCode.getText(e).match(/\w+(\.\w+){2,}/g);return t!==null&&t.length>0}function u(e){if(!t(e,`flatMap`))return;let r=d(e);r>=i&&n.report({node:e,messageId:`preferDoForChainedMethods`,data:{count:r.toString()},fix:s?t=>p(t,e):void 0})}function d(e){let t=1,n=e;for(;n.callee.type===`MemberExpression`&&n.callee.object.type===`CallExpression`;){let e=n.callee.property;e.type===`Identifier`&&[`flatMap`,`map`,`filter`,`fold`].includes(e.name)&&t++,n=n.callee.object}return t}function f(e,t){let r=n.sourceCode.getText(t),i=r.match(/(\w+)(\.\w+)+/);if(i){let n=i[1],r=`Do(function* () {
1
+ import{getFunctypeImports as e,isChainedMethodCall as t}from"../utils/functype-detection.js";const n=new Set([`Option`,`Either`,`Try`,`Task`]),r=new Set([`parent`,`loc`,`range`]),i=(e,t)=>new Set([...e,...t]);function a(e){if(e.type!==`CallExpression`)return new Set;let t=e.callee;return t?.type===`Identifier`&&n.has(t.name)?new Set([t.name]):t?.type===`MemberExpression`&&t.object?.type===`Identifier`&&n.has(t.object.name)?new Set([t.object.name]):new Set}function o(e){return Object.entries(e).filter(([e])=>!r.has(e)).flatMap(([,e])=>Array.isArray(e)?e:[e]).filter(e=>typeof e==`object`&&!!e)}function s(e){if(!e||typeof e!=`object`)return new Set;let t=e;return o(t).reduce((e,t)=>i(e,s(t)),a(t))}const c=new Set([`flatMap`,`map`,`filter`,`fold`]);function l(e){if(e.callee.type!==`MemberExpression`||e.callee.object.type!==`CallExpression`)return 1;let t=e.callee.property,n=e.callee.object;return(t.type===`Identifier`&&c.has(t.name)?1:0)+l(n)}function u(e){if(e.type!==`LogicalExpression`||e.operator!==`&&`)return 0;let t=e.right;return+(t.type===`MemberExpression`||t.type===`LogicalExpression`&&t.operator===`&&`)+u(e.left)}const d={meta:{type:`suggestion`,docs:{description:`Prefer Do notation for complex monadic compositions and nested operations`,recommended:!0},fixable:`code`,messages:{preferDoForNestedChecks:`Prefer Do notation for nested null/undefined checks instead of logical AND chains`,preferDoForChainedMethods:`Prefer Do notation for complex flatMap chains ({{count}} levels deep)`,preferDoForMixedMonads:`Consider Do notation when mixing Option, Either, and Try operations`,preferDoAsyncForChainedTasks:`Prefer DoAsync notation for chained async operations`},schema:[{type:`object`,properties:{minChainDepth:{type:`integer`,minimum:2,default:3},detectMixedMonads:{type:`boolean`,default:!0}},additionalProperties:!1}]},create(n){let r=n.options[0]||{},i=r.minChainDepth||3,a=r.detectMixedMonads!==!1,o=e(n),c=o.functions.has(`Do`)||o.functions.has(`DoAsync`);function d(e){e.operator===`&&`&&u(e)>=2&&f(e)&&n.report({node:e,messageId:`preferDoForNestedChecks`,fix:c?t=>m(t,e):void 0})}function f(e){let t=n.sourceCode.getText(e).match(/\w+(\.\w+){2,}/g);return t!==null&&t.length>0}function p(e){if(!t(e,`flatMap`))return;let r=l(e);r>=i&&n.report({node:e,messageId:`preferDoForChainedMethods`,data:{count:r.toString()},fix:c?t=>h(t,e):void 0})}function m(e,t){let r=n.sourceCode.getText(t),i=r.match(/(\w+)(\.\w+)+/);if(i){let n=i[1],r=`Do(function* () {
2
2
  const obj = yield* $(Option(${n}))
3
3
  return yield* $(Option(obj${i[0].substring(n.length)}))
4
- })`;return e.replaceText(t,r)}return e.replaceText(t,`/* TODO: Convert to Do notation */ ${r}`)}function p(e,t){let r=n.sourceCode.getText(t);return e.replaceText(t,`/* TODO: Consider Do notation for complex chains */ ${r}`)}function m(e){if(!a)return;let t=n.sourceCode.getText(e);[`Option`,`Either`,`Try`,`Task`].filter(e=>t.includes(e)).length>=2&&n.report({node:e,messageId:`preferDoForMixedMonads`})}return{LogicalExpression(e){c(e)},CallExpression(e){u(e),m(e)}}}};export{n as default};
4
+ })`;return e.replaceText(t,r)}return e.replaceText(t,`/* TODO: Convert to Do notation */ ${r}`)}function h(e,t){let r=n.sourceCode.getText(t);return e.replaceText(t,`/* TODO: Consider Do notation for complex chains */ ${r}`)}function g(e){a&&s(e).size>=2&&n.report({node:e,messageId:`preferDoForMixedMonads`})}return{LogicalExpression(e){d(e)},CallExpression(e){p(e),g(e)}}}};export{d as default};
5
5
  //# sourceMappingURL=prefer-do-notation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-do-notation.js","names":[],"sources":["../../src/rules/prefer-do-notation.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImports, isChainedMethodCall } from \"../utils/functype-detection\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer Do notation for complex monadic compositions and nested operations\",\n recommended: true,\n },\n fixable: \"code\",\n messages: {\n preferDoForNestedChecks: \"Prefer Do notation for nested null/undefined checks instead of logical AND chains\",\n preferDoForChainedMethods: \"Prefer Do notation for complex flatMap chains ({{count}} levels deep)\",\n preferDoForMixedMonads: \"Consider Do notation when mixing Option, Either, and Try operations\",\n preferDoAsyncForChainedTasks: \"Prefer DoAsync notation for chained async operations\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n minChainDepth: {\n type: \"integer\",\n minimum: 2,\n default: 3,\n },\n detectMixedMonads: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const options = context.options[0] || {}\n const minChainDepth = options.minChainDepth || 3\n const detectMixedMonads = options.detectMixedMonads !== false\n\n const functypeImports = getFunctypeImports(context)\n\n // Track if Do notation is already imported\n const hasDoImport = functypeImports.functions.has(\"Do\") || functypeImports.functions.has(\"DoAsync\")\n\n /**\n * Detect nested null/undefined checks like: a && a.b && a.b.c\n */\n function checkNestedNullChecks(node: ASTNode): void {\n if (node.operator !== \"&&\") return\n\n let depth = 0\n let current: ASTNode = node\n\n // Count the depth of && chains with property access\n while (current.type === \"LogicalExpression\" && current.operator === \"&&\") {\n if (\n current.right.type === \"MemberExpression\" ||\n (current.right.type === \"LogicalExpression\" && current.right.operator === \"&&\")\n ) {\n depth++\n }\n current = current.left\n }\n\n // Check if this looks like nested property access guarding\n if (depth >= 2 && hasNestedPropertyAccess(node)) {\n context.report({\n node,\n messageId: \"preferDoForNestedChecks\",\n fix: hasDoImport ? (fixer) => fixNestedChecks(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Check if logical expression contains nested property access patterns\n */\n function hasNestedPropertyAccess(node: ASTNode): boolean {\n const text = context.sourceCode.getText(node)\n\n // Look for patterns like: obj && obj.prop && obj.prop.nested\n const nestedPattern = /\\w+(\\.\\w+){2,}/g\n const matches = text.match(nestedPattern)\n\n return matches !== null && matches.length > 0\n }\n\n /**\n * Detect long flatMap chains\n */\n function checkChainedMethods(node: ASTNode): void {\n if (!isChainedMethodCall(node, \"flatMap\")) return\n\n const chainDepth = getChainDepth(node)\n\n if (chainDepth >= minChainDepth) {\n context.report({\n node,\n messageId: \"preferDoForChainedMethods\",\n data: { count: chainDepth.toString() },\n fix: hasDoImport ? (fixer) => fixChainedMethods(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Calculate chain depth for method calls\n */\n function getChainDepth(node: ASTNode): number {\n let depth = 1\n let current = node\n\n while (current.callee.type === \"MemberExpression\" && current.callee.object.type === \"CallExpression\") {\n const method = current.callee.property\n if (method.type === \"Identifier\" && [\"flatMap\", \"map\", \"filter\", \"fold\"].includes(method.name)) {\n depth++\n }\n current = current.callee.object\n }\n\n return depth\n }\n\n /**\n * Auto-fix for nested null checks\n */\n function fixNestedChecks(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // Simple transformation for common patterns\n // a && a.b && a.b.c -> Do(function* () { const x = yield* $(Option(a)); const y = yield* $(Option(x.b)); return Option(y.c) })\n const match = text.match(/(\\w+)(\\.\\w+)+/)\n if (match) {\n const baseVar = match[1]\n const chain = match[0]\n\n const doNotation = `Do(function* () {\n const obj = yield* $(Option(${baseVar}))\n return yield* $(Option(obj${chain.substring(baseVar.length)}))\n})`\n\n return fixer.replaceText(node, doNotation)\n }\n\n return fixer.replaceText(node, `/* TODO: Convert to Do notation */ ${text}`)\n }\n\n /**\n * Auto-fix for chained methods (basic)\n */\n function fixChainedMethods(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // For now, just add a comment suggesting Do notation\n return fixer.replaceText(node, `/* TODO: Consider Do notation for complex chains */ ${text}`)\n }\n\n /**\n * Detect mixed monad usage\n */\n function checkMixedMonads(node: ASTNode): void {\n if (!detectMixedMonads) return\n\n const text = context.sourceCode.getText(node)\n const monadTypes = [\"Option\", \"Either\", \"Try\", \"Task\"]\n const foundMonads = monadTypes.filter((type) => text.includes(type))\n\n if (foundMonads.length >= 2) {\n context.report({\n node,\n messageId: \"preferDoForMixedMonads\",\n })\n }\n }\n\n return {\n LogicalExpression(node) {\n checkNestedNullChecks(node)\n },\n\n CallExpression(node) {\n checkChainedMethods(node)\n checkMixedMonads(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
1
+ {"version":3,"file":"prefer-do-notation.js","names":[],"sources":["../../src/rules/prefer-do-notation.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImports, isChainedMethodCall } from \"../utils/functype-detection\"\n\nconst MONAD_TYPES: ReadonlySet<string> = new Set([\"Option\", \"Either\", \"Try\", \"Task\"])\n\n/** AST keys that don't represent syntax children — back-edges and source metadata. */\nconst NON_CHILD_KEYS: ReadonlySet<string> = new Set([\"parent\", \"loc\", \"range\"])\n\nconst union = <T>(a: ReadonlySet<T>, b: ReadonlySet<T>): ReadonlySet<T> => new Set([...a, ...b])\n\n/**\n * Set of monad names that *this single AST node* names as a constructor call.\n * Does not look at children. Returns empty set for any non-monad-constructor node.\n */\nfunction monadAt(node: ASTNode): ReadonlySet<string> {\n if (node.type !== \"CallExpression\") return new Set()\n const callee = node.callee\n // Bare constructor: Option(...), Try(...), Either(...), Task(...)\n if (callee?.type === \"Identifier\" && MONAD_TYPES.has(callee.name)) {\n return new Set([callee.name])\n }\n // Companion call: Option.none(), Either.right(x), Try.success(v)\n if (\n callee?.type === \"MemberExpression\" &&\n callee.object?.type === \"Identifier\" &&\n MONAD_TYPES.has(callee.object.name)\n ) {\n return new Set([callee.object.name])\n }\n return new Set()\n}\n\n/**\n * AST children of `node` worth recursing into. Drops back-edges and source\n * metadata; flattens array-valued keys (e.g. `arguments`); filters non-object\n * leaves (literals, raw strings, numbers).\n */\nfunction astChildren(node: ASTNode): readonly unknown[] {\n return Object.entries(node)\n .filter(([k]) => !NON_CHILD_KEYS.has(k))\n .flatMap(([, v]) => (Array.isArray(v) ? v : [v]))\n .filter((v) => v !== null && typeof v === \"object\")\n}\n\n/**\n * Pure recursion: the set of all monad names reachable from `node` (including\n * `node` itself if it is a monad constructor call). No mutation, no\n * accumulator parameter — just a fold over the children's results.\n */\nfunction monadsIn(node: unknown): ReadonlySet<string> {\n if (!node || typeof node !== \"object\") return new Set()\n const ast = node as ASTNode\n return astChildren(ast).reduce<ReadonlySet<string>>((acc, child) => union(acc, monadsIn(child)), monadAt(ast))\n}\n\nconst CHAIN_METHODS: ReadonlySet<string> = new Set([\"flatMap\", \"map\", \"filter\", \"fold\"])\n\n/**\n * Length of the chain of `.flatMap()/.map()/.filter()/.fold()` calls walking\n * left from `node`. Returns 1 for a single call, N for N stacked calls. Pure\n * tail recursion — no mutable counter, no `while` loop.\n */\nfunction getChainDepth(node: ASTNode): number {\n if (node.callee.type !== \"MemberExpression\" || node.callee.object.type !== \"CallExpression\") return 1\n const method = node.callee.property\n const next = node.callee.object as ASTNode\n const here = method.type === \"Identifier\" && CHAIN_METHODS.has(method.name) ? 1 : 0\n return here + getChainDepth(next)\n}\n\n/**\n * Depth of a logical-AND chain (`a && b && c && ...`) where each right-hand\n * side is either a MemberExpression or another && chain — the shape that\n * indicates nested property-access guarding rather than arbitrary booleans.\n */\nfunction andChainDepth(node: ASTNode): number {\n if (node.type !== \"LogicalExpression\" || node.operator !== \"&&\") return 0\n const rhs = node.right\n const here = rhs.type === \"MemberExpression\" || (rhs.type === \"LogicalExpression\" && rhs.operator === \"&&\") ? 1 : 0\n return here + andChainDepth(node.left as ASTNode)\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer Do notation for complex monadic compositions and nested operations\",\n recommended: true,\n },\n fixable: \"code\",\n messages: {\n preferDoForNestedChecks: \"Prefer Do notation for nested null/undefined checks instead of logical AND chains\",\n preferDoForChainedMethods: \"Prefer Do notation for complex flatMap chains ({{count}} levels deep)\",\n preferDoForMixedMonads: \"Consider Do notation when mixing Option, Either, and Try operations\",\n preferDoAsyncForChainedTasks: \"Prefer DoAsync notation for chained async operations\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n minChainDepth: {\n type: \"integer\",\n minimum: 2,\n default: 3,\n },\n detectMixedMonads: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const options = context.options[0] || {}\n const minChainDepth = options.minChainDepth || 3\n const detectMixedMonads = options.detectMixedMonads !== false\n\n const functypeImports = getFunctypeImports(context)\n\n // Track if Do notation is already imported\n const hasDoImport = functypeImports.functions.has(\"Do\") || functypeImports.functions.has(\"DoAsync\")\n\n /**\n * Detect nested null/undefined checks like: a && a.b && a.b.c\n */\n function checkNestedNullChecks(node: ASTNode): void {\n if (node.operator !== \"&&\") return\n\n // Check if this looks like nested property access guarding\n if (andChainDepth(node) >= 2 && hasNestedPropertyAccess(node)) {\n context.report({\n node,\n messageId: \"preferDoForNestedChecks\",\n fix: hasDoImport ? (fixer) => fixNestedChecks(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Check if logical expression contains nested property access patterns\n */\n function hasNestedPropertyAccess(node: ASTNode): boolean {\n // Look for patterns like: obj && obj.prop && obj.prop.nested\n const matches = context.sourceCode.getText(node).match(/\\w+(\\.\\w+){2,}/g)\n return matches !== null && matches.length > 0\n }\n\n /**\n * Detect long flatMap chains\n */\n function checkChainedMethods(node: ASTNode): void {\n if (!isChainedMethodCall(node, \"flatMap\")) return\n\n const chainDepth = getChainDepth(node)\n\n if (chainDepth >= minChainDepth) {\n context.report({\n node,\n messageId: \"preferDoForChainedMethods\",\n data: { count: chainDepth.toString() },\n fix: hasDoImport ? (fixer) => fixChainedMethods(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Auto-fix for nested null checks\n */\n function fixNestedChecks(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // Simple transformation for common patterns\n // a && a.b && a.b.c -> Do(function* () { const x = yield* $(Option(a)); const y = yield* $(Option(x.b)); return Option(y.c) })\n const match = text.match(/(\\w+)(\\.\\w+)+/)\n if (match) {\n const baseVar = match[1]\n const chain = match[0]\n\n const doNotation = `Do(function* () {\n const obj = yield* $(Option(${baseVar}))\n return yield* $(Option(obj${chain.substring(baseVar.length)}))\n})`\n\n return fixer.replaceText(node, doNotation)\n }\n\n return fixer.replaceText(node, `/* TODO: Convert to Do notation */ ${text}`)\n }\n\n /**\n * Auto-fix for chained methods (basic)\n */\n function fixChainedMethods(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // For now, just add a comment suggesting Do notation\n return fixer.replaceText(node, `/* TODO: Consider Do notation for complex chains */ ${text}`)\n }\n\n /**\n * Detect mixed monad usage by walking the AST under `node` and counting\n * *distinct* monad constructor calls — not substring matches.\n *\n * A \"monad constructor call\" is a CallExpression whose callee is either:\n * - a bare identifier matching a known monad type (`Option(...)`, `Try(...)`),\n * or\n * - a member expression whose object is a known monad type\n * (`Option.none()`, `Either.right(...)`, `Try.success(...)`).\n *\n * Method calls like `.toEither(...)`, `.toOption()`, `.toTry()` are NOT\n * constructor calls — they're the idiomatic *conversion* API for crossing\n * monad boundaries. The old substring-based check fired on these because\n * `.toEither` contains \"Either\" as a substring; the AST check correctly\n * sees them as method calls, not new monad constructions.\n *\n * Implementation is pure recursion over the AST: `monadAt` reports what\n * a single node names; `monadsIn` returns the union of `monadAt(self)`\n * with the results from each child. No mutable accumulator, no for-loops\n * driving state changes.\n */\n function checkMixedMonads(node: ASTNode): void {\n if (!detectMixedMonads) return\n if (monadsIn(node).size >= 2) {\n context.report({ node, messageId: \"preferDoForMixedMonads\" })\n }\n }\n\n return {\n LogicalExpression(node) {\n checkNestedNullChecks(node)\n },\n\n CallExpression(node) {\n checkChainedMethods(node)\n checkMixedMonads(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"6FAKA,MAAM,EAAmC,IAAI,IAAI,CAAC,SAAU,SAAU,MAAO,MAAM,CAAC,EAG9E,EAAsC,IAAI,IAAI,CAAC,SAAU,MAAO,OAAO,CAAC,EAExE,GAAY,EAAmB,IAAsC,IAAI,IAAI,CAAC,GAAG,EAAG,GAAG,CAAC,CAAC,EAM/F,SAAS,EAAQ,EAAoC,CACnD,GAAI,EAAK,OAAS,iBAAkB,OAAO,IAAI,IAC/C,IAAM,EAAS,EAAK,OAapB,OAXI,GAAQ,OAAS,cAAgB,EAAY,IAAI,EAAO,IAAI,EACvD,IAAI,IAAI,CAAC,EAAO,IAAI,CAAC,EAI5B,GAAQ,OAAS,oBACjB,EAAO,QAAQ,OAAS,cACxB,EAAY,IAAI,EAAO,OAAO,IAAI,EAE3B,IAAI,IAAI,CAAC,EAAO,OAAO,IAAI,CAAC,EAE9B,IAAI,GACb,CAOA,SAAS,EAAY,EAAmC,CACtD,OAAO,OAAO,QAAQ,CAAI,CAAC,CACxB,QAAQ,CAAC,KAAO,CAAC,EAAe,IAAI,CAAC,CAAC,CAAC,CACvC,SAAS,EAAG,KAAQ,MAAM,QAAQ,CAAC,EAAI,EAAI,CAAC,CAAC,CAAE,CAAC,CAChD,OAAQ,GAAoB,OAAO,GAAM,YAA3B,CAAmC,CACtD,CAOA,SAAS,EAAS,EAAoC,CACpD,GAAI,CAAC,GAAQ,OAAO,GAAS,SAAU,OAAO,IAAI,IAClD,IAAM,EAAM,EACZ,OAAO,EAAY,CAAG,CAAC,CAAC,QAA6B,EAAK,IAAU,EAAM,EAAK,EAAS,CAAK,CAAC,EAAG,EAAQ,CAAG,CAAC,CAC/G,CAEA,MAAM,EAAqC,IAAI,IAAI,CAAC,UAAW,MAAO,SAAU,MAAM,CAAC,EAOvF,SAAS,EAAc,EAAuB,CAC5C,GAAI,EAAK,OAAO,OAAS,oBAAsB,EAAK,OAAO,OAAO,OAAS,iBAAkB,MAAO,GACpG,IAAM,EAAS,EAAK,OAAO,SACrB,EAAO,EAAK,OAAO,OAEzB,OADa,EAAO,OAAS,cAAgB,EAAc,IAAI,EAAO,IAAI,EAAI,EAAI,GACpE,EAAc,CAAI,CAClC,CAOA,SAAS,EAAc,EAAuB,CAC5C,GAAI,EAAK,OAAS,qBAAuB,EAAK,WAAa,KAAM,MAAO,GACxE,IAAM,EAAM,EAAK,MAEjB,MADa,IAAI,OAAS,oBAAuB,EAAI,OAAS,qBAAuB,EAAI,WAAa,MACxF,EAAc,EAAK,IAAe,CAClD,CAEA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,4EACb,YAAa,EACf,EACA,QAAS,OACT,SAAU,CACR,wBAAyB,oFACzB,0BAA2B,wEAC3B,uBAAwB,sEACxB,6BAA8B,sDAChC,EACA,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,cAAe,CACb,KAAM,UACN,QAAS,EACT,QAAS,CACX,EACA,kBAAmB,CACjB,KAAM,UACN,QAAS,EACX,CACF,EACA,qBAAsB,EACxB,CACF,CACF,EACA,OAAO,EAAS,CACd,IAAM,EAAU,EAAQ,QAAQ,IAAM,CAAC,EACjC,EAAgB,EAAQ,eAAiB,EACzC,EAAoB,EAAQ,oBAAsB,GAElD,EAAkB,EAAmB,CAAO,EAG5C,EAAc,EAAgB,UAAU,IAAI,IAAI,GAAK,EAAgB,UAAU,IAAI,SAAS,EAKlG,SAAS,EAAsB,EAAqB,CAC9C,EAAK,WAAa,MAGlB,EAAc,CAAI,GAAK,GAAK,EAAwB,CAAI,GAC1D,EAAQ,OAAO,CACb,OACA,UAAW,0BACX,IAAK,EAAe,GAAU,EAAgB,EAAO,CAAI,EAAI,IAAA,EAC/D,CAAC,CAEL,CAKA,SAAS,EAAwB,EAAwB,CAEvD,IAAM,EAAU,EAAQ,WAAW,QAAQ,CAAI,CAAC,CAAC,MAAM,iBAAiB,EACxE,OAAO,IAAY,MAAQ,EAAQ,OAAS,CAC9C,CAKA,SAAS,EAAoB,EAAqB,CAChD,GAAI,CAAC,EAAoB,EAAM,SAAS,EAAG,OAE3C,IAAM,EAAa,EAAc,CAAI,EAEjC,GAAc,GAChB,EAAQ,OAAO,CACb,OACA,UAAW,4BACX,KAAM,CAAE,MAAO,EAAW,SAAS,CAAE,EACrC,IAAK,EAAe,GAAU,EAAkB,EAAO,CAAI,EAAI,IAAA,EACjE,CAAC,CAEL,CAKA,SAAS,EAAgB,EAAuB,EAAyB,CACvE,IAAM,EAAO,EAAQ,WAAW,QAAQ,CAAI,EAItC,EAAQ,EAAK,MAAM,eAAe,EACxC,GAAI,EAAO,CACT,IAAM,EAAU,EAAM,GAGhB,EAAa;gCACK,EAAQ;8BAHlB,EAAM,EAIO,CAAC,UAAU,EAAQ,MAAM,EAAE;IAGtD,OAAO,EAAM,YAAY,EAAM,CAAU,CAC3C,CAEA,OAAO,EAAM,YAAY,EAAM,sCAAsC,GAAM,CAC7E,CAKA,SAAS,EAAkB,EAAuB,EAAyB,CACzE,IAAM,EAAO,EAAQ,WAAW,QAAQ,CAAI,EAG5C,OAAO,EAAM,YAAY,EAAM,uDAAuD,GAAM,CAC9F,CAuBA,SAAS,EAAiB,EAAqB,CACxC,GACD,EAAS,CAAI,CAAC,CAAC,MAAQ,GACzB,EAAQ,OAAO,CAAE,OAAM,UAAW,wBAAyB,CAAC,CAEhE,CAEA,MAAO,CACL,kBAAkB,EAAM,CACtB,EAAsB,CAAI,CAC5B,EAEA,eAAe,EAAM,CACnB,EAAoB,CAAI,EACxB,EAAiB,CAAI,CACvB,CACF,CACF,CACF"}
@@ -1,2 +1,2 @@
1
- import{createImportFixer as e,hasFunctypeSymbol as t}from"../utils/import-fixer.js";const n={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer Either<E, T> over try/catch blocks and throw statements`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverTryCatch:`Prefer Either<Error, T> over try/catch block`,preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`,suggestTry:`Replace with Try(() => ...)`,suggestTryFromPromise:`Replace with Try.fromPromise(...)`,suggestEitherLeft:`Replace with Either.left(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(n){let r=(n.options[0]||{}).allowThrowInTests!==!1;function i(){let e=n.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function a(e){if(!e)return!1;if(e.type===`ThrowStatement`){let t=e.parent;for(;t;){if(t.type===`CatchClause`)return!1;t=t.parent}return!0}if(e.type===`CatchClause`)return!1;for(let t in e){if(t===`parent`)continue;let n=e[t];if(Array.isArray(n)){for(let e of n)if(e&&typeof e==`object`&&a(e))return!0}else if(n&&typeof n==`object`&&a(n))return!0}return!1}function o(e){if(!(r&&i())&&e.body&&a(e.body)){let t=e.returnType?.typeAnnotation;if(t){let r=n.sourceCode.getText(t);r.includes(`Either`)||n.report({node:e.id||e,messageId:`preferEitherReturn`,data:{type:r}})}}}function s(e){return e.block?.body?.length===1}function c(e){if(!e.handler?.body)return!1;let t=e.handler.body.body;return t.length===0||t.length===1&&(t[0].type===`ReturnStatement`||t[0].type===`ExpressionStatement`)}function l(e){let t=e.block.body[0];return t.type===`ReturnStatement`&&t.argument?.type===`AwaitExpression`||t.type===`ExpressionStatement`&&t.expression?.type===`AwaitExpression`}function u(e){return e?[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`].includes(e.type):!1}function d(e){let t=e.parent;if(!t)return!1;if(t.type===`BlockStatement`&&u(t.parent))return!0;if(t.type===`BlockStatement`&&t.parent?.type===`IfStatement`){let e=t.parent.parent;return e?.type===`BlockStatement`&&u(e.parent)}return!1}return{TryStatement(a){if(r&&i()||a.handler&&a.handler.body&&a.handler.body.body.some(e=>e.type===`ThrowStatement`))return;let o=n.sourceCode,u=[];if(s(a)&&c(a)){let n=a.block.body[0];if(n.type===`ReturnStatement`){let r=n.argument,i=o.getText(r);if(l(a)){let e=r.type===`AwaitExpression`?r.argument:r,t=o.getText(e);u.push({messageId:`suggestTryFromPromise`,fix(e){return e.replaceText(a,`return Try.fromPromise(${t})`)}})}else u.push({messageId:`suggestTry`,fix(e){return e.replaceText(a,`return Try(() => ${i})`)}});t(o,`Try`)||u.push({messageId:`suggestAddImport`,data:{symbol:`Try`},fix:e(o,`Try`)})}}n.report({node:a,messageId:`preferEitherOverTryCatch`,suggest:u})},ThrowStatement(a){if(r&&i())return;let o=a.parent;for(;o;){if(o.type===`CatchClause`)return;o=o.parent}let s=n.sourceCode,c=[];if(d(a)){let n=a.argument,r=s.getText(n),i=n?.type===`NewExpression`&&n.callee?.type===`Identifier`&&n.callee.name===`Error`?r:`new Error(String(${r}))`;c.push({messageId:`suggestEitherLeft`,fix(e){return e.replaceText(a,`return Either.left(${i})`)}}),t(s,`Either`)||c.push({messageId:`suggestAddImport`,data:{symbol:`Either`},fix:e(s,`Either`)})}n.report({node:a,messageId:`preferEitherOverThrow`,suggest:c})},FunctionDeclaration(e){o(e)},ArrowFunctionExpression(e){o(e)}}}};export{n as default};
1
+ import{createImportFixer as e,hasFunctypeSymbol as t}from"../utils/import-fixer.js";const n=new Set([`parent`,`loc`,`range`]);function r(e){return Object.entries(e).filter(([e])=>!n.has(e)).flatMap(([,e])=>Array.isArray(e)?e:[e]).filter(e=>typeof e==`object`&&!!e)}function i(e){let t=e?.parent;return t?t.type===`CatchClause`||i(t):!1}const a={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Use Either/Left/Right for typed domain errors instead of throwing`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`,suggestEitherLeft:`Replace with Either.left(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(n){let a=(n.options[0]||{}).allowThrowInTests!==!1;function o(){let e=n.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function s(e){return e?e.type===`ThrowStatement`?!i(e):e.type===`CatchClause`?!1:r(e).some(e=>s(e)):!1}function c(e){if(!(a&&o())&&e.body&&s(e.body)){let t=e.returnType?.typeAnnotation;if(t){let r=n.sourceCode.getText(t);r.includes(`Either`)||n.report({node:e.id||e,messageId:`preferEitherReturn`,data:{type:r}})}}}function l(e){return e?[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`].includes(e.type):!1}function u(e){let t=e.parent;if(!t)return!1;if(t.type===`BlockStatement`&&l(t.parent))return!0;if(t.type===`BlockStatement`&&t.parent?.type===`IfStatement`){let e=t.parent.parent;return e?.type===`BlockStatement`&&l(e.parent)}return!1}return{ThrowStatement(r){if(a&&o()||i(r))return;let s=n.sourceCode,c=[];if(u(r)){let n=r.argument,i=s.getText(n),a=n?.type===`NewExpression`&&n.callee?.type===`Identifier`&&n.callee.name===`Error`?i:`new Error(String(${i}))`;c.push({messageId:`suggestEitherLeft`,fix(e){return e.replaceText(r,`return Either.left(${a})`)}}),t(s,`Either`)||c.push({messageId:`suggestAddImport`,data:{symbol:`Either`},fix:e(s,`Either`)})}n.report({node:r,messageId:`preferEitherOverThrow`,suggest:c})},FunctionDeclaration(e){c(e)},ArrowFunctionExpression(e){c(e)}}}};export{a as default};
2
2
  //# sourceMappingURL=prefer-either.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-either.js","names":[],"sources":["../../src/rules/prefer-either.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer Either<E, T> over try/catch blocks and throw statements\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowThrowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferEitherOverTryCatch: \"Prefer Either<Error, T> over try/catch block\",\n preferEitherOverThrow: \"Prefer Either.left(error) over throw statement\",\n preferEitherReturn: \"Consider returning Either<Error, {{type}}> instead of throwing\",\n suggestTry: \"Replace with Try(() => ...)\",\n suggestTryFromPromise: \"Replace with Try.fromPromise(...)\",\n suggestEitherLeft: \"Replace with Either.left(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowThrowInTests = options.allowThrowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function hasThrowStatementsOutsideCatch(node: ASTNode): boolean {\n if (!node) return false\n\n if (node.type === \"ThrowStatement\") {\n // Check if this throw is inside a catch block\n let parent = node.parent\n while (parent) {\n if (parent.type === \"CatchClause\") return false\n parent = parent.parent\n }\n return true\n }\n\n // Skip catch blocks when recursing\n if (node.type === \"CatchClause\") return false\n\n // Recursively check child nodes\n for (const key in node) {\n if (key === \"parent\") continue // Avoid circular references\n const child = node[key]\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && hasThrowStatementsOutsideCatch(item)) {\n return true\n }\n }\n } else if (child && typeof child === \"object\" && hasThrowStatementsOutsideCatch(child)) {\n return true\n }\n }\n\n return false\n }\n\n function checkFunctionForThrows(node: ASTNode): void {\n // Allow functions in test files\n if (allowThrowInTests && isInTestFile()) return\n\n if (!node.body) return\n\n // Only report function-level errors if there are throws NOT in catch blocks\n const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body)\n if (hasThrowsNotInCatch) {\n const returnType = node.returnType?.typeAnnotation\n if (returnType) {\n const sourceCode = context.sourceCode\n const returnTypeText = sourceCode.getText(returnType)\n\n // Don't report if already using Either\n if (!returnTypeText.includes(\"Either\")) {\n context.report({\n node: node.id || node,\n messageId: \"preferEitherReturn\",\n data: { type: returnTypeText },\n })\n }\n }\n }\n }\n\n function isSimpleTryBody(node: ASTNode): boolean {\n return node.block?.body?.length === 1\n }\n\n function isSimpleCatch(node: ASTNode): boolean {\n if (!node.handler?.body) return false\n const catchBody = node.handler.body.body\n return (\n catchBody.length === 0 ||\n (catchBody.length === 1 &&\n (catchBody[0].type === \"ReturnStatement\" || catchBody[0].type === \"ExpressionStatement\"))\n )\n }\n\n function tryBodyHasAwait(node: ASTNode): boolean {\n const stmt = node.block.body[0]\n if (stmt.type === \"ReturnStatement\" && stmt.argument?.type === \"AwaitExpression\") return true\n if (stmt.type === \"ExpressionStatement\" && stmt.expression?.type === \"AwaitExpression\") return true\n return false\n }\n\n function isFunctionLike(node: ASTNode): boolean {\n if (!node) return false\n return [\"FunctionDeclaration\", \"FunctionExpression\", \"ArrowFunctionExpression\"].includes(node.type)\n }\n\n function isDirectInFunctionBody(node: ASTNode): boolean {\n const parent = node.parent\n if (!parent) return false\n if (parent.type === \"BlockStatement\" && isFunctionLike(parent.parent)) return true\n if (parent.type === \"BlockStatement\" && parent.parent?.type === \"IfStatement\") {\n const ifParent = parent.parent.parent\n return ifParent?.type === \"BlockStatement\" && isFunctionLike(ifParent.parent)\n }\n return false\n }\n\n return {\n TryStatement(node: ASTNode) {\n // Allow try/catch in test files\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow try/catch that re-throws in the catch block (even with logging)\n if (node.handler && node.handler.body) {\n const catchBody = node.handler.body.body\n const hasRethrow = catchBody.some((stmt: ASTNode) => stmt.type === \"ThrowStatement\")\n if (hasRethrow) return\n }\n\n const sourceCode = context.sourceCode\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSimpleTryBody(node) && isSimpleCatch(node)) {\n const tryStmt = node.block.body[0]\n const isReturn = tryStmt.type === \"ReturnStatement\"\n\n // Only suggest when the try body is a return statement — non-return expression\n // replacements would produce syntactically ambiguous code without knowing the context\n if (isReturn) {\n const expr = tryStmt.argument\n const exprText = sourceCode.getText(expr)\n\n if (tryBodyHasAwait(node)) {\n const awaitExpr = expr.type === \"AwaitExpression\" ? expr.argument : expr\n const innerText = sourceCode.getText(awaitExpr)\n suggest.push({\n messageId: \"suggestTryFromPromise\",\n fix(fixer) {\n return fixer.replaceText(node, `return Try.fromPromise(${innerText})`)\n },\n })\n } else {\n suggest.push({\n messageId: \"suggestTry\",\n fix(fixer) {\n return fixer.replaceText(node, `return Try(() => ${exprText})`)\n },\n })\n }\n\n if (!hasFunctypeSymbol(sourceCode, \"Try\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Try\" },\n fix: createImportFixer(sourceCode, \"Try\"),\n })\n }\n }\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverTryCatch\",\n suggest,\n })\n },\n\n ThrowStatement(node: ASTNode) {\n // Allow throws in test files if configured\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow re-throwing in catch blocks (common pattern)\n let parent = node.parent\n while (parent) {\n if (parent.type === \"CatchClause\") return\n parent = parent.parent\n }\n\n const sourceCode = context.sourceCode\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isDirectInFunctionBody(node)) {\n const throwArg = node.argument\n const argText = sourceCode.getText(throwArg)\n const isErrorExpr =\n throwArg?.type === \"NewExpression\" &&\n throwArg.callee?.type === \"Identifier\" &&\n throwArg.callee.name === \"Error\"\n const eitherArg = isErrorExpr ? argText : `new Error(String(${argText}))`\n\n suggest.push({\n messageId: \"suggestEitherLeft\",\n fix(fixer) {\n return fixer.replaceText(node, `return Either.left(${eitherArg})`)\n },\n })\n\n if (!hasFunctypeSymbol(sourceCode, \"Either\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Either\" },\n fix: createImportFixer(sourceCode, \"Either\"),\n })\n }\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverThrow\",\n suggest,\n })\n },\n\n FunctionDeclaration(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n\n ArrowFunctionExpression(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
1
+ {"version":3,"file":"prefer-either.js","names":[],"sources":["../../src/rules/prefer-either.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\n/** AST keys that don't represent syntax children — back-edges and source metadata. */\nconst NON_CHILD_KEYS: ReadonlySet<string> = new Set([\"parent\", \"loc\", \"range\"])\n\n/** AST children of `node` (drops back-edges, flattens array-valued keys, filters non-object leaves). */\nfunction astChildren(node: ASTNode): readonly unknown[] {\n return Object.entries(node)\n .filter(([k]) => !NON_CHILD_KEYS.has(k))\n .flatMap(([, v]) => (Array.isArray(v) ? v : [v]))\n .filter((v) => v !== null && typeof v === \"object\")\n}\n\n/** True iff any ancestor of `node` is a `CatchClause`. Pure tail recursion. */\nfunction insideCatchClause(node: ASTNode | null | undefined): boolean {\n const parent = node?.parent as ASTNode | undefined\n if (!parent) return false\n return parent.type === \"CatchClause\" || insideCatchClause(parent)\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Use Either/Left/Right for typed domain errors instead of throwing\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowThrowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferEitherOverThrow: \"Prefer Either.left(error) over throw statement\",\n preferEitherReturn: \"Consider returning Either<Error, {{type}}> instead of throwing\",\n suggestEitherLeft: \"Replace with Either.left(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowThrowInTests = options.allowThrowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function hasThrowStatementsOutsideCatch(node: ASTNode): boolean {\n if (!node) return false\n\n // A throw that's *not* inside a catch is the thing we're looking for.\n if (node.type === \"ThrowStatement\") return !insideCatchClause(node)\n\n // Catch bodies don't count — re-throws inside them are allowed.\n if (node.type === \"CatchClause\") return false\n\n // Recurse: true iff any child satisfies the predicate.\n return astChildren(node).some((child) => hasThrowStatementsOutsideCatch(child as ASTNode))\n }\n\n function checkFunctionForThrows(node: ASTNode): void {\n // Allow functions in test files\n if (allowThrowInTests && isInTestFile()) return\n\n if (!node.body) return\n\n // Only report function-level errors if there are throws NOT in catch blocks\n const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body)\n if (hasThrowsNotInCatch) {\n const returnType = node.returnType?.typeAnnotation\n if (returnType) {\n const sourceCode = context.sourceCode\n const returnTypeText = sourceCode.getText(returnType)\n\n // Don't report if already using Either\n if (!returnTypeText.includes(\"Either\")) {\n context.report({\n node: node.id || node,\n messageId: \"preferEitherReturn\",\n data: { type: returnTypeText },\n })\n }\n }\n }\n }\n\n function isFunctionLike(node: ASTNode): boolean {\n if (!node) return false\n return [\"FunctionDeclaration\", \"FunctionExpression\", \"ArrowFunctionExpression\"].includes(node.type)\n }\n\n function isDirectInFunctionBody(node: ASTNode): boolean {\n const parent = node.parent\n if (!parent) return false\n if (parent.type === \"BlockStatement\" && isFunctionLike(parent.parent)) return true\n if (parent.type === \"BlockStatement\" && parent.parent?.type === \"IfStatement\") {\n const ifParent = parent.parent.parent\n return ifParent?.type === \"BlockStatement\" && isFunctionLike(ifParent.parent)\n }\n return false\n }\n\n return {\n ThrowStatement(node: ASTNode) {\n // Allow throws in test files if configured\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow re-throwing in catch blocks (common pattern)\n if (insideCatchClause(node)) return\n\n const sourceCode = context.sourceCode\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isDirectInFunctionBody(node)) {\n const throwArg = node.argument\n const argText = sourceCode.getText(throwArg)\n const isErrorExpr =\n throwArg?.type === \"NewExpression\" &&\n throwArg.callee?.type === \"Identifier\" &&\n throwArg.callee.name === \"Error\"\n const eitherArg = isErrorExpr ? argText : `new Error(String(${argText}))`\n\n suggest.push({\n messageId: \"suggestEitherLeft\",\n fix(fixer) {\n return fixer.replaceText(node, `return Either.left(${eitherArg})`)\n },\n })\n\n if (!hasFunctypeSymbol(sourceCode, \"Either\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Either\" },\n fix: createImportFixer(sourceCode, \"Either\"),\n })\n }\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverThrow\",\n suggest,\n })\n },\n\n FunctionDeclaration(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n\n ArrowFunctionExpression(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e={meta:{type:`suggestion`,docs:{description:`Prefer .flatMap() over .map().flat() and nested transformations`,recommended:!0},fixable:`code`,schema:[{type:`object`,properties:{checkNestedMaps:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferFlatMapOverMapFlat:`Use .flatMap() instead of .map().flat()`,preferFlatMapNested:`Consider .flatMap() for nested transformations that return arrays`,preferFlatMapChain:`Use .flatMap() instead of chained .map() operations that flatten results`}},create(e){let t=(e.options[0]||{}).checkNestedMaps!==!1;function n(e){if(e.type!==`CallExpression`)return!1;let t=e.callee;if(t.type!==`MemberExpression`)return!1;if(t.property.name===`flat`){let e=t.object;if(e.type===`CallExpression`&&e.callee.type===`MemberExpression`&&e.callee.property.name===`map`)return!0}return!1}function r(e){let t=e.elements??[];return t.some(e=>e&&e.type===`SpreadElement`)?!1:t.some(e=>e&&e.type===`ArrayExpression`)}function i(e){if(!e||!e.body)return!1;if(e.body.type===`ArrayExpression`)return r(e.body);if(e.body.type===`CallExpression`){let t=e.body;if(t.callee.type===`MemberExpression`){let e=t.callee.property.name;if([`map`,`filter`,`slice`,`concat`,`split`].includes(e))return!0}}if(e.body.type===`BlockStatement`){let t=e.body.body;for(let e of t)if(e.type===`ReturnStatement`&&e.argument){if(e.argument.type===`ArrayExpression`){if(r(e.argument))return!0;continue}if(e.argument.type===`CallExpression`){let t=e.argument;if(t.callee.type===`MemberExpression`){let e=t.callee.property.name;if([`map`,`filter`,`slice`,`concat`,`split`].includes(e))return!0}}}}return!1}function a(e){if(e.type!==`CallExpression`)return!1;let t=e.callee;if(t.type!==`MemberExpression`)return!1;if(t.property.name===`map`){let t=e.arguments[0];if(t&&(t.type===`ArrowFunctionExpression`||t.type===`FunctionExpression`))return i(t)}return!1}return{CallExpression(r){if(n(r)){let t=e.sourceCode;e.report({node:r,messageId:`preferFlatMapOverMapFlat`,fix(e){let n=r.callee.object,i=t.getText(n).replace(/\.map\s*\(/,`.flatMap(`);return e.replaceText(r,i)}});return}if(r.callee.type===`MemberExpression`&&r.callee.property.name===`map`){let t=r.callee.object;if(t.type===`CallExpression`&&t.callee.type===`MemberExpression`&&t.callee.property.name===`map`){let n=t.arguments[0];if(n&&i(n)){e.report({node:t,messageId:`preferFlatMapChain`});return}}}if(t&&a(r)){if(r.parent&&r.parent.type===`MemberExpression`&&r.parent.parent&&r.parent.parent.type===`CallExpression`&&r.parent.property.name===`flat`)return;let t=r.callee?.object;if(t?.type===`CallExpression`&&t.callee?.type===`MemberExpression`&&t.callee?.property?.name===`map`||r.parent?.type===`MemberExpression`&&r.parent.parent?.type===`CallExpression`&&r.parent.parent.callee?.property?.name===`map`)return;e.report({node:r,messageId:`preferFlatMapNested`})}}}}};export{e as default};
1
+ const e=new Set([`map`,`filter`,`slice`,`concat`,`split`]),t={meta:{type:`suggestion`,docs:{description:`Prefer .flatMap() over .map().flat() and nested transformations`,recommended:!0},fixable:`code`,schema:[{type:`object`,properties:{checkNestedMaps:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferFlatMapOverMapFlat:`Use .flatMap() instead of .map().flat()`,preferFlatMapNested:`Consider .flatMap() for nested transformations that return arrays`,preferFlatMapChain:`Use .flatMap() instead of chained .map() operations that flatten results`}},create(t){let n=(t.options[0]||{}).checkNestedMaps!==!1;function r(e){if(e.type!==`CallExpression`)return!1;let t=e.callee;if(t.type!==`MemberExpression`)return!1;if(t.property.name===`flat`){let e=t.object;if(e.type===`CallExpression`&&e.callee.type===`MemberExpression`&&e.callee.property.name===`map`)return!0}return!1}function i(e){let t=e.elements??[];return t.some(e=>e&&e.type===`SpreadElement`)?!1:t.some(e=>e&&e.type===`ArrayExpression`)}function a(t){if(!t||!t.body)return!1;if(t.body.type===`ArrayExpression`)return i(t.body);if(t.body.type===`CallExpression`){let n=t.body;if(n.callee.type===`MemberExpression`){let t=n.callee.property.name;if(e.has(t))return!0}}return t.body.type===`BlockStatement`?t.body.body.some(o):!1}function o(t){return t.type!==`ReturnStatement`||!t.argument?!1:t.argument.type===`ArrayExpression`?i(t.argument):!!(t.argument.type===`CallExpression`&&t.argument.callee.type===`MemberExpression`&&e.has(t.argument.callee.property.name))}function s(e){if(e.type!==`CallExpression`)return!1;let t=e.callee;if(t.type!==`MemberExpression`)return!1;if(t.property.name===`map`){let t=e.arguments[0];if(t&&(t.type===`ArrowFunctionExpression`||t.type===`FunctionExpression`))return a(t)}return!1}function c(e){let t=new Set([`Option`,`Some`,`None`,`Either`,`Right`,`Left`,`Try`,`Success`,`Failure`,`Task`,`Ok`,`Err`,`IO`]);return!!(e.type===`CallExpression`&&e.callee?.type===`Identifier`&&t.has(e.callee.name)||e.type===`CallExpression`&&e.callee?.type===`MemberExpression`&&e.callee.object?.type===`Identifier`&&t.has(e.callee.object.name))}return{CallExpression(e){if(r(e)){let n=t.sourceCode;t.report({node:e,messageId:`preferFlatMapOverMapFlat`,fix(t){let r=e.callee.object,i=n.getText(r).replace(/\.map\s*\(/,`.flatMap(`);return t.replaceText(e,i)}});return}if(e.callee.type===`MemberExpression`&&e.callee.property.name===`map`){let n=e.callee.object;if(n.type===`CallExpression`&&n.callee.type===`MemberExpression`&&n.callee.property.name===`map`){let e=n.arguments[0];if(e&&a(e)){t.report({node:n,messageId:`preferFlatMapChain`});return}}}if(n&&s(e)){if(e.parent&&e.parent.type===`MemberExpression`&&e.parent.parent&&e.parent.parent.type===`CallExpression`&&e.parent.property.name===`flat`)return;let n=e.callee?.object;if(n?.type===`CallExpression`&&n.callee?.type===`MemberExpression`&&n.callee?.property?.name===`map`||e.parent?.type===`MemberExpression`&&e.parent.parent?.type===`CallExpression`&&e.parent.parent.callee?.property?.name===`map`||n&&c(n))return;t.report({node:e,messageId:`preferFlatMapNested`})}}}}};export{t as default};
2
2
  //# sourceMappingURL=prefer-flatmap.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-flatmap.js","names":[],"sources":["../../src/rules/prefer-flatmap.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer .flatMap() over .map().flat() and nested transformations\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n checkNestedMaps: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFlatMapOverMapFlat: \"Use .flatMap() instead of .map().flat()\",\n preferFlatMapNested: \"Consider .flatMap() for nested transformations that return arrays\",\n preferFlatMapChain: \"Use .flatMap() instead of chained .map() operations that flatten results\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const checkNestedMaps = options.checkNestedMaps !== false\n\n function isMapFollowedByFlat(node: ASTNode): boolean {\n if (node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n if (callee.type !== \"MemberExpression\") return false\n\n // Check if this is .flat()\n if (callee.property.name === \"flat\") {\n const object = callee.object\n\n // Check if the object is a .map() call\n if (\n object.type === \"CallExpression\" &&\n object.callee.type === \"MemberExpression\" &&\n object.callee.property.name === \"map\"\n ) {\n return true\n }\n }\n\n return false\n }\n\n /**\n * Heuristic for array literals that look like flatten candidates.\n * Skips append/identity patterns (`[...arr, x]`, `[...arr]`) and tuple\n * shapes (`[k, v]`) — those are not nested arrays, so .flatMap()\n * doesn't apply. Only flag when at least one element is itself an\n * ArrayExpression, signaling a true nested-array shape.\n */\n function isFlattenCandidateArrayLiteral(arrayExpr: ASTNode): boolean {\n const elements = arrayExpr.elements ?? []\n if (elements.some((el: ASTNode | null) => el && el.type === \"SpreadElement\")) {\n return false\n }\n return elements.some((el: ASTNode | null) => el && el.type === \"ArrayExpression\")\n }\n\n function returnsArray(functionNode: ASTNode): boolean {\n if (!functionNode || !functionNode.body) return false\n\n // Arrow function with expression body\n if (functionNode.body.type === \"ArrayExpression\") {\n return isFlattenCandidateArrayLiteral(functionNode.body)\n }\n\n // Arrow function with call expression body\n if (functionNode.body.type === \"CallExpression\") {\n const call = functionNode.body\n if (call.callee.type === \"MemberExpression\") {\n const methodName = call.callee.property.name\n // Common methods that return arrays\n if ([\"map\", \"filter\", \"slice\", \"concat\", \"split\"].includes(methodName)) {\n return true\n }\n }\n }\n\n // Function with block body\n if (functionNode.body.type === \"BlockStatement\") {\n const statements = functionNode.body.body\n\n // Look for return statements that return arrays\n for (const stmt of statements) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n if (stmt.argument.type === \"ArrayExpression\") {\n if (isFlattenCandidateArrayLiteral(stmt.argument)) return true\n continue\n }\n\n // Check for method calls that return arrays\n if (stmt.argument.type === \"CallExpression\") {\n const call = stmt.argument\n if (call.callee.type === \"MemberExpression\") {\n const methodName = call.callee.property.name\n // Common methods that return arrays\n if ([\"map\", \"filter\", \"slice\", \"concat\", \"split\"].includes(methodName)) {\n return true\n }\n }\n }\n }\n }\n }\n\n return false\n }\n\n function isNestedMapReturningArrays(node: ASTNode): boolean {\n if (node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n if (callee.type !== \"MemberExpression\") return false\n\n // Check if this is .map()\n if (callee.property.name === \"map\") {\n const callback = node.arguments[0]\n if (callback && (callback.type === \"ArrowFunctionExpression\" || callback.type === \"FunctionExpression\")) {\n return returnsArray(callback)\n }\n }\n\n return false\n }\n\n return {\n CallExpression(node: ASTNode) {\n // Check for .map().flat() pattern first (highest priority)\n if (isMapFollowedByFlat(node)) {\n const sourceCode = context.sourceCode\n\n context.report({\n node,\n messageId: \"preferFlatMapOverMapFlat\",\n fix(fixer) {\n // Get the .map() call\n const mapCall = (node.callee as ASTNode).object\n const mapCallText = sourceCode.getText(mapCall)\n\n // Replace .map() with .flatMap() and remove .flat()\n const flatMapText = mapCallText.replace(/\\.map\\s*\\(/, \".flatMap(\")\n\n return fixer.replaceText(node, flatMapText)\n },\n })\n return // Don't check other patterns if we found .map().flat()\n }\n\n // Check for chained maps where intermediate results are arrays (highest priority after map().flat())\n if (node.callee.type === \"MemberExpression\" && node.callee.property.name === \"map\") {\n const object = node.callee.object\n if (\n object.type === \"CallExpression\" &&\n object.callee.type === \"MemberExpression\" &&\n object.callee.property.name === \"map\"\n ) {\n // Check if the first map returns arrays\n const firstMapCallback = object.arguments[0]\n if (firstMapCallback && returnsArray(firstMapCallback)) {\n context.report({\n node: object, // Report on the first map call\n messageId: \"preferFlatMapChain\",\n })\n return // Don't check other patterns for this chain\n }\n }\n }\n\n // Check for nested maps that return arrays (but not if they're part of map().flat() or chains)\n if (checkNestedMaps && isNestedMapReturningArrays(node)) {\n // Don't flag if this map is immediately followed by flat()\n if (\n node.parent &&\n node.parent.type === \"MemberExpression\" &&\n node.parent.parent &&\n node.parent.parent.type === \"CallExpression\" &&\n node.parent.property.name === \"flat\"\n ) {\n return // Skip - this will be handled by the map().flat() rule\n }\n\n // Don't flag if this map is part of a chain (either as first or second map)\n const object = node.callee?.object\n if (\n object?.type === \"CallExpression\" &&\n object.callee?.type === \"MemberExpression\" &&\n object.callee?.property?.name === \"map\"\n ) {\n return // Skip - this is part of a chain\n }\n\n // Check if this map feeds into another map\n if (\n node.parent?.type === \"MemberExpression\" &&\n node.parent.parent?.type === \"CallExpression\" &&\n node.parent.parent.callee?.property?.name === \"map\"\n ) {\n return // Skip - this feeds into a chain\n }\n\n context.report({\n node,\n messageId: \"preferFlatMapNested\",\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,kEACb,YAAa,EACf,EACA,QAAS,OACT,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,gBAAiB,CACf,KAAM,UACN,QAAS,EACX,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,yBAA0B,0CAC1B,oBAAqB,oEACrB,mBAAoB,0EACtB,CACF,EAEA,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,CAAC,EAAA,CACP,kBAAoB,GAEpD,SAAS,EAAoB,EAAwB,CACnD,GAAI,EAAK,OAAS,iBAAkB,MAAO,GAE3C,IAAM,EAAS,EAAK,OACpB,GAAI,EAAO,OAAS,mBAAoB,MAAO,GAG/C,GAAI,EAAO,SAAS,OAAS,OAAQ,CACnC,IAAM,EAAS,EAAO,OAGtB,GACE,EAAO,OAAS,kBAChB,EAAO,OAAO,OAAS,oBACvB,EAAO,OAAO,SAAS,OAAS,MAEhC,MAAO,EAEX,CAEA,MAAO,EACT,CASA,SAAS,EAA+B,EAA6B,CACnE,IAAM,EAAW,EAAU,UAAY,CAAC,EAIxC,OAHI,EAAS,KAAM,GAAuB,GAAM,EAAG,OAAS,eAAe,EAClE,GAEF,EAAS,KAAM,GAAuB,GAAM,EAAG,OAAS,iBAAiB,CAClF,CAEA,SAAS,EAAa,EAAgC,CACpD,GAAI,CAAC,GAAgB,CAAC,EAAa,KAAM,MAAO,GAGhD,GAAI,EAAa,KAAK,OAAS,kBAC7B,OAAO,EAA+B,EAAa,IAAI,EAIzD,GAAI,EAAa,KAAK,OAAS,iBAAkB,CAC/C,IAAM,EAAO,EAAa,KAC1B,GAAI,EAAK,OAAO,OAAS,mBAAoB,CAC3C,IAAM,EAAa,EAAK,OAAO,SAAS,KAExC,GAAI,CAAC,MAAO,SAAU,QAAS,SAAU,OAAO,CAAC,CAAC,SAAS,CAAU,EACnE,MAAO,EAEX,CACF,CAGA,GAAI,EAAa,KAAK,OAAS,iBAAkB,CAC/C,IAAM,EAAa,EAAa,KAAK,KAGrC,IAAK,IAAM,KAAQ,EACjB,GAAI,EAAK,OAAS,mBAAqB,EAAK,SAAU,CACpD,GAAI,EAAK,SAAS,OAAS,kBAAmB,CAC5C,GAAI,EAA+B,EAAK,QAAQ,EAAG,MAAO,GAC1D,QACF,CAGA,GAAI,EAAK,SAAS,OAAS,iBAAkB,CAC3C,IAAM,EAAO,EAAK,SAClB,GAAI,EAAK,OAAO,OAAS,mBAAoB,CAC3C,IAAM,EAAa,EAAK,OAAO,SAAS,KAExC,GAAI,CAAC,MAAO,SAAU,QAAS,SAAU,OAAO,CAAC,CAAC,SAAS,CAAU,EACnE,MAAO,EAEX,CACF,CACF,CAEJ,CAEA,MAAO,EACT,CAEA,SAAS,EAA2B,EAAwB,CAC1D,GAAI,EAAK,OAAS,iBAAkB,MAAO,GAE3C,IAAM,EAAS,EAAK,OACpB,GAAI,EAAO,OAAS,mBAAoB,MAAO,GAG/C,GAAI,EAAO,SAAS,OAAS,MAAO,CAClC,IAAM,EAAW,EAAK,UAAU,GAChC,GAAI,IAAa,EAAS,OAAS,2BAA6B,EAAS,OAAS,sBAChF,OAAO,EAAa,CAAQ,CAEhC,CAEA,MAAO,EACT,CAEA,MAAO,CACL,eAAe,EAAe,CAE5B,GAAI,EAAoB,CAAI,EAAG,CAC7B,IAAM,EAAa,EAAQ,WAE3B,EAAQ,OAAO,CACb,OACA,UAAW,2BACX,IAAI,EAAO,CAET,IAAM,EAAW,EAAK,OAAmB,OAInC,EAHc,EAAW,QAAQ,CAGT,CAAC,CAAC,QAAQ,aAAc,WAAW,EAEjE,OAAO,EAAM,YAAY,EAAM,CAAW,CAC5C,CACF,CAAC,EACD,MACF,CAGA,GAAI,EAAK,OAAO,OAAS,oBAAsB,EAAK,OAAO,SAAS,OAAS,MAAO,CAClF,IAAM,EAAS,EAAK,OAAO,OAC3B,GACE,EAAO,OAAS,kBAChB,EAAO,OAAO,OAAS,oBACvB,EAAO,OAAO,SAAS,OAAS,MAChC,CAEA,IAAM,EAAmB,EAAO,UAAU,GAC1C,GAAI,GAAoB,EAAa,CAAgB,EAAG,CACtD,EAAQ,OAAO,CACb,KAAM,EACN,UAAW,oBACb,CAAC,EACD,MACF,CACF,CACF,CAGA,GAAI,GAAmB,EAA2B,CAAI,EAAG,CAEvD,GACE,EAAK,QACL,EAAK,OAAO,OAAS,oBACrB,EAAK,OAAO,QACZ,EAAK,OAAO,OAAO,OAAS,kBAC5B,EAAK,OAAO,SAAS,OAAS,OAE9B,OAIF,IAAM,EAAS,EAAK,QAAQ,OAU5B,GARE,GAAQ,OAAS,kBACjB,EAAO,QAAQ,OAAS,oBACxB,EAAO,QAAQ,UAAU,OAAS,OAOlC,EAAK,QAAQ,OAAS,oBACtB,EAAK,OAAO,QAAQ,OAAS,kBAC7B,EAAK,OAAO,OAAO,QAAQ,UAAU,OAAS,MAE9C,OAGF,EAAQ,OAAO,CACb,OACA,UAAW,qBACb,CAAC,CACH,CACF,CACF,CACF,CACF"}
1
+ {"version":3,"file":"prefer-flatmap.js","names":[],"sources":["../../src/rules/prefer-flatmap.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\n/** Array-prototype methods whose call expression always returns an array. */\nconst ARRAY_PRODUCING_METHODS: ReadonlySet<string> = new Set([\"map\", \"filter\", \"slice\", \"concat\", \"split\"])\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer .flatMap() over .map().flat() and nested transformations\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n checkNestedMaps: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFlatMapOverMapFlat: \"Use .flatMap() instead of .map().flat()\",\n preferFlatMapNested: \"Consider .flatMap() for nested transformations that return arrays\",\n preferFlatMapChain: \"Use .flatMap() instead of chained .map() operations that flatten results\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const checkNestedMaps = options.checkNestedMaps !== false\n\n function isMapFollowedByFlat(node: ASTNode): boolean {\n if (node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n if (callee.type !== \"MemberExpression\") return false\n\n // Check if this is .flat()\n if (callee.property.name === \"flat\") {\n const object = callee.object\n\n // Check if the object is a .map() call\n if (\n object.type === \"CallExpression\" &&\n object.callee.type === \"MemberExpression\" &&\n object.callee.property.name === \"map\"\n ) {\n return true\n }\n }\n\n return false\n }\n\n /**\n * Heuristic for array literals that look like flatten candidates.\n * Skips append/identity patterns (`[...arr, x]`, `[...arr]`) and tuple\n * shapes (`[k, v]`) — those are not nested arrays, so .flatMap()\n * doesn't apply. Only flag when at least one element is itself an\n * ArrayExpression, signaling a true nested-array shape.\n */\n function isFlattenCandidateArrayLiteral(arrayExpr: ASTNode): boolean {\n const elements = arrayExpr.elements ?? []\n if (elements.some((el: ASTNode | null) => el && el.type === \"SpreadElement\")) {\n return false\n }\n return elements.some((el: ASTNode | null) => el && el.type === \"ArrayExpression\")\n }\n\n function returnsArray(functionNode: ASTNode): boolean {\n if (!functionNode || !functionNode.body) return false\n\n // Arrow function with expression body\n if (functionNode.body.type === \"ArrayExpression\") {\n return isFlattenCandidateArrayLiteral(functionNode.body)\n }\n\n // Arrow function with call expression body\n if (functionNode.body.type === \"CallExpression\") {\n const call = functionNode.body\n if (call.callee.type === \"MemberExpression\") {\n const methodName = call.callee.property.name\n // Common methods that return arrays\n if (ARRAY_PRODUCING_METHODS.has(methodName)) {\n return true\n }\n }\n }\n\n // Function with block body — any return statement returning an array shape?\n if (functionNode.body.type === \"BlockStatement\") {\n return functionNode.body.body.some(returnsArrayShape)\n }\n\n return false\n }\n\n /** A single statement that's `return <array-shaped-expression>`. */\n function returnsArrayShape(stmt: ASTNode): boolean {\n if (stmt.type !== \"ReturnStatement\" || !stmt.argument) return false\n if (stmt.argument.type === \"ArrayExpression\") return isFlattenCandidateArrayLiteral(stmt.argument)\n if (\n stmt.argument.type === \"CallExpression\" &&\n stmt.argument.callee.type === \"MemberExpression\" &&\n ARRAY_PRODUCING_METHODS.has(stmt.argument.callee.property.name)\n ) {\n return true\n }\n return false\n }\n\n function isNestedMapReturningArrays(node: ASTNode): boolean {\n if (node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n if (callee.type !== \"MemberExpression\") return false\n\n // Check if this is .map()\n if (callee.property.name === \"map\") {\n const callback = node.arguments[0]\n if (callback && (callback.type === \"ArrowFunctionExpression\" || callback.type === \"FunctionExpression\")) {\n return returnsArray(callback)\n }\n }\n\n return false\n }\n\n /**\n * Skip the warning when the receiver (`.map`'s left side) is a known\n * non-collection monad constructor call. The rule has no type info, so\n * it can't tell `arr.map(cb => arr2)` (collection, .flatMap is the fix)\n * from `Try(...).map(cb => arr)` (non-collection — .flatMap on Try\n * expects Try<T>, not an array, so .flatMap is NOT the fix).\n *\n * We can't enumerate every non-collection receiver, but we *can* detect\n * the unambiguous case: the receiver is literally `Try(...)` /\n * `Option(...)` / `Either(...)` / `Right(...)` / `Left(...)` / etc.\n * When that holds, suppress the warning. For bare identifiers (`items`)\n * or array literals or array-method chains, we can't tell, so the rule\n * still fires as before.\n */\n function isNonCollectionMonadReceiver(node: ASTNode): boolean {\n const monadConstructors = new Set([\n \"Option\",\n \"Some\",\n \"None\",\n \"Either\",\n \"Right\",\n \"Left\",\n \"Try\",\n \"Success\",\n \"Failure\",\n \"Task\",\n \"Ok\",\n \"Err\",\n \"IO\",\n ])\n // Bare constructor: Try(...), Option(...), Right(x), etc.\n if (\n node.type === \"CallExpression\" &&\n node.callee?.type === \"Identifier\" &&\n monadConstructors.has(node.callee.name)\n ) {\n return true\n }\n // Companion call: Either.right(x), Option.none(), Try.success(v)\n if (\n node.type === \"CallExpression\" &&\n node.callee?.type === \"MemberExpression\" &&\n node.callee.object?.type === \"Identifier\" &&\n monadConstructors.has(node.callee.object.name)\n ) {\n return true\n }\n return false\n }\n\n return {\n CallExpression(node: ASTNode) {\n // Check for .map().flat() pattern first (highest priority)\n if (isMapFollowedByFlat(node)) {\n const sourceCode = context.sourceCode\n\n context.report({\n node,\n messageId: \"preferFlatMapOverMapFlat\",\n fix(fixer) {\n // Get the .map() call\n const mapCall = (node.callee as ASTNode).object\n const mapCallText = sourceCode.getText(mapCall)\n\n // Replace .map() with .flatMap() and remove .flat()\n const flatMapText = mapCallText.replace(/\\.map\\s*\\(/, \".flatMap(\")\n\n return fixer.replaceText(node, flatMapText)\n },\n })\n return // Don't check other patterns if we found .map().flat()\n }\n\n // Check for chained maps where intermediate results are arrays (highest priority after map().flat())\n if (node.callee.type === \"MemberExpression\" && node.callee.property.name === \"map\") {\n const object = node.callee.object\n if (\n object.type === \"CallExpression\" &&\n object.callee.type === \"MemberExpression\" &&\n object.callee.property.name === \"map\"\n ) {\n // Check if the first map returns arrays\n const firstMapCallback = object.arguments[0]\n if (firstMapCallback && returnsArray(firstMapCallback)) {\n context.report({\n node: object, // Report on the first map call\n messageId: \"preferFlatMapChain\",\n })\n return // Don't check other patterns for this chain\n }\n }\n }\n\n // Check for nested maps that return arrays (but not if they're part of map().flat() or chains)\n if (checkNestedMaps && isNestedMapReturningArrays(node)) {\n // Don't flag if this map is immediately followed by flat()\n if (\n node.parent &&\n node.parent.type === \"MemberExpression\" &&\n node.parent.parent &&\n node.parent.parent.type === \"CallExpression\" &&\n node.parent.property.name === \"flat\"\n ) {\n return // Skip - this will be handled by the map().flat() rule\n }\n\n // Don't flag if this map is part of a chain (either as first or second map)\n const object = node.callee?.object\n if (\n object?.type === \"CallExpression\" &&\n object.callee?.type === \"MemberExpression\" &&\n object.callee?.property?.name === \"map\"\n ) {\n return // Skip - this is part of a chain\n }\n\n // Check if this map feeds into another map\n if (\n node.parent?.type === \"MemberExpression\" &&\n node.parent.parent?.type === \"CallExpression\" &&\n node.parent.parent.callee?.property?.name === \"map\"\n ) {\n return // Skip - this feeds into a chain\n }\n\n // Don't flag when the receiver is a known non-collection monad\n // constructor — .flatMap on Try / Option / Either / etc. expects\n // a wrapped value, not an array, so suggesting it is wrong.\n if (object && isNonCollectionMonadReceiver(object)) {\n return\n }\n\n context.report({\n node,\n messageId: \"preferFlatMapNested\",\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e={meta:{type:`suggestion`,docs:{description:`Prefer .fold() over if/else chains when working with monadic types`,recommended:!0},fixable:`code`,schema:[{type:`object`,properties:{minComplexity:{type:`integer`,minimum:1,default:2}},additionalProperties:!1}],messages:{preferFold:`Prefer .fold() over if/else when working with {{type}} types`,preferFoldTernary:`Consider using .fold() instead of ternary operator for {{type}}`}},create(e){let t=(e.options[0]||{}).minComplexity||2;function n(e,t){if(e.type===`BlockStatement`){let n=e.body;return n.length===1&&n[0].type===`ReturnStatement`?t.getText(n[0].argument):t.getText(e).slice(1,-1).trim()}else return t.getText(e)}function r(e,t){let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return e.replace(RegExp(`${n}\\.get\\(\\)`,`g`),`value`)}function i(t){let i=e.sourceCode;if(t.type!==`IfStatement`)return null;let a=t.test,o=t.consequent,s=t.alternate;if(!o||!s)return null;let c=null,l=!1;if(a.type===`CallExpression`&&a.callee.type===`MemberExpression`){let e=a.callee.property.name;c=i.getText(a.callee.object),e===`isSome`||e===`isRight`||e===`isSuccess`?l=!1:(e===`isNone`||e===`isEmpty`||e===`isLeft`||e===`isFailure`)&&(l=!0)}if(!c||o.type===`BlockStatement`&&(o.body.length!==1||o.body[0].type!==`ReturnStatement`))return null;if(s.type===`BlockStatement`){if(s.body.length!==1||s.body[0].type!==`ReturnStatement`)return null}else if(s.type===`IfStatement`)return null;let u=n(o,i),d=n(s,i);if(l){let e=r(d,c);return`${c}.fold(() => ${u}, (value) => ${e})`}else{let e=r(u,c);return`${c}.fold(() => ${d}, (value) => ${e})`}}function a(t){let n=e.sourceCode;if(t.type!==`ConditionalExpression`)return null;let i=t.test,a=t.consequent,o=t.alternate,s=null,c=!1;if(i.type===`CallExpression`&&i.callee.type===`MemberExpression`){let e=i.callee.property.name;s=n.getText(i.callee.object),e===`isSome`||e===`isRight`||e===`isSuccess`?c=!1:(e===`isNone`||e===`isEmpty`||e===`isLeft`||e===`isFailure`)&&(c=!0)}if(!s)return null;let l=n.getText(a),u=n.getText(o);if(c){let e=r(u,s);return`${s}.fold(() => ${l}, (value) => ${e})`}else{let e=r(l,s);return`${s}.fold(() => ${u}, (value) => ${e})`}}function o(e){if(e.type===`CallExpression`&&e.callee.type===`MemberExpression`){let t=e.callee.property.name;return[`isSome`,`isNone`,`isEmpty`,`isRight`,`isLeft`,`isSuccess`,`isFailure`].includes(t)}return!1}function s(t){let n=e.sourceCode.getText(t);if(/\.(isSome|isNone|isEmpty|isDefined)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Option`};if(/\.(isLeft|isRight)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Either`};if(/\.(isSuccess|isFailure)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Result`};if(t.type===`BinaryExpression`){if((t.operator===`===`||t.operator===`!==`||t.operator===`==`||t.operator===`!=`)&&(t.left.type===`Literal`&&(t.left.value===null||t.left.value===void 0)||t.right.type===`Literal`&&(t.right.value===null||t.right.value===void 0)))return{isMonadic:!0,type:`Option`};if(t.operator===`==`||t.operator===`!=`||t.operator===`===`||t.operator===`!==`){let e=t.left.type===`Identifier`&&t.left.name===`undefined`,n=t.right.type===`Identifier`&&t.right.name===`undefined`;if(e||n)return{isMonadic:!0,type:`Option`}}}return{isMonadic:!1,type:``}}function c(n){let r=n.test,a=s(r);if(!a.isMonadic||n.parent&&n.parent.type===`IfStatement`)return;let c=1,l=n;for(;l.alternate&&(c++,l.alternate.type===`IfStatement`);)l=l.alternate;c>=t&&e.report({node:n,messageId:`preferFold`,data:{type:a.type},fix(e){if(!o(n.test))return null;let t=i(n);return t?e.replaceText(n,t):null}})}return{IfStatement(e){c(e)},ConditionalExpression(t){let n=s(t.test);n.isMonadic&&e.report({node:t,messageId:`preferFoldTernary`,data:{type:n.type},fix(e){if(!o(t.test))return null;let n=a(t);return n?e.replaceText(t,n):null}})}}}};export{e as default};
1
+ const e=new Set([`isSome`,`isRight`,`isSuccess`]),t=new Set([`isNone`,`isEmpty`,`isLeft`,`isFailure`]);function n(n,r){if(n.type!==`CallExpression`||n.callee.type!==`MemberExpression`)return null;let i=n.callee.property.name,a=r.getText(n.callee.object);return e.has(i)?{obj:a,isNegated:!1}:t.has(i)?{obj:a,isNegated:!0}:null}function r(e){return e.alternate?e.alternate.type===`IfStatement`?1+r(e.alternate):2:1}const i={meta:{type:`suggestion`,docs:{description:`Prefer .fold() over if/else chains when working with monadic types`,recommended:!0},fixable:`code`,schema:[{type:`object`,properties:{minComplexity:{type:`integer`,minimum:1,default:2}},additionalProperties:!1}],messages:{preferFold:`Prefer .fold() over if/else when working with {{type}} types`,preferFoldTernary:`Consider using .fold() instead of ternary operator for {{type}}`}},create(e){let t=(e.options[0]||{}).minComplexity||2;function i(e,t){if(e.type===`BlockStatement`){let n=e.body;return n.length===1&&n[0].type===`ReturnStatement`?t.getText(n[0].argument):t.getText(e).slice(1,-1).trim()}else return t.getText(e)}function a(e,t){let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return e.replace(RegExp(`${n}\\.get\\(\\)`,`g`),`value`)}function o(t){let r=e.sourceCode;if(t.type!==`IfStatement`)return null;let o=t.test,s=t.consequent,c=t.alternate;if(!s||!c)return null;let l=n(o,r);if(!l)return null;let{obj:u,isNegated:d}=l;if(s.type===`BlockStatement`&&(s.body.length!==1||s.body[0].type!==`ReturnStatement`))return null;if(c.type===`BlockStatement`){if(c.body.length!==1||c.body[0].type!==`ReturnStatement`)return null}else if(c.type===`IfStatement`)return null;let f=i(s,r),p=i(c,r);return d?`${u}.fold(() => ${f}, (value) => ${a(p,u)})`:`${u}.fold(() => ${p}, (value) => ${a(f,u)})`}function s(t){let r=e.sourceCode;if(t.type!==`ConditionalExpression`)return null;let i=t.test,o=t.consequent,s=t.alternate,c=n(i,r);if(!c)return null;let{obj:l,isNegated:u}=c,d=r.getText(o),f=r.getText(s);return u?`${l}.fold(() => ${d}, (value) => ${a(f,l)})`:`${l}.fold(() => ${f}, (value) => ${a(d,l)})`}function c(e){if(e.type===`CallExpression`&&e.callee.type===`MemberExpression`){let t=e.callee.property.name;return[`isSome`,`isNone`,`isEmpty`,`isRight`,`isLeft`,`isSuccess`,`isFailure`].includes(t)}return!1}function l(t){let n=e.sourceCode.getText(t);if(/\.(isSome|isNone|isEmpty|isDefined)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Option`};if(/\.(isLeft|isRight)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Either`};if(/\.(isSuccess|isFailure)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Result`};if(t.type===`BinaryExpression`){if((t.operator===`===`||t.operator===`!==`||t.operator===`==`||t.operator===`!=`)&&(t.left.type===`Literal`&&(t.left.value===null||t.left.value===void 0)||t.right.type===`Literal`&&(t.right.value===null||t.right.value===void 0)))return{isMonadic:!0,type:`Option`};if(t.operator===`==`||t.operator===`!=`||t.operator===`===`||t.operator===`!==`){let e=t.left.type===`Identifier`&&t.left.name===`undefined`,n=t.right.type===`Identifier`&&t.right.name===`undefined`;if(e||n)return{isMonadic:!0,type:`Option`}}}return{isMonadic:!1,type:``}}function u(n){let i=n.test,a=l(i);a.isMonadic&&(n.parent&&n.parent.type===`IfStatement`||r(n)>=t&&e.report({node:n,messageId:`preferFold`,data:{type:a.type},fix(e){if(!c(n.test))return null;let t=o(n);return t?e.replaceText(n,t):null}}))}return{IfStatement(e){u(e)},ConditionalExpression(t){let n=l(t.test);n.isMonadic&&e.report({node:t,messageId:`preferFoldTernary`,data:{type:n.type},fix(e){if(!c(t.test))return null;let n=s(t);return n?e.replaceText(t,n):null}})}}}};export{i as default};
2
2
  //# sourceMappingURL=prefer-fold.js.map