eslint-plugin-functype 2.104.0 → 2.104.2
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 +2 -1
- package/dist/configs/recommended.d.ts +1 -0
- package/dist/configs/recommended.js +1 -1
- package/dist/configs/recommended.js.map +1 -1
- package/dist/configs/strict.d.ts +1 -0
- package/dist/configs/strict.js +1 -1
- package/dist/configs/strict.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/rules/index.d.ts +3 -1
- package/dist/rules/index.js +1 -1
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/prefer-do-notation.js +2 -2
- package/dist/rules/prefer-do-notation.js.map +1 -1
- package/dist/rules/prefer-either.js +1 -1
- package/dist/rules/prefer-either.js.map +1 -1
- package/dist/rules/prefer-try.d.ts +7 -0
- package/dist/rules/prefer-try.js +2 -0
- package/dist/rules/prefer-try.js.map +1 -0
- package/package.json +4 -4
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
|
|
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"}
|
package/dist/configs/strict.d.ts
CHANGED
package/dist/configs/strict.js
CHANGED
|
@@ -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;
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -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
|
package/dist/rules/index.js
CHANGED
|
@@ -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
|
|
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
|
package/dist/rules/index.js.map
CHANGED
|
@@ -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":"
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import{getFunctypeImports as e,isChainedMethodCall as t}from"../utils/functype-detection.js";const n=new Set([`Option`,`Either`,`Try`,`Task`]),r=new Set([`parent`,`loc`,`range`]),i=(e,t)=>new Set([...e,...t]);function a(e){if(e.type!==`CallExpression`)return new Set;let t=e.callee;return t?.type===`Identifier`&&n.has(t.name)?new Set([t.name]):t?.type===`MemberExpression`&&t.object?.type===`Identifier`&&n.has(t.object.name)?new Set([t.object.name]):new Set}function o(e){return Object.entries(e).filter(([e])=>!r.has(e)).flatMap(([,e])=>Array.isArray(e)?e:[e]).filter(e=>typeof e==`object`&&!!e)}function s(e){if(!e||typeof e!=`object`)return new Set;let t=e;return o(t).reduce((e,t)=>i(e,s(t)),a(t))}const c=new Set([`flatMap`,`map`,`filter`,`fold`]);function
|
|
1
|
+
import{getFunctypeImports as e,isChainedMethodCall as t}from"../utils/functype-detection.js";const n=new Set([`Option`,`Either`,`Try`,`Task`]),r=new Set([`parent`,`loc`,`range`]),i=(e,t)=>new Set([...e,...t]);function a(e){if(e.type!==`CallExpression`)return new Set;let t=e.callee;return t?.type===`Identifier`&&n.has(t.name)?new Set([t.name]):t?.type===`MemberExpression`&&t.object?.type===`Identifier`&&n.has(t.object.name)?new Set([t.object.name]):new Set}function o(e){return Object.entries(e).filter(([e])=>!r.has(e)).flatMap(([,e])=>Array.isArray(e)?e:[e]).filter(e=>typeof e==`object`&&!!e)}function s(e){if(!e||typeof e!=`object`)return new Set;let t=e;return o(t).reduce((e,t)=>i(e,s(t)),a(t))}const c=new Set([`flatMap`,`map`,`filter`,`fold`]),l=new Set([`flatMap`,`map`,`filter`]);function u(e){if(e.callee.type!==`MemberExpression`||e.callee.object.type!==`CallExpression`)return 1;let t=e.callee.property,n=e.callee.object;return(t.type===`Identifier`&&c.has(t.name)?1:0)+u(n)}function d(e){if(e.callee?.type!==`MemberExpression`||e.callee.object?.type!==`CallExpression`)return 0;let t=e.callee.property,n=e.callee.object;return(t.type===`Identifier`&&l.has(t.name)?1:0)+d(n)}function f(e){if(e.callee?.type!==`MemberExpression`)return!1;let t=e.callee.property;return t.type!==`Identifier`||!l.has(t.name)?!1:d(e)>=1}function p(e){if(e.type!==`LogicalExpression`||e.operator!==`&&`)return 0;let t=e.right;return+(t.type===`MemberExpression`||t.type===`LogicalExpression`&&t.operator===`&&`)+p(e.left)}const m={meta:{type:`suggestion`,docs:{description:`Prefer Do notation for complex monadic compositions and nested operations`,recommended:!0},fixable:`code`,messages:{preferDoForNestedChecks:`Prefer Do notation for nested null/undefined checks instead of logical AND chains`,preferDoForChainedMethods:`Prefer Do notation for complex flatMap chains ({{count}} levels deep)`,preferDoForMixedMonads:`Consider Do notation when mixing Option, Either, and Try operations`,preferDoAsyncForChainedTasks:`Prefer DoAsync notation for chained async operations`},schema:[{type:`object`,properties:{minChainDepth:{type:`integer`,minimum:2,default:3},detectMixedMonads:{type:`boolean`,default:!0}},additionalProperties:!1}]},create(n){let r=n.options[0]||{},i=r.minChainDepth||3,a=r.detectMixedMonads!==!1,o=e(n),c=o.functions.has(`Do`)||o.functions.has(`DoAsync`);function l(e){e.operator===`&&`&&p(e)>=2&&d(e)&&n.report({node:e,messageId:`preferDoForNestedChecks`,fix:c?t=>h(t,e):void 0})}function d(e){let t=n.sourceCode.getText(e).match(/\w+(\.\w+){2,}/g);return t!==null&&t.length>0}function m(e){if(!t(e,`flatMap`))return;let r=u(e);r>=i&&n.report({node:e,messageId:`preferDoForChainedMethods`,data:{count:r.toString()},fix:c?t=>g(t,e):void 0})}function h(e,t){let r=n.sourceCode.getText(t),i=r.match(/(\w+)(\.\w+)+/);if(i){let n=i[1],r=`Do(function* () {
|
|
2
2
|
const obj = yield* $(Option(${n}))
|
|
3
3
|
return yield* $(Option(obj${i[0].substring(n.length)}))
|
|
4
|
-
})`;return e.replaceText(t,r)}return e.replaceText(t,`/* TODO: Convert to Do notation */ ${r}`)}function
|
|
4
|
+
})`;return e.replaceText(t,r)}return e.replaceText(t,`/* TODO: Convert to Do notation */ ${r}`)}function g(e,t){let r=n.sourceCode.getText(t);return e.replaceText(t,`/* TODO: Consider Do notation for complex chains */ ${r}`)}function _(e){a&&f(e)&&s(e).size>=2&&n.report({node:e,messageId:`preferDoForMixedMonads`})}return{LogicalExpression(e){l(e)},CallExpression(e){m(e),_(e)}}}};export{m as default};
|
|
5
5
|
//# sourceMappingURL=prefer-do-notation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-do-notation.js","names":[],"sources":["../../src/rules/prefer-do-notation.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImports, isChainedMethodCall } from \"../utils/functype-detection\"\n\nconst MONAD_TYPES: ReadonlySet<string> = new Set([\"Option\", \"Either\", \"Try\", \"Task\"])\n\n/** AST keys that don't represent syntax children — back-edges and source metadata. */\nconst NON_CHILD_KEYS: ReadonlySet<string> = new Set([\"parent\", \"loc\", \"range\"])\n\nconst union = <T>(a: ReadonlySet<T>, b: ReadonlySet<T>): ReadonlySet<T> => new Set([...a, ...b])\n\n/**\n * Set of monad names that *this single AST node* names as a constructor call.\n * Does not look at children. Returns empty set for any non-monad-constructor node.\n */\nfunction monadAt(node: ASTNode): ReadonlySet<string> {\n if (node.type !== \"CallExpression\") return new Set()\n const callee = node.callee\n // Bare constructor: Option(...), Try(...), Either(...), Task(...)\n if (callee?.type === \"Identifier\" && MONAD_TYPES.has(callee.name)) {\n return new Set([callee.name])\n }\n // Companion call: Option.none(), Either.right(x), Try.success(v)\n if (\n callee?.type === \"MemberExpression\" &&\n callee.object?.type === \"Identifier\" &&\n MONAD_TYPES.has(callee.object.name)\n ) {\n return new Set([callee.object.name])\n }\n return new Set()\n}\n\n/**\n * AST children of `node` worth recursing into. Drops back-edges and source\n * metadata; flattens array-valued keys (e.g. `arguments`); filters non-object\n * leaves (literals, raw strings, numbers).\n */\nfunction astChildren(node: ASTNode): readonly unknown[] {\n return Object.entries(node)\n .filter(([k]) => !NON_CHILD_KEYS.has(k))\n .flatMap(([, v]) => (Array.isArray(v) ? v : [v]))\n .filter((v) => v !== null && typeof v === \"object\")\n}\n\n/**\n * Pure recursion: the set of all monad names reachable from `node` (including\n * `node` itself if it is a monad constructor call). No mutation, no\n * accumulator parameter — just a fold over the children's results.\n */\nfunction monadsIn(node: unknown): ReadonlySet<string> {\n if (!node || typeof node !== \"object\") return new Set()\n const ast = node as ASTNode\n return astChildren(ast).reduce<ReadonlySet<string>>((acc, child) => union(acc, monadsIn(child)), monadAt(ast))\n}\n\nconst CHAIN_METHODS: ReadonlySet<string> = new Set([\"flatMap\", \"map\", \"filter\", \"fold\"])\n\n/**\n * Length of the chain of `.flatMap()/.map()/.filter()/.fold()` calls walking\n * left from `node`. Returns 1 for a single call, N for N stacked calls. Pure\n * tail recursion — no mutable counter, no `while` loop.\n */\nfunction getChainDepth(node: ASTNode): number {\n if (node.callee.type !== \"MemberExpression\" || node.callee.object.type !== \"CallExpression\") return 1\n const method = node.callee.property\n const next = node.callee.object as ASTNode\n const here = method.type === \"Identifier\" && CHAIN_METHODS.has(method.name) ? 1 : 0\n return here + getChainDepth(next)\n}\n\n/**\n * Depth of a logical-AND chain (`a && b && c && ...`) where each right-hand\n * side is either a MemberExpression or another && chain — the shape that\n * indicates nested property-access guarding rather than arbitrary booleans.\n */\nfunction andChainDepth(node: ASTNode): number {\n if (node.type !== \"LogicalExpression\" || node.operator !== \"&&\") return 0\n const rhs = node.right\n const here = rhs.type === \"MemberExpression\" || (rhs.type === \"LogicalExpression\" && rhs.operator === \"&&\") ? 1 : 0\n return here + andChainDepth(node.left as ASTNode)\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer Do notation for complex monadic compositions and nested operations\",\n recommended: true,\n },\n fixable: \"code\",\n messages: {\n preferDoForNestedChecks: \"Prefer Do notation for nested null/undefined checks instead of logical AND chains\",\n preferDoForChainedMethods: \"Prefer Do notation for complex flatMap chains ({{count}} levels deep)\",\n preferDoForMixedMonads: \"Consider Do notation when mixing Option, Either, and Try operations\",\n preferDoAsyncForChainedTasks: \"Prefer DoAsync notation for chained async operations\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n minChainDepth: {\n type: \"integer\",\n minimum: 2,\n default: 3,\n },\n detectMixedMonads: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const options = context.options[0] || {}\n const minChainDepth = options.minChainDepth || 3\n const detectMixedMonads = options.detectMixedMonads !== false\n\n const functypeImports = getFunctypeImports(context)\n\n // Track if Do notation is already imported\n const hasDoImport = functypeImports.functions.has(\"Do\") || functypeImports.functions.has(\"DoAsync\")\n\n /**\n * Detect nested null/undefined checks like: a && a.b && a.b.c\n */\n function checkNestedNullChecks(node: ASTNode): void {\n if (node.operator !== \"&&\") return\n\n // Check if this looks like nested property access guarding\n if (andChainDepth(node) >= 2 && hasNestedPropertyAccess(node)) {\n context.report({\n node,\n messageId: \"preferDoForNestedChecks\",\n fix: hasDoImport ? (fixer) => fixNestedChecks(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Check if logical expression contains nested property access patterns\n */\n function hasNestedPropertyAccess(node: ASTNode): boolean {\n // Look for patterns like: obj && obj.prop && obj.prop.nested\n const matches = context.sourceCode.getText(node).match(/\\w+(\\.\\w+){2,}/g)\n return matches !== null && matches.length > 0\n }\n\n /**\n * Detect long flatMap chains\n */\n function checkChainedMethods(node: ASTNode): void {\n if (!isChainedMethodCall(node, \"flatMap\")) return\n\n const chainDepth = getChainDepth(node)\n\n if (chainDepth >= minChainDepth) {\n context.report({\n node,\n messageId: \"preferDoForChainedMethods\",\n data: { count: chainDepth.toString() },\n fix: hasDoImport ? (fixer) => fixChainedMethods(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Auto-fix for nested null checks\n */\n function fixNestedChecks(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // Simple transformation for common patterns\n // a && a.b && a.b.c -> Do(function* () { const x = yield* $(Option(a)); const y = yield* $(Option(x.b)); return Option(y.c) })\n const match = text.match(/(\\w+)(\\.\\w+)+/)\n if (match) {\n const baseVar = match[1]\n const chain = match[0]\n\n const doNotation = `Do(function* () {\n const obj = yield* $(Option(${baseVar}))\n return yield* $(Option(obj${chain.substring(baseVar.length)}))\n})`\n\n return fixer.replaceText(node, doNotation)\n }\n\n return fixer.replaceText(node, `/* TODO: Convert to Do notation */ ${text}`)\n }\n\n /**\n * Auto-fix for chained methods (basic)\n */\n function fixChainedMethods(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // For now, just add a comment suggesting Do notation\n return fixer.replaceText(node, `/* TODO: Consider Do notation for complex chains */ ${text}`)\n }\n\n /**\n * Detect mixed monad usage by walking the AST under `node` and counting\n * *distinct* monad constructor calls — not substring matches.\n *\n * A \"monad constructor call\" is a CallExpression whose callee is either:\n * - a bare identifier matching a known monad type (`Option(...)`, `Try(...)`),\n * or\n * - a member expression whose object is a known monad type\n * (`Option.none()`, `Either.right(...)`, `Try.success(...)`).\n *\n * Method calls like `.toEither(...)`, `.toOption()`, `.toTry()` are NOT\n * constructor calls — they're the idiomatic *conversion* API for crossing\n * monad boundaries. The old substring-based check fired on these because\n * `.toEither` contains \"Either\" as a substring; the AST check correctly\n * sees them as method calls, not new monad constructions.\n *\n * Implementation is pure recursion over the AST: `monadAt` reports what\n * a single node names; `monadsIn` returns the union of `monadAt(self)`\n * with the results from each child. No mutable accumulator, no for-loops\n * driving state changes.\n */\n function checkMixedMonads(node: ASTNode): void {\n if (!detectMixedMonads) return\n if (monadsIn(node).size >= 2) {\n context.report({ node, messageId: \"preferDoForMixedMonads\" })\n }\n }\n\n return {\n LogicalExpression(node) {\n checkNestedNullChecks(node)\n },\n\n CallExpression(node) {\n checkChainedMethods(node)\n checkMixedMonads(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prefer-do-notation.js","names":[],"sources":["../../src/rules/prefer-do-notation.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImports, isChainedMethodCall } from \"../utils/functype-detection\"\n\nconst MONAD_TYPES: ReadonlySet<string> = new Set([\"Option\", \"Either\", \"Try\", \"Task\"])\n\n/** AST keys that don't represent syntax children — back-edges and source metadata. */\nconst NON_CHILD_KEYS: ReadonlySet<string> = new Set([\"parent\", \"loc\", \"range\"])\n\nconst union = <T>(a: ReadonlySet<T>, b: ReadonlySet<T>): ReadonlySet<T> => new Set([...a, ...b])\n\n/**\n * Set of monad names that *this single AST node* names as a constructor call.\n * Does not look at children. Returns empty set for any non-monad-constructor node.\n */\nfunction monadAt(node: ASTNode): ReadonlySet<string> {\n if (node.type !== \"CallExpression\") return new Set()\n const callee = node.callee\n // Bare constructor: Option(...), Try(...), Either(...), Task(...)\n if (callee?.type === \"Identifier\" && MONAD_TYPES.has(callee.name)) {\n return new Set([callee.name])\n }\n // Companion call: Option.none(), Either.right(x), Try.success(v)\n if (\n callee?.type === \"MemberExpression\" &&\n callee.object?.type === \"Identifier\" &&\n MONAD_TYPES.has(callee.object.name)\n ) {\n return new Set([callee.object.name])\n }\n return new Set()\n}\n\n/**\n * AST children of `node` worth recursing into. Drops back-edges and source\n * metadata; flattens array-valued keys (e.g. `arguments`); filters non-object\n * leaves (literals, raw strings, numbers).\n */\nfunction astChildren(node: ASTNode): readonly unknown[] {\n return Object.entries(node)\n .filter(([k]) => !NON_CHILD_KEYS.has(k))\n .flatMap(([, v]) => (Array.isArray(v) ? v : [v]))\n .filter((v) => v !== null && typeof v === \"object\")\n}\n\n/**\n * Pure recursion: the set of all monad names reachable from `node` (including\n * `node` itself if it is a monad constructor call). No mutation, no\n * accumulator parameter — just a fold over the children's results.\n */\nfunction monadsIn(node: unknown): ReadonlySet<string> {\n if (!node || typeof node !== \"object\") return new Set()\n const ast = node as ASTNode\n return astChildren(ast).reduce<ReadonlySet<string>>((acc, child) => union(acc, monadsIn(child)), monadAt(ast))\n}\n\nconst CHAIN_METHODS: ReadonlySet<string> = new Set([\"flatMap\", \"map\", \"filter\", \"fold\"])\n\n/**\n * Methods that *compose* monadic operations (stay in the monad and feed the\n * next step). Distinct from terminal handlers (`fold`, `match`, `getOrElse`)\n * which *exit* the monad — those are conversions, not composition, and Do\n * notation doesn't apply to them.\n */\nconst COMPOSITION_METHODS: ReadonlySet<string> = new Set([\"flatMap\", \"map\", \"filter\"])\n\n/**\n * Length of the chain of `.flatMap()/.map()/.filter()/.fold()` calls walking\n * left from `node`. Returns 1 for a single call, N for N stacked calls. Pure\n * tail recursion — no mutable counter, no `while` loop.\n */\nfunction getChainDepth(node: ASTNode): number {\n if (node.callee.type !== \"MemberExpression\" || node.callee.object.type !== \"CallExpression\") return 1\n const method = node.callee.property\n const next = node.callee.object as ASTNode\n const here = method.type === \"Identifier\" && CHAIN_METHODS.has(method.name) ? 1 : 0\n return here + getChainDepth(next)\n}\n\n/**\n * Number of composition methods (`flatMap`/`map`/`filter`) stacked under\n * `node` via `.callee.object`. 0 if the callee isn't a member expression on\n * another call. Excludes terminal handlers by design.\n */\nfunction compositionChainDepth(node: ASTNode): number {\n if (node.callee?.type !== \"MemberExpression\" || node.callee.object?.type !== \"CallExpression\") return 0\n const method = node.callee.property\n const next = node.callee.object as ASTNode\n const here = method.type === \"Identifier\" && COMPOSITION_METHODS.has(method.name) ? 1 : 0\n return here + compositionChainDepth(next)\n}\n\n/**\n * True when `node`'s outermost call is a composition method AND at least one\n * more composition method appears below it — i.e. this is a real\n * `.flatMap(...).flatMap(...)`-style chain, not a terminal exit like\n * `.fold(...)` or a bare receiver like `option.flatMap(...)`.\n */\nfunction isCompositionChain(node: ASTNode): boolean {\n if (node.callee?.type !== \"MemberExpression\") return false\n const method = node.callee.property\n if (method.type !== \"Identifier\" || !COMPOSITION_METHODS.has(method.name)) return false\n return compositionChainDepth(node) >= 1\n}\n\n/**\n * Depth of a logical-AND chain (`a && b && c && ...`) where each right-hand\n * side is either a MemberExpression or another && chain — the shape that\n * indicates nested property-access guarding rather than arbitrary booleans.\n */\nfunction andChainDepth(node: ASTNode): number {\n if (node.type !== \"LogicalExpression\" || node.operator !== \"&&\") return 0\n const rhs = node.right\n const here = rhs.type === \"MemberExpression\" || (rhs.type === \"LogicalExpression\" && rhs.operator === \"&&\") ? 1 : 0\n return here + andChainDepth(node.left as ASTNode)\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer Do notation for complex monadic compositions and nested operations\",\n recommended: true,\n },\n fixable: \"code\",\n messages: {\n preferDoForNestedChecks: \"Prefer Do notation for nested null/undefined checks instead of logical AND chains\",\n preferDoForChainedMethods: \"Prefer Do notation for complex flatMap chains ({{count}} levels deep)\",\n preferDoForMixedMonads: \"Consider Do notation when mixing Option, Either, and Try operations\",\n preferDoAsyncForChainedTasks: \"Prefer DoAsync notation for chained async operations\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n minChainDepth: {\n type: \"integer\",\n minimum: 2,\n default: 3,\n },\n detectMixedMonads: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const options = context.options[0] || {}\n const minChainDepth = options.minChainDepth || 3\n const detectMixedMonads = options.detectMixedMonads !== false\n\n const functypeImports = getFunctypeImports(context)\n\n // Track if Do notation is already imported\n const hasDoImport = functypeImports.functions.has(\"Do\") || functypeImports.functions.has(\"DoAsync\")\n\n /**\n * Detect nested null/undefined checks like: a && a.b && a.b.c\n */\n function checkNestedNullChecks(node: ASTNode): void {\n if (node.operator !== \"&&\") return\n\n // Check if this looks like nested property access guarding\n if (andChainDepth(node) >= 2 && hasNestedPropertyAccess(node)) {\n context.report({\n node,\n messageId: \"preferDoForNestedChecks\",\n fix: hasDoImport ? (fixer) => fixNestedChecks(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Check if logical expression contains nested property access patterns\n */\n function hasNestedPropertyAccess(node: ASTNode): boolean {\n // Look for patterns like: obj && obj.prop && obj.prop.nested\n const matches = context.sourceCode.getText(node).match(/\\w+(\\.\\w+){2,}/g)\n return matches !== null && matches.length > 0\n }\n\n /**\n * Detect long flatMap chains\n */\n function checkChainedMethods(node: ASTNode): void {\n if (!isChainedMethodCall(node, \"flatMap\")) return\n\n const chainDepth = getChainDepth(node)\n\n if (chainDepth >= minChainDepth) {\n context.report({\n node,\n messageId: \"preferDoForChainedMethods\",\n data: { count: chainDepth.toString() },\n fix: hasDoImport ? (fixer) => fixChainedMethods(fixer, node) : undefined,\n })\n }\n }\n\n /**\n * Auto-fix for nested null checks\n */\n function fixNestedChecks(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // Simple transformation for common patterns\n // a && a.b && a.b.c -> Do(function* () { const x = yield* $(Option(a)); const y = yield* $(Option(x.b)); return Option(y.c) })\n const match = text.match(/(\\w+)(\\.\\w+)+/)\n if (match) {\n const baseVar = match[1]\n const chain = match[0]\n\n const doNotation = `Do(function* () {\n const obj = yield* $(Option(${baseVar}))\n return yield* $(Option(obj${chain.substring(baseVar.length)}))\n})`\n\n return fixer.replaceText(node, doNotation)\n }\n\n return fixer.replaceText(node, `/* TODO: Convert to Do notation */ ${text}`)\n }\n\n /**\n * Auto-fix for chained methods (basic)\n */\n function fixChainedMethods(fixer: Rule.RuleFixer, node: ASTNode): Rule.Fix {\n const text = context.sourceCode.getText(node)\n\n // For now, just add a comment suggesting Do notation\n return fixer.replaceText(node, `/* TODO: Consider Do notation for complex chains */ ${text}`)\n }\n\n /**\n * Detect mixed monad usage by walking the AST under `node` and counting\n * *distinct* monad constructor calls — not substring matches.\n *\n * A \"monad constructor call\" is a CallExpression whose callee is either:\n * - a bare identifier matching a known monad type (`Option(...)`, `Try(...)`),\n * or\n * - a member expression whose object is a known monad type\n * (`Option.none()`, `Either.right(...)`, `Try.success(...)`).\n *\n * Method calls like `.toEither(...)`, `.toOption()`, `.toTry()` are NOT\n * constructor calls — they're the idiomatic *conversion* API for crossing\n * monad boundaries. The old substring-based check fired on these because\n * `.toEither` contains \"Either\" as a substring; the AST check correctly\n * sees them as method calls, not new monad constructions.\n *\n * Implementation is pure recursion over the AST: `monadAt` reports what\n * a single node names; `monadsIn` returns the union of `monadAt(self)`\n * with the results from each child. No mutable accumulator, no for-loops\n * driving state changes.\n */\n function checkMixedMonads(node: ASTNode): void {\n if (!detectMixedMonads) return\n // Only flag genuine composition chains (.flatMap/.map/.filter stacked at\n // least twice deep). Terminal handlers (.fold/.match/.getOrElse) and\n // bare co-occurrence in arg lists are conversions, not compositions —\n // Do notation doesn't apply. See issue #205.\n if (!isCompositionChain(node)) return\n if (monadsIn(node).size >= 2) {\n context.report({ node, messageId: \"preferDoForMixedMonads\" })\n }\n }\n\n return {\n LogicalExpression(node) {\n checkNestedNullChecks(node)\n },\n\n CallExpression(node) {\n checkChainedMethods(node)\n checkMixedMonads(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
|
|
@@ -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:`
|
|
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,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.
|
|
3
|
+
"version": "2.104.2",
|
|
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",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^24.13.2",
|
|
43
|
-
"@typescript-eslint/rule-tester": "^8.61.
|
|
43
|
+
"@typescript-eslint/rule-tester": "^8.61.1",
|
|
44
44
|
"eslint-config-prettier": "^10.1.8",
|
|
45
|
-
"ts-builds": "^3.
|
|
45
|
+
"ts-builds": "^3.2.0",
|
|
46
46
|
"tsdown": "^0.22.2",
|
|
47
|
-
"functype": "^1.4.
|
|
47
|
+
"functype": "^1.4.2"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public",
|