eslint-plugin-functype 2.0.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- const e={meta:{type:`suggestion`,docs:{description:`Prefer Either<E, T> over try/catch blocks and throw statements`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverTryCatch:`Prefer Either<Error, T> over try/catch block`,preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`}},create(e){let t=(e.options[0]||{}).allowThrowInTests!==!1;function n(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}function r(e){if(!e)return!1;if(e.type===`ThrowStatement`){let t=e.parent;for(;t;){if(t.type===`CatchClause`)return!1;t=t.parent}return!0}if(e.type===`CatchClause`)return!1;for(let t in e){if(t===`parent`)continue;let n=e[t];if(Array.isArray(n)){for(let e of n)if(e&&typeof e==`object`&&r(e))return!0}else if(n&&typeof n==`object`&&r(n))return!0}return!1}return{TryStatement(r){t&&n()||r.handler&&r.handler.body&&r.handler.body.body.some(e=>e.type===`ThrowStatement`)||e.report({node:r,messageId:`preferEitherOverTryCatch`})},ThrowStatement(r){if(t&&n())return;let i=r.parent;for(;i;){if(i.type===`CatchClause`)return;i=i.parent}e.report({node:r,messageId:`preferEitherOverThrow`})},FunctionDeclaration(i){if(!(t&&n())&&i.body&&r(i.body)){let t=i.returnType?.typeAnnotation;if(t){let n=e.sourceCode.getText(t);n.includes(`Either`)||e.report({node:i.id||i,messageId:`preferEitherReturn`,data:{type:n}})}}}}}};export{e as default};
1
+ const e={meta:{type:`suggestion`,docs:{description:`Prefer Either<E, T> over try/catch blocks and throw statements`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverTryCatch:`Prefer Either<Error, T> over try/catch block`,preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`}},create(e){let t=(e.options[0]||{}).allowThrowInTests!==!1;function n(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}function r(e){if(!e)return!1;if(e.type===`ThrowStatement`){let t=e.parent;for(;t;){if(t.type===`CatchClause`)return!1;t=t.parent}return!0}if(e.type===`CatchClause`)return!1;for(let t in e){if(t===`parent`)continue;let n=e[t];if(Array.isArray(n)){for(let e of n)if(e&&typeof e==`object`&&r(e))return!0}else if(n&&typeof n==`object`&&r(n))return!0}return!1}function i(i){if(!(t&&n())&&i.body&&r(i.body)){let t=i.returnType?.typeAnnotation;if(t){let n=e.sourceCode.getText(t);n.includes(`Either`)||e.report({node:i.id||i,messageId:`preferEitherReturn`,data:{type:n}})}}}return{TryStatement(r){t&&n()||r.handler&&r.handler.body&&r.handler.body.body.some(e=>e.type===`ThrowStatement`)||e.report({node:r,messageId:`preferEitherOverTryCatch`})},ThrowStatement(r){if(t&&n())return;let i=r.parent;for(;i;){if(i.type===`CatchClause`)return;i=i.parent}e.report({node:r,messageId:`preferEitherOverThrow`})},FunctionDeclaration(e){i(e)},ArrowFunctionExpression(e){i(e)}}}};export{e as default};
2
2
  //# sourceMappingURL=prefer-either.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-either.js","names":[],"sources":["../../src/rules/prefer-either.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\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 },\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 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 context.report({\n node,\n messageId: \"preferEitherOverTryCatch\",\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 context.report({\n node,\n messageId: \"preferEitherOverThrow\",\n })\n },\n\n FunctionDeclaration(node: ASTNode) {\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 // (throws in catch blocks are handled by ThrowStatement rule)\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 },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,iEACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,kBAAmB,CACjB,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,yBAA0B,+CAC1B,sBAAuB,iDACvB,mBAAoB,iEACrB,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACN,oBAAsB,GAExD,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,EAA+B,EAAwB,CAC9D,GAAI,CAAC,EAAM,MAAO,GAElB,GAAI,EAAK,OAAS,iBAAkB,CAElC,IAAI,EAAS,EAAK,OAClB,KAAO,GAAQ,CACb,GAAI,EAAO,OAAS,cAAe,MAAO,GAC1C,EAAS,EAAO,OAElB,MAAO,GAIT,GAAI,EAAK,OAAS,cAAe,MAAO,GAGxC,IAAK,IAAM,KAAO,EAAM,CACtB,GAAI,IAAQ,SAAU,SACtB,IAAM,EAAQ,EAAK,GACnB,GAAI,MAAM,QAAQ,EAAM,MACjB,IAAM,KAAQ,EACjB,GAAI,GAAQ,OAAO,GAAS,UAAY,EAA+B,EAAK,CAC1E,MAAO,WAGF,GAAS,OAAO,GAAU,UAAY,EAA+B,EAAM,CACpF,MAAO,GAIX,MAAO,GAGT,MAAO,CACL,aAAa,EAAe,CAEtB,GAAqB,GAAc,EAGnC,EAAK,SAAW,EAAK,QAAQ,MACb,EAAK,QAAQ,KAAK,KACP,KAAM,GAAkB,EAAK,OAAS,iBAAiB,EAItF,EAAQ,OAAO,CACb,OACA,UAAW,2BACZ,CAAC,EAGJ,eAAe,EAAe,CAE5B,GAAI,GAAqB,GAAc,CAAE,OAGzC,IAAI,EAAS,EAAK,OAClB,KAAO,GAAQ,CACb,GAAI,EAAO,OAAS,cAAe,OACnC,EAAS,EAAO,OAGlB,EAAQ,OAAO,CACb,OACA,UAAW,wBACZ,CAAC,EAGJ,oBAAoB,EAAe,CAE7B,QAAqB,GAAc,GAElC,EAAK,MAIkB,EAA+B,EAAK,KAAK,CAC5C,CACvB,IAAM,EAAa,EAAK,YAAY,eACpC,GAAI,EAAY,CAEd,IAAM,EADa,EAAQ,WACO,QAAQ,EAAW,CAGhD,EAAe,SAAS,SAAS,EACpC,EAAQ,OAAO,CACb,KAAM,EAAK,IAAM,EACjB,UAAW,qBACX,KAAM,CAAE,KAAM,EAAgB,CAC/B,CAAC,IAKX,EAEJ"}
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\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\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 },\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 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 context.report({\n node,\n messageId: \"preferEitherOverTryCatch\",\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 context.report({\n node,\n messageId: \"preferEitherOverThrow\",\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":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,iEACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,kBAAmB,CACjB,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,yBAA0B,+CAC1B,sBAAuB,iDACvB,mBAAoB,iEACrB,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACN,oBAAsB,GAExD,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,EAA+B,EAAwB,CAC9D,GAAI,CAAC,EAAM,MAAO,GAElB,GAAI,EAAK,OAAS,iBAAkB,CAElC,IAAI,EAAS,EAAK,OAClB,KAAO,GAAQ,CACb,GAAI,EAAO,OAAS,cAAe,MAAO,GAC1C,EAAS,EAAO,OAElB,MAAO,GAIT,GAAI,EAAK,OAAS,cAAe,MAAO,GAGxC,IAAK,IAAM,KAAO,EAAM,CACtB,GAAI,IAAQ,SAAU,SACtB,IAAM,EAAQ,EAAK,GACnB,GAAI,MAAM,QAAQ,EAAM,MACjB,IAAM,KAAQ,EACjB,GAAI,GAAQ,OAAO,GAAS,UAAY,EAA+B,EAAK,CAC1E,MAAO,WAGF,GAAS,OAAO,GAAU,UAAY,EAA+B,EAAM,CACpF,MAAO,GAIX,MAAO,GAGT,SAAS,EAAuB,EAAqB,CAE/C,QAAqB,GAAc,GAElC,EAAK,MAGkB,EAA+B,EAAK,KAAK,CAC5C,CACvB,IAAM,EAAa,EAAK,YAAY,eACpC,GAAI,EAAY,CAEd,IAAM,EADa,EAAQ,WACO,QAAQ,EAAW,CAGhD,EAAe,SAAS,SAAS,EACpC,EAAQ,OAAO,CACb,KAAM,EAAK,IAAM,EACjB,UAAW,qBACX,KAAM,CAAE,KAAM,EAAgB,CAC/B,CAAC,GAMV,MAAO,CACL,aAAa,EAAe,CAEtB,GAAqB,GAAc,EAGnC,EAAK,SAAW,EAAK,QAAQ,MACb,EAAK,QAAQ,KAAK,KACP,KAAM,GAAkB,EAAK,OAAS,iBAAiB,EAItF,EAAQ,OAAO,CACb,OACA,UAAW,2BACZ,CAAC,EAGJ,eAAe,EAAe,CAE5B,GAAI,GAAqB,GAAc,CAAE,OAGzC,IAAI,EAAS,EAAK,OAClB,KAAO,GAAQ,CACb,GAAI,EAAO,OAAS,cAAe,OACnC,EAAS,EAAO,OAGlB,EAAQ,OAAO,CACb,OACA,UAAW,wBACZ,CAAC,EAGJ,oBAAoB,EAAe,CACjC,EAAuB,EAAK,EAG9B,wBAAwB,EAAe,CACrC,EAAuB,EAAK,EAE/B,EAEJ"}
@@ -1,2 +1,2 @@
1
- const e={meta:{type:`suggestion`,docs:{description:`Prefer .fold() over if/else chains when working with monadic types`,recommended:!0},fixable:`code`,schema:[{type:`object`,properties:{minComplexity:{type:`integer`,minimum:1,default:2}},additionalProperties:!1}],messages:{preferFold:`Prefer .fold() over if/else when working with {{type}} types`,preferFoldTernary:`Consider using .fold() instead of ternary operator for {{type}}`}},create(e){let t=(e.options[0]||{}).minComplexity||2;function n(e,t){if(e.type===`BlockStatement`){let n=e.body;return n.length===1&&n[0].type===`ReturnStatement`?t.getText(n[0].argument):t.getText(e).slice(1,-1).trim()}else return t.getText(e)}function r(t){let r=e.sourceCode;if(t.type!==`IfStatement`)return null;let i=t.test,a=t.consequent,o=t.alternate;if(!a||!o)return null;let s=null,c=!1;if(i.type===`CallExpression`&&i.callee.type===`MemberExpression`){let e=i.callee.property.name;s=r.getText(i.callee.object),e===`isSome`||e===`isRight`||e===`isSuccess`?c=!1:(e===`isNone`||e===`isEmpty`||e===`isLeft`||e===`isFailure`)&&(c=!0)}if(!s||a.type===`BlockStatement`&&(a.body.length!==1||a.body[0].type!==`ReturnStatement`))return null;if(o.type===`BlockStatement`){if(o.body.length!==1||o.body[0].type!==`ReturnStatement`)return null}else if(o.type===`IfStatement`)return null;let l=n(a,r),u=n(o,r);return c?`${s}.fold(() => ${l}, () => ${u})`:`${s}.fold(() => ${u}, (value) => ${l})`}function i(t){let n=e.sourceCode;if(t.type!==`ConditionalExpression`)return null;let r=t.test,i=t.consequent,a=t.alternate,o=null,s=!1;if(r.type===`CallExpression`&&r.callee.type===`MemberExpression`){let e=r.callee.property.name;o=n.getText(r.callee.object),e===`isSome`||e===`isRight`||e===`isSuccess`?s=!1:(e===`isNone`||e===`isEmpty`||e===`isLeft`||e===`isFailure`)&&(s=!0)}if(!o)return null;let c=n.getText(i),l=n.getText(a);return s?`${o}.fold(() => ${c}, () => ${l})`:`${o}.fold(() => ${l}, (value) => ${c})`}function a(e){if(e.type===`CallExpression`&&e.callee.type===`MemberExpression`){let t=e.callee.property.name;return[`isSome`,`isNone`,`isEmpty`,`isRight`,`isLeft`,`isSuccess`,`isFailure`].includes(t)}return!1}function o(t){let n=e.sourceCode.getText(t);if(/\.(isSome|isNone|isEmpty|isDefined)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Option`};if(/\.(isLeft|isRight)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Either`};if(/\.(isSuccess|isFailure)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Result`};if(t.type===`BinaryExpression`){if((t.operator===`===`||t.operator===`!==`)&&(t.left.type===`Literal`&&(t.left.value===null||t.left.value===void 0)||t.right.type===`Literal`&&(t.right.value===null||t.right.value===void 0)))return{isMonadic:!0,type:`Option`};if(t.operator===`==`||t.operator===`!=`||t.operator===`===`||t.operator===`!==`){let e=t.left.type===`Identifier`&&t.left.name===`undefined`||t.left.type===`Literal`&&t.left.value===void 0,n=t.right.type===`Identifier`&&t.right.name===`undefined`||t.right.type===`Literal`&&t.right.value===void 0;if(e||n)return{isMonadic:!0,type:`Option`}}}return{isMonadic:!1,type:``}}function s(n){let i=n.test,s=o(i);if(!s.isMonadic||n.parent&&n.parent.type===`IfStatement`)return;let c=1,l=n;for(;l.alternate&&(c++,l.alternate.type===`IfStatement`);)l=l.alternate;c>=t&&e.report({node:n,messageId:`preferFold`,data:{type:s.type},fix(e){if(!a(n.test))return null;let t=r(n);return t?e.replaceText(n,t):null}})}return{IfStatement(e){s(e)},ConditionalExpression(t){let n=o(t.test);n.isMonadic&&e.report({node:t,messageId:`preferFoldTernary`,data:{type:n.type},fix(e){if(!a(t.test))return null;let n=i(t);return n?e.replaceText(t,n):null}})}}}};export{e as default};
1
+ const e={meta:{type:`suggestion`,docs:{description:`Prefer .fold() over if/else chains when working with monadic types`,recommended:!0},fixable:`code`,schema:[{type:`object`,properties:{minComplexity:{type:`integer`,minimum:1,default:2}},additionalProperties:!1}],messages:{preferFold:`Prefer .fold() over if/else when working with {{type}} types`,preferFoldTernary:`Consider using .fold() instead of ternary operator for {{type}}`}},create(e){let t=(e.options[0]||{}).minComplexity||2;function n(e,t){if(e.type===`BlockStatement`){let n=e.body;return n.length===1&&n[0].type===`ReturnStatement`?t.getText(n[0].argument):t.getText(e).slice(1,-1).trim()}else return t.getText(e)}function r(e,t){let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return e.replace(RegExp(`${n}\\.get\\(\\)`,`g`),`value`)}function i(t){let i=e.sourceCode;if(t.type!==`IfStatement`)return null;let a=t.test,o=t.consequent,s=t.alternate;if(!o||!s)return null;let c=null,l=!1;if(a.type===`CallExpression`&&a.callee.type===`MemberExpression`){let e=a.callee.property.name;c=i.getText(a.callee.object),e===`isSome`||e===`isRight`||e===`isSuccess`?l=!1:(e===`isNone`||e===`isEmpty`||e===`isLeft`||e===`isFailure`)&&(l=!0)}if(!c||o.type===`BlockStatement`&&(o.body.length!==1||o.body[0].type!==`ReturnStatement`))return null;if(s.type===`BlockStatement`){if(s.body.length!==1||s.body[0].type!==`ReturnStatement`)return null}else if(s.type===`IfStatement`)return null;let u=n(o,i),d=n(s,i);if(l){let e=r(d,c);return`${c}.fold(() => ${u}, (value) => ${e})`}else{let e=r(u,c);return`${c}.fold(() => ${d}, (value) => ${e})`}}function a(t){let n=e.sourceCode;if(t.type!==`ConditionalExpression`)return null;let i=t.test,a=t.consequent,o=t.alternate,s=null,c=!1;if(i.type===`CallExpression`&&i.callee.type===`MemberExpression`){let e=i.callee.property.name;s=n.getText(i.callee.object),e===`isSome`||e===`isRight`||e===`isSuccess`?c=!1:(e===`isNone`||e===`isEmpty`||e===`isLeft`||e===`isFailure`)&&(c=!0)}if(!s)return null;let l=n.getText(a),u=n.getText(o);if(c){let e=r(u,s);return`${s}.fold(() => ${l}, (value) => ${e})`}else{let e=r(l,s);return`${s}.fold(() => ${u}, (value) => ${e})`}}function o(e){if(e.type===`CallExpression`&&e.callee.type===`MemberExpression`){let t=e.callee.property.name;return[`isSome`,`isNone`,`isEmpty`,`isRight`,`isLeft`,`isSuccess`,`isFailure`].includes(t)}return!1}function s(t){let n=e.sourceCode.getText(t);if(/\.(isSome|isNone|isEmpty|isDefined)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Option`};if(/\.(isLeft|isRight)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Either`};if(/\.(isSuccess|isFailure)\s*\(\s*\)/.test(n))return{isMonadic:!0,type:`Result`};if(t.type===`BinaryExpression`){if((t.operator===`===`||t.operator===`!==`||t.operator===`==`||t.operator===`!=`)&&(t.left.type===`Literal`&&(t.left.value===null||t.left.value===void 0)||t.right.type===`Literal`&&(t.right.value===null||t.right.value===void 0)))return{isMonadic:!0,type:`Option`};if(t.operator===`==`||t.operator===`!=`||t.operator===`===`||t.operator===`!==`){let e=t.left.type===`Identifier`&&t.left.name===`undefined`,n=t.right.type===`Identifier`&&t.right.name===`undefined`;if(e||n)return{isMonadic:!0,type:`Option`}}}return{isMonadic:!1,type:``}}function c(n){let r=n.test,a=s(r);if(!a.isMonadic||n.parent&&n.parent.type===`IfStatement`)return;let c=1,l=n;for(;l.alternate&&(c++,l.alternate.type===`IfStatement`);)l=l.alternate;c>=t&&e.report({node:n,messageId:`preferFold`,data:{type:a.type},fix(e){if(!o(n.test))return null;let t=i(n);return t?e.replaceText(n,t):null}})}return{IfStatement(e){c(e)},ConditionalExpression(t){let n=s(t.test);n.isMonadic&&e.report({node:t,messageId:`preferFoldTernary`,data:{type:n.type},fix(e){if(!o(t.test))return null;let n=a(t);return n?e.replaceText(t,n):null}})}}}};export{e as default};
2
2
  //# sourceMappingURL=prefer-fold.js.map
@@ -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 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\n if (isNegated) {\n return `${monadicObj}.fold(() => ${thenBody}, () => ${elseBody})`\n } else {\n return `${monadicObj}.fold(() => ${elseBody}, (value) => ${thenBody})`\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\n if (isNegated) {\n return `${monadicObj}.fold(() => ${thenExpr}, () => ${elseExpr})`\n } else {\n return `${monadicObj}.fold(() => ${elseExpr}, (value) => ${thenExpr})`\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 === \"!==\") &&\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 // This might be checking an Option that hasn't been properly typed\n return { isMonadic: true, type: \"Option\" }\n }\n\n // Check for == or != with undefined\n if (node.operator === \"==\" || node.operator === \"!=\" || node.operator === \"===\" || node.operator === \"!==\") {\n const leftIsUndefined =\n (node.left.type === \"Identifier\" && node.left.name === \"undefined\") ||\n (node.left.type === \"Literal\" && node.left.value === undefined)\n const rightIsUndefined =\n (node.right.type === \"Identifier\" && node.right.name === \"undefined\") ||\n (node.right.type === \"Literal\" && node.right.value === 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-functype",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
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",