eslint-plugin-functype 2.104.0 → 2.104.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,8 @@ export default [functype.configs.strict]
30
30
  | Rule | Recommended | Strict | Description |
31
31
  | ------------------------------ | :---------: | :----: | -------------------------------------------------------- |
32
32
  | `functype/prefer-option` | warn | error | Use `Option`/`Some`/`None` instead of `null`/`undefined` |
33
- | `functype/prefer-either` | warn | error | Use `Either`/`Right`/`Left` instead of throwing |
33
+ | `functype/prefer-either` | warn | error | Use `Either`/`Left`/`Right` for typed domain errors |
34
+ | `functype/prefer-try` | warn | error | Prefer `Try(() => …)` for computations that may throw |
34
35
  | `functype/prefer-fold` | warn | warn | Use `fold`/`match` instead of manual unwrapping |
35
36
  | `functype/prefer-map` | warn | warn | Use `map` instead of manual option/either checks |
36
37
  | `functype/prefer-flatmap` | warn | warn | Use `flatMap` instead of nested `map` |
@@ -3,6 +3,7 @@ declare const recommendedRules: {
3
3
  "functype/no-let": string;
4
4
  "functype/prefer-option": string;
5
5
  "functype/prefer-either": string;
6
+ "functype/prefer-try": string;
6
7
  "functype/prefer-fold": string;
7
8
  "functype/prefer-map": string;
8
9
  "functype/prefer-flatmap": string;
@@ -1,2 +1,2 @@
1
- const e={"functype/no-let":`error`,"functype/prefer-option":`warn`,"functype/prefer-either":`warn`,"functype/prefer-fold":`warn`,"functype/prefer-map":`warn`,"functype/prefer-flatmap":`warn`,"functype/prefer-functype-map":`warn`,"functype/prefer-functype-set":`warn`,"functype/no-imperative-loops":`warn`,"functype/prefer-do-notation":`warn`,"functype/no-get-unsafe":`off`,"functype/prefer-list":`off`};export{e as default};
1
+ const e={"functype/no-let":`error`,"functype/prefer-option":`warn`,"functype/prefer-either":`warn`,"functype/prefer-try":`warn`,"functype/prefer-fold":`warn`,"functype/prefer-map":`warn`,"functype/prefer-flatmap":`warn`,"functype/prefer-functype-map":`warn`,"functype/prefer-functype-set":`warn`,"functype/no-imperative-loops":`warn`,"functype/prefer-do-notation":`warn`,"functype/no-get-unsafe":`off`,"functype/prefer-list":`off`};export{e as default};
2
2
  //# sourceMappingURL=recommended.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recommended.js","names":[],"sources":["../../src/configs/recommended.ts"],"sourcesContent":["const recommendedRules = {\n \"functype/no-let\": \"error\",\n \"functype/prefer-option\": \"warn\",\n \"functype/prefer-either\": \"warn\",\n \"functype/prefer-fold\": \"warn\",\n \"functype/prefer-map\": \"warn\",\n \"functype/prefer-flatmap\": \"warn\",\n \"functype/prefer-functype-map\": \"warn\",\n \"functype/prefer-functype-set\": \"warn\",\n \"functype/no-imperative-loops\": \"warn\",\n \"functype/prefer-do-notation\": \"warn\",\n \"functype/no-get-unsafe\": \"off\",\n \"functype/prefer-list\": \"off\",\n}\n\nexport default recommendedRules\n"],"mappings":"AAAA,MAAM,EAAmB,CACvB,kBAAmB,QACnB,yBAA0B,OAC1B,yBAA0B,OAC1B,uBAAwB,OACxB,sBAAuB,OACvB,0BAA2B,OAC3B,+BAAgC,OAChC,+BAAgC,OAChC,+BAAgC,OAChC,8BAA+B,OAC/B,yBAA0B,MAC1B,uBAAwB,KAC1B"}
1
+ {"version":3,"file":"recommended.js","names":[],"sources":["../../src/configs/recommended.ts"],"sourcesContent":["const recommendedRules = {\n \"functype/no-let\": \"error\",\n \"functype/prefer-option\": \"warn\",\n \"functype/prefer-either\": \"warn\",\n \"functype/prefer-try\": \"warn\",\n \"functype/prefer-fold\": \"warn\",\n \"functype/prefer-map\": \"warn\",\n \"functype/prefer-flatmap\": \"warn\",\n \"functype/prefer-functype-map\": \"warn\",\n \"functype/prefer-functype-set\": \"warn\",\n \"functype/no-imperative-loops\": \"warn\",\n \"functype/prefer-do-notation\": \"warn\",\n \"functype/no-get-unsafe\": \"off\",\n \"functype/prefer-list\": \"off\",\n}\n\nexport default recommendedRules\n"],"mappings":"AAAA,MAAM,EAAmB,CACvB,kBAAmB,QACnB,yBAA0B,OAC1B,yBAA0B,OAC1B,sBAAuB,OACvB,uBAAwB,OACxB,sBAAuB,OACvB,0BAA2B,OAC3B,+BAAgC,OAChC,+BAAgC,OAChC,+BAAgC,OAChC,8BAA+B,OAC/B,yBAA0B,MAC1B,uBAAwB,KAC1B"}
@@ -2,6 +2,7 @@
2
2
  declare const strictRules: {
3
3
  "functype/prefer-option": string;
4
4
  "functype/prefer-either": string;
5
+ "functype/prefer-try": string;
5
6
  "functype/no-get-unsafe": string;
6
7
  "functype/prefer-list": string;
7
8
  "functype/prefer-functype-map": string;
@@ -1,2 +1,2 @@
1
- import e from"./recommended.js";const t={...e,"functype/prefer-option":`error`,"functype/prefer-either":`error`,"functype/no-get-unsafe":`error`,"functype/prefer-list":`warn`,"functype/prefer-functype-map":`error`,"functype/prefer-functype-set":`error`,"functype/no-imperative-loops":`error`};export{t as default};
1
+ import e from"./recommended.js";const t={...e,"functype/prefer-option":`error`,"functype/prefer-either":`error`,"functype/prefer-try":`error`,"functype/no-get-unsafe":`error`,"functype/prefer-list":`warn`,"functype/prefer-functype-map":`error`,"functype/prefer-functype-set":`error`,"functype/no-imperative-loops":`error`};export{t as default};
2
2
  //# sourceMappingURL=strict.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"strict.js","names":[],"sources":["../../src/configs/strict.ts"],"sourcesContent":["import recommendedRules from \"./recommended\"\n\nconst strictRules = {\n ...recommendedRules,\n \"functype/prefer-option\": \"error\",\n \"functype/prefer-either\": \"error\",\n \"functype/no-get-unsafe\": \"error\",\n \"functype/prefer-list\": \"warn\",\n \"functype/prefer-functype-map\": \"error\",\n \"functype/prefer-functype-set\": \"error\",\n \"functype/no-imperative-loops\": \"error\",\n}\n\nexport default strictRules\n"],"mappings":"gCAEA,MAAM,EAAc,CAClB,GAAG,EACH,yBAA0B,QAC1B,yBAA0B,QAC1B,yBAA0B,QAC1B,uBAAwB,OACxB,+BAAgC,QAChC,+BAAgC,QAChC,+BAAgC,OAClC"}
1
+ {"version":3,"file":"strict.js","names":[],"sources":["../../src/configs/strict.ts"],"sourcesContent":["import recommendedRules from \"./recommended\"\n\nconst strictRules = {\n ...recommendedRules,\n \"functype/prefer-option\": \"error\",\n \"functype/prefer-either\": \"error\",\n \"functype/prefer-try\": \"error\",\n \"functype/no-get-unsafe\": \"error\",\n \"functype/prefer-list\": \"warn\",\n \"functype/prefer-functype-map\": \"error\",\n \"functype/prefer-functype-set\": \"error\",\n \"functype/no-imperative-loops\": \"error\",\n}\n\nexport default strictRules\n"],"mappings":"gCAEA,MAAM,EAAc,CAClB,GAAG,EACH,yBAA0B,QAC1B,yBAA0B,QAC1B,sBAAuB,QACvB,yBAA0B,QAC1B,uBAAwB,OACxB,+BAAgC,QAChC,+BAAgC,QAChC,+BAAgC,OAClC"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ declare const plugin: {
3
3
  rules: {
4
4
  "prefer-option": import("eslint").Rule.RuleModule;
5
5
  "prefer-either": import("eslint").Rule.RuleModule;
6
+ "prefer-try": import("eslint").Rule.RuleModule;
6
7
  "prefer-list": import("eslint").Rule.RuleModule;
7
8
  "no-get-unsafe": import("eslint").Rule.RuleModule;
8
9
  "no-let": import("eslint").Rule.RuleModule;
@@ -10,11 +10,13 @@ import rule$8 from "./prefer-functype-set.js";
10
10
  import rule$9 from "./prefer-list.js";
11
11
  import rule$10 from "./prefer-map.js";
12
12
  import rule$11 from "./prefer-option.js";
13
+ import rule$12 from "./prefer-try.js";
13
14
 
14
15
  //#region src/rules/index.d.ts
15
16
  declare const _default: {
16
17
  "prefer-option": import("eslint").Rule.RuleModule;
17
18
  "prefer-either": import("eslint").Rule.RuleModule;
19
+ "prefer-try": import("eslint").Rule.RuleModule;
18
20
  "prefer-list": import("eslint").Rule.RuleModule;
19
21
  "no-get-unsafe": import("eslint").Rule.RuleModule;
20
22
  "no-let": import("eslint").Rule.RuleModule;
@@ -27,5 +29,5 @@ declare const _default: {
27
29
  "prefer-do-notation": import("eslint").Rule.RuleModule;
28
30
  };
29
31
  //#endregion
30
- export { _default as default, rule as noGetUnsafe, rule$1 as noImperativeLoops, rule$2 as noLet, rule$3 as preferDoNotation, rule$4 as preferEither, rule$5 as preferFlatmap, rule$6 as preferFold, rule$7 as preferFunctypeMap, rule$8 as preferFunctypeSet, rule$9 as preferList, rule$10 as preferMap, rule$11 as preferOption };
32
+ export { _default as default, rule as noGetUnsafe, rule$1 as noImperativeLoops, rule$2 as noLet, rule$3 as preferDoNotation, rule$4 as preferEither, rule$5 as preferFlatmap, rule$6 as preferFold, rule$7 as preferFunctypeMap, rule$8 as preferFunctypeSet, rule$9 as preferList, rule$10 as preferMap, rule$11 as preferOption, rule$12 as preferTry };
31
33
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- import e from"./no-get-unsafe.js";import t from"./no-imperative-loops.js";import n from"./no-let.js";import r from"./prefer-do-notation.js";import i from"./prefer-either.js";import a from"./prefer-flatmap.js";import o from"./prefer-fold.js";import s from"./prefer-functype-map.js";import c from"./prefer-functype-set.js";import l from"./prefer-list.js";import u from"./prefer-map.js";import d from"./prefer-option.js";var f={"prefer-option":d,"prefer-either":i,"prefer-list":l,"no-get-unsafe":e,"no-let":n,"prefer-fold":o,"prefer-map":u,"prefer-flatmap":a,"prefer-functype-map":s,"prefer-functype-set":c,"no-imperative-loops":t,"prefer-do-notation":r};export{f as default,e as noGetUnsafe,t as noImperativeLoops,n as noLet,r as preferDoNotation,i as preferEither,a as preferFlatmap,o as preferFold,s as preferFunctypeMap,c as preferFunctypeSet,l as preferList,u as preferMap,d as preferOption};
1
+ import e from"./no-get-unsafe.js";import t from"./no-imperative-loops.js";import n from"./no-let.js";import r from"./prefer-do-notation.js";import i from"./prefer-either.js";import a from"./prefer-flatmap.js";import o from"./prefer-fold.js";import s from"./prefer-functype-map.js";import c from"./prefer-functype-set.js";import l from"./prefer-list.js";import u from"./prefer-map.js";import d from"./prefer-option.js";import f from"./prefer-try.js";var p={"prefer-option":d,"prefer-either":i,"prefer-try":f,"prefer-list":l,"no-get-unsafe":e,"no-let":n,"prefer-fold":o,"prefer-map":u,"prefer-flatmap":a,"prefer-functype-map":s,"prefer-functype-set":c,"no-imperative-loops":t,"prefer-do-notation":r};export{p as default,e as noGetUnsafe,t as noImperativeLoops,n as noLet,r as preferDoNotation,i as preferEither,a as preferFlatmap,o as preferFold,s as preferFunctypeMap,c as preferFunctypeSet,l as preferList,u as preferMap,d as preferOption,f as preferTry};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["preferOption","preferEither","preferList","noGetUnsafe","noLet","preferFold","preferMap","preferFlatmap","preferFunctypeMap","preferFunctypeSet","noImperativeLoops","preferDoNotation"],"sources":["../../src/rules/index.ts"],"sourcesContent":["import noGetUnsafe from \"./no-get-unsafe\"\nimport noImperativeLoops from \"./no-imperative-loops\"\nimport noLet from \"./no-let\"\nimport preferDoNotation from \"./prefer-do-notation\"\nimport preferEither from \"./prefer-either\"\nimport preferFlatmap from \"./prefer-flatmap\"\nimport preferFold from \"./prefer-fold\"\nimport preferFunctypeMap from \"./prefer-functype-map\"\nimport preferFunctypeSet from \"./prefer-functype-set\"\nimport preferList from \"./prefer-list\"\nimport preferMap from \"./prefer-map\"\nimport preferOption from \"./prefer-option\"\n\nexport {\n noGetUnsafe,\n noImperativeLoops,\n noLet,\n preferDoNotation,\n preferEither,\n preferFlatmap,\n preferFold,\n preferFunctypeMap,\n preferFunctypeSet,\n preferList,\n preferMap,\n preferOption,\n}\n\nexport default {\n \"prefer-option\": preferOption,\n \"prefer-either\": preferEither,\n \"prefer-list\": preferList,\n \"no-get-unsafe\": noGetUnsafe,\n \"no-let\": noLet,\n \"prefer-fold\": preferFold,\n \"prefer-map\": preferMap,\n \"prefer-flatmap\": preferFlatmap,\n \"prefer-functype-map\": preferFunctypeMap,\n \"prefer-functype-set\": preferFunctypeSet,\n \"no-imperative-loops\": noImperativeLoops,\n \"prefer-do-notation\": preferDoNotation,\n}\n"],"mappings":"kaA4BA,IAAA,EAAe,CACb,gBAAiBA,EACjB,gBAAiBC,EACjB,cAAeC,EACf,gBAAiBC,EACjB,SAAUC,EACV,cAAeC,EACf,aAAcC,EACd,iBAAkBC,EAClB,sBAAuBC,EACvB,sBAAuBC,EACvB,sBAAuBC,EACvB,qBAAsBC,CACxB"}
1
+ {"version":3,"file":"index.js","names":["preferOption","preferEither","preferTry","preferList","noGetUnsafe","noLet","preferFold","preferMap","preferFlatmap","preferFunctypeMap","preferFunctypeSet","noImperativeLoops","preferDoNotation"],"sources":["../../src/rules/index.ts"],"sourcesContent":["import noGetUnsafe from \"./no-get-unsafe\"\nimport noImperativeLoops from \"./no-imperative-loops\"\nimport noLet from \"./no-let\"\nimport preferDoNotation from \"./prefer-do-notation\"\nimport preferEither from \"./prefer-either\"\nimport preferFlatmap from \"./prefer-flatmap\"\nimport preferFold from \"./prefer-fold\"\nimport preferFunctypeMap from \"./prefer-functype-map\"\nimport preferFunctypeSet from \"./prefer-functype-set\"\nimport preferList from \"./prefer-list\"\nimport preferMap from \"./prefer-map\"\nimport preferOption from \"./prefer-option\"\nimport preferTry from \"./prefer-try\"\n\nexport {\n noGetUnsafe,\n noImperativeLoops,\n noLet,\n preferDoNotation,\n preferEither,\n preferFlatmap,\n preferFold,\n preferFunctypeMap,\n preferFunctypeSet,\n preferList,\n preferMap,\n preferOption,\n preferTry,\n}\n\nexport default {\n \"prefer-option\": preferOption,\n \"prefer-either\": preferEither,\n \"prefer-try\": preferTry,\n \"prefer-list\": preferList,\n \"no-get-unsafe\": noGetUnsafe,\n \"no-let\": noLet,\n \"prefer-fold\": preferFold,\n \"prefer-map\": preferMap,\n \"prefer-flatmap\": preferFlatmap,\n \"prefer-functype-map\": preferFunctypeMap,\n \"prefer-functype-set\": preferFunctypeSet,\n \"no-imperative-loops\": noImperativeLoops,\n \"prefer-do-notation\": preferDoNotation,\n}\n"],"mappings":"icA8BA,IAAA,EAAe,CACb,gBAAiBA,EACjB,gBAAiBC,EACjB,aAAcC,EACd,cAAeC,EACf,gBAAiBC,EACjB,SAAUC,EACV,cAAeC,EACf,aAAcC,EACd,iBAAkBC,EAClB,sBAAuBC,EACvB,sBAAuBC,EACvB,sBAAuBC,EACvB,qBAAsBC,CACxB"}
@@ -1,2 +1,2 @@
1
- import{createImportFixer as e,hasFunctypeSymbol as t}from"../utils/import-fixer.js";const n=new Set([`parent`,`loc`,`range`]);function r(e){return Object.entries(e).filter(([e])=>!n.has(e)).flatMap(([,e])=>Array.isArray(e)?e:[e]).filter(e=>typeof e==`object`&&!!e)}function i(e){let t=e?.parent;return t?t.type===`CatchClause`||i(t):!1}const a={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer Either<E, T> over try/catch blocks and throw statements`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverTryCatch:`Prefer Either<Error, T> over try/catch block`,preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`,suggestTry:`Replace with Try(() => ...)`,suggestTryFromPromise:`Replace with Try.fromPromise(...)`,suggestEitherLeft:`Replace with Either.left(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(n){let a=(n.options[0]||{}).allowThrowInTests!==!1;function o(){let e=n.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function s(e){return e?e.type===`ThrowStatement`?!i(e):e.type===`CatchClause`?!1:r(e).some(e=>s(e)):!1}function c(e){if(!(a&&o())&&e.body&&s(e.body)){let t=e.returnType?.typeAnnotation;if(t){let r=n.sourceCode.getText(t);r.includes(`Either`)||n.report({node:e.id||e,messageId:`preferEitherReturn`,data:{type:r}})}}}function l(e){return e.block?.body?.length===1}function u(e){if(!e.handler?.body)return!1;let t=e.handler.body.body;return t.length===0||t.length===1&&(t[0].type===`ReturnStatement`||t[0].type===`ExpressionStatement`)}function d(e){let t=e.block.body[0];return t.type===`ReturnStatement`&&t.argument?.type===`AwaitExpression`||t.type===`ExpressionStatement`&&t.expression?.type===`AwaitExpression`}function f(e){return e?[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`].includes(e.type):!1}function p(e){let t=e.parent;if(!t)return!1;if(t.type===`BlockStatement`&&f(t.parent))return!0;if(t.type===`BlockStatement`&&t.parent?.type===`IfStatement`){let e=t.parent.parent;return e?.type===`BlockStatement`&&f(e.parent)}return!1}return{TryStatement(r){if(a&&o()||r.handler&&r.handler.body&&r.handler.body.body.some(e=>e.type===`ThrowStatement`))return;let i=n.sourceCode,s=[];if(l(r)&&u(r)){let n=r.block.body[0];if(n.type===`ReturnStatement`){let a=n.argument,o=i.getText(a);if(d(r)){let e=a.type===`AwaitExpression`?a.argument:a,t=i.getText(e);s.push({messageId:`suggestTryFromPromise`,fix(e){return e.replaceText(r,`return Try.fromPromise(${t})`)}})}else s.push({messageId:`suggestTry`,fix(e){return e.replaceText(r,`return Try(() => ${o})`)}});t(i,`Try`)||s.push({messageId:`suggestAddImport`,data:{symbol:`Try`},fix:e(i,`Try`)})}}n.report({node:r,messageId:`preferEitherOverTryCatch`,suggest:s})},ThrowStatement(r){if(a&&o()||i(r))return;let s=n.sourceCode,c=[];if(p(r)){let n=r.argument,i=s.getText(n),a=n?.type===`NewExpression`&&n.callee?.type===`Identifier`&&n.callee.name===`Error`?i:`new Error(String(${i}))`;c.push({messageId:`suggestEitherLeft`,fix(e){return e.replaceText(r,`return Either.left(${a})`)}}),t(s,`Either`)||c.push({messageId:`suggestAddImport`,data:{symbol:`Either`},fix:e(s,`Either`)})}n.report({node:r,messageId:`preferEitherOverThrow`,suggest:c})},FunctionDeclaration(e){c(e)},ArrowFunctionExpression(e){c(e)}}}};export{a as default};
1
+ import{createImportFixer as e,hasFunctypeSymbol as t}from"../utils/import-fixer.js";const n=new Set([`parent`,`loc`,`range`]);function r(e){return Object.entries(e).filter(([e])=>!n.has(e)).flatMap(([,e])=>Array.isArray(e)?e:[e]).filter(e=>typeof e==`object`&&!!e)}function i(e){let t=e?.parent;return t?t.type===`CatchClause`||i(t):!1}const a={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Use Either/Left/Right for typed domain errors instead of throwing`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`,suggestEitherLeft:`Replace with Either.left(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(n){let a=(n.options[0]||{}).allowThrowInTests!==!1;function o(){let e=n.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function s(e){return e?e.type===`ThrowStatement`?!i(e):e.type===`CatchClause`?!1:r(e).some(e=>s(e)):!1}function c(e){if(!(a&&o())&&e.body&&s(e.body)){let t=e.returnType?.typeAnnotation;if(t){let r=n.sourceCode.getText(t);r.includes(`Either`)||n.report({node:e.id||e,messageId:`preferEitherReturn`,data:{type:r}})}}}function l(e){return e?[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`].includes(e.type):!1}function u(e){let t=e.parent;if(!t)return!1;if(t.type===`BlockStatement`&&l(t.parent))return!0;if(t.type===`BlockStatement`&&t.parent?.type===`IfStatement`){let e=t.parent.parent;return e?.type===`BlockStatement`&&l(e.parent)}return!1}return{ThrowStatement(r){if(a&&o()||i(r))return;let s=n.sourceCode,c=[];if(u(r)){let n=r.argument,i=s.getText(n),a=n?.type===`NewExpression`&&n.callee?.type===`Identifier`&&n.callee.name===`Error`?i:`new Error(String(${i}))`;c.push({messageId:`suggestEitherLeft`,fix(e){return e.replaceText(r,`return Either.left(${a})`)}}),t(s,`Either`)||c.push({messageId:`suggestAddImport`,data:{symbol:`Either`},fix:e(s,`Either`)})}n.report({node:r,messageId:`preferEitherOverThrow`,suggest:c})},FunctionDeclaration(e){c(e)},ArrowFunctionExpression(e){c(e)}}}};export{a as default};
2
2
  //# sourceMappingURL=prefer-either.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-either.js","names":[],"sources":["../../src/rules/prefer-either.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\n/** AST keys that don't represent syntax children — back-edges and source metadata. */\nconst NON_CHILD_KEYS: ReadonlySet<string> = new Set([\"parent\", \"loc\", \"range\"])\n\n/** AST children of `node` (drops back-edges, flattens array-valued keys, filters non-object leaves). */\nfunction astChildren(node: ASTNode): readonly unknown[] {\n return Object.entries(node)\n .filter(([k]) => !NON_CHILD_KEYS.has(k))\n .flatMap(([, v]) => (Array.isArray(v) ? v : [v]))\n .filter((v) => v !== null && typeof v === \"object\")\n}\n\n/** True iff any ancestor of `node` is a `CatchClause`. Pure tail recursion. */\nfunction insideCatchClause(node: ASTNode | null | undefined): boolean {\n const parent = node?.parent as ASTNode | undefined\n if (!parent) return false\n return parent.type === \"CatchClause\" || insideCatchClause(parent)\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"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 // A throw that's *not* inside a catch is the thing we're looking for.\n if (node.type === \"ThrowStatement\") return !insideCatchClause(node)\n\n // Catch bodies don't count — re-throws inside them are allowed.\n if (node.type === \"CatchClause\") return false\n\n // Recurse: true iff any child satisfies the predicate.\n return astChildren(node).some((child) => hasThrowStatementsOutsideCatch(child as ASTNode))\n }\n\n function checkFunctionForThrows(node: ASTNode): void {\n // Allow functions in test files\n if (allowThrowInTests && isInTestFile()) return\n\n if (!node.body) return\n\n // Only report function-level errors if there are throws NOT in catch blocks\n const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body)\n if (hasThrowsNotInCatch) {\n const returnType = node.returnType?.typeAnnotation\n if (returnType) {\n const sourceCode = context.sourceCode\n const returnTypeText = sourceCode.getText(returnType)\n\n // Don't report if already using Either\n if (!returnTypeText.includes(\"Either\")) {\n context.report({\n node: node.id || node,\n messageId: \"preferEitherReturn\",\n data: { type: returnTypeText },\n })\n }\n }\n }\n }\n\n function 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 if (insideCatchClause(node)) return\n\n const sourceCode = context.sourceCode\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isDirectInFunctionBody(node)) {\n const throwArg = node.argument\n const argText = sourceCode.getText(throwArg)\n const isErrorExpr =\n throwArg?.type === \"NewExpression\" &&\n throwArg.callee?.type === \"Identifier\" &&\n throwArg.callee.name === \"Error\"\n const eitherArg = isErrorExpr ? argText : `new Error(String(${argText}))`\n\n suggest.push({\n messageId: \"suggestEitherLeft\",\n fix(fixer) {\n return fixer.replaceText(node, `return Either.left(${eitherArg})`)\n },\n })\n\n if (!hasFunctypeSymbol(sourceCode, \"Either\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Either\" },\n fix: createImportFixer(sourceCode, \"Either\"),\n })\n }\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverThrow\",\n suggest,\n })\n },\n\n FunctionDeclaration(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n\n ArrowFunctionExpression(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
1
+ {"version":3,"file":"prefer-either.js","names":[],"sources":["../../src/rules/prefer-either.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\n/** AST keys that don't represent syntax children — back-edges and source metadata. */\nconst NON_CHILD_KEYS: ReadonlySet<string> = new Set([\"parent\", \"loc\", \"range\"])\n\n/** AST children of `node` (drops back-edges, flattens array-valued keys, filters non-object leaves). */\nfunction astChildren(node: ASTNode): readonly unknown[] {\n return Object.entries(node)\n .filter(([k]) => !NON_CHILD_KEYS.has(k))\n .flatMap(([, v]) => (Array.isArray(v) ? v : [v]))\n .filter((v) => v !== null && typeof v === \"object\")\n}\n\n/** True iff any ancestor of `node` is a `CatchClause`. Pure tail recursion. */\nfunction insideCatchClause(node: ASTNode | null | undefined): boolean {\n const parent = node?.parent as ASTNode | undefined\n if (!parent) return false\n return parent.type === \"CatchClause\" || insideCatchClause(parent)\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Use Either/Left/Right for typed domain errors instead of throwing\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowThrowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferEitherOverThrow: \"Prefer Either.left(error) over throw statement\",\n preferEitherReturn: \"Consider returning Either<Error, {{type}}> instead of throwing\",\n suggestEitherLeft: \"Replace with Either.left(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowThrowInTests = options.allowThrowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function hasThrowStatementsOutsideCatch(node: ASTNode): boolean {\n if (!node) return false\n\n // A throw that's *not* inside a catch is the thing we're looking for.\n if (node.type === \"ThrowStatement\") return !insideCatchClause(node)\n\n // Catch bodies don't count — re-throws inside them are allowed.\n if (node.type === \"CatchClause\") return false\n\n // Recurse: true iff any child satisfies the predicate.\n return astChildren(node).some((child) => hasThrowStatementsOutsideCatch(child as ASTNode))\n }\n\n function checkFunctionForThrows(node: ASTNode): void {\n // Allow functions in test files\n if (allowThrowInTests && isInTestFile()) return\n\n if (!node.body) return\n\n // Only report function-level errors if there are throws NOT in catch blocks\n const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body)\n if (hasThrowsNotInCatch) {\n const returnType = node.returnType?.typeAnnotation\n if (returnType) {\n const sourceCode = context.sourceCode\n const returnTypeText = sourceCode.getText(returnType)\n\n // Don't report if already using Either\n if (!returnTypeText.includes(\"Either\")) {\n context.report({\n node: node.id || node,\n messageId: \"preferEitherReturn\",\n data: { type: returnTypeText },\n })\n }\n }\n }\n }\n\n function isFunctionLike(node: ASTNode): boolean {\n if (!node) return false\n return [\"FunctionDeclaration\", \"FunctionExpression\", \"ArrowFunctionExpression\"].includes(node.type)\n }\n\n function isDirectInFunctionBody(node: ASTNode): boolean {\n const parent = node.parent\n if (!parent) return false\n if (parent.type === \"BlockStatement\" && isFunctionLike(parent.parent)) return true\n if (parent.type === \"BlockStatement\" && parent.parent?.type === \"IfStatement\") {\n const ifParent = parent.parent.parent\n return ifParent?.type === \"BlockStatement\" && isFunctionLike(ifParent.parent)\n }\n return false\n }\n\n return {\n ThrowStatement(node: ASTNode) {\n // Allow throws in test files if configured\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow re-throwing in catch blocks (common pattern)\n if (insideCatchClause(node)) return\n\n const sourceCode = context.sourceCode\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isDirectInFunctionBody(node)) {\n const throwArg = node.argument\n const argText = sourceCode.getText(throwArg)\n const isErrorExpr =\n throwArg?.type === \"NewExpression\" &&\n throwArg.callee?.type === \"Identifier\" &&\n throwArg.callee.name === \"Error\"\n const eitherArg = isErrorExpr ? argText : `new Error(String(${argText}))`\n\n suggest.push({\n messageId: \"suggestEitherLeft\",\n fix(fixer) {\n return fixer.replaceText(node, `return Either.left(${eitherArg})`)\n },\n })\n\n if (!hasFunctypeSymbol(sourceCode, \"Either\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Either\" },\n fix: createImportFixer(sourceCode, \"Either\"),\n })\n }\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverThrow\",\n suggest,\n })\n },\n\n FunctionDeclaration(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n\n ArrowFunctionExpression(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/prefer-try.d.ts
4
+ declare const rule: Rule.RuleModule;
5
+ //#endregion
6
+ export { rule as default };
7
+ //# sourceMappingURL=prefer-try.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{createImportFixer as e,hasFunctypeSymbol as t}from"../utils/import-fixer.js";const n={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer Try for computations that may throw`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferTryOverTryCatch:`Prefer Try(() => ...) over try/catch block`,suggestTry:`Replace with Try(() => ...)`,suggestTryFromPromise:`Replace with Try.fromPromise(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(n){let r=(n.options[0]||{}).allowThrowInTests!==!1;function i(){let e=n.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function a(e){return e.block?.body?.length===1}function o(e){if(!e.handler?.body)return!1;let t=e.handler.body.body;return t.length===0||t.length===1&&(t[0].type===`ReturnStatement`||t[0].type===`ExpressionStatement`)}function s(e){let t=e.block.body[0];return t.type===`ReturnStatement`&&t.argument?.type===`AwaitExpression`||t.type===`ExpressionStatement`&&t.expression?.type===`AwaitExpression`}return{TryStatement(c){if(r&&i()||c.handler&&c.handler.body&&c.handler.body.body.some(e=>e.type===`ThrowStatement`))return;let l=n.sourceCode,u=[];if(a(c)&&o(c)){let n=c.block.body[0];if(n.type===`ReturnStatement`){let r=n.argument,i=l.getText(r);if(s(c)){let e=r.type===`AwaitExpression`?r.argument:r,t=l.getText(e);u.push({messageId:`suggestTryFromPromise`,fix(e){return e.replaceText(c,`return Try.fromPromise(${t})`)}})}else u.push({messageId:`suggestTry`,fix(e){return e.replaceText(c,`return Try(() => ${i})`)}});t(l,`Try`)||u.push({messageId:`suggestAddImport`,data:{symbol:`Try`},fix:e(l,`Try`)})}}n.report({node:c,messageId:`preferTryOverTryCatch`,suggest:u})}}}};export{n as default};
2
+ //# sourceMappingURL=prefer-try.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-try.js","names":[],"sources":["../../src/rules/prefer-try.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 Try for computations that may throw\",\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 preferTryOverTryCatch: \"Prefer Try(() => ...) over try/catch block\",\n suggestTry: \"Replace with Try(() => ...)\",\n suggestTryFromPromise: \"Replace with Try.fromPromise(...)\",\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 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 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: \"preferTryOverTryCatch\",\n suggest,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"oFAKA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,6CACb,YAAa,EACf,EACA,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,kBAAmB,CACjB,KAAM,UACN,QAAS,EACX,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,sBAAuB,6CACvB,WAAY,8BACZ,sBAAuB,oCACvB,iBAAkB,qCACpB,CACF,EAEA,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,CAAC,EAAA,CACL,oBAAsB,GAExD,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,EAAgB,EAAwB,CAC/C,OAAO,EAAK,OAAO,MAAM,SAAW,CACtC,CAEA,SAAS,EAAc,EAAwB,CAC7C,GAAI,CAAC,EAAK,SAAS,KAAM,MAAO,GAChC,IAAM,EAAY,EAAK,QAAQ,KAAK,KACpC,OACE,EAAU,SAAW,GACpB,EAAU,SAAW,IACnB,EAAU,EAAE,CAAC,OAAS,mBAAqB,EAAU,EAAE,CAAC,OAAS,sBAExE,CAEA,SAAS,EAAgB,EAAwB,CAC/C,IAAM,EAAO,EAAK,MAAM,KAAK,GAG7B,OAFI,EAAK,OAAS,mBAAqB,EAAK,UAAU,OAAS,mBAC3D,EAAK,OAAS,uBAAyB,EAAK,YAAY,OAAS,iBAEvE,CAEA,MAAO,CACL,aAAa,EAAe,CAK1B,GAHI,GAAqB,EAAa,GAGlC,EAAK,SAAW,EAAK,QAAQ,MACb,EAAK,QAAQ,KAAK,KACP,KAAM,GAAkB,EAAK,OAAS,gBACtD,EAAG,OAGlB,IAAM,EAAa,EAAQ,WACrB,EAA6C,CAAC,EAEpD,GAAI,EAAgB,CAAI,GAAK,EAAc,CAAI,EAAG,CAChD,IAAM,EAAU,EAAK,MAAM,KAAK,GAKhC,GAJiB,EAAQ,OAAS,kBAIpB,CACZ,IAAM,EAAO,EAAQ,SACf,EAAW,EAAW,QAAQ,CAAI,EAExC,GAAI,EAAgB,CAAI,EAAG,CACzB,IAAM,EAAY,EAAK,OAAS,kBAAoB,EAAK,SAAW,EAC9D,EAAY,EAAW,QAAQ,CAAS,EAC9C,EAAQ,KAAK,CACX,UAAW,wBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,0BAA0B,EAAU,EAAE,CACvE,CACF,CAAC,CACH,MACE,EAAQ,KAAK,CACX,UAAW,aACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,oBAAoB,EAAS,EAAE,CAChE,CACF,CAAC,EAGE,EAAkB,EAAY,KAAK,GACtC,EAAQ,KAAK,CACX,UAAW,mBACX,KAAM,CAAE,OAAQ,KAAM,EACtB,IAAK,EAAkB,EAAY,KAAK,CAC1C,CAAC,CAEL,CACF,CAEA,EAAQ,OAAO,CACb,OACA,UAAW,wBACX,SACF,CAAC,CACH,CACF,CACF,CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-functype",
3
- "version": "2.104.0",
3
+ "version": "2.104.1",
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",
@@ -44,7 +44,7 @@
44
44
  "eslint-config-prettier": "^10.1.8",
45
45
  "ts-builds": "^3.1.0",
46
46
  "tsdown": "^0.22.2",
47
- "functype": "^1.4.0"
47
+ "functype": "^1.4.1"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public",