eslint-plugin-functype 2.3.0 → 2.60.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jordan Burke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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":";mFAMA,MAAM,EAAS,CACb,MAAO,UACP,OAAQ,UACR,IAAK,WACL,MAAO,WACP,OAAQ,WACR,KAAM,WACN,QAAS,WACT,KAAM,WACP,CAED,SAAS,EAAS,EAAc,EAAoC,CAClE,OAAO,EAAO,GAAS,EAAO,EAAO,MAKvC,SAAS,EAAsB,EAAgC,CAC7D,QAAQ,IAAI,EAAS;6BAAiC,SAAS,CAAC,CAChE,QAAQ,IAAI,EAAS,IAAI,OAAO,GAAG,CAAE,OAAO,CAAC,CAGzC,EAAO,UAAU,OAAS,IAC5B,QAAQ,IAAI,EAAS;cAAkB,QAAQ,CAAC,CAChD,EAAO,UAAU,QAAS,GAAQ,CAChC,IAAM,EAAO,EAAI,SAAW,KAAO,KACnC,QAAQ,IAAI,KAAK,EAAK,GAAG,EAAS,EAAI,KAAM,QAAQ,CAAC,KAAK,EAAI,cAAc,EAC5E,EAIA,EAAO,QAAQ,OAAS,IAC1B,QAAQ,IAAI,EAAS;YAAgB,MAAM,CAAC,CAC5C,EAAO,QAAQ,QAAS,GAAQ,CAC9B,IAAM,EAAO,EAAI,SAAW,MAAQ,KAC9B,EAAQ,EAAI,SAAW,MAAQ,SACrC,QAAQ,IAAI,KAAK,EAAK,GAAG,EAAS,EAAI,KAAM,EAAM,CAAC,KAAK,EAAI,cAAc,EAC1E,CAEE,EAAO,iBACT,QAAQ,IAAI,EAAS;kCAAsC,SAAS,CAAC,CACrE,QAAQ,IAAI,MAAM,EAAS,EAAO,eAAgB,OAAO,GAAG,GAK5D,EAAO,SAAS,OAAS,IAC3B,QAAQ,IAAI,EAAS;eAAmB,SAAS,CAAC,CAClD,EAAO,SAAS,QAAS,GAAY,QAAQ,IAAI,MAAM,IAAU,CAAC,EAIpE,IAAM,EAAS,EAAO,QAAU,iBAAmB,4BAC7C,EAAc,EAAO,QAAU,QAAU,MAC/C,QAAQ,IAAI,EAAS,KAAK,IAAU,EAAY,CAAC,CAGnD,eAAe,GAAsB,CACnC,IAAM,EAAO,QAAQ,KAAK,MAAM,EAAE,CAC5B,EAAW,EAAK,SAAS,SAAS,EAAI,EAAK,SAAS,KAAK,CACzD,EAAY,EAAK,SAAS,UAAU,EAAI,EAAK,SAAS,KAAK,CAC3D,EAAY,EAAK,SAAS,eAAe,EAAI,EAAK,SAAS,UAAU,CAE3E,GAAI,EAAU,CACZ,QAAQ,IAAI,EAAS,2CAA4C,SAAS,CAAC,CAC3E,QAAQ,IAAI;sCAAyC,CACrD,QAAQ,IAAI;UAAa,CACzB,QAAQ,IAAI,0DAA0D,CACtE,QAAQ,IAAI,2CAA2C,CACvD,QAAQ,IAAI,oDAAoD,CAChE,QAAQ,IAAI,8CAA8C,CAC1D,QAAQ,IAAI;sEAAyE,CACrF,OAIF,GAAI,EAAW,CACb,QAAQ,IAAI,EAAS,+CAAgD,SAAS,CAAC,CAC/E,IAAM,EAAS,GAA0B,CACzC,EAAsB,EAAO,CAExB,EAAO,SACV,QAAQ,KAAK,EAAE,CAEjB,OAGF,QAAQ,IAAI,EAAS,2CAA4C,SAAS,CAAC,CAEtE,EAAO,QACV,QAAQ,MAAM,EAAS,8BAA+B,MAAM,CAAC,CAC7D,QAAQ,KAAK,EAAE,EAGjB,QAAQ,IAAI,EAAS;4BAAgC,SAAS,CAAC,CAC/D,QAAQ,IAAI,EAAS,IAAI,OAAO,GAAG,CAAE,OAAO,CAAC,CAE7C,IAAM,EAAQ,OAAO,KAAK,EAAO,MAAM,CAEvC,EAAM,QAAS,GAAa,CAC1B,IAAM,EAAO,EAAO,MAAM,GACpB,EAAW,YAAY,IAQ7B,GANA,QAAQ,IAAI,KAAK,EAAS,IAAK,QAAQ,CAAC,GAAG,EAAS,EAAU,SAAS,GAAG,CAEtE,EAAK,MAAM,MAAM,aACnB,QAAQ,IAAI,KAAK,EAAS,eAAgB,OAAO,CAAC,GAAG,EAAK,KAAK,KAAK,cAAc,CAGhF,EAAK,MAAM,KAAM,CACnB,IAAM,EAAY,EAAK,KAAK,OAAS,UAAY,MAAQ,EAAK,KAAK,OAAS,aAAe,SAAW,OACtG,QAAQ,IAAI,KAAK,EAAS,QAAS,OAAO,CAAC,GAAG,EAAS,EAAK,KAAK,KAAM,EAAU,GAAG,CAGlF,EAAK,MAAM,SACb,QAAQ,IAAI,KAAK,EAAS,WAAY,OAAO,CAAC,GAAG,EAAS,MAAO,QAAQ,GAAG,CAG1E,GACF,QAAQ,IAAI,KAAK,EAAS,SAAU,OAAO,CAAC,IAAI,EAAS,YAAY,EAEvE,CAEF,QAAQ,IAAI,EAAS,iBAAiB,EAAM,OAAO,yBAA0B,SAAS,CAAC,CAEnF,GACF,GAAsB,CAGxB,QAAQ,IAAI,EAAS;UAAc,SAAS,CAAC,CAC7C,QAAQ,IAAI,mDAAmD,CAC/D,QAAQ,IAAI,8CAA8C,CAC1D,QAAQ,IAAI,4CAA4C,CACxD,QAAQ,IAAI,mEAAmE,CAE/E,QAAQ,IAAI,EAAS;WAAe,SAAS,CAAC,CAC9C,QAAQ,IAAI,yEAAyE,CACrF,QAAQ,IAAI,gFAAgF,CAC5F,QAAQ,IAAI,8DAA8D,CAG5E,SAAS,GAA6B,CACpC,QAAQ,IAAI,EAAS;oBAAwB,SAAS,CAAC,CACvD,QAAQ,IAAI,EAAS,IAAI,OAAO,GAAG,CAAE,OAAO,CAAC,CAC7C,QAAQ,IAAI;EAAO,EAAS,2BAA4B,QAAQ,CAAC,CACjE,QAAQ,IAAI,wDAAwD,CACpE,QAAQ,IAAI,qBAAqB,CACjC,QAAQ,IAAI,QAAQ,CACpB,QAAQ,IAAI,+CAA+C,CAC3D,QAAQ,IAAI,iBAAiB,CAC7B,QAAQ,IAAI,6CAA6C,CACzD,QAAQ,IAAI,6CAA6C,CACzD,QAAQ,IAAI,6CAA6C,CACzD,QAAQ,IAAI,UAAU,CACtB,QAAQ,IAAI,QAAQ,CACpB,QAAQ,IAAI,MAAM,CAClB,QAAQ,IAAI;EAAO,EAAS,6CAA8C,QAAQ,CAAC,CACnF,QAAQ,IAAI,wDAAwD,CACpE,QAAQ,IAAI,gDAAgD,CAI9D,GAAM,CAAC,MAAO,GAAU,CACtB,QAAQ,MAAM,EAAS,sBAAuB,MAAM,CAAE,EAAM,CAC5D,QAAQ,KAAK,EAAE,EACf"}
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 +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,MACzB"}
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 +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,QACjC"}
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 +1 @@
1
- {"version":3,"file":"dependency-validator-BBxa9-7D.js","names":[],"sources":["../src/utils/dependency-validator.ts"],"sourcesContent":["// Utility to validate peer dependencies and provide helpful error messages\n\ninterface PeerDependency {\n name: string\n packageName: string\n description: string\n required: boolean\n}\n\nconst PEER_DEPENDENCIES: PeerDependency[] = [\n {\n name: \"@typescript-eslint/eslint-plugin\",\n packageName: \"@typescript-eslint/eslint-plugin\",\n description: \"TypeScript-aware ESLint rules\",\n required: true,\n },\n {\n name: \"@typescript-eslint/parser\",\n packageName: \"@typescript-eslint/parser\",\n description: \"TypeScript parser for ESLint\",\n required: true,\n },\n {\n name: \"eslint-plugin-functional\",\n packageName: \"eslint-plugin-functional\",\n description: \"Functional programming ESLint rules\",\n required: true,\n },\n {\n name: \"eslint-plugin-prettier\",\n packageName: \"eslint-plugin-prettier\",\n description: \"Code formatting rules\",\n required: false,\n },\n {\n name: \"eslint-plugin-simple-import-sort\",\n packageName: \"eslint-plugin-simple-import-sort\",\n description: \"Import sorting rules\",\n required: false,\n },\n {\n name: \"prettier\",\n packageName: \"prettier\",\n description: \"Code formatter\",\n required: false,\n },\n]\n\nexport interface ValidationResult {\n isValid: boolean\n missing: PeerDependency[]\n available: PeerDependency[]\n installCommand: string\n warnings: string[]\n}\n\nfunction tryRequire(packageName: string): boolean {\n try {\n require.resolve(packageName)\n return true\n } catch {\n return false\n }\n}\n\nexport function validatePeerDependencies(): ValidationResult {\n const missing: PeerDependency[] = []\n const available: PeerDependency[] = []\n const warnings: string[] = []\n\n for (const dep of PEER_DEPENDENCIES) {\n if (tryRequire(dep.packageName)) {\n available.push(dep)\n } else {\n missing.push(dep)\n if (dep.required) {\n // Required dependency is missing - this will cause errors\n } else {\n // Optional dependency is missing - add warning\n warnings.push(`Optional plugin '${dep.name}' not found. Some rules will be skipped.`)\n }\n }\n }\n\n const requiredMissing = missing.filter((dep) => dep.required)\n const isValid = requiredMissing.length === 0\n\n // Generate install command for missing dependencies\n const missingPackageNames = missing.map((dep) => dep.packageName)\n const installCommand = missingPackageNames.length > 0 ? `pnpm add -D ${missingPackageNames.join(\" \")}` : \"\"\n\n return {\n isValid,\n missing,\n available,\n installCommand,\n warnings,\n }\n}\n\nexport function createValidationError(result: ValidationResult): Error {\n const requiredMissing = result.missing.filter((dep) => dep.required)\n\n if (requiredMissing.length === 0) {\n return new Error(\"No validation errors\")\n }\n\n const missingList = requiredMissing.map((dep) => ` • ${dep.name} - ${dep.description}`).join(\"\\n\")\n\n const message = [\n \"āŒ Missing required peer dependencies for eslint-plugin-functype:\",\n \"\",\n missingList,\n \"\",\n \"šŸ“¦ Install missing dependencies:\",\n ` ${result.installCommand}`,\n \"\",\n \"šŸ“– See installation guide: https://github.com/jordanburke/eslint-plugin-functype#installation\",\n ].join(\"\\n\")\n\n return new Error(message)\n}\n\nexport function shouldValidateDependencies(): boolean {\n // Skip validation in test environments or when explicitly disabled\n return process.env.NODE_ENV !== \"test\" && process.env.FUNCTYPE_SKIP_VALIDATION !== \"true\"\n}\n"],"mappings":"qEASA,MAAM,EAAsC,CAC1C,CACE,KAAM,mCACN,YAAa,mCACb,YAAa,gCACb,SAAU,GACX,CACD,CACE,KAAM,4BACN,YAAa,4BACb,YAAa,+BACb,SAAU,GACX,CACD,CACE,KAAM,2BACN,YAAa,2BACb,YAAa,sCACb,SAAU,GACX,CACD,CACE,KAAM,yBACN,YAAa,yBACb,YAAa,wBACb,SAAU,GACX,CACD,CACE,KAAM,mCACN,YAAa,mCACb,YAAa,uBACb,SAAU,GACX,CACD,CACE,KAAM,WACN,YAAa,WACb,YAAa,iBACb,SAAU,GACX,CACF,CAUD,SAAS,EAAW,EAA8B,CAChD,GAAI,CAEF,OADA,EAAQ,QAAQ,EAAY,CACrB,QACD,CACN,MAAO,IAIX,SAAgB,GAA6C,CAC3D,IAAM,EAA4B,EAAE,CAC9B,EAA8B,EAAE,CAChC,EAAqB,EAAE,CAE7B,IAAK,IAAM,KAAO,EACZ,EAAW,EAAI,YAAY,CAC7B,EAAU,KAAK,EAAI,EAEnB,EAAQ,KAAK,EAAI,CACb,EAAI,UAIN,EAAS,KAAK,oBAAoB,EAAI,KAAK,0CAA0C,EAM3F,IAAM,EADkB,EAAQ,OAAQ,GAAQ,EAAI,SACrB,CAAC,SAAW,EAGrC,EAAsB,EAAQ,IAAK,GAAQ,EAAI,YAAY,CAGjE,MAAO,CACL,UACA,UACA,YACA,eANqB,EAAoB,OAAS,EAAI,eAAe,EAAoB,KAAK,IAAI,GAAK,GAOvG,WACD,CAGH,SAAgB,EAAsB,EAAiC,CACrE,IAAM,EAAkB,EAAO,QAAQ,OAAQ,GAAQ,EAAI,SAAS,CAEpE,GAAI,EAAgB,SAAW,EAC7B,OAAW,MAAM,uBAAuB,CAK1C,IAAM,EAAU,CACd,mEACA,GAJkB,EAAgB,IAAK,GAAQ,OAAO,EAAI,KAAK,KAAK,EAAI,cAAc,CAAC,KAAK;EAKjF,CACX,GACA,mCACA,MAAM,EAAO,iBACb,GACA,gGACD,CAAC,KAAK;EAAK,CAEZ,OAAW,MAAM,EAAQ,CAG3B,SAAgB,GAAsC,CAEpD,OAAO,QAAQ,IAAI,WAAa,QAAU,QAAQ,IAAI,2BAA6B"}
1
+ {"version":3,"file":"dependency-validator-BBxa9-7D.js","names":[],"sources":["../src/utils/dependency-validator.ts"],"sourcesContent":["// Utility to validate peer dependencies and provide helpful error messages\n\ninterface PeerDependency {\n name: string\n packageName: string\n description: string\n required: boolean\n}\n\nconst PEER_DEPENDENCIES: PeerDependency[] = [\n {\n name: \"@typescript-eslint/eslint-plugin\",\n packageName: \"@typescript-eslint/eslint-plugin\",\n description: \"TypeScript-aware ESLint rules\",\n required: true,\n },\n {\n name: \"@typescript-eslint/parser\",\n packageName: \"@typescript-eslint/parser\",\n description: \"TypeScript parser for ESLint\",\n required: true,\n },\n {\n name: \"eslint-plugin-functional\",\n packageName: \"eslint-plugin-functional\",\n description: \"Functional programming ESLint rules\",\n required: true,\n },\n {\n name: \"eslint-plugin-prettier\",\n packageName: \"eslint-plugin-prettier\",\n description: \"Code formatting rules\",\n required: false,\n },\n {\n name: \"eslint-plugin-simple-import-sort\",\n packageName: \"eslint-plugin-simple-import-sort\",\n description: \"Import sorting rules\",\n required: false,\n },\n {\n name: \"prettier\",\n packageName: \"prettier\",\n description: \"Code formatter\",\n required: false,\n },\n]\n\nexport interface ValidationResult {\n isValid: boolean\n missing: PeerDependency[]\n available: PeerDependency[]\n installCommand: string\n warnings: string[]\n}\n\nfunction tryRequire(packageName: string): boolean {\n try {\n require.resolve(packageName)\n return true\n } catch {\n return false\n }\n}\n\nexport function validatePeerDependencies(): ValidationResult {\n const missing: PeerDependency[] = []\n const available: PeerDependency[] = []\n const warnings: string[] = []\n\n for (const dep of PEER_DEPENDENCIES) {\n if (tryRequire(dep.packageName)) {\n available.push(dep)\n } else {\n missing.push(dep)\n if (dep.required) {\n // Required dependency is missing - this will cause errors\n } else {\n // Optional dependency is missing - add warning\n warnings.push(`Optional plugin '${dep.name}' not found. Some rules will be skipped.`)\n }\n }\n }\n\n const requiredMissing = missing.filter((dep) => dep.required)\n const isValid = requiredMissing.length === 0\n\n // Generate install command for missing dependencies\n const missingPackageNames = missing.map((dep) => dep.packageName)\n const installCommand = missingPackageNames.length > 0 ? `pnpm add -D ${missingPackageNames.join(\" \")}` : \"\"\n\n return {\n isValid,\n missing,\n available,\n installCommand,\n warnings,\n }\n}\n\nexport function createValidationError(result: ValidationResult): Error {\n const requiredMissing = result.missing.filter((dep) => dep.required)\n\n if (requiredMissing.length === 0) {\n return new Error(\"No validation errors\")\n }\n\n const missingList = requiredMissing.map((dep) => ` • ${dep.name} - ${dep.description}`).join(\"\\n\")\n\n const message = [\n \"āŒ Missing required peer dependencies for eslint-plugin-functype:\",\n \"\",\n missingList,\n \"\",\n \"šŸ“¦ Install missing dependencies:\",\n ` ${result.installCommand}`,\n \"\",\n \"šŸ“– See installation guide: https://github.com/jordanburke/eslint-plugin-functype#installation\",\n ].join(\"\\n\")\n\n return new Error(message)\n}\n\nexport function shouldValidateDependencies(): boolean {\n // Skip validation in test environments or when explicitly disabled\n return process.env.NODE_ENV !== \"test\" && process.env.FUNCTYPE_SKIP_VALIDATION !== \"true\"\n}\n"],"mappings":"qEASA,MAAM,EAAsC,CAC1C,CACE,KAAM,mCACN,YAAa,mCACb,YAAa,gCACb,SAAU,EACZ,EACA,CACE,KAAM,4BACN,YAAa,4BACb,YAAa,+BACb,SAAU,EACZ,EACA,CACE,KAAM,2BACN,YAAa,2BACb,YAAa,sCACb,SAAU,EACZ,EACA,CACE,KAAM,yBACN,YAAa,yBACb,YAAa,wBACb,SAAU,EACZ,EACA,CACE,KAAM,mCACN,YAAa,mCACb,YAAa,uBACb,SAAU,EACZ,EACA,CACE,KAAM,WACN,YAAa,WACb,YAAa,iBACb,SAAU,EACZ,CACF,EAUA,SAAS,EAAW,EAA8B,CAChD,GAAI,CAEF,OADA,EAAQ,QAAQ,CAAW,EACpB,EACT,MAAQ,CACN,MAAO,EACT,CACF,CAEA,SAAgB,GAA6C,CAC3D,IAAM,EAA4B,CAAC,EAC7B,EAA8B,CAAC,EAC/B,EAAqB,CAAC,EAE5B,IAAK,IAAM,KAAO,EACZ,EAAW,EAAI,WAAW,EAC5B,EAAU,KAAK,CAAG,GAElB,EAAQ,KAAK,CAAG,EACZ,EAAI,UAIN,EAAS,KAAK,oBAAoB,EAAI,KAAK,yCAAyC,GAM1F,IAAM,EADkB,EAAQ,OAAQ,GAAQ,EAAI,QACtB,EAAE,SAAW,EAGrC,EAAsB,EAAQ,IAAK,GAAQ,EAAI,WAAW,EAGhE,MAAO,CACL,UACA,UACA,YACA,eANqB,EAAoB,OAAS,EAAI,eAAe,EAAoB,KAAK,GAAG,IAAM,GAOvG,UACF,CACF,CAEA,SAAgB,EAAsB,EAAiC,CACrE,IAAM,EAAkB,EAAO,QAAQ,OAAQ,GAAQ,EAAI,QAAQ,EAEnE,GAAI,EAAgB,SAAW,EAC7B,OAAW,MAAM,sBAAsB,EAKzC,IAAM,EAAU,CACd,mEACA,GAJkB,EAAgB,IAAK,GAAQ,OAAO,EAAI,KAAK,KAAK,EAAI,aAAa,EAAE,KAAK;CAKlF,EACV,GACA,mCACA,MAAM,EAAO,iBACb,GACA,+FACF,EAAE,KAAK;CAAI,EAEX,OAAW,MAAM,CAAO,CAC1B,CAEA,SAAgB,GAAsC,CAEpD,OAAO,QAAQ,IAAI,WAAa,QAAU,QAAQ,IAAI,2BAA6B,MACrF"}
package/dist/index.d.ts CHANGED
@@ -1,20 +1,18 @@
1
- import * as _$eslint from "eslint";
2
-
3
1
  //#region src/index.d.ts
4
2
  declare const plugin: {
5
3
  rules: {
6
- "prefer-option": _$eslint.Rule.RuleModule;
7
- "prefer-either": _$eslint.Rule.RuleModule;
8
- "prefer-list": _$eslint.Rule.RuleModule;
9
- "no-get-unsafe": _$eslint.Rule.RuleModule;
10
- "no-let": _$eslint.Rule.RuleModule;
11
- "prefer-fold": _$eslint.Rule.RuleModule;
12
- "prefer-map": _$eslint.Rule.RuleModule;
13
- "prefer-flatmap": _$eslint.Rule.RuleModule;
14
- "prefer-functype-map": _$eslint.Rule.RuleModule;
15
- "prefer-functype-set": _$eslint.Rule.RuleModule;
16
- "no-imperative-loops": _$eslint.Rule.RuleModule;
17
- "prefer-do-notation": _$eslint.Rule.RuleModule;
4
+ "prefer-option": import("eslint").Rule.RuleModule;
5
+ "prefer-either": import("eslint").Rule.RuleModule;
6
+ "prefer-list": import("eslint").Rule.RuleModule;
7
+ "no-get-unsafe": import("eslint").Rule.RuleModule;
8
+ "no-let": import("eslint").Rule.RuleModule;
9
+ "prefer-fold": import("eslint").Rule.RuleModule;
10
+ "prefer-map": import("eslint").Rule.RuleModule;
11
+ "prefer-flatmap": import("eslint").Rule.RuleModule;
12
+ "prefer-functype-map": import("eslint").Rule.RuleModule;
13
+ "prefer-functype-set": import("eslint").Rule.RuleModule;
14
+ "no-imperative-loops": import("eslint").Rule.RuleModule;
15
+ "prefer-do-notation": import("eslint").Rule.RuleModule;
18
16
  };
19
17
  meta: {
20
18
  name: string;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import recommendedRules from \"./configs/recommended\"\nimport strictRules from \"./configs/strict\"\nimport rules from \"./rules\"\n\nconst plugin = {\n rules,\n meta: {\n name: \"eslint-plugin-functype\",\n version: \"2.0.0\",\n },\n configs: {} as Record<string, unknown>,\n}\n\n// Self-referencing plugin in configs (ESLint flat config pattern)\nplugin.configs = {\n recommended: {\n name: \"functype-plugin/recommended\",\n plugins: { functype: plugin },\n rules: recommendedRules,\n },\n strict: {\n name: \"functype-plugin/strict\",\n plugins: { functype: plugin },\n rules: strictRules,\n },\n}\n\nexport default plugin\n"],"mappings":"2GAIA,MAAM,EAAS,CACb,MAAA,EACA,KAAM,CACJ,KAAM,yBACN,QAAS,QACV,CACD,QAAS,EAAE,CACZ,CAGD,EAAO,QAAU,CACf,YAAa,CACX,KAAM,8BACN,QAAS,CAAE,SAAU,EAAQ,CAC7B,MAAO,EACR,CACD,OAAQ,CACN,KAAM,yBACN,QAAS,CAAE,SAAU,EAAQ,CAC7B,MAAO,EACR,CACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import recommendedRules from \"./configs/recommended\"\nimport strictRules from \"./configs/strict\"\nimport rules from \"./rules\"\n\nconst plugin = {\n rules,\n meta: {\n name: \"eslint-plugin-functype\",\n version: \"2.0.0\",\n },\n configs: {} as Record<string, unknown>,\n}\n\n// Self-referencing plugin in configs (ESLint flat config pattern)\nplugin.configs = {\n recommended: {\n name: \"functype-plugin/recommended\",\n plugins: { functype: plugin },\n rules: recommendedRules,\n },\n strict: {\n name: \"functype-plugin/strict\",\n plugins: { functype: plugin },\n rules: strictRules,\n },\n}\n\nexport default plugin\n"],"mappings":"2GAIA,MAAM,EAAS,CACb,MAAA,EACA,KAAM,CACJ,KAAM,yBACN,QAAS,OACX,EACA,QAAS,CAAC,CACZ,EAGA,EAAO,QAAU,CACf,YAAa,CACX,KAAM,8BACN,QAAS,CAAE,SAAU,CAAO,EAC5B,MAAO,CACT,EACA,OAAQ,CACN,KAAM,yBACN,QAAS,CAAE,SAAU,CAAO,EAC5B,MAAO,CACT,CACF"}
@@ -10,22 +10,21 @@ 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 * as _$eslint from "eslint";
14
13
 
15
14
  //#region src/rules/index.d.ts
16
15
  declare const _default: {
17
- "prefer-option": _$eslint.Rule.RuleModule;
18
- "prefer-either": _$eslint.Rule.RuleModule;
19
- "prefer-list": _$eslint.Rule.RuleModule;
20
- "no-get-unsafe": _$eslint.Rule.RuleModule;
21
- "no-let": _$eslint.Rule.RuleModule;
22
- "prefer-fold": _$eslint.Rule.RuleModule;
23
- "prefer-map": _$eslint.Rule.RuleModule;
24
- "prefer-flatmap": _$eslint.Rule.RuleModule;
25
- "prefer-functype-map": _$eslint.Rule.RuleModule;
26
- "prefer-functype-set": _$eslint.Rule.RuleModule;
27
- "no-imperative-loops": _$eslint.Rule.RuleModule;
28
- "prefer-do-notation": _$eslint.Rule.RuleModule;
16
+ "prefer-option": import("eslint").Rule.RuleModule;
17
+ "prefer-either": import("eslint").Rule.RuleModule;
18
+ "prefer-list": import("eslint").Rule.RuleModule;
19
+ "no-get-unsafe": import("eslint").Rule.RuleModule;
20
+ "no-let": import("eslint").Rule.RuleModule;
21
+ "prefer-fold": import("eslint").Rule.RuleModule;
22
+ "prefer-map": import("eslint").Rule.RuleModule;
23
+ "prefer-flatmap": import("eslint").Rule.RuleModule;
24
+ "prefer-functype-map": import("eslint").Rule.RuleModule;
25
+ "prefer-functype-set": import("eslint").Rule.RuleModule;
26
+ "no-imperative-loops": import("eslint").Rule.RuleModule;
27
+ "prefer-do-notation": import("eslint").Rule.RuleModule;
29
28
  };
30
29
  //#endregion
31
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 };
@@ -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,EACvB"}
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 +1 @@
1
- {"version":3,"file":"no-get-unsafe.js","names":[],"sources":["../../src/rules/no-get-unsafe.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Avoid unsafe .get() calls on Option, Either, and other monadic types\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n unsafeMethods: {\n type: \"array\",\n items: { type: \"string\" },\n default: [\"get\", \"getOrThrow\", \"unwrap\", \"expect\"],\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noUnsafeGet: \"Avoid unsafe .{{method}}() call. Use .fold(), .map(), or .getOrElse() instead\",\n noUnsafeGetSuggestion: \"Consider using .getOrElse(defaultValue) or .fold() for safe access\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowInTests = options.allowInTests !== false\n const unsafeMethods = options.unsafeMethods || [\"get\", \"getOrThrow\", \"unwrap\", \"expect\"]\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 isMonadicType(node: ASTNode): boolean {\n // This is a simplified check - in a real implementation, you'd want\n // more sophisticated type checking using TypeScript's type checker\n if (!node) return false\n\n const sourceCode = context.sourceCode\n\n // Check for common patterns that indicate monadic types\n const text = sourceCode.getText(node)\n\n // Direct type checks - constructors or type names\n if (/\\b(Option|Either|Maybe|Result|Some|None|Left|Right)\\b/.test(text)) {\n return true\n }\n\n // Method chains that suggest monadic operations\n if (/\\.(map|flatMap|filter|fold)\\s*\\(/.test(text)) {\n return true\n }\n\n // Variable names that suggest monadic types (case insensitive)\n if (/\\b(option|either|maybe|result|some|none|left|right)\\w*\\b/i.test(text)) {\n return true\n }\n\n // Check the specific node type and name\n if (node.type === \"Identifier\") {\n const varName = node.name.toLowerCase()\n if (/(option|either|maybe|result|some|none|left|right|opt)/.test(varName)) {\n return true\n }\n }\n\n // Check for CallExpression pattern like Some(\"test\").map()\n if (node.type === \"CallExpression\" && node.callee.type === \"MemberExpression\" && node.callee.object) {\n return isMonadicType(node.callee.object)\n }\n\n return false\n }\n\n return {\n CallExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (node.callee.type !== \"MemberExpression\") return\n\n const property = node.callee.property\n if (!property || property.type !== \"Identifier\") return\n\n const methodName = property.name\n if (!unsafeMethods.includes(methodName)) return\n\n // Check if this looks like it's being called on a monadic type\n if (isMonadicType(node.callee.object)) {\n context.report({\n node,\n messageId: \"noUnsafeGet\",\n data: { method: methodName },\n fix(fixer) {\n const sourceCode = context.sourceCode\n const objectText = sourceCode.getText(node.callee.object)\n\n // Choose safer alternative based on method name\n let replacement: string\n if (methodName === \"get\") {\n // Replace .get() with .getOrElse(/* provide default */)\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else if (methodName === \"getOrThrow\") {\n // Replace .getOrThrow() with .getOrElse()\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else if (methodName === \"unwrap\") {\n // Replace .unwrap() with .getOrElse()\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else if (methodName === \"expect\") {\n // Replace .expect(msg) with .getOrElse()\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else {\n // Generic fallback\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n }\n\n return fixer.replaceText(node, replacement)\n },\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,UACN,KAAM,CACJ,YAAa,uEACb,YAAa,GACd,CACD,QAAS,OACT,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACD,cAAe,CACb,KAAM,QACN,MAAO,CAAE,KAAM,SAAU,CACzB,QAAS,CAAC,MAAO,aAAc,SAAU,SAAS,CACnD,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,YAAa,gFACb,sBAAuB,qEACxB,CACF,CAED,OAAO,EAAS,CACd,IAAM,EAAU,EAAQ,QAAQ,IAAM,EAAE,CAClC,EAAe,EAAQ,eAAiB,GACxC,EAAgB,EAAQ,eAAiB,CAAC,MAAO,aAAc,SAAU,SAAS,CAExF,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,SAAS,EAAc,EAAwB,CAG7C,GAAI,CAAC,EAAM,MAAO,GAKlB,IAAM,EAHa,EAAQ,WAGH,QAAQ,EAAK,CAarC,GAVI,wDAAwD,KAAK,EAAK,EAKlE,mCAAmC,KAAK,EAAK,EAK7C,4DAA4D,KAAK,EAAK,CACxE,MAAO,GAIT,GAAI,EAAK,OAAS,aAAc,CAC9B,IAAM,EAAU,EAAK,KAAK,aAAa,CACvC,GAAI,wDAAwD,KAAK,EAAQ,CACvE,MAAO,GASX,OAJI,EAAK,OAAS,kBAAoB,EAAK,OAAO,OAAS,oBAAsB,EAAK,OAAO,OACpF,EAAc,EAAK,OAAO,OAAO,CAGnC,GAGT,MAAO,CACL,eAAe,EAAe,CAG5B,GAFI,GAAgB,GAAc,EAE9B,EAAK,OAAO,OAAS,mBAAoB,OAE7C,IAAM,EAAW,EAAK,OAAO,SAC7B,GAAI,CAAC,GAAY,EAAS,OAAS,aAAc,OAEjD,IAAM,EAAa,EAAS,KACvB,EAAc,SAAS,EAAW,EAGnC,EAAc,EAAK,OAAO,OAAO,EACnC,EAAQ,OAAO,CACb,OACA,UAAW,cACX,KAAM,CAAE,OAAQ,EAAY,CAC5B,IAAI,EAAO,CAET,IAAM,EADa,EAAQ,WACG,QAAQ,EAAK,OAAO,OAAO,CAGrD,EAkBJ,MAjBA,CAcE,EAZc,GAAG,EAAW,+CAevB,EAAM,YAAY,EAAM,EAAY,EAE9C,CAAC,EAGP,EAEJ"}
1
+ {"version":3,"file":"no-get-unsafe.js","names":[],"sources":["../../src/rules/no-get-unsafe.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Avoid unsafe .get() calls on Option, Either, and other monadic types\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n unsafeMethods: {\n type: \"array\",\n items: { type: \"string\" },\n default: [\"get\", \"getOrThrow\", \"unwrap\", \"expect\"],\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noUnsafeGet: \"Avoid unsafe .{{method}}() call. Use .fold(), .map(), or .getOrElse() instead\",\n noUnsafeGetSuggestion: \"Consider using .getOrElse(defaultValue) or .fold() for safe access\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowInTests = options.allowInTests !== false\n const unsafeMethods = options.unsafeMethods || [\"get\", \"getOrThrow\", \"unwrap\", \"expect\"]\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 isMonadicType(node: ASTNode): boolean {\n // This is a simplified check - in a real implementation, you'd want\n // more sophisticated type checking using TypeScript's type checker\n if (!node) return false\n\n const sourceCode = context.sourceCode\n\n // Check for common patterns that indicate monadic types\n const text = sourceCode.getText(node)\n\n // Direct type checks - constructors or type names\n if (/\\b(Option|Either|Maybe|Result|Some|None|Left|Right)\\b/.test(text)) {\n return true\n }\n\n // Method chains that suggest monadic operations\n if (/\\.(map|flatMap|filter|fold)\\s*\\(/.test(text)) {\n return true\n }\n\n // Variable names that suggest monadic types (case insensitive)\n if (/\\b(option|either|maybe|result|some|none|left|right)\\w*\\b/i.test(text)) {\n return true\n }\n\n // Check the specific node type and name\n if (node.type === \"Identifier\") {\n const varName = node.name.toLowerCase()\n if (/(option|either|maybe|result|some|none|left|right|opt)/.test(varName)) {\n return true\n }\n }\n\n // Check for CallExpression pattern like Some(\"test\").map()\n if (node.type === \"CallExpression\" && node.callee.type === \"MemberExpression\" && node.callee.object) {\n return isMonadicType(node.callee.object)\n }\n\n return false\n }\n\n return {\n CallExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (node.callee.type !== \"MemberExpression\") return\n\n const property = node.callee.property\n if (!property || property.type !== \"Identifier\") return\n\n const methodName = property.name\n if (!unsafeMethods.includes(methodName)) return\n\n // Check if this looks like it's being called on a monadic type\n if (isMonadicType(node.callee.object)) {\n context.report({\n node,\n messageId: \"noUnsafeGet\",\n data: { method: methodName },\n fix(fixer) {\n const sourceCode = context.sourceCode\n const objectText = sourceCode.getText(node.callee.object)\n\n // Choose safer alternative based on method name\n let replacement: string\n if (methodName === \"get\") {\n // Replace .get() with .getOrElse(/* provide default */)\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else if (methodName === \"getOrThrow\") {\n // Replace .getOrThrow() with .getOrElse()\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else if (methodName === \"unwrap\") {\n // Replace .unwrap() with .getOrElse()\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else if (methodName === \"expect\") {\n // Replace .expect(msg) with .getOrElse()\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n } else {\n // Generic fallback\n replacement = `${objectText}.getOrElse(/* TODO: provide default value */)`\n }\n\n return fixer.replaceText(node, replacement)\n },\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,UACN,KAAM,CACJ,YAAa,uEACb,YAAa,EACf,EACA,QAAS,OACT,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,EACX,EACA,cAAe,CACb,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,EACxB,QAAS,CAAC,MAAO,aAAc,SAAU,QAAQ,CACnD,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,YAAa,gFACb,sBAAuB,oEACzB,CACF,EAEA,OAAO,EAAS,CACd,IAAM,EAAU,EAAQ,QAAQ,IAAM,CAAC,EACjC,EAAe,EAAQ,eAAiB,GACxC,EAAgB,EAAQ,eAAiB,CAAC,MAAO,aAAc,SAAU,QAAQ,EAEvF,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,EAAc,EAAwB,CAG7C,GAAI,CAAC,EAAM,MAAO,GAKlB,IAAM,EAHa,EAAQ,WAGH,QAAQ,CAAI,EAapC,GAVI,wDAAwD,KAAK,CAAI,GAKjE,mCAAmC,KAAK,CAAI,GAK5C,4DAA4D,KAAK,CAAI,EACvE,MAAO,GAIT,GAAI,EAAK,OAAS,aAAc,CAC9B,IAAM,EAAU,EAAK,KAAK,YAAY,EACtC,GAAI,wDAAwD,KAAK,CAAO,EACtE,MAAO,EAEX,CAOA,OAJI,EAAK,OAAS,kBAAoB,EAAK,OAAO,OAAS,oBAAsB,EAAK,OAAO,OACpF,EAAc,EAAK,OAAO,MAAM,EAGlC,EACT,CAEA,MAAO,CACL,eAAe,EAAe,CAG5B,GAFI,GAAgB,EAAa,GAE7B,EAAK,OAAO,OAAS,mBAAoB,OAE7C,IAAM,EAAW,EAAK,OAAO,SAC7B,GAAI,CAAC,GAAY,EAAS,OAAS,aAAc,OAEjD,IAAM,EAAa,EAAS,KACvB,EAAc,SAAS,CAAU,GAGlC,EAAc,EAAK,OAAO,MAAM,GAClC,EAAQ,OAAO,CACb,OACA,UAAW,cACX,KAAM,CAAE,OAAQ,CAAW,EAC3B,IAAI,EAAO,CAET,IAAM,EADa,EAAQ,WACG,QAAQ,EAAK,OAAO,MAAM,EAGpD,EAkBJ,MAjBA,CAcE,EAZc,GAAG,EAAW,+CAevB,EAAM,YAAY,EAAM,CAAW,CAC5C,CACF,CAAC,CAEL,CACF,CACF,CACF"}
@@ -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 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 +1 @@
1
- {"version":3,"file":"no-let.js","names":[],"sources":["../../src/rules/no-let.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 fixable: \"code\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer const over let — use immutable bindings\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noLet: \"Prefer const over let — use immutable bindings\",\n suggestConst: \"Replace let with const\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\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 return {\n VariableDeclaration(node: ASTNode) {\n if (node.kind !== \"let\") return\n if (allowInTests && isInTestFile()) return\n\n const declaredVars = context.sourceCode.getDeclaredVariables(node)\n const hasReassignment = declaredVars.some((variable: ASTNode) =>\n variable.references.some((ref: ASTNode) => ref.isWrite() && ref.identifier !== variable.defs[0]?.name),\n )\n\n if (!hasReassignment) {\n // Safe to autofix — no reassignment detected\n context.report({\n node,\n messageId: \"noLet\",\n fix(fixer: Rule.RuleFixer) {\n const sourceCode = context.sourceCode\n const firstToken = sourceCode.getFirstToken(node)\n if (firstToken && firstToken.value === \"let\") {\n return fixer.replaceText(firstToken, \"const\")\n }\n return null\n },\n })\n } else {\n // Reassignment found — warn only, with suggestion\n context.report({\n node,\n messageId: \"noLet\",\n suggest: [\n {\n messageId: \"suggestConst\",\n fix(fixer: Rule.RuleFixer) {\n const sourceCode = context.sourceCode\n const firstToken = sourceCode.getFirstToken(node)\n if (firstToken && firstToken.value === \"let\") {\n return fixer.replaceText(firstToken, \"const\")\n }\n return null\n },\n },\n ],\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,QAAS,OACT,eAAgB,GAChB,KAAM,CACJ,YAAa,iDACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,MAAO,iDACP,aAAc,yBACf,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACX,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,MAAO,CACL,oBAAoB,EAAe,CAC7B,EAAK,OAAS,QACd,GAAgB,GAAc,GAEb,EAAQ,WAAW,qBAAqB,EACzB,CAAC,KAAM,GACzC,EAAS,WAAW,KAAM,GAAiB,EAAI,SAAS,EAAI,EAAI,aAAe,EAAS,KAAK,IAAI,KAAK,CAGpF,CAgBlB,EAAQ,OAAO,CACb,OACA,UAAW,QACX,QAAS,CACP,CACE,UAAW,eACX,IAAI,EAAuB,CAEzB,IAAM,EADa,EAAQ,WACG,cAAc,EAAK,CAIjD,OAHI,GAAc,EAAW,QAAU,MAC9B,EAAM,YAAY,EAAY,QAAQ,CAExC,MAEV,CACF,CACF,CAAC,CA9BF,EAAQ,OAAO,CACb,OACA,UAAW,QACX,IAAI,EAAuB,CAEzB,IAAM,EADa,EAAQ,WACG,cAAc,EAAK,CAIjD,OAHI,GAAc,EAAW,QAAU,MAC9B,EAAM,YAAY,EAAY,QAAQ,CAExC,MAEV,CAAC,IAsBP,EAEJ"}
1
+ {"version":3,"file":"no-let.js","names":[],"sources":["../../src/rules/no-let.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 fixable: \"code\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer const over let — use immutable bindings\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noLet: \"Prefer const over let — use immutable bindings\",\n suggestConst: \"Replace let with const\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\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 return {\n VariableDeclaration(node: ASTNode) {\n if (node.kind !== \"let\") return\n if (allowInTests && isInTestFile()) return\n\n const declaredVars = context.sourceCode.getDeclaredVariables(node)\n const hasReassignment = declaredVars.some((variable: ASTNode) =>\n variable.references.some((ref: ASTNode) => ref.isWrite() && ref.identifier !== variable.defs[0]?.name),\n )\n\n if (!hasReassignment) {\n // Safe to autofix — no reassignment detected\n context.report({\n node,\n messageId: \"noLet\",\n fix(fixer: Rule.RuleFixer) {\n const sourceCode = context.sourceCode\n const firstToken = sourceCode.getFirstToken(node)\n if (firstToken && firstToken.value === \"let\") {\n return fixer.replaceText(firstToken, \"const\")\n }\n return null\n },\n })\n } else {\n // Reassignment found — warn only, with suggestion\n context.report({\n node,\n messageId: \"noLet\",\n suggest: [\n {\n messageId: \"suggestConst\",\n fix(fixer: Rule.RuleFixer) {\n const sourceCode = context.sourceCode\n const firstToken = sourceCode.getFirstToken(node)\n if (firstToken && firstToken.value === \"let\") {\n return fixer.replaceText(firstToken, \"const\")\n }\n return null\n },\n },\n ],\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,QAAS,OACT,eAAgB,GAChB,KAAM,CACJ,YAAa,iDACb,YAAa,EACf,EACA,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,EACX,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,MAAO,iDACP,aAAc,wBAChB,CACF,EAEA,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,CAAC,GACV,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,MAAO,CACL,oBAAoB,EAAe,CAC7B,EAAK,OAAS,QACd,GAAgB,EAAa,IAEZ,EAAQ,WAAW,qBAAqB,CAC1B,EAAE,KAAM,GACzC,EAAS,WAAW,KAAM,GAAiB,EAAI,QAAQ,GAAK,EAAI,aAAe,EAAS,KAAK,IAAI,IAAI,CAGpF,EAgBjB,EAAQ,OAAO,CACb,OACA,UAAW,QACX,QAAS,CACP,CACE,UAAW,eACX,IAAI,EAAuB,CAEzB,IAAM,EADa,EAAQ,WACG,cAAc,CAAI,EAIhD,OAHI,GAAc,EAAW,QAAU,MAC9B,EAAM,YAAY,EAAY,OAAO,EAEvC,IACT,CACF,CACF,CACF,CAAC,EA9BD,EAAQ,OAAO,CACb,OACA,UAAW,QACX,IAAI,EAAuB,CAEzB,IAAM,EADa,EAAQ,WACG,cAAc,CAAI,EAIhD,OAHI,GAAc,EAAW,QAAU,MAC9B,EAAM,YAAY,EAAY,OAAO,EAEvC,IACT,CACF,CAAC,GAqBL,CACF,CACF,CACF"}
@@ -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 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 +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\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 +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 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 true\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 return true\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":"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"}
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 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 true\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 return true\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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-fold.js","names":[],"sources":["../../src/rules/prefer-fold.ts"],"sourcesContent":["import type { Rule, SourceCode } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer .fold() over if/else chains when working with monadic types\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n minComplexity: {\n type: \"integer\",\n minimum: 1,\n default: 2,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFold: \"Prefer .fold() over if/else when working with {{type}} types\",\n preferFoldTernary: \"Consider using .fold() instead of ternary operator for {{type}}\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const minComplexity = options.minComplexity || 2\n\n function extractBodyForFold(node: ASTNode, sourceCode: SourceCode): string {\n if (node.type === \"BlockStatement\") {\n const statements = node.body\n if (statements.length === 1 && statements[0].type === \"ReturnStatement\") {\n // Extract just the return value, not the return statement\n return sourceCode.getText(statements[0].argument)\n } else {\n // For complex blocks, keep the full structure but remove outer braces\n return sourceCode.getText(node).slice(1, -1).trim()\n }\n } else {\n return sourceCode.getText(node)\n }\n }\n\n function replaceGetWithValue(body: string, monadicObj: string): string {\n // Replace monadicObj.get() with value, and monadicObj.get().chain with value.chain\n const escaped = monadicObj.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n return body.replace(new RegExp(`${escaped}\\\\.get\\\\(\\\\)`, \"g\"), \"value\")\n }\n\n function generateFoldFromIf(node: ASTNode): string | null {\n const sourceCode = context.sourceCode\n\n if (node.type !== \"IfStatement\") return null\n\n const test = node.test\n const consequent = node.consequent\n const alternate = node.alternate\n\n if (!consequent || !alternate) return null\n\n // Extract the monadic object from the test\n let monadicObj: string | null = null\n let isNegated = false\n\n // Handle patterns like option.isSome(), either.isLeft(), etc.\n if (test.type === \"CallExpression\" && test.callee.type === \"MemberExpression\") {\n const methodName = test.callee.property.name\n monadicObj = sourceCode.getText(test.callee.object)\n\n if (methodName === \"isSome\" || methodName === \"isRight\" || methodName === \"isSuccess\") {\n isNegated = false\n } else if (\n methodName === \"isNone\" ||\n methodName === \"isEmpty\" ||\n methodName === \"isLeft\" ||\n methodName === \"isFailure\"\n ) {\n isNegated = true\n }\n }\n\n if (!monadicObj) return null\n\n // Only handle simple cases - single return statements in blocks, no nested if statements\n if (consequent.type === \"BlockStatement\") {\n if (consequent.body.length !== 1 || consequent.body[0].type !== \"ReturnStatement\") {\n return null // Too complex, don't auto-fix\n }\n }\n\n if (alternate.type === \"BlockStatement\") {\n if (alternate.body.length !== 1 || alternate.body[0].type !== \"ReturnStatement\") {\n return null // Too complex, don't auto-fix\n }\n } else if (alternate.type === \"IfStatement\") {\n return null // Nested if/else is too complex for simple fold pattern\n }\n\n // Extract consequent and alternate bodies\n const thenBody = extractBodyForFold(consequent, sourceCode)\n const elseBody = extractBodyForFold(alternate, sourceCode)\n\n // Generate fold expression — replace .get() calls with value parameter\n if (isNegated) {\n // isNone/isLeft/isFailure: consequent is the \"none\" branch, alternate is the \"some\" branch\n const successBody = replaceGetWithValue(elseBody, monadicObj)\n return `${monadicObj}.fold(() => ${thenBody}, (value) => ${successBody})`\n } else {\n // isSome/isRight/isSuccess: consequent is the \"some\" branch, alternate is the \"none\" branch\n const successBody = replaceGetWithValue(thenBody, monadicObj)\n return `${monadicObj}.fold(() => ${elseBody}, (value) => ${successBody})`\n }\n }\n\n function generateFoldFromTernary(node: ASTNode): string | null {\n const sourceCode = context.sourceCode\n\n if (node.type !== \"ConditionalExpression\") return null\n\n const test = node.test\n const consequent = node.consequent\n const alternate = node.alternate\n\n // Extract the monadic object from the test\n let monadicObj: string | null = null\n let isNegated = false\n\n if (test.type === \"CallExpression\" && test.callee.type === \"MemberExpression\") {\n const methodName = test.callee.property.name\n monadicObj = sourceCode.getText(test.callee.object)\n\n if (methodName === \"isSome\" || methodName === \"isRight\" || methodName === \"isSuccess\") {\n isNegated = false\n } else if (\n methodName === \"isNone\" ||\n methodName === \"isEmpty\" ||\n methodName === \"isLeft\" ||\n methodName === \"isFailure\"\n ) {\n isNegated = true\n }\n }\n\n if (!monadicObj) return null\n\n const thenExpr = sourceCode.getText(consequent)\n const elseExpr = sourceCode.getText(alternate)\n\n // Generate fold expression — replace .get() calls with value parameter\n if (isNegated) {\n const successExpr = replaceGetWithValue(elseExpr, monadicObj)\n return `${monadicObj}.fold(() => ${thenExpr}, (value) => ${successExpr})`\n } else {\n const successExpr = replaceGetWithValue(thenExpr, monadicObj)\n return `${monadicObj}.fold(() => ${elseExpr}, (value) => ${successExpr})`\n }\n }\n\n function shouldAutoFix(node: ASTNode): boolean {\n // Only auto-fix when we detect functype method calls (indicating it's already a functype instance)\n if (node.type === \"CallExpression\" && node.callee.type === \"MemberExpression\") {\n const methodName = node.callee.property.name\n // These methods indicate the object is already a functype instance\n return [\"isSome\", \"isNone\", \"isEmpty\", \"isRight\", \"isLeft\", \"isSuccess\", \"isFailure\"].includes(methodName)\n }\n return false\n }\n\n function isMonadicCheck(node: ASTNode): { isMonadic: boolean; type: string } {\n const sourceCode = context.sourceCode\n const text = sourceCode.getText(node)\n\n // Check for common monadic type checks\n if (/\\.(isSome|isNone|isEmpty|isDefined)\\s*\\(\\s*\\)/.test(text)) {\n return { isMonadic: true, type: \"Option\" }\n }\n\n if (/\\.(isLeft|isRight)\\s*\\(\\s*\\)/.test(text)) {\n return { isMonadic: true, type: \"Either\" }\n }\n\n if (/\\.(isSuccess|isFailure)\\s*\\(\\s*\\)/.test(text)) {\n return { isMonadic: true, type: \"Result\" }\n }\n\n // Check for null/undefined checks on variables that might be Options\n if (node.type === \"BinaryExpression\") {\n if (\n (node.operator === \"===\" || node.operator === \"!==\" || node.operator === \"==\" || node.operator === \"!=\") &&\n ((node.left.type === \"Literal\" && (node.left.value === null || node.left.value === undefined)) ||\n (node.right.type === \"Literal\" && (node.right.value === null || node.right.value === undefined)))\n ) {\n return { isMonadic: true, type: \"Option\" }\n }\n\n // Check for === or == with undefined identifier\n if (node.operator === \"==\" || node.operator === \"!=\" || node.operator === \"===\" || node.operator === \"!==\") {\n const leftIsUndefined = node.left.type === \"Identifier\" && node.left.name === \"undefined\"\n const rightIsUndefined = node.right.type === \"Identifier\" && node.right.name === \"undefined\"\n\n if (leftIsUndefined || rightIsUndefined) {\n return { isMonadic: true, type: \"Option\" }\n }\n }\n }\n\n return { isMonadic: false, type: \"\" }\n }\n\n function analyzeIfStatement(node: ASTNode) {\n const test = node.test\n const monadicInfo = isMonadicCheck(test)\n\n if (!monadicInfo.isMonadic) return\n\n // Don't analyze if this is part of a larger if/else chain\n // (only analyze the outermost if statement)\n if (node.parent && node.parent.type === \"IfStatement\") return\n\n // Count the complexity (if/else if/else chain)\n let complexity = 1\n let current = node\n while (current.alternate) {\n complexity++\n if (current.alternate.type === \"IfStatement\") {\n current = current.alternate\n } else {\n break\n }\n }\n\n if (complexity >= minComplexity) {\n context.report({\n node,\n messageId: \"preferFold\",\n data: { type: monadicInfo.type },\n fix(fixer) {\n // Only auto-fix if we can detect it's already a functype instance\n if (!shouldAutoFix(node.test)) {\n return null\n }\n const replacement = generateFoldFromIf(node)\n if (replacement) {\n return fixer.replaceText(node, replacement)\n }\n return null\n },\n })\n }\n }\n\n return {\n IfStatement(node: ASTNode) {\n analyzeIfStatement(node)\n },\n\n ConditionalExpression(node: ASTNode) {\n const monadicInfo = isMonadicCheck(node.test)\n if (monadicInfo.isMonadic) {\n context.report({\n node,\n messageId: \"preferFoldTernary\",\n data: { type: monadicInfo.type },\n fix(fixer) {\n // Only auto-fix if we can detect it's already a functype instance\n if (!shouldAutoFix(node.test)) {\n return null\n }\n const replacement = generateFoldFromTernary(node)\n if (replacement) {\n return fixer.replaceText(node, replacement)\n }\n return null\n },\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
1
+ {"version":3,"file":"prefer-fold.js","names":[],"sources":["../../src/rules/prefer-fold.ts"],"sourcesContent":["import type { Rule, SourceCode } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer .fold() over if/else chains when working with monadic types\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n minComplexity: {\n type: \"integer\",\n minimum: 1,\n default: 2,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFold: \"Prefer .fold() over if/else when working with {{type}} types\",\n preferFoldTernary: \"Consider using .fold() instead of ternary operator for {{type}}\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const minComplexity = options.minComplexity || 2\n\n function extractBodyForFold(node: ASTNode, sourceCode: SourceCode): string {\n if (node.type === \"BlockStatement\") {\n const statements = node.body\n if (statements.length === 1 && statements[0].type === \"ReturnStatement\") {\n // Extract just the return value, not the return statement\n return sourceCode.getText(statements[0].argument)\n } else {\n // For complex blocks, keep the full structure but remove outer braces\n return sourceCode.getText(node).slice(1, -1).trim()\n }\n } else {\n return sourceCode.getText(node)\n }\n }\n\n function replaceGetWithValue(body: string, monadicObj: string): string {\n // Replace monadicObj.get() with value, and monadicObj.get().chain with value.chain\n const escaped = monadicObj.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n return body.replace(new RegExp(`${escaped}\\\\.get\\\\(\\\\)`, \"g\"), \"value\")\n }\n\n function generateFoldFromIf(node: ASTNode): string | null {\n const sourceCode = context.sourceCode\n\n if (node.type !== \"IfStatement\") return null\n\n const test = node.test\n const consequent = node.consequent\n const alternate = node.alternate\n\n if (!consequent || !alternate) return null\n\n // Extract the monadic object from the test\n let monadicObj: string | null = null\n let isNegated = false\n\n // Handle patterns like option.isSome(), either.isLeft(), etc.\n if (test.type === \"CallExpression\" && test.callee.type === \"MemberExpression\") {\n const methodName = test.callee.property.name\n monadicObj = sourceCode.getText(test.callee.object)\n\n if (methodName === \"isSome\" || methodName === \"isRight\" || methodName === \"isSuccess\") {\n isNegated = false\n } else if (\n methodName === \"isNone\" ||\n methodName === \"isEmpty\" ||\n methodName === \"isLeft\" ||\n methodName === \"isFailure\"\n ) {\n isNegated = true\n }\n }\n\n if (!monadicObj) return null\n\n // Only handle simple cases - single return statements in blocks, no nested if statements\n if (consequent.type === \"BlockStatement\") {\n if (consequent.body.length !== 1 || consequent.body[0].type !== \"ReturnStatement\") {\n return null // Too complex, don't auto-fix\n }\n }\n\n if (alternate.type === \"BlockStatement\") {\n if (alternate.body.length !== 1 || alternate.body[0].type !== \"ReturnStatement\") {\n return null // Too complex, don't auto-fix\n }\n } else if (alternate.type === \"IfStatement\") {\n return null // Nested if/else is too complex for simple fold pattern\n }\n\n // Extract consequent and alternate bodies\n const thenBody = extractBodyForFold(consequent, sourceCode)\n const elseBody = extractBodyForFold(alternate, sourceCode)\n\n // Generate fold expression — replace .get() calls with value parameter\n if (isNegated) {\n // isNone/isLeft/isFailure: consequent is the \"none\" branch, alternate is the \"some\" branch\n const successBody = replaceGetWithValue(elseBody, monadicObj)\n return `${monadicObj}.fold(() => ${thenBody}, (value) => ${successBody})`\n } else {\n // isSome/isRight/isSuccess: consequent is the \"some\" branch, alternate is the \"none\" branch\n const successBody = replaceGetWithValue(thenBody, monadicObj)\n return `${monadicObj}.fold(() => ${elseBody}, (value) => ${successBody})`\n }\n }\n\n function generateFoldFromTernary(node: ASTNode): string | null {\n const sourceCode = context.sourceCode\n\n if (node.type !== \"ConditionalExpression\") return null\n\n const test = node.test\n const consequent = node.consequent\n const alternate = node.alternate\n\n // Extract the monadic object from the test\n let monadicObj: string | null = null\n let isNegated = false\n\n if (test.type === \"CallExpression\" && test.callee.type === \"MemberExpression\") {\n const methodName = test.callee.property.name\n monadicObj = sourceCode.getText(test.callee.object)\n\n if (methodName === \"isSome\" || methodName === \"isRight\" || methodName === \"isSuccess\") {\n isNegated = false\n } else if (\n methodName === \"isNone\" ||\n methodName === \"isEmpty\" ||\n methodName === \"isLeft\" ||\n methodName === \"isFailure\"\n ) {\n isNegated = true\n }\n }\n\n if (!monadicObj) return null\n\n const thenExpr = sourceCode.getText(consequent)\n const elseExpr = sourceCode.getText(alternate)\n\n // Generate fold expression — replace .get() calls with value parameter\n if (isNegated) {\n const successExpr = replaceGetWithValue(elseExpr, monadicObj)\n return `${monadicObj}.fold(() => ${thenExpr}, (value) => ${successExpr})`\n } else {\n const successExpr = replaceGetWithValue(thenExpr, monadicObj)\n return `${monadicObj}.fold(() => ${elseExpr}, (value) => ${successExpr})`\n }\n }\n\n function shouldAutoFix(node: ASTNode): boolean {\n // Only auto-fix when we detect functype method calls (indicating it's already a functype instance)\n if (node.type === \"CallExpression\" && node.callee.type === \"MemberExpression\") {\n const methodName = node.callee.property.name\n // These methods indicate the object is already a functype instance\n return [\"isSome\", \"isNone\", \"isEmpty\", \"isRight\", \"isLeft\", \"isSuccess\", \"isFailure\"].includes(methodName)\n }\n return false\n }\n\n function isMonadicCheck(node: ASTNode): { isMonadic: boolean; type: string } {\n const sourceCode = context.sourceCode\n const text = sourceCode.getText(node)\n\n // Check for common monadic type checks\n if (/\\.(isSome|isNone|isEmpty|isDefined)\\s*\\(\\s*\\)/.test(text)) {\n return { isMonadic: true, type: \"Option\" }\n }\n\n if (/\\.(isLeft|isRight)\\s*\\(\\s*\\)/.test(text)) {\n return { isMonadic: true, type: \"Either\" }\n }\n\n if (/\\.(isSuccess|isFailure)\\s*\\(\\s*\\)/.test(text)) {\n return { isMonadic: true, type: \"Result\" }\n }\n\n // Check for null/undefined checks on variables that might be Options\n if (node.type === \"BinaryExpression\") {\n if (\n (node.operator === \"===\" || node.operator === \"!==\" || node.operator === \"==\" || node.operator === \"!=\") &&\n ((node.left.type === \"Literal\" && (node.left.value === null || node.left.value === undefined)) ||\n (node.right.type === \"Literal\" && (node.right.value === null || node.right.value === undefined)))\n ) {\n return { isMonadic: true, type: \"Option\" }\n }\n\n // Check for === or == with undefined identifier\n if (node.operator === \"==\" || node.operator === \"!=\" || node.operator === \"===\" || node.operator === \"!==\") {\n const leftIsUndefined = node.left.type === \"Identifier\" && node.left.name === \"undefined\"\n const rightIsUndefined = node.right.type === \"Identifier\" && node.right.name === \"undefined\"\n\n if (leftIsUndefined || rightIsUndefined) {\n return { isMonadic: true, type: \"Option\" }\n }\n }\n }\n\n return { isMonadic: false, type: \"\" }\n }\n\n function analyzeIfStatement(node: ASTNode) {\n const test = node.test\n const monadicInfo = isMonadicCheck(test)\n\n if (!monadicInfo.isMonadic) return\n\n // Don't analyze if this is part of a larger if/else chain\n // (only analyze the outermost if statement)\n if (node.parent && node.parent.type === \"IfStatement\") return\n\n // Count the complexity (if/else if/else chain)\n let complexity = 1\n let current = node\n while (current.alternate) {\n complexity++\n if (current.alternate.type === \"IfStatement\") {\n current = current.alternate\n } else {\n break\n }\n }\n\n if (complexity >= minComplexity) {\n context.report({\n node,\n messageId: \"preferFold\",\n data: { type: monadicInfo.type },\n fix(fixer) {\n // Only auto-fix if we can detect it's already a functype instance\n if (!shouldAutoFix(node.test)) {\n return null\n }\n const replacement = generateFoldFromIf(node)\n if (replacement) {\n return fixer.replaceText(node, replacement)\n }\n return null\n },\n })\n }\n }\n\n return {\n IfStatement(node: ASTNode) {\n analyzeIfStatement(node)\n },\n\n ConditionalExpression(node: ASTNode) {\n const monadicInfo = isMonadicCheck(node.test)\n if (monadicInfo.isMonadic) {\n context.report({\n node,\n messageId: \"preferFoldTernary\",\n data: { type: monadicInfo.type },\n fix(fixer) {\n // Only auto-fix if we can detect it's already a functype instance\n if (!shouldAutoFix(node.test)) {\n return null\n }\n const replacement = generateFoldFromTernary(node)\n if (replacement) {\n return fixer.replaceText(node, replacement)\n }\n return null\n },\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-functype-map.js","names":[],"sources":["../../src/rules/prefer-functype-map.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype } from \"../utils/functype-detection\"\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 functype Map<K, V> over native Map for immutable key-value collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFunctypeMap: \"Prefer functype Map<{{keyType}}, {{valueType}}> over native Map\",\n preferFunctypeMapLiteral: \"Prefer Map.of(...) or Map.empty() over new Map()\",\n suggestMapEmpty: \"Replace with Map.empty()\",\n suggestMapOf: \"Replace with Map.of(...)\",\n suggestMapFrom: \"Replace with Map(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\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 const functypeImports = getFunctypeImportsLegacy(context)\n\n function isMapImportedFromFunctype(): boolean {\n return hasFunctypeSymbol(context.sourceCode, \"Map\")\n }\n\n return {\n NewExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Only flag `new Map(...)` calls\n if (!node.callee || node.callee.type !== \"Identifier\" || node.callee.name !== \"Map\") return\n\n // Skip if Map is already imported from functype\n if (isMapImportedFromFunctype()) return\n\n // Skip if already using functype\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const args = node.arguments as ASTNode[]\n\n const suggestions: Rule.SuggestionReportDescriptor[] = []\n\n if (args.length === 0) {\n // new Map() → Map.empty()\n suggestions.push({\n messageId: \"suggestMapEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Map.empty()\")\n },\n })\n } else if (args.length === 1 && args[0].type === \"ArrayExpression\") {\n // new Map([[\"a\", 1], [\"b\", 2]]) → Map.of([\"a\", 1], [\"b\", 2])\n const arrayArg = args[0] as ASTNode\n const elements = arrayArg.elements as ASTNode[]\n const tupleTexts = elements.map((el: ASTNode) => sourceCode.getText(el))\n const mapOfArgs = tupleTexts.join(\", \")\n suggestions.push({\n messageId: \"suggestMapOf\",\n fix(fixer) {\n return fixer.replaceText(node, `Map.of(${mapOfArgs})`)\n },\n })\n } else if (args.length === 1) {\n // new Map(someVar) → Map(someVar)\n const argText = sourceCode.getText(args[0])\n suggestions.push({\n messageId: \"suggestMapFrom\",\n fix(fixer) {\n return fixer.replaceText(node, `Map(${argText})`)\n },\n })\n } else {\n // Generic fallback for any other args\n suggestions.push({\n messageId: \"suggestMapEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Map.empty()\")\n },\n })\n }\n\n if (!isMapImportedFromFunctype()) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Map\" },\n fix: createImportFixer(sourceCode, \"Map\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferFunctypeMapLiteral\",\n suggest: suggestions,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (!node.typeName) return\n\n const sourceCode = context.sourceCode\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n if (typeName !== \"Map\") return\n\n // Skip if Map is already imported from functype\n if (isMapImportedFromFunctype()) return\n\n // Extract key/value type params if present (typeArguments for newer TS-ESLint, typeParameters for older)\n let keyType = \"K\"\n let valueType = \"V\"\n const typeParams = node.typeArguments?.params ?? node.typeParameters?.params\n if (typeParams && typeParams.length >= 2) {\n keyType = sourceCode.getText(typeParams[0])\n valueType = sourceCode.getText(typeParams[1])\n } else if (typeParams && typeParams.length === 1) {\n keyType = sourceCode.getText(typeParams[0])\n }\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestAddImport\",\n data: { symbol: \"Map\" },\n fix: createImportFixer(sourceCode, \"Map\"),\n },\n ]\n\n context.report({\n node,\n messageId: \"preferFunctypeMap\",\n data: { keyType, valueType },\n suggest: suggestions,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"0LAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,gFACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,kBAAmB,kEACnB,yBAA0B,mDAC1B,gBAAiB,2BACjB,aAAc,2BACd,eAAgB,wBAChB,iBAAkB,sCACnB,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACX,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,IAAM,EAAkB,EAAyB,EAAQ,CAEzD,SAAS,GAAqC,CAC5C,OAAO,EAAkB,EAAQ,WAAY,MAAM,CAGrD,MAAO,CACL,cAAc,EAAe,CAU3B,GATI,GAAgB,GAAc,EAG9B,CAAC,EAAK,QAAU,EAAK,OAAO,OAAS,cAAgB,EAAK,OAAO,OAAS,OAG1E,GAA2B,EAG3B,EAAuB,EAAM,EAAgB,CAAE,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAO,EAAK,UAEZ,EAAiD,EAAE,CAEzD,GAAI,EAAK,SAAW,EAElB,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,SACO,EAAK,SAAW,GAAK,EAAK,GAAG,OAAS,kBAAmB,CAKlE,IAAM,EAHW,EAAK,GACI,SACE,IAAK,GAAgB,EAAW,QAAQ,EAAG,CAC3C,CAAC,KAAK,KAAK,CACvC,EAAY,KAAK,CACf,UAAW,eACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAU,GAAG,EAEzD,CAAC,SACO,EAAK,SAAW,EAAG,CAE5B,IAAM,EAAU,EAAW,QAAQ,EAAK,GAAG,CAC3C,EAAY,KAAK,CACf,UAAW,iBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,OAAO,EAAQ,GAAG,EAEpD,CAAC,MAGF,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,CAGC,GAA2B,EAC9B,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,2BACX,QAAS,EACV,CAAC,EAGJ,gBAAgB,EAAe,CAG7B,GAFI,GAAgB,GAAc,EAE9B,CAAC,EAAK,SAAU,OAEpB,IAAM,EAAa,EAAQ,WAM3B,IALiB,EAAK,SAAS,OAAS,aAAe,EAAK,SAAS,KAAO,EAAW,QAAQ,EAAK,SAAS,IAE5F,OAGb,GAA2B,CAAE,OAGjC,IAAI,EAAU,IACV,EAAY,IACV,EAAa,EAAK,eAAe,QAAU,EAAK,gBAAgB,OAClE,GAAc,EAAW,QAAU,GACrC,EAAU,EAAW,QAAQ,EAAW,GAAG,CAC3C,EAAY,EAAW,QAAQ,EAAW,GAAG,EACpC,GAAc,EAAW,SAAW,IAC7C,EAAU,EAAW,QAAQ,EAAW,GAAG,EAG7C,IAAM,EAAiD,CACrD,CACE,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CACF,CAED,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,UAAS,YAAW,CAC5B,QAAS,EACV,CAAC,EAEL,EAEJ"}
1
+ {"version":3,"file":"prefer-functype-map.js","names":[],"sources":["../../src/rules/prefer-functype-map.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype } from \"../utils/functype-detection\"\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 functype Map<K, V> over native Map for immutable key-value collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFunctypeMap: \"Prefer functype Map<{{keyType}}, {{valueType}}> over native Map\",\n preferFunctypeMapLiteral: \"Prefer Map.of(...) or Map.empty() over new Map()\",\n suggestMapEmpty: \"Replace with Map.empty()\",\n suggestMapOf: \"Replace with Map.of(...)\",\n suggestMapFrom: \"Replace with Map(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\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 const functypeImports = getFunctypeImportsLegacy(context)\n\n function isMapImportedFromFunctype(): boolean {\n return hasFunctypeSymbol(context.sourceCode, \"Map\")\n }\n\n return {\n NewExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Only flag `new Map(...)` calls\n if (!node.callee || node.callee.type !== \"Identifier\" || node.callee.name !== \"Map\") return\n\n // Skip if Map is already imported from functype\n if (isMapImportedFromFunctype()) return\n\n // Skip if already using functype\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const args = node.arguments as ASTNode[]\n\n const suggestions: Rule.SuggestionReportDescriptor[] = []\n\n if (args.length === 0) {\n // new Map() → Map.empty()\n suggestions.push({\n messageId: \"suggestMapEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Map.empty()\")\n },\n })\n } else if (args.length === 1 && args[0].type === \"ArrayExpression\") {\n // new Map([[\"a\", 1], [\"b\", 2]]) → Map.of([\"a\", 1], [\"b\", 2])\n const arrayArg = args[0] as ASTNode\n const elements = arrayArg.elements as ASTNode[]\n const tupleTexts = elements.map((el: ASTNode) => sourceCode.getText(el))\n const mapOfArgs = tupleTexts.join(\", \")\n suggestions.push({\n messageId: \"suggestMapOf\",\n fix(fixer) {\n return fixer.replaceText(node, `Map.of(${mapOfArgs})`)\n },\n })\n } else if (args.length === 1) {\n // new Map(someVar) → Map(someVar)\n const argText = sourceCode.getText(args[0])\n suggestions.push({\n messageId: \"suggestMapFrom\",\n fix(fixer) {\n return fixer.replaceText(node, `Map(${argText})`)\n },\n })\n } else {\n // Generic fallback for any other args\n suggestions.push({\n messageId: \"suggestMapEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Map.empty()\")\n },\n })\n }\n\n if (!isMapImportedFromFunctype()) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Map\" },\n fix: createImportFixer(sourceCode, \"Map\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferFunctypeMapLiteral\",\n suggest: suggestions,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (!node.typeName) return\n\n const sourceCode = context.sourceCode\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n if (typeName !== \"Map\") return\n\n // Skip if Map is already imported from functype\n if (isMapImportedFromFunctype()) return\n\n // Extract key/value type params if present (typeArguments for newer TS-ESLint, typeParameters for older)\n let keyType = \"K\"\n let valueType = \"V\"\n const typeParams = node.typeArguments?.params ?? node.typeParameters?.params\n if (typeParams && typeParams.length >= 2) {\n keyType = sourceCode.getText(typeParams[0])\n valueType = sourceCode.getText(typeParams[1])\n } else if (typeParams && typeParams.length === 1) {\n keyType = sourceCode.getText(typeParams[0])\n }\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestAddImport\",\n data: { symbol: \"Map\" },\n fix: createImportFixer(sourceCode, \"Map\"),\n },\n ]\n\n context.report({\n node,\n messageId: \"preferFunctypeMap\",\n data: { keyType, valueType },\n suggest: suggestions,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"0LAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,gFACb,YAAa,EACf,EACA,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,EACX,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,kBAAmB,kEACnB,yBAA0B,mDAC1B,gBAAiB,2BACjB,aAAc,2BACd,eAAgB,wBAChB,iBAAkB,qCACpB,CACF,EAEA,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,CAAC,GACV,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,IAAM,EAAkB,EAAyB,CAAO,EAExD,SAAS,GAAqC,CAC5C,OAAO,EAAkB,EAAQ,WAAY,KAAK,CACpD,CAEA,MAAO,CACL,cAAc,EAAe,CAU3B,GATI,GAAgB,EAAa,GAG7B,CAAC,EAAK,QAAU,EAAK,OAAO,OAAS,cAAgB,EAAK,OAAO,OAAS,OAG1E,EAA0B,GAG1B,EAAuB,EAAM,CAAe,EAAG,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAO,EAAK,UAEZ,EAAiD,CAAC,EAExD,GAAI,EAAK,SAAW,EAElB,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,aAAa,CAC9C,CACF,CAAC,OACI,GAAI,EAAK,SAAW,GAAK,EAAK,GAAG,OAAS,kBAAmB,CAKlE,IAAM,EAHW,EAAK,GACI,SACE,IAAK,GAAgB,EAAW,QAAQ,CAAE,CAC3C,EAAE,KAAK,IAAI,EACtC,EAAY,KAAK,CACf,UAAW,eACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAU,EAAE,CACvD,CACF,CAAC,CACH,MAAO,GAAI,EAAK,SAAW,EAAG,CAE5B,IAAM,EAAU,EAAW,QAAQ,EAAK,EAAE,EAC1C,EAAY,KAAK,CACf,UAAW,iBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,OAAO,EAAQ,EAAE,CAClD,CACF,CAAC,CACH,MAEE,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,aAAa,CAC9C,CACF,CAAC,EAGE,EAA0B,GAC7B,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,KAAM,EACtB,IAAK,EAAkB,EAAY,KAAK,CAC1C,CAAC,EAGH,EAAQ,OAAO,CACb,OACA,UAAW,2BACX,QAAS,CACX,CAAC,CACH,EAEA,gBAAgB,EAAe,CAG7B,GAFI,GAAgB,EAAa,GAE7B,CAAC,EAAK,SAAU,OAEpB,IAAM,EAAa,EAAQ,WAM3B,IALiB,EAAK,SAAS,OAAS,aAAe,EAAK,SAAS,KAAO,EAAW,QAAQ,EAAK,QAAQ,KAE3F,OAGb,EAA0B,EAAG,OAGjC,IAAI,EAAU,IACV,EAAY,IACV,EAAa,EAAK,eAAe,QAAU,EAAK,gBAAgB,OAClE,GAAc,EAAW,QAAU,GACrC,EAAU,EAAW,QAAQ,EAAW,EAAE,EAC1C,EAAY,EAAW,QAAQ,EAAW,EAAE,GACnC,GAAc,EAAW,SAAW,IAC7C,EAAU,EAAW,QAAQ,EAAW,EAAE,GAG5C,IAAM,EAAiD,CACrD,CACE,UAAW,mBACX,KAAM,CAAE,OAAQ,KAAM,EACtB,IAAK,EAAkB,EAAY,KAAK,CAC1C,CACF,EAEA,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,UAAS,WAAU,EAC3B,QAAS,CACX,CAAC,CACH,CACF,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-functype-set.js","names":[],"sources":["../../src/rules/prefer-functype-set.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype } from \"../utils/functype-detection\"\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 functype Set<T> over native Set for immutable collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFunctypeSet: \"Prefer functype Set<{{type}}> over native Set\",\n preferFunctypeSetLiteral: \"Prefer Set.of(...) or Set.empty() over new Set()\",\n suggestSetEmpty: \"Replace with Set.empty()\",\n suggestSetOf: \"Replace with Set.of(...)\",\n suggestSetFrom: \"Replace with Set(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\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 const functypeImports = getFunctypeImportsLegacy(context)\n\n function isSetImportedFromFunctype() {\n return hasFunctypeSymbol(context.sourceCode, \"Set\")\n }\n\n return {\n NewExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Only handle `new Set(...)` calls\n if (!node.callee || node.callee.type !== \"Identifier\" || node.callee.name !== \"Set\") return\n\n // Skip if Set is already imported from functype\n if (isSetImportedFromFunctype()) return\n\n // Skip if already in a functype context\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const args = node.arguments || []\n const suggestions: Rule.SuggestionReportDescriptor[] = []\n\n if (args.length === 0) {\n // new Set() → Set.empty()\n suggestions.push({\n messageId: \"suggestSetEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Set.empty()\")\n },\n })\n } else if (args.length === 1 && args[0].type === \"ArrayExpression\") {\n // new Set([\"a\", \"b\", \"c\"]) → Set.of(\"a\", \"b\", \"c\")\n const arrayNode = args[0]\n const elements = arrayNode.elements || []\n const elementTexts = elements.map((el: ASTNode) => sourceCode.getText(el))\n const argsText = elementTexts.join(\", \")\n suggestions.push({\n messageId: \"suggestSetOf\",\n fix(fixer) {\n return fixer.replaceText(node, `Set.of(${argsText})`)\n },\n })\n } else if (args.length === 1) {\n // new Set(someVar) → Set(someVar)\n const argText = sourceCode.getText(args[0])\n suggestions.push({\n messageId: \"suggestSetFrom\",\n fix(fixer) {\n return fixer.replaceText(node, `Set(${argText})`)\n },\n })\n } else {\n // Fallback for unexpected cases\n suggestions.push({\n messageId: \"suggestSetEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Set.empty()\")\n },\n })\n }\n\n if (!isSetImportedFromFunctype()) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Set\" },\n fix: createImportFixer(sourceCode, \"Set\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferFunctypeSetLiteral\",\n suggest: suggestions,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (!node.typeName) return\n\n const sourceCode = context.sourceCode\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n if (typeName !== \"Set\") return\n\n // Skip if Set is already imported from functype\n if (isSetImportedFromFunctype()) return\n\n // Extract type parameter if present\n let typeParam = \"T\"\n if (node.typeParameters?.params?.[0]) {\n typeParam = sourceCode.getText(node.typeParameters.params[0])\n } else if (node.typeArguments?.params?.[0]) {\n typeParam = sourceCode.getText(node.typeArguments.params[0])\n }\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestAddImport\",\n data: { symbol: \"Set\" },\n fix: createImportFixer(sourceCode, \"Set\"),\n },\n ]\n\n context.report({\n node,\n messageId: \"preferFunctypeSet\",\n data: { type: typeParam },\n suggest: suggestions,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"0LAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,mEACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,kBAAmB,gDACnB,yBAA0B,mDAC1B,gBAAiB,2BACjB,aAAc,2BACd,eAAgB,wBAChB,iBAAkB,sCACnB,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACX,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,IAAM,EAAkB,EAAyB,EAAQ,CAEzD,SAAS,GAA4B,CACnC,OAAO,EAAkB,EAAQ,WAAY,MAAM,CAGrD,MAAO,CACL,cAAc,EAAe,CAU3B,GATI,GAAgB,GAAc,EAG9B,CAAC,EAAK,QAAU,EAAK,OAAO,OAAS,cAAgB,EAAK,OAAO,OAAS,OAG1E,GAA2B,EAG3B,EAAuB,EAAM,EAAgB,CAAE,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAO,EAAK,WAAa,EAAE,CAC3B,EAAiD,EAAE,CAEzD,GAAI,EAAK,SAAW,EAElB,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,SACO,EAAK,SAAW,GAAK,EAAK,GAAG,OAAS,kBAAmB,CAKlE,IAAM,GAHY,EAAK,GACI,UAAY,EAAE,EACX,IAAK,GAAgB,EAAW,QAAQ,EAAG,CAC5C,CAAC,KAAK,KAAK,CACxC,EAAY,KAAK,CACf,UAAW,eACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAS,GAAG,EAExD,CAAC,SACO,EAAK,SAAW,EAAG,CAE5B,IAAM,EAAU,EAAW,QAAQ,EAAK,GAAG,CAC3C,EAAY,KAAK,CACf,UAAW,iBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,OAAO,EAAQ,GAAG,EAEpD,CAAC,MAGF,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,CAGC,GAA2B,EAC9B,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,2BACX,QAAS,EACV,CAAC,EAGJ,gBAAgB,EAAe,CAG7B,GAFI,GAAgB,GAAc,EAE9B,CAAC,EAAK,SAAU,OAEpB,IAAM,EAAa,EAAQ,WAM3B,IALiB,EAAK,SAAS,OAAS,aAAe,EAAK,SAAS,KAAO,EAAW,QAAQ,EAAK,SAAS,IAE5F,OAGb,GAA2B,CAAE,OAGjC,IAAI,EAAY,IACZ,EAAK,gBAAgB,SAAS,GAChC,EAAY,EAAW,QAAQ,EAAK,eAAe,OAAO,GAAG,CACpD,EAAK,eAAe,SAAS,KACtC,EAAY,EAAW,QAAQ,EAAK,cAAc,OAAO,GAAG,EAG9D,IAAM,EAAiD,CACrD,CACE,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CACF,CAED,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,KAAM,EAAW,CACzB,QAAS,EACV,CAAC,EAEL,EAEJ"}
1
+ {"version":3,"file":"prefer-functype-set.js","names":[],"sources":["../../src/rules/prefer-functype-set.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype } from \"../utils/functype-detection\"\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 functype Set<T> over native Set for immutable collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFunctypeSet: \"Prefer functype Set<{{type}}> over native Set\",\n preferFunctypeSetLiteral: \"Prefer Set.of(...) or Set.empty() over new Set()\",\n suggestSetEmpty: \"Replace with Set.empty()\",\n suggestSetOf: \"Replace with Set.of(...)\",\n suggestSetFrom: \"Replace with Set(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\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 const functypeImports = getFunctypeImportsLegacy(context)\n\n function isSetImportedFromFunctype() {\n return hasFunctypeSymbol(context.sourceCode, \"Set\")\n }\n\n return {\n NewExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Only handle `new Set(...)` calls\n if (!node.callee || node.callee.type !== \"Identifier\" || node.callee.name !== \"Set\") return\n\n // Skip if Set is already imported from functype\n if (isSetImportedFromFunctype()) return\n\n // Skip if already in a functype context\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const args = node.arguments || []\n const suggestions: Rule.SuggestionReportDescriptor[] = []\n\n if (args.length === 0) {\n // new Set() → Set.empty()\n suggestions.push({\n messageId: \"suggestSetEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Set.empty()\")\n },\n })\n } else if (args.length === 1 && args[0].type === \"ArrayExpression\") {\n // new Set([\"a\", \"b\", \"c\"]) → Set.of(\"a\", \"b\", \"c\")\n const arrayNode = args[0]\n const elements = arrayNode.elements || []\n const elementTexts = elements.map((el: ASTNode) => sourceCode.getText(el))\n const argsText = elementTexts.join(\", \")\n suggestions.push({\n messageId: \"suggestSetOf\",\n fix(fixer) {\n return fixer.replaceText(node, `Set.of(${argsText})`)\n },\n })\n } else if (args.length === 1) {\n // new Set(someVar) → Set(someVar)\n const argText = sourceCode.getText(args[0])\n suggestions.push({\n messageId: \"suggestSetFrom\",\n fix(fixer) {\n return fixer.replaceText(node, `Set(${argText})`)\n },\n })\n } else {\n // Fallback for unexpected cases\n suggestions.push({\n messageId: \"suggestSetEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Set.empty()\")\n },\n })\n }\n\n if (!isSetImportedFromFunctype()) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Set\" },\n fix: createImportFixer(sourceCode, \"Set\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferFunctypeSetLiteral\",\n suggest: suggestions,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (!node.typeName) return\n\n const sourceCode = context.sourceCode\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n if (typeName !== \"Set\") return\n\n // Skip if Set is already imported from functype\n if (isSetImportedFromFunctype()) return\n\n // Extract type parameter if present\n let typeParam = \"T\"\n if (node.typeParameters?.params?.[0]) {\n typeParam = sourceCode.getText(node.typeParameters.params[0])\n } else if (node.typeArguments?.params?.[0]) {\n typeParam = sourceCode.getText(node.typeArguments.params[0])\n }\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestAddImport\",\n data: { symbol: \"Set\" },\n fix: createImportFixer(sourceCode, \"Set\"),\n },\n ]\n\n context.report({\n node,\n messageId: \"preferFunctypeSet\",\n data: { type: typeParam },\n suggest: suggestions,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-list.js","names":[],"sources":["../../src/rules/prefer-list.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isFunctypeCall } from \"../utils/functype-detection\"\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 List<T> over native arrays for immutable collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowArraysInTests: {\n type: \"boolean\",\n default: true,\n },\n allowReadonlyArrays: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferList: \"Prefer List<{{type}}> over array type {{arrayType}}\",\n preferListLiteral: \"Prefer List.of(...) or List.from([...]) over array literal\",\n suggestListType: \"Replace with List<{{type}}>\",\n suggestListOf: \"Replace with List.of(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowArraysInTests = options.allowArraysInTests !== false\n const allowReadonlyArrays = options.allowReadonlyArrays !== false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\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 findTypeParameter(node: ASTNode, sourceCode: typeof context.sourceCode): string | null {\n // Look for TSTypeParameterInstantiation child\n function findInNode(n: ASTNode): string | null {\n if (n.type === \"TSTypeParameterInstantiation\" && n.params && n.params[0]) {\n return sourceCode.getText(n.params[0])\n }\n\n // Recursively search child nodes\n for (const key in n) {\n if (key === \"parent\") continue\n const child = n[key]\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && item.type) {\n const result = findInNode(item)\n if (result) return result\n }\n }\n } else if (child && typeof child === \"object\" && child.type) {\n const result = findInNode(child)\n if (result) return result\n }\n }\n\n return null\n }\n\n return findInNode(node)\n }\n\n return {\n TSArrayType(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n const elementType = sourceCode.getText(node.elementType)\n const fullType = sourceCode.getText(node)\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: elementType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${elementType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: elementType,\n arrayType: fullType,\n },\n suggest,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n\n // Get type name - handle both simple identifiers and member expressions\n if (!node.typeName) return // No type name found\n\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n // Handle Array<T> syntax\n if (typeName === \"Array\") {\n // Look for type parameters in child nodes\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n\n // Skip if already readonly\n if (allowReadonlyArrays && fullType.startsWith(\"readonly\")) return\n\n const resolvedType = typeParam || \"T\"\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: resolvedType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${resolvedType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: resolvedType,\n arrayType: fullType,\n },\n suggest,\n })\n }\n\n // Handle ReadonlyArray<T> - suggest List even if allowing readonly arrays\n if (typeName === \"ReadonlyArray\") {\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n const resolvedType = typeParam || \"T\"\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: resolvedType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${resolvedType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: resolvedType,\n arrayType: fullType,\n },\n suggest,\n })\n }\n },\n\n ArrayExpression(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n // Only flag non-empty arrays to avoid noise\n if (node.elements.length === 0) return\n\n // Don't flag arrays that are already arguments to List.from() or other functype calls\n let parent = node.parent\n if (parent && isFunctypeCall(parent, functypeImports)) {\n return\n }\n\n // Additional specific check for List.from/List.of patterns\n if (\n parent &&\n parent.type === \"CallExpression\" &&\n parent.callee.type === \"MemberExpression\" &&\n parent.callee.object.type === \"Identifier\" &&\n parent.callee.object.name === \"List\" &&\n [\"from\", \"of\"].includes(parent.callee.property.name)\n ) {\n return\n }\n\n // Don't flag nested array literals - only flag the outermost one\n // Check if this array is inside another array literal\n parent = node.parent\n while (parent) {\n if (parent.type === \"ArrayExpression\") {\n return // Skip nested arrays, let the outer one handle it\n }\n parent = parent.parent\n }\n\n // Don't flag array literals that are already part of a type annotation context\n // (those should be handled by the type checking rules)\n let hasTypeAnnotation = false\n parent = node.parent\n while (parent) {\n if (parent.type === \"VariableDeclarator\" && parent.id?.typeAnnotation) {\n // Skip array literal if there's already a type annotation that would be flagged\n hasTypeAnnotation = true\n break\n }\n if (parent.type === \"TSTypeAnnotation\") {\n hasTypeAnnotation = true\n break\n }\n parent = parent.parent\n }\n\n if (hasTypeAnnotation) return\n\n // Check if any element is a SpreadElement — ambiguous semantics, skip suggestions\n const hasSpread = node.elements.some((el) => el !== null && el.type === \"SpreadElement\")\n\n if (hasSpread) {\n context.report({\n node,\n messageId: \"preferListLiteral\",\n })\n return\n }\n\n const sourceCode = context.sourceCode\n const elementTexts = node.elements.filter((el) => el !== null).map((el) => sourceCode.getText(el))\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListOf\",\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List.of(${elementTexts.join(\", \")})`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferListLiteral\",\n suggest,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
1
+ {"version":3,"file":"prefer-list.js","names":[],"sources":["../../src/rules/prefer-list.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isFunctypeCall } from \"../utils/functype-detection\"\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 List<T> over native arrays for immutable collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowArraysInTests: {\n type: \"boolean\",\n default: true,\n },\n allowReadonlyArrays: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferList: \"Prefer List<{{type}}> over array type {{arrayType}}\",\n preferListLiteral: \"Prefer List.of(...) or List.from([...]) over array literal\",\n suggestListType: \"Replace with List<{{type}}>\",\n suggestListOf: \"Replace with List.of(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowArraysInTests = options.allowArraysInTests !== false\n const allowReadonlyArrays = options.allowReadonlyArrays !== false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\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 findTypeParameter(node: ASTNode, sourceCode: typeof context.sourceCode): string | null {\n // Look for TSTypeParameterInstantiation child\n function findInNode(n: ASTNode): string | null {\n if (n.type === \"TSTypeParameterInstantiation\" && n.params && n.params[0]) {\n return sourceCode.getText(n.params[0])\n }\n\n // Recursively search child nodes\n for (const key in n) {\n if (key === \"parent\") continue\n const child = n[key]\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && item.type) {\n const result = findInNode(item)\n if (result) return result\n }\n }\n } else if (child && typeof child === \"object\" && child.type) {\n const result = findInNode(child)\n if (result) return result\n }\n }\n\n return null\n }\n\n return findInNode(node)\n }\n\n return {\n TSArrayType(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n const elementType = sourceCode.getText(node.elementType)\n const fullType = sourceCode.getText(node)\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: elementType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${elementType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: elementType,\n arrayType: fullType,\n },\n suggest,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n\n // Get type name - handle both simple identifiers and member expressions\n if (!node.typeName) return // No type name found\n\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n // Handle Array<T> syntax\n if (typeName === \"Array\") {\n // Look for type parameters in child nodes\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n\n // Skip if already readonly\n if (allowReadonlyArrays && fullType.startsWith(\"readonly\")) return\n\n const resolvedType = typeParam || \"T\"\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: resolvedType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${resolvedType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: resolvedType,\n arrayType: fullType,\n },\n suggest,\n })\n }\n\n // Handle ReadonlyArray<T> - suggest List even if allowing readonly arrays\n if (typeName === \"ReadonlyArray\") {\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n const resolvedType = typeParam || \"T\"\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: resolvedType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${resolvedType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: resolvedType,\n arrayType: fullType,\n },\n suggest,\n })\n }\n },\n\n ArrayExpression(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n // Only flag non-empty arrays to avoid noise\n if (node.elements.length === 0) return\n\n // Don't flag arrays that are already arguments to List.from() or other functype calls\n let parent = node.parent\n if (parent && isFunctypeCall(parent, functypeImports)) {\n return\n }\n\n // Additional specific check for List.from/List.of patterns\n if (\n parent &&\n parent.type === \"CallExpression\" &&\n parent.callee.type === \"MemberExpression\" &&\n parent.callee.object.type === \"Identifier\" &&\n parent.callee.object.name === \"List\" &&\n [\"from\", \"of\"].includes(parent.callee.property.name)\n ) {\n return\n }\n\n // Don't flag nested array literals - only flag the outermost one\n // Check if this array is inside another array literal\n parent = node.parent\n while (parent) {\n if (parent.type === \"ArrayExpression\") {\n return // Skip nested arrays, let the outer one handle it\n }\n parent = parent.parent\n }\n\n // Don't flag array literals that are already part of a type annotation context\n // (those should be handled by the type checking rules)\n let hasTypeAnnotation = false\n parent = node.parent\n while (parent) {\n if (parent.type === \"VariableDeclarator\" && parent.id?.typeAnnotation) {\n // Skip array literal if there's already a type annotation that would be flagged\n hasTypeAnnotation = true\n break\n }\n if (parent.type === \"TSTypeAnnotation\") {\n hasTypeAnnotation = true\n break\n }\n parent = parent.parent\n }\n\n if (hasTypeAnnotation) return\n\n // Check if any element is a SpreadElement — ambiguous semantics, skip suggestions\n const hasSpread = node.elements.some((el) => el !== null && el.type === \"SpreadElement\")\n\n if (hasSpread) {\n context.report({\n node,\n messageId: \"preferListLiteral\",\n })\n return\n }\n\n const sourceCode = context.sourceCode\n const elementTexts = node.elements.filter((el) => el !== null).map((el) => sourceCode.getText(el))\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListOf\",\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List.of(${elementTexts.join(\", \")})`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferListLiteral\",\n suggest,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-map.js","names":[],"sources":["../../src/rules/prefer-map.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 .map() over manual transformations and imperative patterns\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n checkArrayMethods: {\n type: \"boolean\",\n default: true,\n },\n checkForLoops: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferMapOverLoop: \"Prefer .map() over for loop for transforming {{collection}}\",\n preferMapOverPush: \"Prefer .map() over manual .push() in loop\",\n preferMapChain: \"Consider using .map() for transformation instead of manual property access\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const checkArrayMethods = options.checkArrayMethods !== false\n const checkForLoops = options.checkForLoops !== false\n\n function isForEachToMapSafe(node: ASTNode): boolean {\n // Only auto-fix simple forEach → map transformations on expressions that return values\n // This is safe because both native arrays and functype Lists have these methods\n if (\n node.type !== \"CallExpression\" ||\n node.callee.type !== \"MemberExpression\" ||\n node.callee.property.name !== \"forEach\"\n ) {\n return false\n }\n\n // Check if the callback returns a value (simple property access pattern)\n const callback = node.arguments[0]\n if (callback && (callback.type === \"ArrowFunctionExpression\" || callback.type === \"FunctionExpression\")) {\n const body = callback.body\n // Simple property access in arrow function (e.g., item => item.name)\n return body.type === \"MemberExpression\"\n }\n return false\n }\n\n function isTransformationLoop(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n\n const statements = node.body.body\n if (statements.length === 0) return false\n\n // Look for patterns like: newArray.push(transform(item))\n return statements.some((stmt: ASTNode) => {\n if (stmt.type === \"ExpressionStatement\" && stmt.expression.type === \"CallExpression\") {\n const call = stmt.expression\n return call.callee.type === \"MemberExpression\" && call.callee.property.name === \"push\"\n }\n return false\n })\n }\n\n function isSimplePropertyAccess(node: ASTNode): boolean {\n // Check for patterns like: items.forEach(item => console.log(item.name))\n // These could often be: items.map(item => item.name)\n if (\n node.type === \"CallExpression\" &&\n node.callee.type === \"MemberExpression\" &&\n node.callee.property.name === \"forEach\"\n ) {\n const callback = node.arguments[0]\n if (callback && (callback.type === \"ArrowFunctionExpression\" || callback.type === \"FunctionExpression\")) {\n const body = callback.body\n // Simple property access in arrow function\n if (body.type === \"MemberExpression\") {\n return true\n }\n }\n }\n return false\n }\n\n return {\n ForStatement(node: ASTNode) {\n if (!checkForLoops) return\n\n if (isTransformationLoop(node)) {\n context.report({\n node,\n messageId: \"preferMapOverLoop\",\n data: { collection: \"array\" },\n })\n }\n },\n\n ForInStatement(node: ASTNode) {\n if (!checkForLoops) return\n\n if (isTransformationLoop(node)) {\n context.report({\n node,\n messageId: \"preferMapOverLoop\",\n data: { collection: \"object\" },\n })\n }\n },\n\n ForOfStatement(node: ASTNode) {\n if (!checkForLoops) return\n\n if (isTransformationLoop(node)) {\n context.report({\n node,\n messageId: \"preferMapOverLoop\",\n data: { collection: \"iterable\" },\n })\n }\n },\n\n CallExpression(node: ASTNode) {\n if (!checkArrayMethods) return\n\n // Check for forEach that could be map\n if (node.callee.type === \"MemberExpression\" && node.callee.property.name === \"forEach\") {\n if (isSimplePropertyAccess(node)) {\n context.report({\n node,\n messageId: \"preferMapChain\",\n fix(fixer) {\n // Only auto-fix safe forEach → map transformations\n if (!isForEachToMapSafe(node)) {\n return null\n }\n\n const sourceCode = context.sourceCode\n const text = sourceCode.getText(node)\n\n // Simple replacement: forEach -> map\n const fixedText = text.replace(/\\.forEach\\s*\\(/g, \".map(\")\n return fixer.replaceText(node, fixedText)\n },\n })\n }\n }\n\n // Check for manual push patterns in callbacks\n if (node.callee.type === \"MemberExpression\" && node.callee.property.name === \"push\") {\n // Check if we're inside a forEach or similar iteration\n let parent = node.parent\n while (parent) {\n if (\n parent.type === \"CallExpression\" &&\n parent.callee.type === \"MemberExpression\" &&\n (parent.callee.property.name === \"forEach\" || parent.callee.property.name === \"for\")\n ) {\n context.report({\n node,\n messageId: \"preferMapOverPush\",\n })\n break\n }\n parent = parent.parent\n }\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,oEACb,YAAa,GACd,CACD,QAAS,OACT,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,kBAAmB,CACjB,KAAM,UACN,QAAS,GACV,CACD,cAAe,CACb,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,kBAAmB,8DACnB,kBAAmB,4CACnB,eAAgB,6EACjB,CACF,CAED,OAAO,EAAS,CACd,IAAM,EAAU,EAAQ,QAAQ,IAAM,EAAE,CAClC,EAAoB,EAAQ,oBAAsB,GAClD,EAAgB,EAAQ,gBAAkB,GAEhD,SAAS,EAAmB,EAAwB,CAGlD,GACE,EAAK,OAAS,kBACd,EAAK,OAAO,OAAS,oBACrB,EAAK,OAAO,SAAS,OAAS,UAE9B,MAAO,GAIT,IAAM,EAAW,EAAK,UAAU,GAMhC,OALI,IAAa,EAAS,OAAS,2BAA6B,EAAS,OAAS,sBACnE,EAAS,KAEV,OAAS,mBAEhB,GAGT,SAAS,EAAqB,EAAwB,CACpD,GAAI,CAAC,EAAK,MAAQ,EAAK,KAAK,OAAS,iBAAkB,MAAO,GAE9D,IAAM,EAAa,EAAK,KAAK,KAI7B,OAHI,EAAW,SAAW,EAAU,GAG7B,EAAW,KAAM,GAAkB,CACxC,GAAI,EAAK,OAAS,uBAAyB,EAAK,WAAW,OAAS,iBAAkB,CACpF,IAAM,EAAO,EAAK,WAClB,OAAO,EAAK,OAAO,OAAS,oBAAsB,EAAK,OAAO,SAAS,OAAS,OAElF,MAAO,IACP,CAGJ,SAAS,EAAuB,EAAwB,CAGtD,GACE,EAAK,OAAS,kBACd,EAAK,OAAO,OAAS,oBACrB,EAAK,OAAO,SAAS,OAAS,UAC9B,CACA,IAAM,EAAW,EAAK,UAAU,GAChC,GAAI,IAAa,EAAS,OAAS,2BAA6B,EAAS,OAAS,uBACnE,EAAS,KAEb,OAAS,mBAChB,MAAO,GAIb,MAAO,GAGT,MAAO,CACL,aAAa,EAAe,CACrB,GAED,EAAqB,EAAK,EAC5B,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,WAAY,QAAS,CAC9B,CAAC,EAIN,eAAe,EAAe,CACvB,GAED,EAAqB,EAAK,EAC5B,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,WAAY,SAAU,CAC/B,CAAC,EAIN,eAAe,EAAe,CACvB,GAED,EAAqB,EAAK,EAC5B,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,WAAY,WAAY,CACjC,CAAC,EAIN,eAAe,EAAe,CACvB,OAGD,EAAK,OAAO,OAAS,oBAAsB,EAAK,OAAO,SAAS,OAAS,WACvE,EAAuB,EAAK,EAC9B,EAAQ,OAAO,CACb,OACA,UAAW,iBACX,IAAI,EAAO,CAET,GAAI,CAAC,EAAmB,EAAK,CAC3B,OAAO,KAOT,IAAM,EAJa,EAAQ,WACH,QAAQ,EAGV,CAAC,QAAQ,kBAAmB,QAAQ,CAC1D,OAAO,EAAM,YAAY,EAAM,EAAU,EAE5C,CAAC,CAKF,EAAK,OAAO,OAAS,oBAAsB,EAAK,OAAO,SAAS,OAAS,QAAQ,CAEnF,IAAI,EAAS,EAAK,OAClB,KAAO,GAAQ,CACb,GACE,EAAO,OAAS,kBAChB,EAAO,OAAO,OAAS,qBACtB,EAAO,OAAO,SAAS,OAAS,WAAa,EAAO,OAAO,SAAS,OAAS,OAC9E,CACA,EAAQ,OAAO,CACb,OACA,UAAW,oBACZ,CAAC,CACF,MAEF,EAAS,EAAO,UAIvB,EAEJ"}
1
+ {"version":3,"file":"prefer-map.js","names":[],"sources":["../../src/rules/prefer-map.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 .map() over manual transformations and imperative patterns\",\n recommended: true,\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n checkArrayMethods: {\n type: \"boolean\",\n default: true,\n },\n checkForLoops: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferMapOverLoop: \"Prefer .map() over for loop for transforming {{collection}}\",\n preferMapOverPush: \"Prefer .map() over manual .push() in loop\",\n preferMapChain: \"Consider using .map() for transformation instead of manual property access\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const checkArrayMethods = options.checkArrayMethods !== false\n const checkForLoops = options.checkForLoops !== false\n\n function isForEachToMapSafe(node: ASTNode): boolean {\n // Only auto-fix simple forEach → map transformations on expressions that return values\n // This is safe because both native arrays and functype Lists have these methods\n if (\n node.type !== \"CallExpression\" ||\n node.callee.type !== \"MemberExpression\" ||\n node.callee.property.name !== \"forEach\"\n ) {\n return false\n }\n\n // Check if the callback returns a value (simple property access pattern)\n const callback = node.arguments[0]\n if (callback && (callback.type === \"ArrowFunctionExpression\" || callback.type === \"FunctionExpression\")) {\n const body = callback.body\n // Simple property access in arrow function (e.g., item => item.name)\n return body.type === \"MemberExpression\"\n }\n return false\n }\n\n function isTransformationLoop(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n\n const statements = node.body.body\n if (statements.length === 0) return false\n\n // Look for patterns like: newArray.push(transform(item))\n return statements.some((stmt: ASTNode) => {\n if (stmt.type === \"ExpressionStatement\" && stmt.expression.type === \"CallExpression\") {\n const call = stmt.expression\n return call.callee.type === \"MemberExpression\" && call.callee.property.name === \"push\"\n }\n return false\n })\n }\n\n function isSimplePropertyAccess(node: ASTNode): boolean {\n // Check for patterns like: items.forEach(item => console.log(item.name))\n // These could often be: items.map(item => item.name)\n if (\n node.type === \"CallExpression\" &&\n node.callee.type === \"MemberExpression\" &&\n node.callee.property.name === \"forEach\"\n ) {\n const callback = node.arguments[0]\n if (callback && (callback.type === \"ArrowFunctionExpression\" || callback.type === \"FunctionExpression\")) {\n const body = callback.body\n // Simple property access in arrow function\n if (body.type === \"MemberExpression\") {\n return true\n }\n }\n }\n return false\n }\n\n return {\n ForStatement(node: ASTNode) {\n if (!checkForLoops) return\n\n if (isTransformationLoop(node)) {\n context.report({\n node,\n messageId: \"preferMapOverLoop\",\n data: { collection: \"array\" },\n })\n }\n },\n\n ForInStatement(node: ASTNode) {\n if (!checkForLoops) return\n\n if (isTransformationLoop(node)) {\n context.report({\n node,\n messageId: \"preferMapOverLoop\",\n data: { collection: \"object\" },\n })\n }\n },\n\n ForOfStatement(node: ASTNode) {\n if (!checkForLoops) return\n\n if (isTransformationLoop(node)) {\n context.report({\n node,\n messageId: \"preferMapOverLoop\",\n data: { collection: \"iterable\" },\n })\n }\n },\n\n CallExpression(node: ASTNode) {\n if (!checkArrayMethods) return\n\n // Check for forEach that could be map\n if (node.callee.type === \"MemberExpression\" && node.callee.property.name === \"forEach\") {\n if (isSimplePropertyAccess(node)) {\n context.report({\n node,\n messageId: \"preferMapChain\",\n fix(fixer) {\n // Only auto-fix safe forEach → map transformations\n if (!isForEachToMapSafe(node)) {\n return null\n }\n\n const sourceCode = context.sourceCode\n const text = sourceCode.getText(node)\n\n // Simple replacement: forEach -> map\n const fixedText = text.replace(/\\.forEach\\s*\\(/g, \".map(\")\n return fixer.replaceText(node, fixedText)\n },\n })\n }\n }\n\n // Check for manual push patterns in callbacks\n if (node.callee.type === \"MemberExpression\" && node.callee.property.name === \"push\") {\n // Check if we're inside a forEach or similar iteration\n let parent = node.parent\n while (parent) {\n if (\n parent.type === \"CallExpression\" &&\n parent.callee.type === \"MemberExpression\" &&\n (parent.callee.property.name === \"forEach\" || parent.callee.property.name === \"for\")\n ) {\n context.report({\n node,\n messageId: \"preferMapOverPush\",\n })\n break\n }\n parent = parent.parent\n }\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-option.js","names":[],"sources":["../../src/rules/prefer-option.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype, isFunctypeType } from \"../utils/functype-detection\"\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 Option<T> over nullable types (T | null | undefined)\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowNullableIntersections: {\n type: \"boolean\",\n default: false,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferOption: \"Prefer Option<{{type}}> over nullable type '{{nullable}}'\",\n preferOptionReturn: \"Prefer Option<{{type}}> as return type over nullable '{{nullable}}'\",\n suggestOptionType: \"Replace with Option<{{type}}>\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n // const options = context.options[0] || {}\n // Remove unused variable\n // const _allowNullableIntersections = options.allowNullableIntersections || false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\n\n return {\n TSUnionType(node: ASTNode) {\n if (!node.types || node.types.length < 2) return\n\n const hasNull = node.types.some(\n (type: ASTNode) => type.type === \"TSNullKeyword\" || type.type === \"TSUndefinedKeyword\",\n )\n\n if (!hasNull) return\n\n const nonNullTypes = node.types.filter(\n (type: ASTNode) => type.type !== \"TSNullKeyword\" && type.type !== \"TSUndefinedKeyword\",\n )\n\n if (nonNullTypes.length === 1) {\n const nonNullType = nonNullTypes[0]\n\n // Skip if it's already an Option type or other functype type\n if (isFunctypeType(nonNullType, functypeImports)) return\n\n // Skip if we're already in a functype context\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const nonNullTypeText = sourceCode.getText(nonNullType)\n const fullType = sourceCode.getText(node)\n\n // Skip if it's already an Option type (fallback check)\n if (nonNullTypeText.startsWith(\"Option<\")) return\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestOptionType\",\n data: { type: nonNullTypeText },\n fix(fixer) {\n return fixer.replaceText(node, `Option<${nonNullTypeText}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"Option\")) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Option\" },\n fix: createImportFixer(sourceCode, \"Option\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferOption\",\n data: {\n type: nonNullTypeText,\n nullable: fullType,\n },\n suggest: suggestions,\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"8MAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,8DACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,2BAA4B,CAC1B,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,aAAc,4DACd,mBAAoB,sEACpB,kBAAmB,gCACnB,iBAAkB,sCACnB,CACF,CAED,OAAO,EAAS,CAMd,IAAM,EAAkB,EAAyB,EAAQ,CAEzD,MAAO,CACL,YAAY,EAAe,CAOzB,GANI,CAAC,EAAK,OAAS,EAAK,MAAM,OAAS,GAMnC,CAJY,EAAK,MAAM,KACxB,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,qBAGxD,CAAE,OAEd,IAAM,EAAe,EAAK,MAAM,OAC7B,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,qBACnE,CAED,GAAI,EAAa,SAAW,EAAG,CAC7B,IAAM,EAAc,EAAa,GAMjC,GAHI,EAAe,EAAa,EAAgB,EAG5C,EAAuB,EAAM,EAAgB,CAAE,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAkB,EAAW,QAAQ,EAAY,CACjD,EAAW,EAAW,QAAQ,EAAK,CAGzC,GAAI,EAAgB,WAAW,UAAU,CAAE,OAE3C,IAAM,EAAiD,CACrD,CACE,UAAW,oBACX,KAAM,CAAE,KAAM,EAAiB,CAC/B,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAgB,GAAG,EAE/D,CACF,CAEI,EAAkB,EAAY,SAAS,EAC1C,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,SAAU,CAC1B,IAAK,EAAkB,EAAY,SAAS,CAC7C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,eACX,KAAM,CACJ,KAAM,EACN,SAAU,EACX,CACD,QAAS,EACV,CAAC,GAGP,EAEJ"}
1
+ {"version":3,"file":"prefer-option.js","names":[],"sources":["../../src/rules/prefer-option.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype, isFunctypeType } from \"../utils/functype-detection\"\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 Option<T> over nullable types (T | null | undefined)\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowNullableIntersections: {\n type: \"boolean\",\n default: false,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferOption: \"Prefer Option<{{type}}> over nullable type '{{nullable}}'\",\n preferOptionReturn: \"Prefer Option<{{type}}> as return type over nullable '{{nullable}}'\",\n suggestOptionType: \"Replace with Option<{{type}}>\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n // const options = context.options[0] || {}\n // Remove unused variable\n // const _allowNullableIntersections = options.allowNullableIntersections || false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\n\n return {\n TSUnionType(node: ASTNode) {\n if (!node.types || node.types.length < 2) return\n\n const hasNull = node.types.some(\n (type: ASTNode) => type.type === \"TSNullKeyword\" || type.type === \"TSUndefinedKeyword\",\n )\n\n if (!hasNull) return\n\n const nonNullTypes = node.types.filter(\n (type: ASTNode) => type.type !== \"TSNullKeyword\" && type.type !== \"TSUndefinedKeyword\",\n )\n\n if (nonNullTypes.length === 1) {\n const nonNullType = nonNullTypes[0]\n\n // Skip if it's already an Option type or other functype type\n if (isFunctypeType(nonNullType, functypeImports)) return\n\n // Skip if we're already in a functype context\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const nonNullTypeText = sourceCode.getText(nonNullType)\n const fullType = sourceCode.getText(node)\n\n // Skip if it's already an Option type (fallback check)\n if (nonNullTypeText.startsWith(\"Option<\")) return\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestOptionType\",\n data: { type: nonNullTypeText },\n fix(fixer) {\n return fixer.replaceText(node, `Option<${nonNullTypeText}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"Option\")) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Option\" },\n fix: createImportFixer(sourceCode, \"Option\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferOption\",\n data: {\n type: nonNullTypeText,\n nullable: fullType,\n },\n suggest: suggestions,\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"8MAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,8DACb,YAAa,EACf,EACA,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,2BAA4B,CAC1B,KAAM,UACN,QAAS,EACX,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,aAAc,4DACd,mBAAoB,sEACpB,kBAAmB,gCACnB,iBAAkB,qCACpB,CACF,EAEA,OAAO,EAAS,CAMd,IAAM,EAAkB,EAAyB,CAAO,EAExD,MAAO,CACL,YAAY,EAAe,CAOzB,GANI,CAAC,EAAK,OAAS,EAAK,MAAM,OAAS,GAMnC,CAJY,EAAK,MAAM,KACxB,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,oBAGzD,EAAG,OAEd,IAAM,EAAe,EAAK,MAAM,OAC7B,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,oBACpE,EAEA,GAAI,EAAa,SAAW,EAAG,CAC7B,IAAM,EAAc,EAAa,GAMjC,GAHI,EAAe,EAAa,CAAe,GAG3C,EAAuB,EAAM,CAAe,EAAG,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAkB,EAAW,QAAQ,CAAW,EAChD,EAAW,EAAW,QAAQ,CAAI,EAGxC,GAAI,EAAgB,WAAW,SAAS,EAAG,OAE3C,IAAM,EAAiD,CACrD,CACE,UAAW,oBACX,KAAM,CAAE,KAAM,CAAgB,EAC9B,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAgB,EAAE,CAC7D,CACF,CACF,EAEK,EAAkB,EAAY,QAAQ,GACzC,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,QAAS,EACzB,IAAK,EAAkB,EAAY,QAAQ,CAC7C,CAAC,EAGH,EAAQ,OAAO,CACb,OACA,UAAW,eACX,KAAM,CACJ,KAAM,EACN,SAAU,CACZ,EACA,QAAS,CACX,CAAC,CACH,CACF,CACF,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"functype-detection.js","names":[],"sources":["../../src/utils/functype-detection.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\n/**\n * Utility functions for detecting functype library usage in ESLint rules\n */\n\n/**\n * Check if functype library is imported in the current file\n */\nexport function hasFunctypeImport(context: Rule.RuleContext): boolean {\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n // Look for import statements that import from 'functype'\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Enhanced functype imports tracking\n */\nexport interface FunctypeImports {\n types: Set<string> // Option, Either, List, etc.\n functions: Set<string> // Do, DoAsync, $, etc.\n all: Set<string> // Combined for compatibility\n}\n\n/**\n * Get imported functype symbols from the current file\n */\nexport function getFunctypeImports(context: Rule.RuleContext): FunctypeImports {\n const types = new Set<string>()\n const functions = new Set<string>()\n const all = new Set<string>()\n\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n // Handle named imports: import { Option, Either, Do, $ } from 'functype'\n if (node.specifiers) {\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n const name = spec.imported.name\n all.add(name)\n\n // Categorize imports\n if ([\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\", \"Map\", \"Set\", \"Stack\"].includes(name)) {\n types.add(name)\n } else if ([\"Do\", \"DoAsync\", \"$\"].includes(name)) {\n functions.add(name)\n } else {\n // Other imports (constructors, utilities, etc.)\n functions.add(name)\n }\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n all.add(\"default\")\n functions.add(\"default\")\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n all.add(\"*\")\n types.add(\"*\")\n functions.add(\"*\")\n }\n }\n }\n }\n }\n\n return { types, functions, all }\n}\n\n/**\n * Legacy compatibility - returns the 'all' set\n */\nexport function getFunctypeImportsLegacy(context: Rule.RuleContext): Set<string> {\n return getFunctypeImports(context).all\n}\n\n/**\n * Check if a type reference is using functype types\n */\nexport function isFunctypeType(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n if (!node) return false\n\n // Handle both new and legacy API\n const types = functypeImports instanceof Set ? functypeImports : functypeImports.types || functypeImports.all\n\n // Check direct type names\n if (node.type === \"TSTypeReference\" && node.typeName?.type === \"Identifier\") {\n const typeName = node.typeName.name\n return (\n types.has(typeName) ||\n [\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\", \"Map\", \"Set\", \"Stack\"].includes(typeName)\n )\n }\n\n return false\n}\n\n/**\n * Check if a call expression is using functype methods\n */\nexport function isFunctypeCall(node: ASTNode, functypeImports: Set<string>): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for static method calls like Option.some(), Either.left(), List.of()\n if (callee.type === \"MemberExpression\" && callee.object.type === \"Identifier\") {\n const objectName = callee.object.name\n const methodName = callee.property?.name\n\n // Check if calling methods on imported functype types\n if (functypeImports.has(objectName)) return true\n\n // Check for common functype patterns\n if (\n (objectName === \"Option\" && [\"some\", \"none\", \"of\"].includes(methodName)) ||\n (objectName === \"Either\" && [\"left\", \"right\", \"of\"].includes(methodName)) ||\n (objectName === \"List\" && [\"of\", \"from\", \"empty\"].includes(methodName)) ||\n (objectName === \"Map\" && [\"of\", \"empty\"].includes(methodName)) ||\n (objectName === \"Set\" && [\"of\", \"empty\"].includes(methodName))\n ) {\n return true\n }\n }\n\n // Check for method calls on functype instances like someOption.map()\n if (callee.type === \"MemberExpression\") {\n const methodName = callee.property?.name\n\n // Common functype methods\n if (\n [\n \"map\",\n \"flatMap\",\n \"filter\",\n \"fold\",\n \"foldLeft\",\n \"foldRight\",\n \"getOrElse\",\n \"orElse\",\n \"isEmpty\",\n \"nonEmpty\",\n \"isDefined\",\n \"isSome\",\n \"isNone\",\n \"isLeft\",\n \"isRight\",\n \"toArray\",\n ].includes(methodName)\n ) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Check if current context is already using functype patterns appropriately\n */\nexport function isAlreadyUsingFunctype(node: ASTNode, functypeImports: Set<string>): boolean {\n let parent = node.parent\n\n // Walk up the AST to find functype usage\n while (parent) {\n if (isFunctypeCall(parent as ASTNode, functypeImports) || isFunctypeType(parent as ASTNode, functypeImports)) {\n return true\n }\n parent = parent.parent\n }\n\n return false\n}\n\n/**\n * Check if a variable or parameter is typed with functype types\n */\nexport function hasFunctypeTypeAnnotation(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n // Check for type annotation\n if (node.typeAnnotation?.typeAnnotation) {\n return isFunctypeType(node.typeAnnotation.typeAnnotation, functypeImports)\n }\n\n return false\n}\n\n/**\n * Check if a call expression is a Do notation call\n */\nexport function isDoNotationCall(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for Do() or DoAsync() calls\n if (callee.type === \"Identifier\") {\n const name = callee.name\n return functypeImports.functions.has(name) && [\"Do\", \"DoAsync\"].includes(name)\n }\n\n return false\n}\n\n/**\n * Check if a call expression uses the $ helper function\n */\nexport function isDollarHelper(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for $() calls\n if (callee.type === \"Identifier\" && callee.name === \"$\") {\n return functypeImports.functions.has(\"$\")\n }\n\n return false\n}\n\n/**\n * Check if current expression is inside a Do notation block\n */\nexport function isInsideDoNotation(node: ASTNode, functypeImports: FunctypeImports): boolean {\n let current = node.parent\n\n while (current) {\n if (current.type === \"CallExpression\" && isDoNotationCall(current as ASTNode, functypeImports)) {\n return true\n }\n current = current.parent\n }\n\n return false\n}\n\n/**\n * Check if a method call is a chained method call on functype types\n */\nexport function isChainedMethodCall(node: ASTNode, methodName: string): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n if (\n callee.type === \"MemberExpression\" &&\n callee.property.type === \"Identifier\" &&\n callee.property.name === methodName\n ) {\n return true\n }\n\n return false\n}\n\n/**\n * Detect if code could benefit from Do notation\n */\nexport function shouldUseDoNotation(\n node: ASTNode,\n functypeImports: FunctypeImports,\n): {\n shouldUse: boolean\n reason: \"nested-checks\" | \"chained-flatmaps\" | \"mixed-monads\" | \"async-chains\" | null\n} {\n // Already using Do notation\n if (isInsideDoNotation(node, functypeImports)) {\n return { shouldUse: false, reason: null }\n }\n\n // Check for nested null checks (a && a.b && a.b.c pattern)\n if (node.type === \"LogicalExpression\" && node.operator === \"&&\") {\n const text = node.toString ? node.toString() : \"\"\n if (text.includes(\"&&\") && text.match(/\\w+(\\.\\w+){2,}/)) {\n return { shouldUse: true, reason: \"nested-checks\" }\n }\n }\n\n // Check for chained flatMap calls\n if (isChainedMethodCall(node, \"flatMap\")) {\n // Count chain depth\n let depth = 0\n let current = node\n\n while (current && current.type === \"CallExpression\" && isChainedMethodCall(current, \"flatMap\")) {\n depth++\n if (current.callee.type === \"MemberExpression\") {\n current = current.callee.object as ASTNode\n } else {\n break\n }\n }\n\n if (depth >= 3) {\n return { shouldUse: true, reason: \"chained-flatmaps\" }\n }\n }\n\n return { shouldUse: false, reason: null }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"functype-detection.js","names":[],"sources":["../../src/utils/functype-detection.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\n/**\n * Utility functions for detecting functype library usage in ESLint rules\n */\n\n/**\n * Check if functype library is imported in the current file\n */\nexport function hasFunctypeImport(context: Rule.RuleContext): boolean {\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n // Look for import statements that import from 'functype'\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Enhanced functype imports tracking\n */\nexport interface FunctypeImports {\n types: Set<string> // Option, Either, List, etc.\n functions: Set<string> // Do, DoAsync, $, etc.\n all: Set<string> // Combined for compatibility\n}\n\n/**\n * Get imported functype symbols from the current file\n */\nexport function getFunctypeImports(context: Rule.RuleContext): FunctypeImports {\n const types = new Set<string>()\n const functions = new Set<string>()\n const all = new Set<string>()\n\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n // Handle named imports: import { Option, Either, Do, $ } from 'functype'\n if (node.specifiers) {\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n const name = spec.imported.name\n all.add(name)\n\n // Categorize imports\n if ([\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\", \"Map\", \"Set\", \"Stack\"].includes(name)) {\n types.add(name)\n } else if ([\"Do\", \"DoAsync\", \"$\"].includes(name)) {\n functions.add(name)\n } else {\n // Other imports (constructors, utilities, etc.)\n functions.add(name)\n }\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n all.add(\"default\")\n functions.add(\"default\")\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n all.add(\"*\")\n types.add(\"*\")\n functions.add(\"*\")\n }\n }\n }\n }\n }\n\n return { types, functions, all }\n}\n\n/**\n * Legacy compatibility - returns the 'all' set\n */\nexport function getFunctypeImportsLegacy(context: Rule.RuleContext): Set<string> {\n return getFunctypeImports(context).all\n}\n\n/**\n * Check if a type reference is using functype types\n */\nexport function isFunctypeType(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n if (!node) return false\n\n // Handle both new and legacy API\n const types = functypeImports instanceof Set ? functypeImports : functypeImports.types || functypeImports.all\n\n // Check direct type names\n if (node.type === \"TSTypeReference\" && node.typeName?.type === \"Identifier\") {\n const typeName = node.typeName.name\n return (\n types.has(typeName) ||\n [\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\", \"Map\", \"Set\", \"Stack\"].includes(typeName)\n )\n }\n\n return false\n}\n\n/**\n * Check if a call expression is using functype methods\n */\nexport function isFunctypeCall(node: ASTNode, functypeImports: Set<string>): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for static method calls like Option.some(), Either.left(), List.of()\n if (callee.type === \"MemberExpression\" && callee.object.type === \"Identifier\") {\n const objectName = callee.object.name\n const methodName = callee.property?.name\n\n // Check if calling methods on imported functype types\n if (functypeImports.has(objectName)) return true\n\n // Check for common functype patterns\n if (\n (objectName === \"Option\" && [\"some\", \"none\", \"of\"].includes(methodName)) ||\n (objectName === \"Either\" && [\"left\", \"right\", \"of\"].includes(methodName)) ||\n (objectName === \"List\" && [\"of\", \"from\", \"empty\"].includes(methodName)) ||\n (objectName === \"Map\" && [\"of\", \"empty\"].includes(methodName)) ||\n (objectName === \"Set\" && [\"of\", \"empty\"].includes(methodName))\n ) {\n return true\n }\n }\n\n // Check for method calls on functype instances like someOption.map()\n if (callee.type === \"MemberExpression\") {\n const methodName = callee.property?.name\n\n // Common functype methods\n if (\n [\n \"map\",\n \"flatMap\",\n \"filter\",\n \"fold\",\n \"foldLeft\",\n \"foldRight\",\n \"getOrElse\",\n \"orElse\",\n \"isEmpty\",\n \"nonEmpty\",\n \"isDefined\",\n \"isSome\",\n \"isNone\",\n \"isLeft\",\n \"isRight\",\n \"toArray\",\n ].includes(methodName)\n ) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Check if current context is already using functype patterns appropriately\n */\nexport function isAlreadyUsingFunctype(node: ASTNode, functypeImports: Set<string>): boolean {\n let parent = node.parent\n\n // Walk up the AST to find functype usage\n while (parent) {\n if (isFunctypeCall(parent as ASTNode, functypeImports) || isFunctypeType(parent as ASTNode, functypeImports)) {\n return true\n }\n parent = parent.parent\n }\n\n return false\n}\n\n/**\n * Check if a variable or parameter is typed with functype types\n */\nexport function hasFunctypeTypeAnnotation(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n // Check for type annotation\n if (node.typeAnnotation?.typeAnnotation) {\n return isFunctypeType(node.typeAnnotation.typeAnnotation, functypeImports)\n }\n\n return false\n}\n\n/**\n * Check if a call expression is a Do notation call\n */\nexport function isDoNotationCall(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for Do() or DoAsync() calls\n if (callee.type === \"Identifier\") {\n const name = callee.name\n return functypeImports.functions.has(name) && [\"Do\", \"DoAsync\"].includes(name)\n }\n\n return false\n}\n\n/**\n * Check if a call expression uses the $ helper function\n */\nexport function isDollarHelper(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for $() calls\n if (callee.type === \"Identifier\" && callee.name === \"$\") {\n return functypeImports.functions.has(\"$\")\n }\n\n return false\n}\n\n/**\n * Check if current expression is inside a Do notation block\n */\nexport function isInsideDoNotation(node: ASTNode, functypeImports: FunctypeImports): boolean {\n let current = node.parent\n\n while (current) {\n if (current.type === \"CallExpression\" && isDoNotationCall(current as ASTNode, functypeImports)) {\n return true\n }\n current = current.parent\n }\n\n return false\n}\n\n/**\n * Check if a method call is a chained method call on functype types\n */\nexport function isChainedMethodCall(node: ASTNode, methodName: string): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n if (\n callee.type === \"MemberExpression\" &&\n callee.property.type === \"Identifier\" &&\n callee.property.name === methodName\n ) {\n return true\n }\n\n return false\n}\n\n/**\n * Detect if code could benefit from Do notation\n */\nexport function shouldUseDoNotation(\n node: ASTNode,\n functypeImports: FunctypeImports,\n): {\n shouldUse: boolean\n reason: \"nested-checks\" | \"chained-flatmaps\" | \"mixed-monads\" | \"async-chains\" | null\n} {\n // Already using Do notation\n if (isInsideDoNotation(node, functypeImports)) {\n return { shouldUse: false, reason: null }\n }\n\n // Check for nested null checks (a && a.b && a.b.c pattern)\n if (node.type === \"LogicalExpression\" && node.operator === \"&&\") {\n const text = node.toString ? node.toString() : \"\"\n if (text.includes(\"&&\") && text.match(/\\w+(\\.\\w+){2,}/)) {\n return { shouldUse: true, reason: \"nested-checks\" }\n }\n }\n\n // Check for chained flatMap calls\n if (isChainedMethodCall(node, \"flatMap\")) {\n // Count chain depth\n let depth = 0\n let current = node\n\n while (current && current.type === \"CallExpression\" && isChainedMethodCall(current, \"flatMap\")) {\n depth++\n if (current.callee.type === \"MemberExpression\") {\n current = current.callee.object as ASTNode\n } else {\n break\n }\n }\n\n if (depth >= 3) {\n return { shouldUse: true, reason: \"chained-flatmaps\" }\n }\n }\n\n return { shouldUse: false, reason: null }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"import-fixer.js","names":[],"sources":["../../src/utils/import-fixer.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\nimport type { SourceCode } from \"eslint\"\n\n/**\n * Check if a given symbol is already imported from functype\n */\nexport function hasFunctypeSymbol(sourceCode: SourceCode, symbolName: string): boolean {\n const program = sourceCode.ast\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n if (node.specifiers) {\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportNamespaceSpecifier\") {\n return true\n }\n if (\n spec.type === \"ImportSpecifier\" &&\n spec.imported.type === \"Identifier\" &&\n spec.imported.name === symbolName\n ) {\n return true\n }\n }\n }\n }\n }\n\n return false\n}\n\n/**\n * Create a fixer function that adds a symbol to the functype import.\n * Returns a factory compatible with ESLint suggest[].fix signature.\n */\nexport function createImportFixer(\n sourceCode: SourceCode,\n symbolName: string,\n): (fixer: Rule.RuleFixer) => Rule.Fix | null {\n return (fixer: Rule.RuleFixer): Rule.Fix | null => {\n const program = sourceCode.ast\n\n let existingFunctypeImport: (typeof program.body)[number] | null = null\n let lastImportNode: (typeof program.body)[number] | null = null\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\") {\n lastImportNode = node\n if (node.source.type === \"Literal\" && node.source.value === \"functype\") {\n existingFunctypeImport = node\n }\n }\n }\n\n // If already imported, nothing to do\n if (hasFunctypeSymbol(sourceCode, symbolName)) {\n return null\n }\n\n if (existingFunctypeImport && existingFunctypeImport.type === \"ImportDeclaration\") {\n const importDecl = existingFunctypeImport\n\n // Check for namespace import — can't add named imports alongside it\n const hasNamespace = importDecl.specifiers?.some((s) => s.type === \"ImportNamespaceSpecifier\") ?? false\n if (hasNamespace) {\n return null\n }\n\n // Find the last named specifier and append after it\n const specifiers = importDecl.specifiers ?? []\n const namedSpecifiers = specifiers.filter((s) => s.type === \"ImportSpecifier\")\n\n if (namedSpecifiers.length > 0) {\n const lastSpecifier = namedSpecifiers[namedSpecifiers.length - 1]\n if (lastSpecifier) {\n return fixer.insertTextAfter(lastSpecifier, `, ${symbolName}`)\n }\n }\n\n // No named specifiers yet — insert before closing brace\n const importText = sourceCode.getText(importDecl)\n const newText = importText.replace(/\\{(\\s*)\\}/, `{ ${symbolName} }`)\n return fixer.replaceText(importDecl, newText)\n }\n\n // No existing functype import — add a new one\n const newImport = `import { ${symbolName} } from \"functype\"`\n\n if (lastImportNode) {\n return fixer.insertTextAfter(lastImportNode, `\\n${newImport}`)\n }\n\n // No imports at all — insert at top of file\n const firstNode = program.body[0]\n if (firstNode) {\n return fixer.insertTextBefore(firstNode, `${newImport}\\n`)\n }\n\n return fixer.insertTextBeforeRange([0, 0], `${newImport}\\n`)\n }\n}\n"],"mappings":"AAMA,SAAgB,EAAkB,EAAwB,EAA6B,CACrF,IAAM,EAAU,EAAW,IAE3B,IAAK,IAAM,KAAQ,EAAQ,KACzB,GAAI,EAAK,OAAS,qBAAuB,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,YAC3F,EAAK,WACP,KAAK,IAAM,KAAQ,EAAK,WAItB,GAHI,EAAK,OAAS,4BAIhB,EAAK,OAAS,mBACd,EAAK,SAAS,OAAS,cACvB,EAAK,SAAS,OAAS,EAEvB,MAAO,GAOjB,MAAO,GAOT,SAAgB,EACd,EACA,EAC4C,CAC5C,MAAQ,IAA2C,CACjD,IAAM,EAAU,EAAW,IAEvB,EAA+D,KAC/D,EAAuD,KAE3D,IAAK,IAAM,KAAQ,EAAQ,KACrB,EAAK,OAAS,sBAChB,EAAiB,EACb,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,aAC1D,EAAyB,IAM/B,GAAI,EAAkB,EAAY,EAAW,CAC3C,OAAO,KAGT,GAAI,GAA0B,EAAuB,OAAS,oBAAqB,CACjF,IAAM,EAAa,EAInB,GADqB,EAAW,YAAY,KAAM,GAAM,EAAE,OAAS,2BAA2B,EAAI,GAEhG,OAAO,KAKT,IAAM,GADa,EAAW,YAAc,EAAE,EACX,OAAQ,GAAM,EAAE,OAAS,kBAAkB,CAE9E,GAAI,EAAgB,OAAS,EAAG,CAC9B,IAAM,EAAgB,EAAgB,EAAgB,OAAS,GAC/D,GAAI,EACF,OAAO,EAAM,gBAAgB,EAAe,KAAK,IAAa,CAMlE,IAAM,EADa,EAAW,QAAQ,EACZ,CAAC,QAAQ,YAAa,KAAK,EAAW,IAAI,CACpE,OAAO,EAAM,YAAY,EAAY,EAAQ,CAI/C,IAAM,EAAY,YAAY,EAAW,oBAEzC,GAAI,EACF,OAAO,EAAM,gBAAgB,EAAgB,KAAK,IAAY,CAIhE,IAAM,EAAY,EAAQ,KAAK,GAK/B,OAJI,EACK,EAAM,iBAAiB,EAAW,GAAG,EAAU,IAAI,CAGrD,EAAM,sBAAsB,CAAC,EAAG,EAAE,CAAE,GAAG,EAAU,IAAI"}
1
+ {"version":3,"file":"import-fixer.js","names":[],"sources":["../../src/utils/import-fixer.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\nimport type { SourceCode } from \"eslint\"\n\n/**\n * Check if a given symbol is already imported from functype\n */\nexport function hasFunctypeSymbol(sourceCode: SourceCode, symbolName: string): boolean {\n const program = sourceCode.ast\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n if (node.specifiers) {\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportNamespaceSpecifier\") {\n return true\n }\n if (\n spec.type === \"ImportSpecifier\" &&\n spec.imported.type === \"Identifier\" &&\n spec.imported.name === symbolName\n ) {\n return true\n }\n }\n }\n }\n }\n\n return false\n}\n\n/**\n * Create a fixer function that adds a symbol to the functype import.\n * Returns a factory compatible with ESLint suggest[].fix signature.\n */\nexport function createImportFixer(\n sourceCode: SourceCode,\n symbolName: string,\n): (fixer: Rule.RuleFixer) => Rule.Fix | null {\n return (fixer: Rule.RuleFixer): Rule.Fix | null => {\n const program = sourceCode.ast\n\n let existingFunctypeImport: (typeof program.body)[number] | null = null\n let lastImportNode: (typeof program.body)[number] | null = null\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\") {\n lastImportNode = node\n if (node.source.type === \"Literal\" && node.source.value === \"functype\") {\n existingFunctypeImport = node\n }\n }\n }\n\n // If already imported, nothing to do\n if (hasFunctypeSymbol(sourceCode, symbolName)) {\n return null\n }\n\n if (existingFunctypeImport && existingFunctypeImport.type === \"ImportDeclaration\") {\n const importDecl = existingFunctypeImport\n\n // Check for namespace import — can't add named imports alongside it\n const hasNamespace = importDecl.specifiers?.some((s) => s.type === \"ImportNamespaceSpecifier\") ?? false\n if (hasNamespace) {\n return null\n }\n\n // Find the last named specifier and append after it\n const specifiers = importDecl.specifiers ?? []\n const namedSpecifiers = specifiers.filter((s) => s.type === \"ImportSpecifier\")\n\n if (namedSpecifiers.length > 0) {\n const lastSpecifier = namedSpecifiers[namedSpecifiers.length - 1]\n if (lastSpecifier) {\n return fixer.insertTextAfter(lastSpecifier, `, ${symbolName}`)\n }\n }\n\n // No named specifiers yet — insert before closing brace\n const importText = sourceCode.getText(importDecl)\n const newText = importText.replace(/\\{(\\s*)\\}/, `{ ${symbolName} }`)\n return fixer.replaceText(importDecl, newText)\n }\n\n // No existing functype import — add a new one\n const newImport = `import { ${symbolName} } from \"functype\"`\n\n if (lastImportNode) {\n return fixer.insertTextAfter(lastImportNode, `\\n${newImport}`)\n }\n\n // No imports at all — insert at top of file\n const firstNode = program.body[0]\n if (firstNode) {\n return fixer.insertTextBefore(firstNode, `${newImport}\\n`)\n }\n\n return fixer.insertTextBeforeRange([0, 0], `${newImport}\\n`)\n }\n}\n"],"mappings":"AAMA,SAAgB,EAAkB,EAAwB,EAA6B,CACrF,IAAM,EAAU,EAAW,IAE3B,IAAK,IAAM,KAAQ,EAAQ,KACzB,GAAI,EAAK,OAAS,qBAAuB,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,YAC3F,EAAK,WACP,KAAK,IAAM,KAAQ,EAAK,WAItB,GAHI,EAAK,OAAS,4BAIhB,EAAK,OAAS,mBACd,EAAK,SAAS,OAAS,cACvB,EAAK,SAAS,OAAS,EAEvB,MAAO,EAEX,CAKN,MAAO,EACT,CAMA,SAAgB,EACd,EACA,EAC4C,CAC5C,MAAQ,IAA2C,CACjD,IAAM,EAAU,EAAW,IAEvB,EAA+D,KAC/D,EAAuD,KAE3D,IAAK,IAAM,KAAQ,EAAQ,KACrB,EAAK,OAAS,sBAChB,EAAiB,EACb,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,aAC1D,EAAyB,IAM/B,GAAI,EAAkB,EAAY,CAAU,EAC1C,OAAO,KAGT,GAAI,GAA0B,EAAuB,OAAS,oBAAqB,CACjF,IAAM,EAAa,EAInB,GADqB,EAAW,YAAY,KAAM,GAAM,EAAE,OAAS,0BAA0B,GAAK,GAEhG,OAAO,KAKT,IAAM,GADa,EAAW,YAAc,CAAC,GACV,OAAQ,GAAM,EAAE,OAAS,iBAAiB,EAE7E,GAAI,EAAgB,OAAS,EAAG,CAC9B,IAAM,EAAgB,EAAgB,EAAgB,OAAS,GAC/D,GAAI,EACF,OAAO,EAAM,gBAAgB,EAAe,KAAK,GAAY,CAEjE,CAIA,IAAM,EADa,EAAW,QAAQ,CACb,EAAE,QAAQ,YAAa,KAAK,EAAW,GAAG,EACnE,OAAO,EAAM,YAAY,EAAY,CAAO,CAC9C,CAGA,IAAM,EAAY,YAAY,EAAW,oBAEzC,GAAI,EACF,OAAO,EAAM,gBAAgB,EAAgB,KAAK,GAAW,EAI/D,IAAM,EAAY,EAAQ,KAAK,GAK/B,OAJI,EACK,EAAM,iBAAiB,EAAW,GAAG,EAAU,GAAG,EAGpD,EAAM,sBAAsB,CAAC,EAAG,CAAC,EAAG,GAAG,EAAU,GAAG,CAC7D,CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-functype",
3
- "version": "2.3.0",
3
+ "version": "2.60.6",
4
4
  "description": "Custom ESLint rules for functional TypeScript programming with functype library patterns including Do notation (ESLint 10+)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,26 +34,6 @@
34
34
  "either",
35
35
  "list"
36
36
  ],
37
- "scripts": {
38
- "validate": "ts-builds validate",
39
- "format": "ts-builds format",
40
- "format:check": "ts-builds format:check",
41
- "lint": "ts-builds lint",
42
- "lint:check": "ts-builds lint:check",
43
- "typecheck": "ts-builds typecheck",
44
- "test": "ts-builds test",
45
- "test:watch": "ts-builds test:watch",
46
- "test:ui": "ts-builds test:ui",
47
- "test:coverage": "ts-builds test:coverage",
48
- "build": "ts-builds build",
49
- "dev": "ts-builds dev",
50
- "prepublishOnly": "pnpm validate",
51
- "list-rules": "node dist/cli/list-rules.js",
52
- "list-rules:verbose": "node dist/cli/list-rules.js --verbose",
53
- "list-rules:usage": "node dist/cli/list-rules.js --usage",
54
- "check-deps": "node dist/cli/list-rules.js --check-deps",
55
- "cli:help": "node dist/cli/list-rules.js --help"
56
- },
57
37
  "prettier": "ts-builds/prettier",
58
38
  "peerDependencies": {
59
39
  "eslint": "^10.2.1"
@@ -62,9 +42,13 @@
62
42
  "@types/node": "^24.12.2",
63
43
  "@typescript-eslint/rule-tester": "^8.59.1",
64
44
  "eslint-config-prettier": "^10.1.8",
65
- "functype": "^0.60.2",
66
- "ts-builds": "^2.7.1",
67
- "tsdown": "^0.21.10"
45
+ "ts-builds": "^2.8.0",
46
+ "tsdown": "^0.22.0",
47
+ "functype": "^0.60.6"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "provenance": true
68
52
  },
69
53
  "author": {
70
54
  "name": "Jordan Burke",
@@ -73,15 +57,33 @@
73
57
  "license": "MIT",
74
58
  "repository": {
75
59
  "type": "git",
76
- "url": "https://github.com/jordanburke/eslint-functype",
77
- "directory": "packages/plugin"
60
+ "url": "git+https://github.com/jordanburke/functype.git",
61
+ "directory": "packages/eslint-plugin-functype"
78
62
  },
79
63
  "bugs": {
80
- "url": "https://github.com/jordanburke/eslint-functype/issues"
64
+ "url": "https://github.com/jordanburke/functype/issues"
81
65
  },
82
- "homepage": "https://github.com/jordanburke/eslint-functype#readme",
66
+ "homepage": "https://github.com/jordanburke/functype/tree/main/packages/eslint-plugin-functype#readme",
83
67
  "engines": {
84
68
  "node": ">=24.0.0"
85
69
  },
86
- "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
87
- }
70
+ "scripts": {
71
+ "validate": "ts-builds validate",
72
+ "format": "ts-builds format",
73
+ "format:check": "ts-builds format:check",
74
+ "lint": "ts-builds lint",
75
+ "lint:check": "ts-builds lint:check",
76
+ "typecheck": "ts-builds typecheck",
77
+ "test": "ts-builds test",
78
+ "test:watch": "ts-builds test:watch",
79
+ "test:ui": "ts-builds test:ui",
80
+ "test:coverage": "ts-builds test:coverage",
81
+ "build": "ts-builds build",
82
+ "dev": "ts-builds dev",
83
+ "list-rules": "node dist/cli/list-rules.js",
84
+ "list-rules:verbose": "node dist/cli/list-rules.js --verbose",
85
+ "list-rules:usage": "node dist/cli/list-rules.js --usage",
86
+ "check-deps": "node dist/cli/list-rules.js --check-deps",
87
+ "cli:help": "node dist/cli/list-rules.js --help"
88
+ }
89
+ }