eslint-plugin-functype 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Jordan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,10 +1,13 @@
1
1
  //#region src/configs/recommended.d.ts
2
2
  declare const recommendedRules: {
3
+ "functype/no-let": string;
3
4
  "functype/prefer-option": string;
4
5
  "functype/prefer-either": string;
5
6
  "functype/prefer-fold": string;
6
7
  "functype/prefer-map": string;
7
8
  "functype/prefer-flatmap": string;
9
+ "functype/prefer-functype-map": string;
10
+ "functype/prefer-functype-set": string;
8
11
  "functype/no-imperative-loops": string;
9
12
  "functype/prefer-do-notation": string;
10
13
  "functype/no-get-unsafe": string;
@@ -1,2 +1,2 @@
1
- const e={"functype/prefer-option":`warn`,"functype/prefer-either":`warn`,"functype/prefer-fold":`warn`,"functype/prefer-map":`warn`,"functype/prefer-flatmap":`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-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/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/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,yBAA0B,OAC1B,yBAA0B,OAC1B,uBAAwB,OACxB,sBAAuB,OACvB,0BAA2B,OAC3B,+BAAgC,OAChC,8BAA+B,OAC/B,yBAA0B,MAC1B,uBAAwB,MACzB"}
1
+ {"version":3,"file":"recommended.js","names":[],"sources":["../../src/configs/recommended.ts"],"sourcesContent":["const recommendedRules = {\n \"functype/no-let\": \"error\",\n \"functype/prefer-option\": \"warn\",\n \"functype/prefer-either\": \"warn\",\n \"functype/prefer-fold\": \"warn\",\n \"functype/prefer-map\": \"warn\",\n \"functype/prefer-flatmap\": \"warn\",\n \"functype/prefer-functype-map\": \"warn\",\n \"functype/prefer-functype-set\": \"warn\",\n \"functype/no-imperative-loops\": \"warn\",\n \"functype/prefer-do-notation\": \"warn\",\n \"functype/no-get-unsafe\": \"off\",\n \"functype/prefer-list\": \"off\",\n}\n\nexport default recommendedRules\n"],"mappings":"AAAA,MAAM,EAAmB,CACvB,kBAAmB,QACnB,yBAA0B,OAC1B,yBAA0B,OAC1B,uBAAwB,OACxB,sBAAuB,OACvB,0BAA2B,OAC3B,+BAAgC,OAChC,+BAAgC,OAChC,+BAAgC,OAChC,8BAA+B,OAC/B,yBAA0B,MAC1B,uBAAwB,MACzB"}
@@ -4,10 +4,13 @@ declare const strictRules: {
4
4
  "functype/prefer-either": string;
5
5
  "functype/no-get-unsafe": string;
6
6
  "functype/prefer-list": string;
7
+ "functype/prefer-functype-map": string;
8
+ "functype/prefer-functype-set": string;
9
+ "functype/no-imperative-loops": string;
10
+ "functype/no-let": string;
7
11
  "functype/prefer-fold": string;
8
12
  "functype/prefer-map": string;
9
13
  "functype/prefer-flatmap": string;
10
- "functype/no-imperative-loops": string;
11
14
  "functype/prefer-do-notation": string;
12
15
  };
13
16
  //#endregion
@@ -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`};export{t as default};
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};
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}\n\nexport default strictRules\n"],"mappings":"gCAEA,MAAM,EAAc,CAClB,GAAG,EACH,yBAA0B,QAC1B,yBAA0B,QAC1B,yBAA0B,QAC1B,uBAAwB,OACzB"}
1
+ {"version":3,"file":"strict.js","names":[],"sources":["../../src/configs/strict.ts"],"sourcesContent":["import recommendedRules from \"./recommended\"\n\nconst strictRules = {\n ...recommendedRules,\n \"functype/prefer-option\": \"error\",\n \"functype/prefer-either\": \"error\",\n \"functype/no-get-unsafe\": \"error\",\n \"functype/prefer-list\": \"warn\",\n \"functype/prefer-functype-map\": \"error\",\n \"functype/prefer-functype-set\": \"error\",\n \"functype/no-imperative-loops\": \"error\",\n}\n\nexport default strictRules\n"],"mappings":"gCAEA,MAAM,EAAc,CAClB,GAAG,EACH,yBAA0B,QAC1B,yBAA0B,QAC1B,yBAA0B,QAC1B,uBAAwB,OACxB,+BAAgC,QAChC,+BAAgC,QAChC,+BAAgC,QACjC"}
package/dist/index.d.ts CHANGED
@@ -1,17 +1,20 @@
1
- import * as eslint from "eslint";
1
+ import * as _$eslint from "eslint";
2
2
 
3
3
  //#region src/index.d.ts
4
4
  declare const plugin: {
5
5
  rules: {
6
- "prefer-option": eslint.Rule.RuleModule;
7
- "prefer-either": eslint.Rule.RuleModule;
8
- "prefer-list": eslint.Rule.RuleModule;
9
- "no-get-unsafe": eslint.Rule.RuleModule;
10
- "prefer-fold": eslint.Rule.RuleModule;
11
- "prefer-map": eslint.Rule.RuleModule;
12
- "prefer-flatmap": eslint.Rule.RuleModule;
13
- "no-imperative-loops": eslint.Rule.RuleModule;
14
- "prefer-do-notation": eslint.Rule.RuleModule;
6
+ "prefer-option": _$eslint.Rule.RuleModule;
7
+ "prefer-either": _$eslint.Rule.RuleModule;
8
+ "prefer-list": _$eslint.Rule.RuleModule;
9
+ "no-get-unsafe": _$eslint.Rule.RuleModule;
10
+ "no-let": _$eslint.Rule.RuleModule;
11
+ "prefer-fold": _$eslint.Rule.RuleModule;
12
+ "prefer-map": _$eslint.Rule.RuleModule;
13
+ "prefer-flatmap": _$eslint.Rule.RuleModule;
14
+ "prefer-functype-map": _$eslint.Rule.RuleModule;
15
+ "prefer-functype-set": _$eslint.Rule.RuleModule;
16
+ "no-imperative-loops": _$eslint.Rule.RuleModule;
17
+ "prefer-do-notation": _$eslint.Rule.RuleModule;
15
18
  };
16
19
  meta: {
17
20
  name: string;
@@ -1,26 +1,32 @@
1
1
  import rule from "./no-get-unsafe.js";
2
2
  import rule$1 from "./no-imperative-loops.js";
3
- import rule$2 from "./prefer-do-notation.js";
4
- import rule$3 from "./prefer-either.js";
5
- import rule$4 from "./prefer-flatmap.js";
6
- import rule$5 from "./prefer-fold.js";
7
- import rule$6 from "./prefer-list.js";
8
- import rule$7 from "./prefer-map.js";
9
- import rule$8 from "./prefer-option.js";
10
- import * as eslint from "eslint";
3
+ import rule$2 from "./no-let.js";
4
+ import rule$3 from "./prefer-do-notation.js";
5
+ import rule$4 from "./prefer-either.js";
6
+ import rule$5 from "./prefer-flatmap.js";
7
+ import rule$6 from "./prefer-fold.js";
8
+ import rule$7 from "./prefer-functype-map.js";
9
+ import rule$8 from "./prefer-functype-set.js";
10
+ import rule$9 from "./prefer-list.js";
11
+ import rule$10 from "./prefer-map.js";
12
+ import rule$11 from "./prefer-option.js";
13
+ import * as _$eslint from "eslint";
11
14
 
12
15
  //#region src/rules/index.d.ts
13
16
  declare const _default: {
14
- "prefer-option": eslint.Rule.RuleModule;
15
- "prefer-either": eslint.Rule.RuleModule;
16
- "prefer-list": eslint.Rule.RuleModule;
17
- "no-get-unsafe": eslint.Rule.RuleModule;
18
- "prefer-fold": eslint.Rule.RuleModule;
19
- "prefer-map": eslint.Rule.RuleModule;
20
- "prefer-flatmap": eslint.Rule.RuleModule;
21
- "no-imperative-loops": eslint.Rule.RuleModule;
22
- "prefer-do-notation": eslint.Rule.RuleModule;
17
+ "prefer-option": _$eslint.Rule.RuleModule;
18
+ "prefer-either": _$eslint.Rule.RuleModule;
19
+ "prefer-list": _$eslint.Rule.RuleModule;
20
+ "no-get-unsafe": _$eslint.Rule.RuleModule;
21
+ "no-let": _$eslint.Rule.RuleModule;
22
+ "prefer-fold": _$eslint.Rule.RuleModule;
23
+ "prefer-map": _$eslint.Rule.RuleModule;
24
+ "prefer-flatmap": _$eslint.Rule.RuleModule;
25
+ "prefer-functype-map": _$eslint.Rule.RuleModule;
26
+ "prefer-functype-set": _$eslint.Rule.RuleModule;
27
+ "no-imperative-loops": _$eslint.Rule.RuleModule;
28
+ "prefer-do-notation": _$eslint.Rule.RuleModule;
23
29
  };
24
30
  //#endregion
25
- export { _default as default, rule as noGetUnsafe, rule$1 as noImperativeLoops, rule$2 as preferDoNotation, rule$3 as preferEither, rule$4 as preferFlatmap, rule$5 as preferFold, rule$6 as preferList, rule$7 as preferMap, rule$8 as preferOption };
31
+ 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 };
26
32
  //# 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"./prefer-do-notation.js";import r from"./prefer-either.js";import i from"./prefer-flatmap.js";import a from"./prefer-fold.js";import o from"./prefer-list.js";import s from"./prefer-map.js";import c from"./prefer-option.js";var l={"prefer-option":c,"prefer-either":r,"prefer-list":o,"no-get-unsafe":e,"prefer-fold":a,"prefer-map":s,"prefer-flatmap":i,"no-imperative-loops":t,"prefer-do-notation":n};export{l as default,e as noGetUnsafe,t as noImperativeLoops,n as preferDoNotation,r as preferEither,i as preferFlatmap,a as preferFold,o as preferList,s as preferMap,c 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";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};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["preferOption","preferEither","preferList","noGetUnsafe","preferFold","preferMap","preferFlatmap","noImperativeLoops","preferDoNotation"],"sources":["../../src/rules/index.ts"],"sourcesContent":["import noGetUnsafe from \"./no-get-unsafe\"\nimport noImperativeLoops from \"./no-imperative-loops\"\nimport preferDoNotation from \"./prefer-do-notation\"\nimport preferEither from \"./prefer-either\"\nimport preferFlatmap from \"./prefer-flatmap\"\nimport preferFold from \"./prefer-fold\"\nimport preferList from \"./prefer-list\"\nimport preferMap from \"./prefer-map\"\nimport preferOption from \"./prefer-option\"\n\nexport {\n noGetUnsafe,\n noImperativeLoops,\n preferDoNotation,\n preferEither,\n preferFlatmap,\n preferFold,\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 \"prefer-fold\": preferFold,\n \"prefer-map\": preferMap,\n \"prefer-flatmap\": preferFlatmap,\n \"no-imperative-loops\": noImperativeLoops,\n \"prefer-do-notation\": preferDoNotation,\n}\n"],"mappings":"uTAsBA,IAAA,EAAe,CACb,gBAAiBA,EACjB,gBAAiBC,EACjB,cAAeC,EACf,gBAAiBC,EACjB,cAAeC,EACf,aAAcC,EACd,iBAAkBC,EAClB,sBAAuBC,EACvB,qBAAsBC,EACvB"}
1
+ {"version":3,"file":"index.js","names":["preferOption","preferEither","preferList","noGetUnsafe","noLet","preferFold","preferMap","preferFlatmap","preferFunctypeMap","preferFunctypeSet","noImperativeLoops","preferDoNotation"],"sources":["../../src/rules/index.ts"],"sourcesContent":["import noGetUnsafe from \"./no-get-unsafe\"\nimport noImperativeLoops from \"./no-imperative-loops\"\nimport noLet from \"./no-let\"\nimport preferDoNotation from \"./prefer-do-notation\"\nimport preferEither from \"./prefer-either\"\nimport preferFlatmap from \"./prefer-flatmap\"\nimport preferFold from \"./prefer-fold\"\nimport preferFunctypeMap from \"./prefer-functype-map\"\nimport preferFunctypeSet from \"./prefer-functype-set\"\nimport preferList from \"./prefer-list\"\nimport preferMap from \"./prefer-map\"\nimport preferOption from \"./prefer-option\"\n\nexport {\n noGetUnsafe,\n noImperativeLoops,\n noLet,\n preferDoNotation,\n preferEither,\n preferFlatmap,\n preferFold,\n preferFunctypeMap,\n preferFunctypeSet,\n preferList,\n preferMap,\n preferOption,\n}\n\nexport default {\n \"prefer-option\": preferOption,\n \"prefer-either\": preferEither,\n \"prefer-list\": preferList,\n \"no-get-unsafe\": noGetUnsafe,\n \"no-let\": noLet,\n \"prefer-fold\": preferFold,\n \"prefer-map\": preferMap,\n \"prefer-flatmap\": preferFlatmap,\n \"prefer-functype-map\": preferFunctypeMap,\n \"prefer-functype-set\": preferFunctypeSet,\n \"no-imperative-loops\": noImperativeLoops,\n \"prefer-do-notation\": preferDoNotation,\n}\n"],"mappings":"kaA4BA,IAAA,EAAe,CACb,gBAAiBA,EACjB,gBAAiBC,EACjB,cAAeC,EACf,gBAAiBC,EACjB,SAAUC,EACV,cAAeC,EACf,aAAcC,EACd,iBAAkBC,EAClB,sBAAuBC,EACvB,sBAAuBC,EACvB,sBAAuBC,EACvB,qBAAsBC,EACvB"}
@@ -1,2 +1,2 @@
1
- const e={meta:{type:`suggestion`,docs:{description:`Prefer functional iteration methods over imperative for loops`,recommended:!0},schema:[{type:`object`,properties:{allowForIndexAccess:{type:`boolean`,default:!1},allowWhileLoops:{type:`boolean`,default:!1},allowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{noForLoop:`Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops`,noForInLoop:`Prefer Object.keys().forEach() or functional methods over for..in loops`,noForOfLoop:`Prefer .forEach() or .map() over for..of loops`,noWhileLoop:`Prefer functional iteration or recursion over while loops`,noDoWhileLoop:`Prefer functional iteration or recursion over do..while loops`,suggestFunctional:`Consider: {{suggestion}}`}},create(e){let t=e.options[0]||{},n=t.allowForIndexAccess||!1,r=t.allowWhileLoops||!1,i=t.allowInTests!==!1;function a(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}function o(t){if(!t.body||t.body.type!==`BlockStatement`)return!1;let n=e.sourceCode.getText(t.body);return/\[\s*\w+\s*\]/.test(n)&&t.init&&t.init.type===`VariableDeclaration`}return{ForStatement(t){i&&a()||n&&o(t)||e.report({node:t,messageId:`noForLoop`})},ForInStatement(t){i&&a()||e.report({node:t,messageId:`noForInLoop`})},ForOfStatement(t){i&&a()||e.report({node:t,messageId:`noForOfLoop`})},WhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noWhileLoop`})},DoWhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noDoWhileLoop`})}}}};export{e as default};
1
+ const e={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer functional iteration methods over imperative for loops`,recommended:!0},schema:[{type:`object`,properties:{allowForIndexAccess:{type:`boolean`,default:!1},allowWhileLoops:{type:`boolean`,default:!1},allowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{noForLoop:`Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops`,noForInLoop:`Prefer Object.keys().forEach() or functional methods over for..in loops`,noForOfLoop:`Prefer .forEach() or .map() over for..of loops`,noWhileLoop:`Prefer functional iteration or recursion over while loops`,noDoWhileLoop:`Prefer functional iteration or recursion over do..while loops`,suggestForEach:`Replace with {{iterable}}.forEach(...)`,suggestObjectKeys:`Replace with Object.keys({{object}}).forEach(...)`}},create(e){let t=e.options[0]||{},n=t.allowForIndexAccess||!1,r=t.allowWhileLoops||!1,i=t.allowInTests!==!1;function a(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}function o(t){if(!t.body||t.body.type!==`BlockStatement`)return!1;let n=e.sourceCode.getText(t.body);return/\[\s*\w+\s*\]/.test(n)&&t.init&&t.init.type===`VariableDeclaration`}function s(e){return!e.body||e.body.type!==`BlockStatement`?!1:e.body.body.length===1}function c(e){let t=e.left;if(!t)return!1;if(t.type===`VariableDeclaration`&&t.declarations[0]){let e=t.declarations[0].id;return e.type===`ObjectPattern`||e.type===`ArrayPattern`}return!1}function l(t){let n=e.sourceCode.getText(t.body);return/\b(break|continue)\b/.test(n)}return{ForStatement(t){i&&a()||n&&o(t)||e.report({node:t,messageId:`noForLoop`})},ForInStatement(t){if(i&&a())return;let n=[];if(s(t)&&!c(t)&&!l(t)){let r=e.sourceCode,i=t.body.body[0];if(i&&i.type===`ExpressionStatement`){let e=r.getText(i),a=r.getText(t.right),o=t.left,s;s=o.type===`VariableDeclaration`&&o.declarations[0]?r.getText(o.declarations[0].id):r.getText(o),n.push({messageId:`suggestObjectKeys`,data:{object:a},fix(n){return n.replaceText(t,`Object.keys(${a}).forEach((${s}) => {\n ${e}\n})`)}})}}e.report({node:t,messageId:`noForInLoop`,suggest:n.length>0?n:void 0})},ForOfStatement(t){if(i&&a())return;let n=[];if(s(t)&&!c(t)&&!l(t)){let r=e.sourceCode,i=t.body.body[0];if(i&&i.type===`ExpressionStatement`){let e=r.getText(i),a=r.getText(t.right),o=t.left,s;s=o.type===`VariableDeclaration`&&o.declarations[0]?r.getText(o.declarations[0].id):r.getText(o),e.includes(`.push(`)||n.push({messageId:`suggestForEach`,data:{iterable:a},fix(n){return n.replaceText(t,`${a}.forEach((${s}) => {\n ${e}\n})`)}})}}e.report({node:t,messageId:`noForOfLoop`,suggest:n.length>0?n:void 0})},WhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noWhileLoop`})},DoWhileStatement(t){r||i&&a()||e.report({node:t,messageId:`noDoWhileLoop`})}}}};export{e as default};
2
2
  //# sourceMappingURL=no-imperative-loops.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"no-imperative-loops.js","names":[],"sources":["../../src/rules/no-imperative-loops.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer functional iteration methods over imperative for loops\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowForIndexAccess: {\n type: \"boolean\",\n default: false,\n },\n allowWhileLoops: {\n type: \"boolean\",\n default: false,\n },\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noForLoop: \"Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops\",\n noForInLoop: \"Prefer Object.keys().forEach() or functional methods over for..in loops\",\n noForOfLoop: \"Prefer .forEach() or .map() over for..of loops\",\n noWhileLoop: \"Prefer functional iteration or recursion over while loops\",\n noDoWhileLoop: \"Prefer functional iteration or recursion over do..while loops\",\n suggestFunctional: \"Consider: {{suggestion}}\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowForIndexAccess = options.allowForIndexAccess || false\n const allowWhileLoops = options.allowWhileLoops || false\n const allowInTests = options.allowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function needsIndexAccess(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n\n // Look for array index access patterns like arr[i]\n return /\\[\\s*\\w+\\s*\\]/.test(bodyText) && node.init && node.init.type === \"VariableDeclaration\"\n }\n\n function getSuggestionForForLoop(node: ASTNode): string {\n if (!node.body) return \"functional iteration method\"\n\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n\n // Simple heuristics for suggestions\n if (bodyText.includes(\"if\") && bodyText.includes(\"push\")) {\n return \"array.filter().map() for conditional transformation\"\n } else if (bodyText.includes(\"push\")) {\n return \"array.map() to transform elements\"\n } else if (bodyText.includes(\"console.log\") || bodyText.includes(\"print\")) {\n return \"array.forEach() for side effects\"\n } else if (bodyText.includes(\"+=\") || bodyText.includes(\"sum\")) {\n return \"array.reduce() for accumulation\"\n }\n\n return \"functional iteration method (.map, .filter, .forEach, .reduce)\"\n }\n\n // Remove unused function to fix lint error\n // getSuggestionForForLoop could be used for better error messages in the future\n // Mark as used to avoid lint error:\n void getSuggestionForForLoop\n\n return {\n ForStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Allow traditional for loops if they need index access and option is set\n if (allowForIndexAccess && needsIndexAccess(node)) return\n\n // Remove unused suggestion variable\n // const _suggestion = getSuggestionForForLoop(node)\n context.report({\n node,\n messageId: \"noForLoop\",\n })\n },\n\n ForInStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noForInLoop\",\n })\n },\n\n ForOfStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noForOfLoop\",\n })\n },\n\n WhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noWhileLoop\",\n })\n },\n\n DoWhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noDoWhileLoop\",\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,gEACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,oBAAqB,CACnB,KAAM,UACN,QAAS,GACV,CACD,gBAAiB,CACf,KAAM,UACN,QAAS,GACV,CACD,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,UAAW,8EACX,YAAa,0EACb,YAAa,iDACb,YAAa,4DACb,cAAe,gEACf,kBAAmB,2BACpB,CACF,CAED,OAAO,EAAS,CACd,IAAM,EAAU,EAAQ,QAAQ,IAAM,EAAE,CAClC,EAAsB,EAAQ,qBAAuB,GACrD,EAAkB,EAAQ,iBAAmB,GAC7C,EAAe,EAAQ,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,SAAS,EAAiB,EAAwB,CAChD,GAAI,CAAC,EAAK,MAAQ,EAAK,KAAK,OAAS,iBAAkB,MAAO,GAG9D,IAAM,EADa,EAAQ,WACC,QAAQ,EAAK,KAAK,CAG9C,MAAO,gBAAgB,KAAK,EAAS,EAAI,EAAK,MAAQ,EAAK,KAAK,OAAS,sBA4B3E,MAAO,CACL,aAAa,EAAe,CACtB,GAAgB,GAAc,EAG9B,GAAuB,EAAiB,EAAK,EAIjD,EAAQ,OAAO,CACb,OACA,UAAW,YACZ,CAAC,EAGJ,eAAe,EAAe,CACxB,GAAgB,GAAc,EAElC,EAAQ,OAAO,CACb,OACA,UAAW,cACZ,CAAC,EAGJ,eAAe,EAAe,CACxB,GAAgB,GAAc,EAElC,EAAQ,OAAO,CACb,OACA,UAAW,cACZ,CAAC,EAGJ,eAAe,EAAe,CACxB,GACA,GAAgB,GAAc,EAElC,EAAQ,OAAO,CACb,OACA,UAAW,cACZ,CAAC,EAGJ,iBAAiB,EAAe,CAC1B,GACA,GAAgB,GAAc,EAElC,EAAQ,OAAO,CACb,OACA,UAAW,gBACZ,CAAC,EAEL,EAEJ"}
1
+ {"version":3,"file":"no-imperative-loops.js","names":[],"sources":["../../src/rules/no-imperative-loops.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer functional iteration methods over imperative for loops\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowForIndexAccess: {\n type: \"boolean\",\n default: false,\n },\n allowWhileLoops: {\n type: \"boolean\",\n default: false,\n },\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noForLoop: \"Prefer functional methods (.map, .filter, .forEach, .reduce) over for loops\",\n noForInLoop: \"Prefer Object.keys().forEach() or functional methods over for..in loops\",\n noForOfLoop: \"Prefer .forEach() or .map() over for..of loops\",\n noWhileLoop: \"Prefer functional iteration or recursion over while loops\",\n noDoWhileLoop: \"Prefer functional iteration or recursion over do..while loops\",\n suggestForEach: \"Replace with {{iterable}}.forEach(...)\",\n suggestObjectKeys: \"Replace with Object.keys({{object}}).forEach(...)\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowForIndexAccess = options.allowForIndexAccess || false\n const allowWhileLoops = options.allowWhileLoops || false\n const allowInTests = options.allowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function needsIndexAccess(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n\n // Look for array index access patterns like arr[i]\n return /\\[\\s*\\w+\\s*\\]/.test(bodyText) && node.init && node.init.type === \"VariableDeclaration\"\n }\n\n function isSingleStatementBody(node: ASTNode): boolean {\n if (!node.body || node.body.type !== \"BlockStatement\") return false\n return node.body.body.length === 1\n }\n\n function hasDestructuringVariable(node: ASTNode): boolean {\n const left = node.left\n if (!left) return false\n if (left.type === \"VariableDeclaration\" && left.declarations[0]) {\n const id = left.declarations[0].id\n return id.type === \"ObjectPattern\" || id.type === \"ArrayPattern\"\n }\n return false\n }\n\n function bodyContainsBreakOrContinue(node: ASTNode): boolean {\n const sourceCode = context.sourceCode\n const bodyText = sourceCode.getText(node.body)\n return /\\b(break|continue)\\b/.test(bodyText)\n }\n\n return {\n ForStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Allow traditional for loops if they need index access and option is set\n if (allowForIndexAccess && needsIndexAccess(node)) return\n\n context.report({\n node,\n messageId: \"noForLoop\",\n })\n },\n\n ForInStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSingleStatementBody(node) && !hasDestructuringVariable(node) && !bodyContainsBreakOrContinue(node)) {\n const sourceCode = context.sourceCode\n const bodyStmt = node.body.body[0]\n\n if (bodyStmt && bodyStmt.type === \"ExpressionStatement\") {\n const stmtText = sourceCode.getText(bodyStmt)\n const objText = sourceCode.getText(node.right)\n\n const left = node.left\n let keyVar: string\n if (left.type === \"VariableDeclaration\" && left.declarations[0]) {\n keyVar = sourceCode.getText(left.declarations[0].id)\n } else {\n keyVar = sourceCode.getText(left)\n }\n\n suggest.push({\n messageId: \"suggestObjectKeys\",\n data: { object: objText },\n fix(fixer) {\n return fixer.replaceText(node, `Object.keys(${objText}).forEach((${keyVar}) => {\\n ${stmtText}\\n})`)\n },\n })\n }\n }\n\n context.report({\n node,\n messageId: \"noForInLoop\",\n suggest: suggest.length > 0 ? suggest : undefined,\n })\n },\n\n ForOfStatement(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSingleStatementBody(node) && !hasDestructuringVariable(node) && !bodyContainsBreakOrContinue(node)) {\n const sourceCode = context.sourceCode\n const bodyStmt = node.body.body[0]\n\n if (bodyStmt && bodyStmt.type === \"ExpressionStatement\") {\n const stmtText = sourceCode.getText(bodyStmt)\n const iterableText = sourceCode.getText(node.right)\n\n const varDecl = node.left\n let varName: string\n if (varDecl.type === \"VariableDeclaration\" && varDecl.declarations[0]) {\n varName = sourceCode.getText(varDecl.declarations[0].id)\n } else {\n varName = sourceCode.getText(varDecl)\n }\n\n if (!stmtText.includes(\".push(\")) {\n suggest.push({\n messageId: \"suggestForEach\",\n data: { iterable: iterableText },\n fix(fixer) {\n return fixer.replaceText(node, `${iterableText}.forEach((${varName}) => {\\n ${stmtText}\\n})`)\n },\n })\n }\n }\n }\n\n context.report({\n node,\n messageId: \"noForOfLoop\",\n suggest: suggest.length > 0 ? suggest : undefined,\n })\n },\n\n WhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noWhileLoop\",\n })\n },\n\n DoWhileStatement(node: ASTNode) {\n if (allowWhileLoops) return\n if (allowInTests && isInTestFile()) return\n\n context.report({\n node,\n messageId: \"noDoWhileLoop\",\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/no-let.d.ts
4
+ declare const rule: Rule.RuleModule;
5
+ //#endregion
6
+ export { rule as default };
7
+ //# sourceMappingURL=no-let.d.ts.map
@@ -0,0 +1,2 @@
1
+ const e={meta:{type:`suggestion`,fixable:`code`,hasSuggestions:!0,docs:{description:`Prefer const over let — use immutable bindings`,recommended:!0},schema:[{type:`object`,properties:{allowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{noLet:`Prefer const over let — use immutable bindings`,suggestConst:`Replace let with const`}},create(e){let t=(e.options[0]||{}).allowInTests!==!1;function n(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}return{VariableDeclaration(r){r.kind===`let`&&(t&&n()||(e.sourceCode.getDeclaredVariables(r).some(e=>e.references.some(t=>t.isWrite()&&t.identifier!==e.defs[0]?.name))?e.report({node:r,messageId:`noLet`,suggest:[{messageId:`suggestConst`,fix(t){let n=e.sourceCode.getFirstToken(r);return n&&n.value===`let`?t.replaceText(n,`const`):null}}]}):e.report({node:r,messageId:`noLet`,fix(t){let n=e.sourceCode.getFirstToken(r);return n&&n.value===`let`?t.replaceText(n,`const`):null}})))}}}};export{e as default};
2
+ //# sourceMappingURL=no-let.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-let.js","names":[],"sources":["../../src/rules/no-let.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n fixable: \"code\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer const over let — use immutable bindings\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n noLet: \"Prefer const over let — use immutable bindings\",\n suggestConst: \"Replace let with const\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowInTests = options.allowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n return {\n VariableDeclaration(node: ASTNode) {\n if (node.kind !== \"let\") return\n if (allowInTests && isInTestFile()) return\n\n const declaredVars = context.sourceCode.getDeclaredVariables(node)\n const hasReassignment = declaredVars.some((variable: ASTNode) =>\n variable.references.some((ref: ASTNode) => ref.isWrite() && ref.identifier !== variable.defs[0]?.name),\n )\n\n if (!hasReassignment) {\n // Safe to autofix — no reassignment detected\n context.report({\n node,\n messageId: \"noLet\",\n fix(fixer: Rule.RuleFixer) {\n const sourceCode = context.sourceCode\n const firstToken = sourceCode.getFirstToken(node)\n if (firstToken && firstToken.value === \"let\") {\n return fixer.replaceText(firstToken, \"const\")\n }\n return null\n },\n })\n } else {\n // Reassignment found — warn only, with suggestion\n context.report({\n node,\n messageId: \"noLet\",\n suggest: [\n {\n messageId: \"suggestConst\",\n fix(fixer: Rule.RuleFixer) {\n const sourceCode = context.sourceCode\n const firstToken = sourceCode.getFirstToken(node)\n if (firstToken && firstToken.value === \"let\") {\n return fixer.replaceText(firstToken, \"const\")\n }\n return null\n },\n },\n ],\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,QAAS,OACT,eAAgB,GAChB,KAAM,CACJ,YAAa,iDACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,MAAO,iDACP,aAAc,yBACf,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACX,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,MAAO,CACL,oBAAoB,EAAe,CAC7B,EAAK,OAAS,QACd,GAAgB,GAAc,GAEb,EAAQ,WAAW,qBAAqB,EAAK,CAC7B,KAAM,GACzC,EAAS,WAAW,KAAM,GAAiB,EAAI,SAAS,EAAI,EAAI,aAAe,EAAS,KAAK,IAAI,KAAK,CACvG,CAkBC,EAAQ,OAAO,CACb,OACA,UAAW,QACX,QAAS,CACP,CACE,UAAW,eACX,IAAI,EAAuB,CAEzB,IAAM,EADa,EAAQ,WACG,cAAc,EAAK,CAIjD,OAHI,GAAc,EAAW,QAAU,MAC9B,EAAM,YAAY,EAAY,QAAQ,CAExC,MAEV,CACF,CACF,CAAC,CA9BF,EAAQ,OAAO,CACb,OACA,UAAW,QACX,IAAI,EAAuB,CAEzB,IAAM,EADa,EAAQ,WACG,cAAc,EAAK,CAIjD,OAHI,GAAc,EAAW,QAAU,MAC9B,EAAM,YAAY,EAAY,QAAQ,CAExC,MAEV,CAAC,IAsBP,EAEJ"}
@@ -1,2 +1,2 @@
1
- const e={meta:{type:`suggestion`,docs:{description:`Prefer Either<E, T> over try/catch blocks and throw statements`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverTryCatch:`Prefer Either<Error, T> over try/catch block`,preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`}},create(e){let t=(e.options[0]||{}).allowThrowInTests!==!1;function n(){let t=e.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(t)||t.includes(`__tests__`)||t.includes(`/test/`)||t.includes(`/tests/`)}function r(e){if(!e)return!1;if(e.type===`ThrowStatement`){let t=e.parent;for(;t;){if(t.type===`CatchClause`)return!1;t=t.parent}return!0}if(e.type===`CatchClause`)return!1;for(let t in e){if(t===`parent`)continue;let n=e[t];if(Array.isArray(n)){for(let e of n)if(e&&typeof e==`object`&&r(e))return!0}else if(n&&typeof n==`object`&&r(n))return!0}return!1}function i(i){if(!(t&&n())&&i.body&&r(i.body)){let t=i.returnType?.typeAnnotation;if(t){let n=e.sourceCode.getText(t);n.includes(`Either`)||e.report({node:i.id||i,messageId:`preferEitherReturn`,data:{type:n}})}}}return{TryStatement(r){t&&n()||r.handler&&r.handler.body&&r.handler.body.body.some(e=>e.type===`ThrowStatement`)||e.report({node:r,messageId:`preferEitherOverTryCatch`})},ThrowStatement(r){if(t&&n())return;let i=r.parent;for(;i;){if(i.type===`CatchClause`)return;i=i.parent}e.report({node:r,messageId:`preferEitherOverThrow`})},FunctionDeclaration(e){i(e)},ArrowFunctionExpression(e){i(e)}}}};export{e as default};
1
+ import{createImportFixer as e,hasFunctypeSymbol as t}from"../utils/import-fixer.js";const n={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer Either<E, T> over try/catch blocks and throw statements`,recommended:!0},schema:[{type:`object`,properties:{allowThrowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferEitherOverTryCatch:`Prefer Either<Error, T> over try/catch block`,preferEitherOverThrow:`Prefer Either.left(error) over throw statement`,preferEitherReturn:`Consider returning Either<Error, {{type}}> instead of throwing`,suggestTry:`Replace with Try(() => ...)`,suggestTryFromPromise:`Replace with Try.fromPromise(...)`,suggestEitherLeft:`Replace with Either.left(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(n){let r=(n.options[0]||{}).allowThrowInTests!==!1;function i(){let e=n.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function a(e){if(!e)return!1;if(e.type===`ThrowStatement`){let t=e.parent;for(;t;){if(t.type===`CatchClause`)return!1;t=t.parent}return!0}if(e.type===`CatchClause`)return!1;for(let t in e){if(t===`parent`)continue;let n=e[t];if(Array.isArray(n)){for(let e of n)if(e&&typeof e==`object`&&a(e))return!0}else if(n&&typeof n==`object`&&a(n))return!0}return!1}function o(e){if(!(r&&i())&&e.body&&a(e.body)){let t=e.returnType?.typeAnnotation;if(t){let r=n.sourceCode.getText(t);r.includes(`Either`)||n.report({node:e.id||e,messageId:`preferEitherReturn`,data:{type:r}})}}}function s(e){return e.block?.body?.length===1}function c(e){if(!e.handler?.body)return!1;let t=e.handler.body.body;return t.length===0||t.length===1&&(t[0].type===`ReturnStatement`||t[0].type===`ExpressionStatement`)}function l(e){let t=e.block.body[0];return t.type===`ReturnStatement`&&t.argument?.type===`AwaitExpression`||t.type===`ExpressionStatement`&&t.expression?.type===`AwaitExpression`}function u(e){return e?[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`].includes(e.type):!1}function d(e){let t=e.parent;if(!t)return!1;if(t.type===`BlockStatement`&&u(t.parent))return!0;if(t.type===`BlockStatement`&&t.parent?.type===`IfStatement`){let e=t.parent.parent;return e?.type===`BlockStatement`&&u(e.parent)}return!1}return{TryStatement(a){if(r&&i()||a.handler&&a.handler.body&&a.handler.body.body.some(e=>e.type===`ThrowStatement`))return;let o=n.sourceCode,u=[];if(s(a)&&c(a)){let n=a.block.body[0];if(n.type===`ReturnStatement`){let r=n.argument,i=o.getText(r);if(l(a)){let e=r.type===`AwaitExpression`?r.argument:r,t=o.getText(e);u.push({messageId:`suggestTryFromPromise`,fix(e){return e.replaceText(a,`return Try.fromPromise(${t})`)}})}else u.push({messageId:`suggestTry`,fix(e){return e.replaceText(a,`return Try(() => ${i})`)}});t(o,`Try`)||u.push({messageId:`suggestAddImport`,data:{symbol:`Try`},fix:e(o,`Try`)})}}n.report({node:a,messageId:`preferEitherOverTryCatch`,suggest:u})},ThrowStatement(a){if(r&&i())return;let o=a.parent;for(;o;){if(o.type===`CatchClause`)return;o=o.parent}let s=n.sourceCode,c=[];if(d(a)){let n=a.argument,r=s.getText(n),i=n?.type===`NewExpression`&&n.callee?.type===`Identifier`&&n.callee.name===`Error`?r:`new Error(String(${r}))`;c.push({messageId:`suggestEitherLeft`,fix(e){return e.replaceText(a,`return Either.left(${i})`)}}),t(s,`Either`)||c.push({messageId:`suggestAddImport`,data:{symbol:`Either`},fix:e(s,`Either`)})}n.report({node:a,messageId:`preferEitherOverThrow`,suggest:c})},FunctionDeclaration(e){o(e)},ArrowFunctionExpression(e){o(e)}}}};export{n as default};
2
2
  //# sourceMappingURL=prefer-either.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-either.js","names":[],"sources":["../../src/rules/prefer-either.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer Either<E, T> over try/catch blocks and throw statements\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowThrowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferEitherOverTryCatch: \"Prefer Either<Error, T> over try/catch block\",\n preferEitherOverThrow: \"Prefer Either.left(error) over throw statement\",\n preferEitherReturn: \"Consider returning Either<Error, {{type}}> instead of throwing\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowThrowInTests = options.allowThrowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function hasThrowStatementsOutsideCatch(node: ASTNode): boolean {\n if (!node) return false\n\n if (node.type === \"ThrowStatement\") {\n // Check if this throw is inside a catch block\n let parent = node.parent\n while (parent) {\n if (parent.type === \"CatchClause\") return false\n parent = parent.parent\n }\n return true\n }\n\n // Skip catch blocks when recursing\n if (node.type === \"CatchClause\") return false\n\n // Recursively check child nodes\n for (const key in node) {\n if (key === \"parent\") continue // Avoid circular references\n const child = node[key]\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && hasThrowStatementsOutsideCatch(item)) {\n return true\n }\n }\n } else if (child && typeof child === \"object\" && hasThrowStatementsOutsideCatch(child)) {\n return true\n }\n }\n\n return false\n }\n\n function checkFunctionForThrows(node: ASTNode): void {\n // Allow functions in test files\n if (allowThrowInTests && isInTestFile()) return\n\n if (!node.body) return\n\n // Only report function-level errors if there are throws NOT in catch blocks\n const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body)\n if (hasThrowsNotInCatch) {\n const returnType = node.returnType?.typeAnnotation\n if (returnType) {\n const sourceCode = context.sourceCode\n const returnTypeText = sourceCode.getText(returnType)\n\n // Don't report if already using Either\n if (!returnTypeText.includes(\"Either\")) {\n context.report({\n node: node.id || node,\n messageId: \"preferEitherReturn\",\n data: { type: returnTypeText },\n })\n }\n }\n }\n }\n\n return {\n TryStatement(node: ASTNode) {\n // Allow try/catch in test files\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow try/catch that re-throws in the catch block (even with logging)\n if (node.handler && node.handler.body) {\n const catchBody = node.handler.body.body\n const hasRethrow = catchBody.some((stmt: ASTNode) => stmt.type === \"ThrowStatement\")\n if (hasRethrow) return\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverTryCatch\",\n })\n },\n\n ThrowStatement(node: ASTNode) {\n // Allow throws in test files if configured\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow re-throwing in catch blocks (common pattern)\n let parent = node.parent\n while (parent) {\n if (parent.type === \"CatchClause\") return\n parent = parent.parent\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverThrow\",\n })\n },\n\n FunctionDeclaration(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n\n ArrowFunctionExpression(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"AAIA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,iEACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,kBAAmB,CACjB,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,yBAA0B,+CAC1B,sBAAuB,iDACvB,mBAAoB,iEACrB,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACN,oBAAsB,GAExD,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,SAAS,EAA+B,EAAwB,CAC9D,GAAI,CAAC,EAAM,MAAO,GAElB,GAAI,EAAK,OAAS,iBAAkB,CAElC,IAAI,EAAS,EAAK,OAClB,KAAO,GAAQ,CACb,GAAI,EAAO,OAAS,cAAe,MAAO,GAC1C,EAAS,EAAO,OAElB,MAAO,GAIT,GAAI,EAAK,OAAS,cAAe,MAAO,GAGxC,IAAK,IAAM,KAAO,EAAM,CACtB,GAAI,IAAQ,SAAU,SACtB,IAAM,EAAQ,EAAK,GACnB,GAAI,MAAM,QAAQ,EAAM,MACjB,IAAM,KAAQ,EACjB,GAAI,GAAQ,OAAO,GAAS,UAAY,EAA+B,EAAK,CAC1E,MAAO,WAGF,GAAS,OAAO,GAAU,UAAY,EAA+B,EAAM,CACpF,MAAO,GAIX,MAAO,GAGT,SAAS,EAAuB,EAAqB,CAE/C,QAAqB,GAAc,GAElC,EAAK,MAGkB,EAA+B,EAAK,KAAK,CAC5C,CACvB,IAAM,EAAa,EAAK,YAAY,eACpC,GAAI,EAAY,CAEd,IAAM,EADa,EAAQ,WACO,QAAQ,EAAW,CAGhD,EAAe,SAAS,SAAS,EACpC,EAAQ,OAAO,CACb,KAAM,EAAK,IAAM,EACjB,UAAW,qBACX,KAAM,CAAE,KAAM,EAAgB,CAC/B,CAAC,GAMV,MAAO,CACL,aAAa,EAAe,CAEtB,GAAqB,GAAc,EAGnC,EAAK,SAAW,EAAK,QAAQ,MACb,EAAK,QAAQ,KAAK,KACP,KAAM,GAAkB,EAAK,OAAS,iBAAiB,EAItF,EAAQ,OAAO,CACb,OACA,UAAW,2BACZ,CAAC,EAGJ,eAAe,EAAe,CAE5B,GAAI,GAAqB,GAAc,CAAE,OAGzC,IAAI,EAAS,EAAK,OAClB,KAAO,GAAQ,CACb,GAAI,EAAO,OAAS,cAAe,OACnC,EAAS,EAAO,OAGlB,EAAQ,OAAO,CACb,OACA,UAAW,wBACZ,CAAC,EAGJ,oBAAoB,EAAe,CACjC,EAAuB,EAAK,EAG9B,wBAAwB,EAAe,CACrC,EAAuB,EAAK,EAE/B,EAEJ"}
1
+ {"version":3,"file":"prefer-either.js","names":[],"sources":["../../src/rules/prefer-either.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer Either<E, T> over try/catch blocks and throw statements\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowThrowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferEitherOverTryCatch: \"Prefer Either<Error, T> over try/catch block\",\n preferEitherOverThrow: \"Prefer Either.left(error) over throw statement\",\n preferEitherReturn: \"Consider returning Either<Error, {{type}}> instead of throwing\",\n suggestTry: \"Replace with Try(() => ...)\",\n suggestTryFromPromise: \"Replace with Try.fromPromise(...)\",\n suggestEitherLeft: \"Replace with Either.left(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowThrowInTests = options.allowThrowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function hasThrowStatementsOutsideCatch(node: ASTNode): boolean {\n if (!node) return false\n\n if (node.type === \"ThrowStatement\") {\n // Check if this throw is inside a catch block\n let parent = node.parent\n while (parent) {\n if (parent.type === \"CatchClause\") return false\n parent = parent.parent\n }\n return true\n }\n\n // Skip catch blocks when recursing\n if (node.type === \"CatchClause\") return false\n\n // Recursively check child nodes\n for (const key in node) {\n if (key === \"parent\") continue // Avoid circular references\n const child = node[key]\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && hasThrowStatementsOutsideCatch(item)) {\n return true\n }\n }\n } else if (child && typeof child === \"object\" && hasThrowStatementsOutsideCatch(child)) {\n return true\n }\n }\n\n return false\n }\n\n function checkFunctionForThrows(node: ASTNode): void {\n // Allow functions in test files\n if (allowThrowInTests && isInTestFile()) return\n\n if (!node.body) return\n\n // Only report function-level errors if there are throws NOT in catch blocks\n const hasThrowsNotInCatch = hasThrowStatementsOutsideCatch(node.body)\n if (hasThrowsNotInCatch) {\n const returnType = node.returnType?.typeAnnotation\n if (returnType) {\n const sourceCode = context.sourceCode\n const returnTypeText = sourceCode.getText(returnType)\n\n // Don't report if already using Either\n if (!returnTypeText.includes(\"Either\")) {\n context.report({\n node: node.id || node,\n messageId: \"preferEitherReturn\",\n data: { type: returnTypeText },\n })\n }\n }\n }\n }\n\n function isSimpleTryBody(node: ASTNode): boolean {\n return node.block?.body?.length === 1\n }\n\n function isSimpleCatch(node: ASTNode): boolean {\n if (!node.handler?.body) return false\n const catchBody = node.handler.body.body\n return (\n catchBody.length === 0 ||\n (catchBody.length === 1 &&\n (catchBody[0].type === \"ReturnStatement\" || catchBody[0].type === \"ExpressionStatement\"))\n )\n }\n\n function tryBodyHasAwait(node: ASTNode): boolean {\n const stmt = node.block.body[0]\n if (stmt.type === \"ReturnStatement\" && stmt.argument?.type === \"AwaitExpression\") return true\n if (stmt.type === \"ExpressionStatement\" && stmt.expression?.type === \"AwaitExpression\") return true\n return false\n }\n\n function isFunctionLike(node: ASTNode): boolean {\n if (!node) return false\n return [\"FunctionDeclaration\", \"FunctionExpression\", \"ArrowFunctionExpression\"].includes(node.type)\n }\n\n function isDirectInFunctionBody(node: ASTNode): boolean {\n const parent = node.parent\n if (!parent) return false\n if (parent.type === \"BlockStatement\" && isFunctionLike(parent.parent)) return true\n if (parent.type === \"BlockStatement\" && parent.parent?.type === \"IfStatement\") {\n const ifParent = parent.parent.parent\n return ifParent?.type === \"BlockStatement\" && isFunctionLike(ifParent.parent)\n }\n return false\n }\n\n return {\n TryStatement(node: ASTNode) {\n // Allow try/catch in test files\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow try/catch that re-throws in the catch block (even with logging)\n if (node.handler && node.handler.body) {\n const catchBody = node.handler.body.body\n const hasRethrow = catchBody.some((stmt: ASTNode) => stmt.type === \"ThrowStatement\")\n if (hasRethrow) return\n }\n\n const sourceCode = context.sourceCode\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isSimpleTryBody(node) && isSimpleCatch(node)) {\n const tryStmt = node.block.body[0]\n const isReturn = tryStmt.type === \"ReturnStatement\"\n\n // Only suggest when the try body is a return statement — non-return expression\n // replacements would produce syntactically ambiguous code without knowing the context\n if (isReturn) {\n const expr = tryStmt.argument\n const exprText = sourceCode.getText(expr)\n\n if (tryBodyHasAwait(node)) {\n const awaitExpr = expr.type === \"AwaitExpression\" ? expr.argument : expr\n const innerText = sourceCode.getText(awaitExpr)\n suggest.push({\n messageId: \"suggestTryFromPromise\",\n fix(fixer) {\n return fixer.replaceText(node, `return Try.fromPromise(${innerText})`)\n },\n })\n } else {\n suggest.push({\n messageId: \"suggestTry\",\n fix(fixer) {\n return fixer.replaceText(node, `return Try(() => ${exprText})`)\n },\n })\n }\n\n if (!hasFunctypeSymbol(sourceCode, \"Try\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Try\" },\n fix: createImportFixer(sourceCode, \"Try\"),\n })\n }\n }\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverTryCatch\",\n suggest,\n })\n },\n\n ThrowStatement(node: ASTNode) {\n // Allow throws in test files if configured\n if (allowThrowInTests && isInTestFile()) return\n\n // Allow re-throwing in catch blocks (common pattern)\n let parent = node.parent\n while (parent) {\n if (parent.type === \"CatchClause\") return\n parent = parent.parent\n }\n\n const sourceCode = context.sourceCode\n const suggest: Rule.SuggestionReportDescriptor[] = []\n\n if (isDirectInFunctionBody(node)) {\n const throwArg = node.argument\n const argText = sourceCode.getText(throwArg)\n const isErrorExpr =\n throwArg?.type === \"NewExpression\" &&\n throwArg.callee?.type === \"Identifier\" &&\n throwArg.callee.name === \"Error\"\n const eitherArg = isErrorExpr ? argText : `new Error(String(${argText}))`\n\n suggest.push({\n messageId: \"suggestEitherLeft\",\n fix(fixer) {\n return fixer.replaceText(node, `return Either.left(${eitherArg})`)\n },\n })\n\n if (!hasFunctypeSymbol(sourceCode, \"Either\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Either\" },\n fix: createImportFixer(sourceCode, \"Either\"),\n })\n }\n }\n\n context.report({\n node,\n messageId: \"preferEitherOverThrow\",\n suggest,\n })\n },\n\n FunctionDeclaration(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n\n ArrowFunctionExpression(node: ASTNode) {\n checkFunctionForThrows(node)\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/prefer-functype-map.d.ts
4
+ declare const rule: Rule.RuleModule;
5
+ //#endregion
6
+ export { rule as default };
7
+ //# sourceMappingURL=prefer-functype-map.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{getFunctypeImportsLegacy as e,isAlreadyUsingFunctype as t}from"../utils/functype-detection.js";import{createImportFixer as n,hasFunctypeSymbol as r}from"../utils/import-fixer.js";const i={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer functype Map<K, V> over native Map for immutable key-value collections`,recommended:!0},schema:[{type:`object`,properties:{allowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferFunctypeMap:`Prefer functype Map<{{keyType}}, {{valueType}}> over native Map`,preferFunctypeMapLiteral:`Prefer Map.of(...) or Map.empty() over new Map()`,suggestMapEmpty:`Replace with Map.empty()`,suggestMapOf:`Replace with Map.of(...)`,suggestMapFrom:`Replace with Map(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(i){let a=(i.options[0]||{}).allowInTests!==!1;function o(){let e=i.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}let s=e(i);function c(){return r(i.sourceCode,`Map`)}return{NewExpression(e){if(a&&o()||!e.callee||e.callee.type!==`Identifier`||e.callee.name!==`Map`||c()||t(e,s))return;let r=i.sourceCode,l=e.arguments,u=[];if(l.length===0)u.push({messageId:`suggestMapEmpty`,fix(t){return t.replaceText(e,`Map.empty()`)}});else if(l.length===1&&l[0].type===`ArrayExpression`){let t=l[0].elements.map(e=>r.getText(e)).join(`, `);u.push({messageId:`suggestMapOf`,fix(n){return n.replaceText(e,`Map.of(${t})`)}})}else if(l.length===1){let t=r.getText(l[0]);u.push({messageId:`suggestMapFrom`,fix(n){return n.replaceText(e,`Map(${t})`)}})}else u.push({messageId:`suggestMapEmpty`,fix(t){return t.replaceText(e,`Map.empty()`)}});c()||u.push({messageId:`suggestAddImport`,data:{symbol:`Map`},fix:n(r,`Map`)}),i.report({node:e,messageId:`preferFunctypeMapLiteral`,suggest:u})},TSTypeReference(e){if(a&&o()||!e.typeName)return;let t=i.sourceCode;if((e.typeName.type===`Identifier`?e.typeName.name:t.getText(e.typeName))!==`Map`||c())return;let r=`K`,s=`V`,l=e.typeArguments?.params??e.typeParameters?.params;l&&l.length>=2?(r=t.getText(l[0]),s=t.getText(l[1])):l&&l.length===1&&(r=t.getText(l[0]));let u=[{messageId:`suggestAddImport`,data:{symbol:`Map`},fix:n(t,`Map`)}];i.report({node:e,messageId:`preferFunctypeMap`,data:{keyType:r,valueType:s},suggest:u})}}}};export{i as default};
2
+ //# sourceMappingURL=prefer-functype-map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-functype-map.js","names":[],"sources":["../../src/rules/prefer-functype-map.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype } from \"../utils/functype-detection\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer functype Map<K, V> over native Map for immutable key-value collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFunctypeMap: \"Prefer functype Map<{{keyType}}, {{valueType}}> over native Map\",\n preferFunctypeMapLiteral: \"Prefer Map.of(...) or Map.empty() over new Map()\",\n suggestMapEmpty: \"Replace with Map.empty()\",\n suggestMapOf: \"Replace with Map.of(...)\",\n suggestMapFrom: \"Replace with Map(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowInTests = options.allowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n const functypeImports = getFunctypeImportsLegacy(context)\n\n function isMapImportedFromFunctype(): boolean {\n return hasFunctypeSymbol(context.sourceCode, \"Map\")\n }\n\n return {\n NewExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Only flag `new Map(...)` calls\n if (!node.callee || node.callee.type !== \"Identifier\" || node.callee.name !== \"Map\") return\n\n // Skip if Map is already imported from functype\n if (isMapImportedFromFunctype()) return\n\n // Skip if already using functype\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const args = node.arguments as ASTNode[]\n\n const suggestions: Rule.SuggestionReportDescriptor[] = []\n\n if (args.length === 0) {\n // new Map() → Map.empty()\n suggestions.push({\n messageId: \"suggestMapEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Map.empty()\")\n },\n })\n } else if (args.length === 1 && args[0].type === \"ArrayExpression\") {\n // new Map([[\"a\", 1], [\"b\", 2]]) → Map.of([\"a\", 1], [\"b\", 2])\n const arrayArg = args[0] as ASTNode\n const elements = arrayArg.elements as ASTNode[]\n const tupleTexts = elements.map((el: ASTNode) => sourceCode.getText(el))\n const mapOfArgs = tupleTexts.join(\", \")\n suggestions.push({\n messageId: \"suggestMapOf\",\n fix(fixer) {\n return fixer.replaceText(node, `Map.of(${mapOfArgs})`)\n },\n })\n } else if (args.length === 1) {\n // new Map(someVar) → Map(someVar)\n const argText = sourceCode.getText(args[0])\n suggestions.push({\n messageId: \"suggestMapFrom\",\n fix(fixer) {\n return fixer.replaceText(node, `Map(${argText})`)\n },\n })\n } else {\n // Generic fallback for any other args\n suggestions.push({\n messageId: \"suggestMapEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Map.empty()\")\n },\n })\n }\n\n if (!isMapImportedFromFunctype()) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Map\" },\n fix: createImportFixer(sourceCode, \"Map\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferFunctypeMapLiteral\",\n suggest: suggestions,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (!node.typeName) return\n\n const sourceCode = context.sourceCode\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n if (typeName !== \"Map\") return\n\n // Skip if Map is already imported from functype\n if (isMapImportedFromFunctype()) return\n\n // Extract key/value type params if present (typeArguments for newer TS-ESLint, typeParameters for older)\n let keyType = \"K\"\n let valueType = \"V\"\n const typeParams = node.typeArguments?.params ?? node.typeParameters?.params\n if (typeParams && typeParams.length >= 2) {\n keyType = sourceCode.getText(typeParams[0])\n valueType = sourceCode.getText(typeParams[1])\n } else if (typeParams && typeParams.length === 1) {\n keyType = sourceCode.getText(typeParams[0])\n }\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestAddImport\",\n data: { symbol: \"Map\" },\n fix: createImportFixer(sourceCode, \"Map\"),\n },\n ]\n\n context.report({\n node,\n messageId: \"preferFunctypeMap\",\n data: { keyType, valueType },\n suggest: suggestions,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"0LAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,gFACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,kBAAmB,kEACnB,yBAA0B,mDAC1B,gBAAiB,2BACjB,aAAc,2BACd,eAAgB,wBAChB,iBAAkB,sCACnB,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACX,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,IAAM,EAAkB,EAAyB,EAAQ,CAEzD,SAAS,GAAqC,CAC5C,OAAO,EAAkB,EAAQ,WAAY,MAAM,CAGrD,MAAO,CACL,cAAc,EAAe,CAU3B,GATI,GAAgB,GAAc,EAG9B,CAAC,EAAK,QAAU,EAAK,OAAO,OAAS,cAAgB,EAAK,OAAO,OAAS,OAG1E,GAA2B,EAG3B,EAAuB,EAAM,EAAgB,CAAE,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAO,EAAK,UAEZ,EAAiD,EAAE,CAEzD,GAAI,EAAK,SAAW,EAElB,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,SACO,EAAK,SAAW,GAAK,EAAK,GAAG,OAAS,kBAAmB,CAKlE,IAAM,EAHW,EAAK,GACI,SACE,IAAK,GAAgB,EAAW,QAAQ,EAAG,CAAC,CAC3C,KAAK,KAAK,CACvC,EAAY,KAAK,CACf,UAAW,eACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAU,GAAG,EAEzD,CAAC,SACO,EAAK,SAAW,EAAG,CAE5B,IAAM,EAAU,EAAW,QAAQ,EAAK,GAAG,CAC3C,EAAY,KAAK,CACf,UAAW,iBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,OAAO,EAAQ,GAAG,EAEpD,CAAC,MAGF,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,CAGC,GAA2B,EAC9B,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,2BACX,QAAS,EACV,CAAC,EAGJ,gBAAgB,EAAe,CAG7B,GAFI,GAAgB,GAAc,EAE9B,CAAC,EAAK,SAAU,OAEpB,IAAM,EAAa,EAAQ,WAM3B,IALiB,EAAK,SAAS,OAAS,aAAe,EAAK,SAAS,KAAO,EAAW,QAAQ,EAAK,SAAS,IAE5F,OAGb,GAA2B,CAAE,OAGjC,IAAI,EAAU,IACV,EAAY,IACV,EAAa,EAAK,eAAe,QAAU,EAAK,gBAAgB,OAClE,GAAc,EAAW,QAAU,GACrC,EAAU,EAAW,QAAQ,EAAW,GAAG,CAC3C,EAAY,EAAW,QAAQ,EAAW,GAAG,EACpC,GAAc,EAAW,SAAW,IAC7C,EAAU,EAAW,QAAQ,EAAW,GAAG,EAG7C,IAAM,EAAiD,CACrD,CACE,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CACF,CAED,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,UAAS,YAAW,CAC5B,QAAS,EACV,CAAC,EAEL,EAEJ"}
@@ -0,0 +1,7 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/prefer-functype-set.d.ts
4
+ declare const rule: Rule.RuleModule;
5
+ //#endregion
6
+ export { rule as default };
7
+ //# sourceMappingURL=prefer-functype-set.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{getFunctypeImportsLegacy as e,isAlreadyUsingFunctype as t}from"../utils/functype-detection.js";import{createImportFixer as n,hasFunctypeSymbol as r}from"../utils/import-fixer.js";const i={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer functype Set<T> over native Set for immutable collections`,recommended:!0},schema:[{type:`object`,properties:{allowInTests:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferFunctypeSet:`Prefer functype Set<{{type}}> over native Set`,preferFunctypeSetLiteral:`Prefer Set.of(...) or Set.empty() over new Set()`,suggestSetEmpty:`Replace with Set.empty()`,suggestSetOf:`Replace with Set.of(...)`,suggestSetFrom:`Replace with Set(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(i){let a=(i.options[0]||{}).allowInTests!==!1;function o(){let e=i.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}let s=e(i);function c(){return r(i.sourceCode,`Set`)}return{NewExpression(e){if(a&&o()||!e.callee||e.callee.type!==`Identifier`||e.callee.name!==`Set`||c()||t(e,s))return;let r=i.sourceCode,l=e.arguments||[],u=[];if(l.length===0)u.push({messageId:`suggestSetEmpty`,fix(t){return t.replaceText(e,`Set.empty()`)}});else if(l.length===1&&l[0].type===`ArrayExpression`){let t=(l[0].elements||[]).map(e=>r.getText(e)).join(`, `);u.push({messageId:`suggestSetOf`,fix(n){return n.replaceText(e,`Set.of(${t})`)}})}else if(l.length===1){let t=r.getText(l[0]);u.push({messageId:`suggestSetFrom`,fix(n){return n.replaceText(e,`Set(${t})`)}})}else u.push({messageId:`suggestSetEmpty`,fix(t){return t.replaceText(e,`Set.empty()`)}});c()||u.push({messageId:`suggestAddImport`,data:{symbol:`Set`},fix:n(r,`Set`)}),i.report({node:e,messageId:`preferFunctypeSetLiteral`,suggest:u})},TSTypeReference(e){if(a&&o()||!e.typeName)return;let t=i.sourceCode;if((e.typeName.type===`Identifier`?e.typeName.name:t.getText(e.typeName))!==`Set`||c())return;let r=`T`;e.typeParameters?.params?.[0]?r=t.getText(e.typeParameters.params[0]):e.typeArguments?.params?.[0]&&(r=t.getText(e.typeArguments.params[0]));let s=[{messageId:`suggestAddImport`,data:{symbol:`Set`},fix:n(t,`Set`)}];i.report({node:e,messageId:`preferFunctypeSet`,data:{type:r},suggest:s})}}}};export{i as default};
2
+ //# sourceMappingURL=prefer-functype-set.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-functype-set.js","names":[],"sources":["../../src/rules/prefer-functype-set.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype } from \"../utils/functype-detection\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer functype Set<T> over native Set for immutable collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowInTests: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferFunctypeSet: \"Prefer functype Set<{{type}}> over native Set\",\n preferFunctypeSetLiteral: \"Prefer Set.of(...) or Set.empty() over new Set()\",\n suggestSetEmpty: \"Replace with Set.empty()\",\n suggestSetOf: \"Replace with Set.of(...)\",\n suggestSetFrom: \"Replace with Set(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowInTests = options.allowInTests !== false\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n const functypeImports = getFunctypeImportsLegacy(context)\n\n function isSetImportedFromFunctype() {\n return hasFunctypeSymbol(context.sourceCode, \"Set\")\n }\n\n return {\n NewExpression(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n // Only handle `new Set(...)` calls\n if (!node.callee || node.callee.type !== \"Identifier\" || node.callee.name !== \"Set\") return\n\n // Skip if Set is already imported from functype\n if (isSetImportedFromFunctype()) return\n\n // Skip if already in a functype context\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const args = node.arguments || []\n const suggestions: Rule.SuggestionReportDescriptor[] = []\n\n if (args.length === 0) {\n // new Set() → Set.empty()\n suggestions.push({\n messageId: \"suggestSetEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Set.empty()\")\n },\n })\n } else if (args.length === 1 && args[0].type === \"ArrayExpression\") {\n // new Set([\"a\", \"b\", \"c\"]) → Set.of(\"a\", \"b\", \"c\")\n const arrayNode = args[0]\n const elements = arrayNode.elements || []\n const elementTexts = elements.map((el: ASTNode) => sourceCode.getText(el))\n const argsText = elementTexts.join(\", \")\n suggestions.push({\n messageId: \"suggestSetOf\",\n fix(fixer) {\n return fixer.replaceText(node, `Set.of(${argsText})`)\n },\n })\n } else if (args.length === 1) {\n // new Set(someVar) → Set(someVar)\n const argText = sourceCode.getText(args[0])\n suggestions.push({\n messageId: \"suggestSetFrom\",\n fix(fixer) {\n return fixer.replaceText(node, `Set(${argText})`)\n },\n })\n } else {\n // Fallback for unexpected cases\n suggestions.push({\n messageId: \"suggestSetEmpty\",\n fix(fixer) {\n return fixer.replaceText(node, \"Set.empty()\")\n },\n })\n }\n\n if (!isSetImportedFromFunctype()) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Set\" },\n fix: createImportFixer(sourceCode, \"Set\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferFunctypeSetLiteral\",\n suggest: suggestions,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowInTests && isInTestFile()) return\n\n if (!node.typeName) return\n\n const sourceCode = context.sourceCode\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n if (typeName !== \"Set\") return\n\n // Skip if Set is already imported from functype\n if (isSetImportedFromFunctype()) return\n\n // Extract type parameter if present\n let typeParam = \"T\"\n if (node.typeParameters?.params?.[0]) {\n typeParam = sourceCode.getText(node.typeParameters.params[0])\n } else if (node.typeArguments?.params?.[0]) {\n typeParam = sourceCode.getText(node.typeArguments.params[0])\n }\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestAddImport\",\n data: { symbol: \"Set\" },\n fix: createImportFixer(sourceCode, \"Set\"),\n },\n ]\n\n context.report({\n node,\n messageId: \"preferFunctypeSet\",\n data: { type: typeParam },\n suggest: suggestions,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"0LAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,mEACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,aAAc,CACZ,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,kBAAmB,gDACnB,yBAA0B,mDAC1B,gBAAiB,2BACjB,aAAc,2BACd,eAAgB,wBAChB,iBAAkB,sCACnB,CACF,CAED,OAAO,EAAS,CAEd,IAAM,GADU,EAAQ,QAAQ,IAAM,EAAE,EACX,eAAiB,GAE9C,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,IAAM,EAAkB,EAAyB,EAAQ,CAEzD,SAAS,GAA4B,CACnC,OAAO,EAAkB,EAAQ,WAAY,MAAM,CAGrD,MAAO,CACL,cAAc,EAAe,CAU3B,GATI,GAAgB,GAAc,EAG9B,CAAC,EAAK,QAAU,EAAK,OAAO,OAAS,cAAgB,EAAK,OAAO,OAAS,OAG1E,GAA2B,EAG3B,EAAuB,EAAM,EAAgB,CAAE,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAO,EAAK,WAAa,EAAE,CAC3B,EAAiD,EAAE,CAEzD,GAAI,EAAK,SAAW,EAElB,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,SACO,EAAK,SAAW,GAAK,EAAK,GAAG,OAAS,kBAAmB,CAKlE,IAAM,GAHY,EAAK,GACI,UAAY,EAAE,EACX,IAAK,GAAgB,EAAW,QAAQ,EAAG,CAAC,CAC5C,KAAK,KAAK,CACxC,EAAY,KAAK,CACf,UAAW,eACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAS,GAAG,EAExD,CAAC,SACO,EAAK,SAAW,EAAG,CAE5B,IAAM,EAAU,EAAW,QAAQ,EAAK,GAAG,CAC3C,EAAY,KAAK,CACf,UAAW,iBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,OAAO,EAAQ,GAAG,EAEpD,CAAC,MAGF,EAAY,KAAK,CACf,UAAW,kBACX,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,cAAc,EAEhD,CAAC,CAGC,GAA2B,EAC9B,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,2BACX,QAAS,EACV,CAAC,EAGJ,gBAAgB,EAAe,CAG7B,GAFI,GAAgB,GAAc,EAE9B,CAAC,EAAK,SAAU,OAEpB,IAAM,EAAa,EAAQ,WAM3B,IALiB,EAAK,SAAS,OAAS,aAAe,EAAK,SAAS,KAAO,EAAW,QAAQ,EAAK,SAAS,IAE5F,OAGb,GAA2B,CAAE,OAGjC,IAAI,EAAY,IACZ,EAAK,gBAAgB,SAAS,GAChC,EAAY,EAAW,QAAQ,EAAK,eAAe,OAAO,GAAG,CACpD,EAAK,eAAe,SAAS,KACtC,EAAY,EAAW,QAAQ,EAAK,cAAc,OAAO,GAAG,EAG9D,IAAM,EAAiD,CACrD,CACE,UAAW,mBACX,KAAM,CAAE,OAAQ,MAAO,CACvB,IAAK,EAAkB,EAAY,MAAM,CAC1C,CACF,CAED,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,KAAM,CAAE,KAAM,EAAW,CACzB,QAAS,EACV,CAAC,EAEL,EAEJ"}
@@ -1,2 +1,2 @@
1
- import{getFunctypeImportsLegacy as e,isFunctypeCall as t}from"../utils/functype-detection.js";const n={meta:{type:`suggestion`,docs:{description:`Prefer List<T> over native arrays for immutable collections`,recommended:!0},schema:[{type:`object`,properties:{allowArraysInTests:{type:`boolean`,default:!0},allowReadonlyArrays:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferList:`Prefer List<{{type}}> over array type {{arrayType}}`,preferListLiteral:`Prefer List.of(...) or List.from([...]) over array literal`}},create(n){let r=n.options[0]||{},i=r.allowArraysInTests!==!1,a=r.allowReadonlyArrays!==!1,o=e(n);function s(){let e=n.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function c(e,t){function n(e){if(e.type===`TSTypeParameterInstantiation`&&e.params&&e.params[0])return t.getText(e.params[0]);for(let t in e){if(t===`parent`)continue;let r=e[t];if(Array.isArray(r)){for(let e of r)if(e&&typeof e==`object`&&e.type){let t=n(e);if(t)return t}}else if(r&&typeof r==`object`&&r.type){let e=n(r);if(e)return e}}return null}return n(e)}return{TSArrayType(e){if(i&&s())return;let t=n.sourceCode,r=t.getText(e.elementType),a=t.getText(e);n.report({node:e,messageId:`preferList`,data:{type:r,arrayType:a}})},TSTypeReference(e){if(i&&s())return;let t=n.sourceCode;if(!e.typeName)return;let r=e.typeName.type===`Identifier`?e.typeName.name:t.getText(e.typeName);if(r===`Array`){let r=c(e,t),i=t.getText(e);if(a&&i.startsWith(`readonly`))return;n.report({node:e,messageId:`preferList`,data:{type:r||`T`,arrayType:i}})}if(r===`ReadonlyArray`){let r=c(e,t),i=t.getText(e);n.report({node:e,messageId:`preferList`,data:{type:r||`T`,arrayType:i}})}},ArrayExpression(e){if(i&&s()||e.elements.length===0)return;let r=e.parent;if(r&&t(r,o)||r&&r.type===`CallExpression`&&r.callee.type===`MemberExpression`&&r.callee.object.type===`Identifier`&&r.callee.object.name===`List`&&[`from`,`of`].includes(r.callee.property.name))return;for(r=e.parent;r;){if(r.type===`ArrayExpression`)return;r=r.parent}let a=!1;for(r=e.parent;r;){if(r.type===`VariableDeclarator`&&r.id?.typeAnnotation){a=!0;break}if(r.type===`TSTypeAnnotation`){a=!0;break}r=r.parent}a||n.report({node:e,messageId:`preferListLiteral`})}}}};export{n as default};
1
+ import{getFunctypeImportsLegacy as e,isFunctypeCall as t}from"../utils/functype-detection.js";import{createImportFixer as n,hasFunctypeSymbol as r}from"../utils/import-fixer.js";const i={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer List<T> over native arrays for immutable collections`,recommended:!0},schema:[{type:`object`,properties:{allowArraysInTests:{type:`boolean`,default:!0},allowReadonlyArrays:{type:`boolean`,default:!0}},additionalProperties:!1}],messages:{preferList:`Prefer List<{{type}}> over array type {{arrayType}}`,preferListLiteral:`Prefer List.of(...) or List.from([...]) over array literal`,suggestListType:`Replace with List<{{type}}>`,suggestListOf:`Replace with List.of(...)`,suggestAddImport:`Add {{symbol}} import from functype`}},create(i){let a=i.options[0]||{},o=a.allowArraysInTests!==!1,s=a.allowReadonlyArrays!==!1,c=e(i);function l(){let e=i.filename;return/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(e)||e.includes(`__tests__`)||e.includes(`/test/`)||e.includes(`/tests/`)}function u(e,t){function n(e){if(e.type===`TSTypeParameterInstantiation`&&e.params&&e.params[0])return t.getText(e.params[0]);for(let t in e){if(t===`parent`)continue;let r=e[t];if(Array.isArray(r)){for(let e of r)if(e&&typeof e==`object`&&e.type){let t=n(e);if(t)return t}}else if(r&&typeof r==`object`&&r.type){let e=n(r);if(e)return e}}return null}return n(e)}return{TSArrayType(e){if(o&&l())return;let t=i.sourceCode,a=t.getText(e.elementType),s=t.getText(e),c=[{messageId:`suggestListType`,data:{type:a},fix(t){return t.replaceText(e,`List<${a}>`)}}];r(t,`List`)||c.push({messageId:`suggestAddImport`,data:{symbol:`List`},fix:n(t,`List`)}),i.report({node:e,messageId:`preferList`,data:{type:a,arrayType:s},suggest:c})},TSTypeReference(e){if(o&&l())return;let t=i.sourceCode;if(!e.typeName)return;let a=e.typeName.type===`Identifier`?e.typeName.name:t.getText(e.typeName);if(a===`Array`){let a=u(e,t),o=t.getText(e);if(s&&o.startsWith(`readonly`))return;let c=a||`T`,l=[{messageId:`suggestListType`,data:{type:c},fix(t){return t.replaceText(e,`List<${c}>`)}}];r(t,`List`)||l.push({messageId:`suggestAddImport`,data:{symbol:`List`},fix:n(t,`List`)}),i.report({node:e,messageId:`preferList`,data:{type:c,arrayType:o},suggest:l})}if(a===`ReadonlyArray`){let a=u(e,t),o=t.getText(e),s=a||`T`,c=[{messageId:`suggestListType`,data:{type:s},fix(t){return t.replaceText(e,`List<${s}>`)}}];r(t,`List`)||c.push({messageId:`suggestAddImport`,data:{symbol:`List`},fix:n(t,`List`)}),i.report({node:e,messageId:`preferList`,data:{type:s,arrayType:o},suggest:c})}},ArrayExpression(e){if(o&&l()||e.elements.length===0)return;let a=e.parent;if(a&&t(a,c)||a&&a.type===`CallExpression`&&a.callee.type===`MemberExpression`&&a.callee.object.type===`Identifier`&&a.callee.object.name===`List`&&[`from`,`of`].includes(a.callee.property.name))return;for(a=e.parent;a;){if(a.type===`ArrayExpression`)return;a=a.parent}let s=!1;for(a=e.parent;a;){if(a.type===`VariableDeclarator`&&a.id?.typeAnnotation){s=!0;break}if(a.type===`TSTypeAnnotation`){s=!0;break}a=a.parent}if(s)return;if(e.elements.some(e=>e!==null&&e.type===`SpreadElement`)){i.report({node:e,messageId:`preferListLiteral`});return}let u=i.sourceCode,d=e.elements.filter(e=>e!==null).map(e=>u.getText(e)),f=[{messageId:`suggestListOf`,fix(t){return t.replaceText(e,`List.of(${d.join(`, `)})`)}}];r(u,`List`)||f.push({messageId:`suggestAddImport`,data:{symbol:`List`},fix:n(u,`List`)}),i.report({node:e,messageId:`preferListLiteral`,suggest:f})}}}};export{i as default};
2
2
  //# sourceMappingURL=prefer-list.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-list.js","names":[],"sources":["../../src/rules/prefer-list.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isFunctypeCall } from \"../utils/functype-detection\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer List<T> over native arrays for immutable collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowArraysInTests: {\n type: \"boolean\",\n default: true,\n },\n allowReadonlyArrays: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferList: \"Prefer List<{{type}}> over array type {{arrayType}}\",\n preferListLiteral: \"Prefer List.of(...) or List.from([...]) over array literal\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowArraysInTests = options.allowArraysInTests !== false\n const allowReadonlyArrays = options.allowReadonlyArrays !== false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function findTypeParameter(node: ASTNode, sourceCode: typeof context.sourceCode): string | null {\n // Look for TSTypeParameterInstantiation child\n function findInNode(n: ASTNode): string | null {\n if (n.type === \"TSTypeParameterInstantiation\" && n.params && n.params[0]) {\n return sourceCode.getText(n.params[0])\n }\n\n // Recursively search child nodes\n for (const key in n) {\n if (key === \"parent\") continue\n const child = n[key]\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && item.type) {\n const result = findInNode(item)\n if (result) return result\n }\n }\n } else if (child && typeof child === \"object\" && child.type) {\n const result = findInNode(child)\n if (result) return result\n }\n }\n\n return null\n }\n\n return findInNode(node)\n }\n\n return {\n TSArrayType(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n const elementType = sourceCode.getText(node.elementType)\n const fullType = sourceCode.getText(node)\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: elementType,\n arrayType: fullType,\n },\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n\n // Get type name - handle both simple identifiers and member expressions\n if (!node.typeName) return // No type name found\n\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n // Handle Array<T> syntax\n if (typeName === \"Array\") {\n // Look for type parameters in child nodes\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n\n // Skip if already readonly\n if (allowReadonlyArrays && fullType.startsWith(\"readonly\")) return\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: typeParam || \"T\",\n arrayType: fullType,\n },\n })\n }\n\n // Handle ReadonlyArray<T> - suggest List even if allowing readonly arrays\n if (typeName === \"ReadonlyArray\") {\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: typeParam || \"T\",\n arrayType: fullType,\n },\n })\n }\n },\n\n ArrayExpression(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n // Only flag non-empty arrays to avoid noise\n if (node.elements.length === 0) return\n\n // Don't flag arrays that are already arguments to List.from() or other functype calls\n let parent = node.parent\n if (parent && isFunctypeCall(parent, functypeImports)) {\n return\n }\n\n // Additional specific check for List.from/List.of patterns\n if (\n parent &&\n parent.type === \"CallExpression\" &&\n parent.callee.type === \"MemberExpression\" &&\n parent.callee.object.type === \"Identifier\" &&\n parent.callee.object.name === \"List\" &&\n [\"from\", \"of\"].includes(parent.callee.property.name)\n ) {\n return\n }\n\n // Don't flag nested array literals - only flag the outermost one\n // Check if this array is inside another array literal\n parent = node.parent\n while (parent) {\n if (parent.type === \"ArrayExpression\") {\n return // Skip nested arrays, let the outer one handle it\n }\n parent = parent.parent\n }\n\n // Don't flag array literals that are already part of a type annotation context\n // (those should be handled by the type checking rules)\n let hasTypeAnnotation = false\n parent = node.parent\n while (parent) {\n if (parent.type === \"VariableDeclarator\" && parent.id?.typeAnnotation) {\n // Skip array literal if there's already a type annotation that would be flagged\n hasTypeAnnotation = true\n break\n }\n if (parent.type === \"TSTypeAnnotation\") {\n hasTypeAnnotation = true\n break\n }\n parent = parent.parent\n }\n\n if (hasTypeAnnotation) return\n\n context.report({\n node,\n messageId: \"preferListLiteral\",\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"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"}
1
+ {"version":3,"file":"prefer-list.js","names":[],"sources":["../../src/rules/prefer-list.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isFunctypeCall } from \"../utils/functype-detection\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer List<T> over native arrays for immutable collections\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowArraysInTests: {\n type: \"boolean\",\n default: true,\n },\n allowReadonlyArrays: {\n type: \"boolean\",\n default: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferList: \"Prefer List<{{type}}> over array type {{arrayType}}\",\n preferListLiteral: \"Prefer List.of(...) or List.from([...]) over array literal\",\n suggestListType: \"Replace with List<{{type}}>\",\n suggestListOf: \"Replace with List.of(...)\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n const options = context.options[0] || {}\n const allowArraysInTests = options.allowArraysInTests !== false\n const allowReadonlyArrays = options.allowReadonlyArrays !== false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\n\n function isInTestFile() {\n const filename = context.filename\n return (\n /\\.(test|spec)\\.(ts|js|tsx|jsx)$/.test(filename) ||\n filename.includes(\"__tests__\") ||\n filename.includes(\"/test/\") ||\n filename.includes(\"/tests/\")\n )\n }\n\n function findTypeParameter(node: ASTNode, sourceCode: typeof context.sourceCode): string | null {\n // Look for TSTypeParameterInstantiation child\n function findInNode(n: ASTNode): string | null {\n if (n.type === \"TSTypeParameterInstantiation\" && n.params && n.params[0]) {\n return sourceCode.getText(n.params[0])\n }\n\n // Recursively search child nodes\n for (const key in n) {\n if (key === \"parent\") continue\n const child = n[key]\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && item.type) {\n const result = findInNode(item)\n if (result) return result\n }\n }\n } else if (child && typeof child === \"object\" && child.type) {\n const result = findInNode(child)\n if (result) return result\n }\n }\n\n return null\n }\n\n return findInNode(node)\n }\n\n return {\n TSArrayType(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n const elementType = sourceCode.getText(node.elementType)\n const fullType = sourceCode.getText(node)\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: elementType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${elementType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: elementType,\n arrayType: fullType,\n },\n suggest,\n })\n },\n\n TSTypeReference(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n const sourceCode = context.sourceCode\n\n // Get type name - handle both simple identifiers and member expressions\n if (!node.typeName) return // No type name found\n\n const typeName = node.typeName.type === \"Identifier\" ? node.typeName.name : sourceCode.getText(node.typeName)\n\n // Handle Array<T> syntax\n if (typeName === \"Array\") {\n // Look for type parameters in child nodes\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n\n // Skip if already readonly\n if (allowReadonlyArrays && fullType.startsWith(\"readonly\")) return\n\n const resolvedType = typeParam || \"T\"\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: resolvedType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${resolvedType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: resolvedType,\n arrayType: fullType,\n },\n suggest,\n })\n }\n\n // Handle ReadonlyArray<T> - suggest List even if allowing readonly arrays\n if (typeName === \"ReadonlyArray\") {\n const typeParam = findTypeParameter(node, sourceCode)\n const fullType = sourceCode.getText(node)\n const resolvedType = typeParam || \"T\"\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListType\",\n data: { type: resolvedType },\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List<${resolvedType}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferList\",\n data: {\n type: resolvedType,\n arrayType: fullType,\n },\n suggest,\n })\n }\n },\n\n ArrayExpression(node: ASTNode) {\n if (allowArraysInTests && isInTestFile()) return\n\n // Only flag non-empty arrays to avoid noise\n if (node.elements.length === 0) return\n\n // Don't flag arrays that are already arguments to List.from() or other functype calls\n let parent = node.parent\n if (parent && isFunctypeCall(parent, functypeImports)) {\n return\n }\n\n // Additional specific check for List.from/List.of patterns\n if (\n parent &&\n parent.type === \"CallExpression\" &&\n parent.callee.type === \"MemberExpression\" &&\n parent.callee.object.type === \"Identifier\" &&\n parent.callee.object.name === \"List\" &&\n [\"from\", \"of\"].includes(parent.callee.property.name)\n ) {\n return\n }\n\n // Don't flag nested array literals - only flag the outermost one\n // Check if this array is inside another array literal\n parent = node.parent\n while (parent) {\n if (parent.type === \"ArrayExpression\") {\n return // Skip nested arrays, let the outer one handle it\n }\n parent = parent.parent\n }\n\n // Don't flag array literals that are already part of a type annotation context\n // (those should be handled by the type checking rules)\n let hasTypeAnnotation = false\n parent = node.parent\n while (parent) {\n if (parent.type === \"VariableDeclarator\" && parent.id?.typeAnnotation) {\n // Skip array literal if there's already a type annotation that would be flagged\n hasTypeAnnotation = true\n break\n }\n if (parent.type === \"TSTypeAnnotation\") {\n hasTypeAnnotation = true\n break\n }\n parent = parent.parent\n }\n\n if (hasTypeAnnotation) return\n\n // Check if any element is a SpreadElement — ambiguous semantics, skip suggestions\n const hasSpread = node.elements.some((el) => el !== null && el.type === \"SpreadElement\")\n\n if (hasSpread) {\n context.report({\n node,\n messageId: \"preferListLiteral\",\n })\n return\n }\n\n const sourceCode = context.sourceCode\n const elementTexts = node.elements.filter((el) => el !== null).map((el) => sourceCode.getText(el))\n\n const suggest: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestListOf\",\n fix(fixer: Rule.RuleFixer) {\n return fixer.replaceText(node, `List.of(${elementTexts.join(\", \")})`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"List\")) {\n suggest.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"List\" },\n fix: createImportFixer(sourceCode, \"List\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferListLiteral\",\n suggest,\n })\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"kLAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,8DACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,mBAAoB,CAClB,KAAM,UACN,QAAS,GACV,CACD,oBAAqB,CACnB,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,WAAY,sDACZ,kBAAmB,6DACnB,gBAAiB,8BACjB,cAAe,4BACf,iBAAkB,sCACnB,CACF,CAED,OAAO,EAAS,CACd,IAAM,EAAU,EAAQ,QAAQ,IAAM,EAAE,CAClC,EAAqB,EAAQ,qBAAuB,GACpD,EAAsB,EAAQ,sBAAwB,GAGtD,EAAkB,EAAyB,EAAQ,CAEzD,SAAS,GAAe,CACtB,IAAM,EAAW,EAAQ,SACzB,MACE,kCAAkC,KAAK,EAAS,EAChD,EAAS,SAAS,YAAY,EAC9B,EAAS,SAAS,SAAS,EAC3B,EAAS,SAAS,UAAU,CAIhC,SAAS,EAAkB,EAAe,EAAsD,CAE9F,SAAS,EAAW,EAA2B,CAC7C,GAAI,EAAE,OAAS,gCAAkC,EAAE,QAAU,EAAE,OAAO,GACpE,OAAO,EAAW,QAAQ,EAAE,OAAO,GAAG,CAIxC,IAAK,IAAM,KAAO,EAAG,CACnB,GAAI,IAAQ,SAAU,SACtB,IAAM,EAAQ,EAAE,GAChB,GAAI,MAAM,QAAQ,EAAM,MACjB,IAAM,KAAQ,EACjB,GAAI,GAAQ,OAAO,GAAS,UAAY,EAAK,KAAM,CACjD,IAAM,EAAS,EAAW,EAAK,CAC/B,GAAI,EAAQ,OAAO,WAGd,GAAS,OAAO,GAAU,UAAY,EAAM,KAAM,CAC3D,IAAM,EAAS,EAAW,EAAM,CAChC,GAAI,EAAQ,OAAO,GAIvB,OAAO,KAGT,OAAO,EAAW,EAAK,CAGzB,MAAO,CACL,YAAY,EAAe,CACzB,GAAI,GAAsB,GAAc,CAAE,OAE1C,IAAM,EAAa,EAAQ,WACrB,EAAc,EAAW,QAAQ,EAAK,YAAY,CAClD,EAAW,EAAW,QAAQ,EAAK,CAEnC,EAA6C,CACjD,CACE,UAAW,kBACX,KAAM,CAAE,KAAM,EAAa,CAC3B,IAAI,EAAuB,CACzB,OAAO,EAAM,YAAY,EAAM,QAAQ,EAAY,GAAG,EAEzD,CACF,CAEI,EAAkB,EAAY,OAAO,EACxC,EAAQ,KAAK,CACX,UAAW,mBACX,KAAM,CAAE,OAAQ,OAAQ,CACxB,IAAK,EAAkB,EAAY,OAAO,CAC3C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,aACX,KAAM,CACJ,KAAM,EACN,UAAW,EACZ,CACD,UACD,CAAC,EAGJ,gBAAgB,EAAe,CAC7B,GAAI,GAAsB,GAAc,CAAE,OAE1C,IAAM,EAAa,EAAQ,WAG3B,GAAI,CAAC,EAAK,SAAU,OAEpB,IAAM,EAAW,EAAK,SAAS,OAAS,aAAe,EAAK,SAAS,KAAO,EAAW,QAAQ,EAAK,SAAS,CAG7G,GAAI,IAAa,QAAS,CAExB,IAAM,EAAY,EAAkB,EAAM,EAAW,CAC/C,EAAW,EAAW,QAAQ,EAAK,CAGzC,GAAI,GAAuB,EAAS,WAAW,WAAW,CAAE,OAE5D,IAAM,EAAe,GAAa,IAE5B,EAA6C,CACjD,CACE,UAAW,kBACX,KAAM,CAAE,KAAM,EAAc,CAC5B,IAAI,EAAuB,CACzB,OAAO,EAAM,YAAY,EAAM,QAAQ,EAAa,GAAG,EAE1D,CACF,CAEI,EAAkB,EAAY,OAAO,EACxC,EAAQ,KAAK,CACX,UAAW,mBACX,KAAM,CAAE,OAAQ,OAAQ,CACxB,IAAK,EAAkB,EAAY,OAAO,CAC3C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,aACX,KAAM,CACJ,KAAM,EACN,UAAW,EACZ,CACD,UACD,CAAC,CAIJ,GAAI,IAAa,gBAAiB,CAChC,IAAM,EAAY,EAAkB,EAAM,EAAW,CAC/C,EAAW,EAAW,QAAQ,EAAK,CACnC,EAAe,GAAa,IAE5B,EAA6C,CACjD,CACE,UAAW,kBACX,KAAM,CAAE,KAAM,EAAc,CAC5B,IAAI,EAAuB,CACzB,OAAO,EAAM,YAAY,EAAM,QAAQ,EAAa,GAAG,EAE1D,CACF,CAEI,EAAkB,EAAY,OAAO,EACxC,EAAQ,KAAK,CACX,UAAW,mBACX,KAAM,CAAE,OAAQ,OAAQ,CACxB,IAAK,EAAkB,EAAY,OAAO,CAC3C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,aACX,KAAM,CACJ,KAAM,EACN,UAAW,EACZ,CACD,UACD,CAAC,GAIN,gBAAgB,EAAe,CAI7B,GAHI,GAAsB,GAAc,EAGpC,EAAK,SAAS,SAAW,EAAG,OAGhC,IAAI,EAAS,EAAK,OAMlB,GALI,GAAU,EAAe,EAAQ,EAAgB,EAMnD,GACA,EAAO,OAAS,kBAChB,EAAO,OAAO,OAAS,oBACvB,EAAO,OAAO,OAAO,OAAS,cAC9B,EAAO,OAAO,OAAO,OAAS,QAC9B,CAAC,OAAQ,KAAK,CAAC,SAAS,EAAO,OAAO,SAAS,KAAK,CAEpD,OAMF,IADA,EAAS,EAAK,OACP,GAAQ,CACb,GAAI,EAAO,OAAS,kBAClB,OAEF,EAAS,EAAO,OAKlB,IAAI,EAAoB,GAExB,IADA,EAAS,EAAK,OACP,GAAQ,CACb,GAAI,EAAO,OAAS,sBAAwB,EAAO,IAAI,eAAgB,CAErE,EAAoB,GACpB,MAEF,GAAI,EAAO,OAAS,mBAAoB,CACtC,EAAoB,GACpB,MAEF,EAAS,EAAO,OAGlB,GAAI,EAAmB,OAKvB,GAFkB,EAAK,SAAS,KAAM,GAAO,IAAO,MAAQ,EAAG,OAAS,gBAAgB,CAEzE,CACb,EAAQ,OAAO,CACb,OACA,UAAW,oBACZ,CAAC,CACF,OAGF,IAAM,EAAa,EAAQ,WACrB,EAAe,EAAK,SAAS,OAAQ,GAAO,IAAO,KAAK,CAAC,IAAK,GAAO,EAAW,QAAQ,EAAG,CAAC,CAE5F,EAA6C,CACjD,CACE,UAAW,gBACX,IAAI,EAAuB,CACzB,OAAO,EAAM,YAAY,EAAM,WAAW,EAAa,KAAK,KAAK,CAAC,GAAG,EAExE,CACF,CAEI,EAAkB,EAAY,OAAO,EACxC,EAAQ,KAAK,CACX,UAAW,mBACX,KAAM,CAAE,OAAQ,OAAQ,CACxB,IAAK,EAAkB,EAAY,OAAO,CAC3C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,oBACX,UACD,CAAC,EAEL,EAEJ"}
@@ -1,2 +1,2 @@
1
- import{getFunctypeImportsLegacy as e,isAlreadyUsingFunctype as t,isFunctypeType as n}from"../utils/functype-detection.js";const r={meta:{type:`suggestion`,docs:{description:`Prefer Option<T> over nullable types (T | null | undefined)`,recommended:!0},schema:[{type:`object`,properties:{allowNullableIntersections:{type:`boolean`,default:!1}},additionalProperties:!1}],messages:{preferOption:`Prefer Option<{{type}}> over nullable type '{{nullable}}'`,preferOptionReturn:`Prefer Option<{{type}}> as return type over nullable '{{nullable}}'`}},create(r){let i=e(r);return{TSUnionType(e){if(!e.types||e.types.length<2||!e.types.some(e=>e.type===`TSNullKeyword`||e.type===`TSUndefinedKeyword`))return;let a=e.types.filter(e=>e.type!==`TSNullKeyword`&&e.type!==`TSUndefinedKeyword`);if(a.length===1){let o=a[0];if(n(o,i)||t(e,i))return;let s=r.sourceCode,c=s.getText(o),l=s.getText(e);if(c.startsWith(`Option<`))return;r.report({node:e,messageId:`preferOption`,data:{type:c,nullable:l}})}}}}};export{r as default};
1
+ import{getFunctypeImportsLegacy as e,isAlreadyUsingFunctype as t,isFunctypeType as n}from"../utils/functype-detection.js";import{createImportFixer as r,hasFunctypeSymbol as i}from"../utils/import-fixer.js";const a={meta:{type:`suggestion`,hasSuggestions:!0,docs:{description:`Prefer Option<T> over nullable types (T | null | undefined)`,recommended:!0},schema:[{type:`object`,properties:{allowNullableIntersections:{type:`boolean`,default:!1}},additionalProperties:!1}],messages:{preferOption:`Prefer Option<{{type}}> over nullable type '{{nullable}}'`,preferOptionReturn:`Prefer Option<{{type}}> as return type over nullable '{{nullable}}'`,suggestOptionType:`Replace with Option<{{type}}>`,suggestAddImport:`Add {{symbol}} import from functype`}},create(a){let o=e(a);return{TSUnionType(e){if(!e.types||e.types.length<2||!e.types.some(e=>e.type===`TSNullKeyword`||e.type===`TSUndefinedKeyword`))return;let s=e.types.filter(e=>e.type!==`TSNullKeyword`&&e.type!==`TSUndefinedKeyword`);if(s.length===1){let c=s[0];if(n(c,o)||t(e,o))return;let l=a.sourceCode,u=l.getText(c),d=l.getText(e);if(u.startsWith(`Option<`))return;let f=[{messageId:`suggestOptionType`,data:{type:u},fix(t){return t.replaceText(e,`Option<${u}>`)}}];i(l,`Option`)||f.push({messageId:`suggestAddImport`,data:{symbol:`Option`},fix:r(l,`Option`)}),a.report({node:e,messageId:`preferOption`,data:{type:u,nullable:d},suggest:f})}}}}};export{a as default};
2
2
  //# sourceMappingURL=prefer-option.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-option.js","names":[],"sources":["../../src/rules/prefer-option.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype, isFunctypeType } from \"../utils/functype-detection\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer Option<T> over nullable types (T | null | undefined)\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowNullableIntersections: {\n type: \"boolean\",\n default: false,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferOption: \"Prefer Option<{{type}}> over nullable type '{{nullable}}'\",\n preferOptionReturn: \"Prefer Option<{{type}}> as return type over nullable '{{nullable}}'\",\n },\n },\n\n create(context) {\n // const options = context.options[0] || {}\n // Remove unused variable\n // const _allowNullableIntersections = options.allowNullableIntersections || false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\n\n return {\n TSUnionType(node: ASTNode) {\n if (!node.types || node.types.length < 2) return\n\n const hasNull = node.types.some(\n (type: ASTNode) => type.type === \"TSNullKeyword\" || type.type === \"TSUndefinedKeyword\",\n )\n\n if (!hasNull) return\n\n const nonNullTypes = node.types.filter(\n (type: ASTNode) => type.type !== \"TSNullKeyword\" && type.type !== \"TSUndefinedKeyword\",\n )\n\n if (nonNullTypes.length === 1) {\n const nonNullType = nonNullTypes[0]\n\n // Skip if it's already an Option type or other functype type\n if (isFunctypeType(nonNullType, functypeImports)) return\n\n // Skip if we're already in a functype context\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const nonNullTypeText = sourceCode.getText(nonNullType)\n const fullType = sourceCode.getText(node)\n\n // Skip if it's already an Option type (fallback check)\n if (nonNullTypeText.startsWith(\"Option<\")) return\n\n context.report({\n node,\n messageId: \"preferOption\",\n data: {\n type: nonNullTypeText,\n nullable: fullType,\n },\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"0HAKA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,KAAM,CACJ,YAAa,8DACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,2BAA4B,CAC1B,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,aAAc,4DACd,mBAAoB,sEACrB,CACF,CAED,OAAO,EAAS,CAMd,IAAM,EAAkB,EAAyB,EAAQ,CAEzD,MAAO,CACL,YAAY,EAAe,CAOzB,GANI,CAAC,EAAK,OAAS,EAAK,MAAM,OAAS,GAMnC,CAJY,EAAK,MAAM,KACxB,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,qBACnE,CAEa,OAEd,IAAM,EAAe,EAAK,MAAM,OAC7B,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,qBACnE,CAED,GAAI,EAAa,SAAW,EAAG,CAC7B,IAAM,EAAc,EAAa,GAMjC,GAHI,EAAe,EAAa,EAAgB,EAG5C,EAAuB,EAAM,EAAgB,CAAE,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAkB,EAAW,QAAQ,EAAY,CACjD,EAAW,EAAW,QAAQ,EAAK,CAGzC,GAAI,EAAgB,WAAW,UAAU,CAAE,OAE3C,EAAQ,OAAO,CACb,OACA,UAAW,eACX,KAAM,CACJ,KAAM,EACN,SAAU,EACX,CACF,CAAC,GAGP,EAEJ"}
1
+ {"version":3,"file":"prefer-option.js","names":[],"sources":["../../src/rules/prefer-option.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\nimport { getFunctypeImportsLegacy, isAlreadyUsingFunctype, isFunctypeType } from \"../utils/functype-detection\"\nimport { createImportFixer, hasFunctypeSymbol } from \"../utils/import-fixer\"\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description: \"Prefer Option<T> over nullable types (T | null | undefined)\",\n recommended: true,\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowNullableIntersections: {\n type: \"boolean\",\n default: false,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferOption: \"Prefer Option<{{type}}> over nullable type '{{nullable}}'\",\n preferOptionReturn: \"Prefer Option<{{type}}> as return type over nullable '{{nullable}}'\",\n suggestOptionType: \"Replace with Option<{{type}}>\",\n suggestAddImport: \"Add {{symbol}} import from functype\",\n },\n },\n\n create(context) {\n // const options = context.options[0] || {}\n // Remove unused variable\n // const _allowNullableIntersections = options.allowNullableIntersections || false\n\n // Get functype imports if available (but still apply rule even without explicit import)\n const functypeImports = getFunctypeImportsLegacy(context)\n\n return {\n TSUnionType(node: ASTNode) {\n if (!node.types || node.types.length < 2) return\n\n const hasNull = node.types.some(\n (type: ASTNode) => type.type === \"TSNullKeyword\" || type.type === \"TSUndefinedKeyword\",\n )\n\n if (!hasNull) return\n\n const nonNullTypes = node.types.filter(\n (type: ASTNode) => type.type !== \"TSNullKeyword\" && type.type !== \"TSUndefinedKeyword\",\n )\n\n if (nonNullTypes.length === 1) {\n const nonNullType = nonNullTypes[0]\n\n // Skip if it's already an Option type or other functype type\n if (isFunctypeType(nonNullType, functypeImports)) return\n\n // Skip if we're already in a functype context\n if (isAlreadyUsingFunctype(node, functypeImports)) return\n\n const sourceCode = context.sourceCode\n const nonNullTypeText = sourceCode.getText(nonNullType)\n const fullType = sourceCode.getText(node)\n\n // Skip if it's already an Option type (fallback check)\n if (nonNullTypeText.startsWith(\"Option<\")) return\n\n const suggestions: Rule.SuggestionReportDescriptor[] = [\n {\n messageId: \"suggestOptionType\",\n data: { type: nonNullTypeText },\n fix(fixer) {\n return fixer.replaceText(node, `Option<${nonNullTypeText}>`)\n },\n },\n ]\n\n if (!hasFunctypeSymbol(sourceCode, \"Option\")) {\n suggestions.push({\n messageId: \"suggestAddImport\",\n data: { symbol: \"Option\" },\n fix: createImportFixer(sourceCode, \"Option\"),\n })\n }\n\n context.report({\n node,\n messageId: \"preferOption\",\n data: {\n type: nonNullTypeText,\n nullable: fullType,\n },\n suggest: suggestions,\n })\n }\n },\n }\n },\n}\n\nexport default rule\n"],"mappings":"8MAMA,MAAM,EAAwB,CAC5B,KAAM,CACJ,KAAM,aACN,eAAgB,GAChB,KAAM,CACJ,YAAa,8DACb,YAAa,GACd,CACD,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,2BAA4B,CAC1B,KAAM,UACN,QAAS,GACV,CACF,CACD,qBAAsB,GACvB,CACF,CACD,SAAU,CACR,aAAc,4DACd,mBAAoB,sEACpB,kBAAmB,gCACnB,iBAAkB,sCACnB,CACF,CAED,OAAO,EAAS,CAMd,IAAM,EAAkB,EAAyB,EAAQ,CAEzD,MAAO,CACL,YAAY,EAAe,CAOzB,GANI,CAAC,EAAK,OAAS,EAAK,MAAM,OAAS,GAMnC,CAJY,EAAK,MAAM,KACxB,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,qBACnE,CAEa,OAEd,IAAM,EAAe,EAAK,MAAM,OAC7B,GAAkB,EAAK,OAAS,iBAAmB,EAAK,OAAS,qBACnE,CAED,GAAI,EAAa,SAAW,EAAG,CAC7B,IAAM,EAAc,EAAa,GAMjC,GAHI,EAAe,EAAa,EAAgB,EAG5C,EAAuB,EAAM,EAAgB,CAAE,OAEnD,IAAM,EAAa,EAAQ,WACrB,EAAkB,EAAW,QAAQ,EAAY,CACjD,EAAW,EAAW,QAAQ,EAAK,CAGzC,GAAI,EAAgB,WAAW,UAAU,CAAE,OAE3C,IAAM,EAAiD,CACrD,CACE,UAAW,oBACX,KAAM,CAAE,KAAM,EAAiB,CAC/B,IAAI,EAAO,CACT,OAAO,EAAM,YAAY,EAAM,UAAU,EAAgB,GAAG,EAE/D,CACF,CAEI,EAAkB,EAAY,SAAS,EAC1C,EAAY,KAAK,CACf,UAAW,mBACX,KAAM,CAAE,OAAQ,SAAU,CAC1B,IAAK,EAAkB,EAAY,SAAS,CAC7C,CAAC,CAGJ,EAAQ,OAAO,CACb,OACA,UAAW,eACX,KAAM,CACJ,KAAM,EACN,SAAU,EACX,CACD,QAAS,EACV,CAAC,GAGP,EAEJ"}
@@ -1,2 +1,2 @@
1
- function e(e){let t=e.sourceCode.ast;for(let e of t.body)if(e.type===`ImportDeclaration`&&e.source.type===`Literal`&&e.source.value===`functype`)return!0;return!1}function t(e){let t=new Set,n=new Set,r=new Set,i=e.sourceCode.ast;for(let e of i.body)if(e.type===`ImportDeclaration`&&e.source.type===`Literal`&&e.source.value===`functype`&&e.specifiers)for(let i of e.specifiers)if(i.type===`ImportSpecifier`&&i.imported.type===`Identifier`){let e=i.imported.name;r.add(e),[`Option`,`Either`,`List`,`LazyList`,`Task`,`Try`].includes(e)?t.add(e):([`Do`,`DoAsync`,`$`].includes(e),n.add(e))}else i.type===`ImportDefaultSpecifier`?(r.add(`default`),n.add(`default`)):i.type===`ImportNamespaceSpecifier`&&(r.add(`*`),t.add(`*`),n.add(`*`));return{types:t,functions:n,all:r}}function n(e){return t(e).all}function r(e,t){if(!e)return!1;let n=t instanceof Set?t:t.types||t.all;if(e.type===`TSTypeReference`&&e.typeName?.type===`Identifier`){let t=e.typeName.name;return n.has(t)||[`Option`,`Either`,`List`,`LazyList`,`Task`,`Try`].includes(t)}return!1}function i(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;if(n.type===`MemberExpression`&&n.object.type===`Identifier`){let e=n.object.name,r=n.property?.name;if(t.has(e)||e===`Option`&&[`some`,`none`,`of`].includes(r)||e===`Either`&&[`left`,`right`,`of`].includes(r)||e===`List`&&[`of`,`from`,`empty`].includes(r))return!0}if(n.type===`MemberExpression`){let e=n.property?.name;if([`map`,`flatMap`,`filter`,`fold`,`foldLeft`,`foldRight`,`getOrElse`,`orElse`,`isEmpty`,`nonEmpty`,`isDefined`,`isSome`,`isNone`,`isLeft`,`isRight`,`toArray`].includes(e))return!0}return!1}function a(e,t){let n=e.parent;for(;n;){if(i(n,t)||r(n,t))return!0;n=n.parent}return!1}function o(e,t){return e.typeAnnotation?.typeAnnotation?r(e.typeAnnotation.typeAnnotation,t):!1}function s(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;if(n.type===`Identifier`){let e=n.name;return t.functions.has(e)&&[`Do`,`DoAsync`].includes(e)}return!1}function c(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;return n.type===`Identifier`&&n.name===`$`?t.functions.has(`$`):!1}function l(e,t){let n=e.parent;for(;n;){if(n.type===`CallExpression`&&s(n,t))return!0;n=n.parent}return!1}function u(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;return n.type===`MemberExpression`&&n.property.type===`Identifier`&&n.property.name===t}function d(e,t){if(l(e,t))return{shouldUse:!1,reason:null};if(e.type===`LogicalExpression`&&e.operator===`&&`){let t=e.toString?e.toString():``;if(t.includes(`&&`)&&t.match(/\w+(\.\w+){2,}/))return{shouldUse:!0,reason:`nested-checks`}}if(u(e,`flatMap`)){let t=0,n=e;for(;n&&n.type===`CallExpression`&&u(n,`flatMap`)&&(t++,n.callee.type===`MemberExpression`);)n=n.callee.object;if(t>=3)return{shouldUse:!0,reason:`chained-flatmaps`}}return{shouldUse:!1,reason:null}}export{t as getFunctypeImports,n as getFunctypeImportsLegacy,e as hasFunctypeImport,o as hasFunctypeTypeAnnotation,a as isAlreadyUsingFunctype,u as isChainedMethodCall,s as isDoNotationCall,c as isDollarHelper,i as isFunctypeCall,r as isFunctypeType,l as isInsideDoNotation,d as shouldUseDoNotation};
1
+ function e(e){let t=e.sourceCode.ast;for(let e of t.body)if(e.type===`ImportDeclaration`&&e.source.type===`Literal`&&e.source.value===`functype`)return!0;return!1}function t(e){let t=new Set,n=new Set,r=new Set,i=e.sourceCode.ast;for(let e of i.body)if(e.type===`ImportDeclaration`&&e.source.type===`Literal`&&e.source.value===`functype`&&e.specifiers)for(let i of e.specifiers)if(i.type===`ImportSpecifier`&&i.imported.type===`Identifier`){let e=i.imported.name;r.add(e),[`Option`,`Either`,`List`,`LazyList`,`Task`,`Try`,`Map`,`Set`,`Stack`].includes(e)?t.add(e):([`Do`,`DoAsync`,`$`].includes(e),n.add(e))}else i.type===`ImportDefaultSpecifier`?(r.add(`default`),n.add(`default`)):i.type===`ImportNamespaceSpecifier`&&(r.add(`*`),t.add(`*`),n.add(`*`));return{types:t,functions:n,all:r}}function n(e){return t(e).all}function r(e,t){if(!e)return!1;let n=t instanceof Set?t:t.types||t.all;if(e.type===`TSTypeReference`&&e.typeName?.type===`Identifier`){let t=e.typeName.name;return n.has(t)||[`Option`,`Either`,`List`,`LazyList`,`Task`,`Try`,`Map`,`Set`,`Stack`].includes(t)}return!1}function i(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;if(n.type===`MemberExpression`&&n.object.type===`Identifier`){let e=n.object.name,r=n.property?.name;if(t.has(e)||e===`Option`&&[`some`,`none`,`of`].includes(r)||e===`Either`&&[`left`,`right`,`of`].includes(r)||e===`List`&&[`of`,`from`,`empty`].includes(r)||e===`Map`&&[`of`,`empty`].includes(r)||e===`Set`&&[`of`,`empty`].includes(r))return!0}if(n.type===`MemberExpression`){let e=n.property?.name;if([`map`,`flatMap`,`filter`,`fold`,`foldLeft`,`foldRight`,`getOrElse`,`orElse`,`isEmpty`,`nonEmpty`,`isDefined`,`isSome`,`isNone`,`isLeft`,`isRight`,`toArray`].includes(e))return!0}return!1}function a(e,t){let n=e.parent;for(;n;){if(i(n,t)||r(n,t))return!0;n=n.parent}return!1}function o(e,t){return e.typeAnnotation?.typeAnnotation?r(e.typeAnnotation.typeAnnotation,t):!1}function s(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;if(n.type===`Identifier`){let e=n.name;return t.functions.has(e)&&[`Do`,`DoAsync`].includes(e)}return!1}function c(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;return n.type===`Identifier`&&n.name===`$`?t.functions.has(`$`):!1}function l(e,t){let n=e.parent;for(;n;){if(n.type===`CallExpression`&&s(n,t))return!0;n=n.parent}return!1}function u(e,t){if(!e||e.type!==`CallExpression`)return!1;let n=e.callee;return n.type===`MemberExpression`&&n.property.type===`Identifier`&&n.property.name===t}function d(e,t){if(l(e,t))return{shouldUse:!1,reason:null};if(e.type===`LogicalExpression`&&e.operator===`&&`){let t=e.toString?e.toString():``;if(t.includes(`&&`)&&t.match(/\w+(\.\w+){2,}/))return{shouldUse:!0,reason:`nested-checks`}}if(u(e,`flatMap`)){let t=0,n=e;for(;n&&n.type===`CallExpression`&&u(n,`flatMap`)&&(t++,n.callee.type===`MemberExpression`);)n=n.callee.object;if(t>=3)return{shouldUse:!0,reason:`chained-flatmaps`}}return{shouldUse:!1,reason:null}}export{t as getFunctypeImports,n as getFunctypeImportsLegacy,e as hasFunctypeImport,o as hasFunctypeTypeAnnotation,a as isAlreadyUsingFunctype,u as isChainedMethodCall,s as isDoNotationCall,c as isDollarHelper,i as isFunctypeCall,r as isFunctypeType,l as isInsideDoNotation,d as shouldUseDoNotation};
2
2
  //# sourceMappingURL=functype-detection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"functype-detection.js","names":[],"sources":["../../src/utils/functype-detection.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\n/**\n * Utility functions for detecting functype library usage in ESLint rules\n */\n\n/**\n * Check if functype library is imported in the current file\n */\nexport function hasFunctypeImport(context: Rule.RuleContext): boolean {\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n // Look for import statements that import from 'functype'\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Enhanced functype imports tracking\n */\nexport interface FunctypeImports {\n types: Set<string> // Option, Either, List, etc.\n functions: Set<string> // Do, DoAsync, $, etc.\n all: Set<string> // Combined for compatibility\n}\n\n/**\n * Get imported functype symbols from the current file\n */\nexport function getFunctypeImports(context: Rule.RuleContext): FunctypeImports {\n const types = new Set<string>()\n const functions = new Set<string>()\n const all = new Set<string>()\n\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n // Handle named imports: import { Option, Either, Do, $ } from 'functype'\n if (node.specifiers) {\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n const name = spec.imported.name\n all.add(name)\n\n // Categorize imports\n if ([\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\"].includes(name)) {\n types.add(name)\n } else if ([\"Do\", \"DoAsync\", \"$\"].includes(name)) {\n functions.add(name)\n } else {\n // Other imports (constructors, utilities, etc.)\n functions.add(name)\n }\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n all.add(\"default\")\n functions.add(\"default\")\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n all.add(\"*\")\n types.add(\"*\")\n functions.add(\"*\")\n }\n }\n }\n }\n }\n\n return { types, functions, all }\n}\n\n/**\n * Legacy compatibility - returns the 'all' set\n */\nexport function getFunctypeImportsLegacy(context: Rule.RuleContext): Set<string> {\n return getFunctypeImports(context).all\n}\n\n/**\n * Check if a type reference is using functype types\n */\nexport function isFunctypeType(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n if (!node) return false\n\n // Handle both new and legacy API\n const types = functypeImports instanceof Set ? functypeImports : functypeImports.types || functypeImports.all\n\n // Check direct type names\n if (node.type === \"TSTypeReference\" && node.typeName?.type === \"Identifier\") {\n const typeName = node.typeName.name\n return types.has(typeName) || [\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\"].includes(typeName)\n }\n\n return false\n}\n\n/**\n * Check if a call expression is using functype methods\n */\nexport function isFunctypeCall(node: ASTNode, functypeImports: Set<string>): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for static method calls like Option.some(), Either.left(), List.of()\n if (callee.type === \"MemberExpression\" && callee.object.type === \"Identifier\") {\n const objectName = callee.object.name\n const methodName = callee.property?.name\n\n // Check if calling methods on imported functype types\n if (functypeImports.has(objectName)) return true\n\n // Check for common functype patterns\n if (\n (objectName === \"Option\" && [\"some\", \"none\", \"of\"].includes(methodName)) ||\n (objectName === \"Either\" && [\"left\", \"right\", \"of\"].includes(methodName)) ||\n (objectName === \"List\" && [\"of\", \"from\", \"empty\"].includes(methodName))\n ) {\n return true\n }\n }\n\n // Check for method calls on functype instances like someOption.map()\n if (callee.type === \"MemberExpression\") {\n const methodName = callee.property?.name\n\n // Common functype methods\n if (\n [\n \"map\",\n \"flatMap\",\n \"filter\",\n \"fold\",\n \"foldLeft\",\n \"foldRight\",\n \"getOrElse\",\n \"orElse\",\n \"isEmpty\",\n \"nonEmpty\",\n \"isDefined\",\n \"isSome\",\n \"isNone\",\n \"isLeft\",\n \"isRight\",\n \"toArray\",\n ].includes(methodName)\n ) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Check if current context is already using functype patterns appropriately\n */\nexport function isAlreadyUsingFunctype(node: ASTNode, functypeImports: Set<string>): boolean {\n let parent = node.parent\n\n // Walk up the AST to find functype usage\n while (parent) {\n if (isFunctypeCall(parent as ASTNode, functypeImports) || isFunctypeType(parent as ASTNode, functypeImports)) {\n return true\n }\n parent = parent.parent\n }\n\n return false\n}\n\n/**\n * Check if a variable or parameter is typed with functype types\n */\nexport function hasFunctypeTypeAnnotation(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n // Check for type annotation\n if (node.typeAnnotation?.typeAnnotation) {\n return isFunctypeType(node.typeAnnotation.typeAnnotation, functypeImports)\n }\n\n return false\n}\n\n/**\n * Check if a call expression is a Do notation call\n */\nexport function isDoNotationCall(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for Do() or DoAsync() calls\n if (callee.type === \"Identifier\") {\n const name = callee.name\n return functypeImports.functions.has(name) && [\"Do\", \"DoAsync\"].includes(name)\n }\n\n return false\n}\n\n/**\n * Check if a call expression uses the $ helper function\n */\nexport function isDollarHelper(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for $() calls\n if (callee.type === \"Identifier\" && callee.name === \"$\") {\n return functypeImports.functions.has(\"$\")\n }\n\n return false\n}\n\n/**\n * Check if current expression is inside a Do notation block\n */\nexport function isInsideDoNotation(node: ASTNode, functypeImports: FunctypeImports): boolean {\n let current = node.parent\n\n while (current) {\n if (current.type === \"CallExpression\" && isDoNotationCall(current as ASTNode, functypeImports)) {\n return true\n }\n current = current.parent\n }\n\n return false\n}\n\n/**\n * Check if a method call is a chained method call on functype types\n */\nexport function isChainedMethodCall(node: ASTNode, methodName: string): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n if (\n callee.type === \"MemberExpression\" &&\n callee.property.type === \"Identifier\" &&\n callee.property.name === methodName\n ) {\n return true\n }\n\n return false\n}\n\n/**\n * Detect if code could benefit from Do notation\n */\nexport function shouldUseDoNotation(\n node: ASTNode,\n functypeImports: FunctypeImports,\n): {\n shouldUse: boolean\n reason: \"nested-checks\" | \"chained-flatmaps\" | \"mixed-monads\" | \"async-chains\" | null\n} {\n // Already using Do notation\n if (isInsideDoNotation(node, functypeImports)) {\n return { shouldUse: false, reason: null }\n }\n\n // Check for nested null checks (a && a.b && a.b.c pattern)\n if (node.type === \"LogicalExpression\" && node.operator === \"&&\") {\n const text = node.toString ? node.toString() : \"\"\n if (text.includes(\"&&\") && text.match(/\\w+(\\.\\w+){2,}/)) {\n return { shouldUse: true, reason: \"nested-checks\" }\n }\n }\n\n // Check for chained flatMap calls\n if (isChainedMethodCall(node, \"flatMap\")) {\n // Count chain depth\n let depth = 0\n let current = node\n\n while (current && current.type === \"CallExpression\" && isChainedMethodCall(current, \"flatMap\")) {\n depth++\n if (current.callee.type === \"MemberExpression\") {\n current = current.callee.object as ASTNode\n } else {\n break\n }\n }\n\n if (depth >= 3) {\n return { shouldUse: true, reason: \"chained-flatmaps\" }\n }\n }\n\n return { shouldUse: false, reason: null }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"functype-detection.js","names":[],"sources":["../../src/utils/functype-detection.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\n\nimport type { ASTNode } from \"../types/ast\"\n\n/**\n * Utility functions for detecting functype library usage in ESLint rules\n */\n\n/**\n * Check if functype library is imported in the current file\n */\nexport function hasFunctypeImport(context: Rule.RuleContext): boolean {\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n // Look for import statements that import from 'functype'\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Enhanced functype imports tracking\n */\nexport interface FunctypeImports {\n types: Set<string> // Option, Either, List, etc.\n functions: Set<string> // Do, DoAsync, $, etc.\n all: Set<string> // Combined for compatibility\n}\n\n/**\n * Get imported functype symbols from the current file\n */\nexport function getFunctypeImports(context: Rule.RuleContext): FunctypeImports {\n const types = new Set<string>()\n const functions = new Set<string>()\n const all = new Set<string>()\n\n const sourceCode = context.sourceCode\n const program = sourceCode.ast\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n // Handle named imports: import { Option, Either, Do, $ } from 'functype'\n if (node.specifiers) {\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportSpecifier\" && spec.imported.type === \"Identifier\") {\n const name = spec.imported.name\n all.add(name)\n\n // Categorize imports\n if ([\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\", \"Map\", \"Set\", \"Stack\"].includes(name)) {\n types.add(name)\n } else if ([\"Do\", \"DoAsync\", \"$\"].includes(name)) {\n functions.add(name)\n } else {\n // Other imports (constructors, utilities, etc.)\n functions.add(name)\n }\n } else if (spec.type === \"ImportDefaultSpecifier\") {\n all.add(\"default\")\n functions.add(\"default\")\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n all.add(\"*\")\n types.add(\"*\")\n functions.add(\"*\")\n }\n }\n }\n }\n }\n\n return { types, functions, all }\n}\n\n/**\n * Legacy compatibility - returns the 'all' set\n */\nexport function getFunctypeImportsLegacy(context: Rule.RuleContext): Set<string> {\n return getFunctypeImports(context).all\n}\n\n/**\n * Check if a type reference is using functype types\n */\nexport function isFunctypeType(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n if (!node) return false\n\n // Handle both new and legacy API\n const types = functypeImports instanceof Set ? functypeImports : functypeImports.types || functypeImports.all\n\n // Check direct type names\n if (node.type === \"TSTypeReference\" && node.typeName?.type === \"Identifier\") {\n const typeName = node.typeName.name\n return (\n types.has(typeName) ||\n [\"Option\", \"Either\", \"List\", \"LazyList\", \"Task\", \"Try\", \"Map\", \"Set\", \"Stack\"].includes(typeName)\n )\n }\n\n return false\n}\n\n/**\n * Check if a call expression is using functype methods\n */\nexport function isFunctypeCall(node: ASTNode, functypeImports: Set<string>): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for static method calls like Option.some(), Either.left(), List.of()\n if (callee.type === \"MemberExpression\" && callee.object.type === \"Identifier\") {\n const objectName = callee.object.name\n const methodName = callee.property?.name\n\n // Check if calling methods on imported functype types\n if (functypeImports.has(objectName)) return true\n\n // Check for common functype patterns\n if (\n (objectName === \"Option\" && [\"some\", \"none\", \"of\"].includes(methodName)) ||\n (objectName === \"Either\" && [\"left\", \"right\", \"of\"].includes(methodName)) ||\n (objectName === \"List\" && [\"of\", \"from\", \"empty\"].includes(methodName)) ||\n (objectName === \"Map\" && [\"of\", \"empty\"].includes(methodName)) ||\n (objectName === \"Set\" && [\"of\", \"empty\"].includes(methodName))\n ) {\n return true\n }\n }\n\n // Check for method calls on functype instances like someOption.map()\n if (callee.type === \"MemberExpression\") {\n const methodName = callee.property?.name\n\n // Common functype methods\n if (\n [\n \"map\",\n \"flatMap\",\n \"filter\",\n \"fold\",\n \"foldLeft\",\n \"foldRight\",\n \"getOrElse\",\n \"orElse\",\n \"isEmpty\",\n \"nonEmpty\",\n \"isDefined\",\n \"isSome\",\n \"isNone\",\n \"isLeft\",\n \"isRight\",\n \"toArray\",\n ].includes(methodName)\n ) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Check if current context is already using functype patterns appropriately\n */\nexport function isAlreadyUsingFunctype(node: ASTNode, functypeImports: Set<string>): boolean {\n let parent = node.parent\n\n // Walk up the AST to find functype usage\n while (parent) {\n if (isFunctypeCall(parent as ASTNode, functypeImports) || isFunctypeType(parent as ASTNode, functypeImports)) {\n return true\n }\n parent = parent.parent\n }\n\n return false\n}\n\n/**\n * Check if a variable or parameter is typed with functype types\n */\nexport function hasFunctypeTypeAnnotation(node: ASTNode, functypeImports: FunctypeImports | Set<string>): boolean {\n // Check for type annotation\n if (node.typeAnnotation?.typeAnnotation) {\n return isFunctypeType(node.typeAnnotation.typeAnnotation, functypeImports)\n }\n\n return false\n}\n\n/**\n * Check if a call expression is a Do notation call\n */\nexport function isDoNotationCall(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for Do() or DoAsync() calls\n if (callee.type === \"Identifier\") {\n const name = callee.name\n return functypeImports.functions.has(name) && [\"Do\", \"DoAsync\"].includes(name)\n }\n\n return false\n}\n\n/**\n * Check if a call expression uses the $ helper function\n */\nexport function isDollarHelper(node: ASTNode, functypeImports: FunctypeImports): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n // Check for $() calls\n if (callee.type === \"Identifier\" && callee.name === \"$\") {\n return functypeImports.functions.has(\"$\")\n }\n\n return false\n}\n\n/**\n * Check if current expression is inside a Do notation block\n */\nexport function isInsideDoNotation(node: ASTNode, functypeImports: FunctypeImports): boolean {\n let current = node.parent\n\n while (current) {\n if (current.type === \"CallExpression\" && isDoNotationCall(current as ASTNode, functypeImports)) {\n return true\n }\n current = current.parent\n }\n\n return false\n}\n\n/**\n * Check if a method call is a chained method call on functype types\n */\nexport function isChainedMethodCall(node: ASTNode, methodName: string): boolean {\n if (!node || node.type !== \"CallExpression\") return false\n\n const callee = node.callee\n\n if (\n callee.type === \"MemberExpression\" &&\n callee.property.type === \"Identifier\" &&\n callee.property.name === methodName\n ) {\n return true\n }\n\n return false\n}\n\n/**\n * Detect if code could benefit from Do notation\n */\nexport function shouldUseDoNotation(\n node: ASTNode,\n functypeImports: FunctypeImports,\n): {\n shouldUse: boolean\n reason: \"nested-checks\" | \"chained-flatmaps\" | \"mixed-monads\" | \"async-chains\" | null\n} {\n // Already using Do notation\n if (isInsideDoNotation(node, functypeImports)) {\n return { shouldUse: false, reason: null }\n }\n\n // Check for nested null checks (a && a.b && a.b.c pattern)\n if (node.type === \"LogicalExpression\" && node.operator === \"&&\") {\n const text = node.toString ? node.toString() : \"\"\n if (text.includes(\"&&\") && text.match(/\\w+(\\.\\w+){2,}/)) {\n return { shouldUse: true, reason: \"nested-checks\" }\n }\n }\n\n // Check for chained flatMap calls\n if (isChainedMethodCall(node, \"flatMap\")) {\n // Count chain depth\n let depth = 0\n let current = node\n\n while (current && current.type === \"CallExpression\" && isChainedMethodCall(current, \"flatMap\")) {\n depth++\n if (current.callee.type === \"MemberExpression\") {\n current = current.callee.object as ASTNode\n } else {\n break\n }\n }\n\n if (depth >= 3) {\n return { shouldUse: true, reason: \"chained-flatmaps\" }\n }\n }\n\n return { shouldUse: false, reason: null }\n}\n"],"mappings":"AAWA,SAAgB,EAAkB,EAAoC,CAEpE,IAAM,EADa,EAAQ,WACA,IAG3B,IAAK,IAAM,KAAQ,EAAQ,KACzB,GAAI,EAAK,OAAS,qBAAuB,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,WAC/F,MAAO,GAIX,MAAO,GAeT,SAAgB,EAAmB,EAA4C,CAC7E,IAAM,EAAQ,IAAI,IACZ,EAAY,IAAI,IAChB,EAAM,IAAI,IAGV,EADa,EAAQ,WACA,IAE3B,IAAK,IAAM,KAAQ,EAAQ,KACzB,GAAI,EAAK,OAAS,qBAAuB,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,YAE3F,EAAK,eACF,IAAM,KAAQ,EAAK,WACtB,GAAI,EAAK,OAAS,mBAAqB,EAAK,SAAS,OAAS,aAAc,CAC1E,IAAM,EAAO,EAAK,SAAS,KAC3B,EAAI,IAAI,EAAK,CAGT,CAAC,SAAU,SAAU,OAAQ,WAAY,OAAQ,MAAO,MAAO,MAAO,QAAQ,CAAC,SAAS,EAAK,CAC/F,EAAM,IAAI,EAAK,EACN,CAAC,KAAM,UAAW,IAAI,CAAC,SAAS,EAAK,CAC9C,EAAU,IAAI,EAAK,OAKZ,EAAK,OAAS,0BACvB,EAAI,IAAI,UAAU,CAClB,EAAU,IAAI,UAAU,EACf,EAAK,OAAS,6BACvB,EAAI,IAAI,IAAI,CACZ,EAAM,IAAI,IAAI,CACd,EAAU,IAAI,IAAI,EAO5B,MAAO,CAAE,QAAO,YAAW,MAAK,CAMlC,SAAgB,EAAyB,EAAwC,CAC/E,OAAO,EAAmB,EAAQ,CAAC,IAMrC,SAAgB,EAAe,EAAe,EAAyD,CACrG,GAAI,CAAC,EAAM,MAAO,GAGlB,IAAM,EAAQ,aAA2B,IAAM,EAAkB,EAAgB,OAAS,EAAgB,IAG1G,GAAI,EAAK,OAAS,mBAAqB,EAAK,UAAU,OAAS,aAAc,CAC3E,IAAM,EAAW,EAAK,SAAS,KAC/B,OACE,EAAM,IAAI,EAAS,EACnB,CAAC,SAAU,SAAU,OAAQ,WAAY,OAAQ,MAAO,MAAO,MAAO,QAAQ,CAAC,SAAS,EAAS,CAIrG,MAAO,GAMT,SAAgB,EAAe,EAAe,EAAuC,CACnF,GAAI,CAAC,GAAQ,EAAK,OAAS,iBAAkB,MAAO,GAEpD,IAAM,EAAS,EAAK,OAGpB,GAAI,EAAO,OAAS,oBAAsB,EAAO,OAAO,OAAS,aAAc,CAC7E,IAAM,EAAa,EAAO,OAAO,KAC3B,EAAa,EAAO,UAAU,KAMpC,GAHI,EAAgB,IAAI,EAAW,EAIhC,IAAe,UAAY,CAAC,OAAQ,OAAQ,KAAK,CAAC,SAAS,EAAW,EACtE,IAAe,UAAY,CAAC,OAAQ,QAAS,KAAK,CAAC,SAAS,EAAW,EACvE,IAAe,QAAU,CAAC,KAAM,OAAQ,QAAQ,CAAC,SAAS,EAAW,EACrE,IAAe,OAAS,CAAC,KAAM,QAAQ,CAAC,SAAS,EAAW,EAC5D,IAAe,OAAS,CAAC,KAAM,QAAQ,CAAC,SAAS,EAAW,CAE7D,MAAO,GAKX,GAAI,EAAO,OAAS,mBAAoB,CACtC,IAAM,EAAa,EAAO,UAAU,KAGpC,GACE,CACE,MACA,UACA,SACA,OACA,WACA,YACA,YACA,SACA,UACA,WACA,YACA,SACA,SACA,SACA,UACA,UACD,CAAC,SAAS,EAAW,CAEtB,MAAO,GAIX,MAAO,GAMT,SAAgB,EAAuB,EAAe,EAAuC,CAC3F,IAAI,EAAS,EAAK,OAGlB,KAAO,GAAQ,CACb,GAAI,EAAe,EAAmB,EAAgB,EAAI,EAAe,EAAmB,EAAgB,CAC1G,MAAO,GAET,EAAS,EAAO,OAGlB,MAAO,GAMT,SAAgB,EAA0B,EAAe,EAAyD,CAMhH,OAJI,EAAK,gBAAgB,eAChB,EAAe,EAAK,eAAe,eAAgB,EAAgB,CAGrE,GAMT,SAAgB,EAAiB,EAAe,EAA2C,CACzF,GAAI,CAAC,GAAQ,EAAK,OAAS,iBAAkB,MAAO,GAEpD,IAAM,EAAS,EAAK,OAGpB,GAAI,EAAO,OAAS,aAAc,CAChC,IAAM,EAAO,EAAO,KACpB,OAAO,EAAgB,UAAU,IAAI,EAAK,EAAI,CAAC,KAAM,UAAU,CAAC,SAAS,EAAK,CAGhF,MAAO,GAMT,SAAgB,EAAe,EAAe,EAA2C,CACvF,GAAI,CAAC,GAAQ,EAAK,OAAS,iBAAkB,MAAO,GAEpD,IAAM,EAAS,EAAK,OAOpB,OAJI,EAAO,OAAS,cAAgB,EAAO,OAAS,IAC3C,EAAgB,UAAU,IAAI,IAAI,CAGpC,GAMT,SAAgB,EAAmB,EAAe,EAA2C,CAC3F,IAAI,EAAU,EAAK,OAEnB,KAAO,GAAS,CACd,GAAI,EAAQ,OAAS,kBAAoB,EAAiB,EAAoB,EAAgB,CAC5F,MAAO,GAET,EAAU,EAAQ,OAGpB,MAAO,GAMT,SAAgB,EAAoB,EAAe,EAA6B,CAC9E,GAAI,CAAC,GAAQ,EAAK,OAAS,iBAAkB,MAAO,GAEpD,IAAM,EAAS,EAAK,OAUpB,OAPE,EAAO,OAAS,oBAChB,EAAO,SAAS,OAAS,cACzB,EAAO,SAAS,OAAS,EAW7B,SAAgB,EACd,EACA,EAIA,CAEA,GAAI,EAAmB,EAAM,EAAgB,CAC3C,MAAO,CAAE,UAAW,GAAO,OAAQ,KAAM,CAI3C,GAAI,EAAK,OAAS,qBAAuB,EAAK,WAAa,KAAM,CAC/D,IAAM,EAAO,EAAK,SAAW,EAAK,UAAU,CAAG,GAC/C,GAAI,EAAK,SAAS,KAAK,EAAI,EAAK,MAAM,iBAAiB,CACrD,MAAO,CAAE,UAAW,GAAM,OAAQ,gBAAiB,CAKvD,GAAI,EAAoB,EAAM,UAAU,CAAE,CAExC,IAAI,EAAQ,EACR,EAAU,EAEd,KAAO,GAAW,EAAQ,OAAS,kBAAoB,EAAoB,EAAS,UAAU,GAC5F,IACI,EAAQ,OAAO,OAAS,qBAC1B,EAAU,EAAQ,OAAO,OAM7B,GAAI,GAAS,EACX,MAAO,CAAE,UAAW,GAAM,OAAQ,mBAAoB,CAI1D,MAAO,CAAE,UAAW,GAAO,OAAQ,KAAM"}
@@ -0,0 +1,15 @@
1
+ import { Rule, SourceCode } from "eslint";
2
+
3
+ //#region src/utils/import-fixer.d.ts
4
+ /**
5
+ * Check if a given symbol is already imported from functype
6
+ */
7
+ declare function hasFunctypeSymbol(sourceCode: SourceCode, symbolName: string): boolean;
8
+ /**
9
+ * Create a fixer function that adds a symbol to the functype import.
10
+ * Returns a factory compatible with ESLint suggest[].fix signature.
11
+ */
12
+ declare function createImportFixer(sourceCode: SourceCode, symbolName: string): (fixer: Rule.RuleFixer) => Rule.Fix | null;
13
+ //#endregion
14
+ export { createImportFixer, hasFunctypeSymbol };
15
+ //# sourceMappingURL=import-fixer.d.ts.map
@@ -0,0 +1,2 @@
1
+ function e(e,t){let n=e.ast;for(let e of n.body)if(e.type===`ImportDeclaration`&&e.source.type===`Literal`&&e.source.value===`functype`&&e.specifiers){for(let n of e.specifiers)if(n.type===`ImportNamespaceSpecifier`||n.type===`ImportSpecifier`&&n.imported.type===`Identifier`&&n.imported.name===t)return!0}return!1}function t(t,n){return r=>{let i=t.ast,a=null,o=null;for(let e of i.body)e.type===`ImportDeclaration`&&(o=e,e.source.type===`Literal`&&e.source.value===`functype`&&(a=e));if(e(t,n))return null;if(a&&a.type===`ImportDeclaration`){let e=a;if(e.specifiers?.some(e=>e.type===`ImportNamespaceSpecifier`)??!1)return null;let i=(e.specifiers??[]).filter(e=>e.type===`ImportSpecifier`);if(i.length>0){let e=i[i.length-1];if(e)return r.insertTextAfter(e,`, ${n}`)}let o=t.getText(e).replace(/\{(\s*)\}/,`{ ${n} }`);return r.replaceText(e,o)}let s=`import { ${n} } from "functype"`;if(o)return r.insertTextAfter(o,`\n${s}`);let c=i.body[0];return c?r.insertTextBefore(c,`${s}\n`):r.insertTextBeforeRange([0,0],`${s}\n`)}}export{t as createImportFixer,e as hasFunctypeSymbol};
2
+ //# sourceMappingURL=import-fixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-fixer.js","names":[],"sources":["../../src/utils/import-fixer.ts"],"sourcesContent":["import type { Rule } from \"eslint\"\nimport type { SourceCode } from \"eslint\"\n\n/**\n * Check if a given symbol is already imported from functype\n */\nexport function hasFunctypeSymbol(sourceCode: SourceCode, symbolName: string): boolean {\n const program = sourceCode.ast\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\" && node.source.type === \"Literal\" && node.source.value === \"functype\") {\n if (node.specifiers) {\n for (const spec of node.specifiers) {\n if (spec.type === \"ImportNamespaceSpecifier\") {\n return true\n }\n if (\n spec.type === \"ImportSpecifier\" &&\n spec.imported.type === \"Identifier\" &&\n spec.imported.name === symbolName\n ) {\n return true\n }\n }\n }\n }\n }\n\n return false\n}\n\n/**\n * Create a fixer function that adds a symbol to the functype import.\n * Returns a factory compatible with ESLint suggest[].fix signature.\n */\nexport function createImportFixer(\n sourceCode: SourceCode,\n symbolName: string,\n): (fixer: Rule.RuleFixer) => Rule.Fix | null {\n return (fixer: Rule.RuleFixer): Rule.Fix | null => {\n const program = sourceCode.ast\n\n let existingFunctypeImport: (typeof program.body)[number] | null = null\n let lastImportNode: (typeof program.body)[number] | null = null\n\n for (const node of program.body) {\n if (node.type === \"ImportDeclaration\") {\n lastImportNode = node\n if (node.source.type === \"Literal\" && node.source.value === \"functype\") {\n existingFunctypeImport = node\n }\n }\n }\n\n // If already imported, nothing to do\n if (hasFunctypeSymbol(sourceCode, symbolName)) {\n return null\n }\n\n if (existingFunctypeImport && existingFunctypeImport.type === \"ImportDeclaration\") {\n const importDecl = existingFunctypeImport\n\n // Check for namespace import — can't add named imports alongside it\n const hasNamespace = importDecl.specifiers?.some((s) => s.type === \"ImportNamespaceSpecifier\") ?? false\n if (hasNamespace) {\n return null\n }\n\n // Find the last named specifier and append after it\n const specifiers = importDecl.specifiers ?? []\n const namedSpecifiers = specifiers.filter((s) => s.type === \"ImportSpecifier\")\n\n if (namedSpecifiers.length > 0) {\n const lastSpecifier = namedSpecifiers[namedSpecifiers.length - 1]\n if (lastSpecifier) {\n return fixer.insertTextAfter(lastSpecifier, `, ${symbolName}`)\n }\n }\n\n // No named specifiers yet — insert before closing brace\n const importText = sourceCode.getText(importDecl)\n const newText = importText.replace(/\\{(\\s*)\\}/, `{ ${symbolName} }`)\n return fixer.replaceText(importDecl, newText)\n }\n\n // No existing functype import — add a new one\n const newImport = `import { ${symbolName} } from \"functype\"`\n\n if (lastImportNode) {\n return fixer.insertTextAfter(lastImportNode, `\\n${newImport}`)\n }\n\n // No imports at all — insert at top of file\n const firstNode = program.body[0]\n if (firstNode) {\n return fixer.insertTextBefore(firstNode, `${newImport}\\n`)\n }\n\n return fixer.insertTextBeforeRange([0, 0], `${newImport}\\n`)\n }\n}\n"],"mappings":"AAMA,SAAgB,EAAkB,EAAwB,EAA6B,CACrF,IAAM,EAAU,EAAW,IAE3B,IAAK,IAAM,KAAQ,EAAQ,KACzB,GAAI,EAAK,OAAS,qBAAuB,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,YAC3F,EAAK,WACP,KAAK,IAAM,KAAQ,EAAK,WAItB,GAHI,EAAK,OAAS,4BAIhB,EAAK,OAAS,mBACd,EAAK,SAAS,OAAS,cACvB,EAAK,SAAS,OAAS,EAEvB,MAAO,GAOjB,MAAO,GAOT,SAAgB,EACd,EACA,EAC4C,CAC5C,MAAQ,IAA2C,CACjD,IAAM,EAAU,EAAW,IAEvB,EAA+D,KAC/D,EAAuD,KAE3D,IAAK,IAAM,KAAQ,EAAQ,KACrB,EAAK,OAAS,sBAChB,EAAiB,EACb,EAAK,OAAO,OAAS,WAAa,EAAK,OAAO,QAAU,aAC1D,EAAyB,IAM/B,GAAI,EAAkB,EAAY,EAAW,CAC3C,OAAO,KAGT,GAAI,GAA0B,EAAuB,OAAS,oBAAqB,CACjF,IAAM,EAAa,EAInB,GADqB,EAAW,YAAY,KAAM,GAAM,EAAE,OAAS,2BAA2B,EAAI,GAEhG,OAAO,KAKT,IAAM,GADa,EAAW,YAAc,EAAE,EACX,OAAQ,GAAM,EAAE,OAAS,kBAAkB,CAE9E,GAAI,EAAgB,OAAS,EAAG,CAC9B,IAAM,EAAgB,EAAgB,EAAgB,OAAS,GAC/D,GAAI,EACF,OAAO,EAAM,gBAAgB,EAAe,KAAK,IAAa,CAMlE,IAAM,EADa,EAAW,QAAQ,EAAW,CACtB,QAAQ,YAAa,KAAK,EAAW,IAAI,CACpE,OAAO,EAAM,YAAY,EAAY,EAAQ,CAI/C,IAAM,EAAY,YAAY,EAAW,oBAEzC,GAAI,EACF,OAAO,EAAM,gBAAgB,EAAgB,KAAK,IAAY,CAIhE,IAAM,EAAY,EAAQ,KAAK,GAK/B,OAJI,EACK,EAAM,iBAAiB,EAAW,GAAG,EAAU,IAAI,CAGrD,EAAM,sBAAsB,CAAC,EAAG,EAAE,CAAE,GAAG,EAAU,IAAI"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-functype",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Custom ESLint rules for functional TypeScript programming with functype library patterns including Do notation (ESLint 10+)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,37 +34,17 @@
34
34
  "either",
35
35
  "list"
36
36
  ],
37
- "scripts": {
38
- "validate": "ts-builds validate",
39
- "format": "ts-builds format",
40
- "format:check": "ts-builds format:check",
41
- "lint": "ts-builds lint",
42
- "lint:check": "ts-builds lint:check",
43
- "typecheck": "ts-builds typecheck",
44
- "test": "ts-builds test",
45
- "test:watch": "ts-builds test:watch",
46
- "test:ui": "ts-builds test:ui",
47
- "test:coverage": "ts-builds test:coverage",
48
- "build": "ts-builds build",
49
- "dev": "ts-builds dev",
50
- "prepublishOnly": "pnpm validate",
51
- "list-rules": "node dist/cli/list-rules.js",
52
- "list-rules:verbose": "node dist/cli/list-rules.js --verbose",
53
- "list-rules:usage": "node dist/cli/list-rules.js --usage",
54
- "check-deps": "node dist/cli/list-rules.js --check-deps",
55
- "cli:help": "node dist/cli/list-rules.js --help"
56
- },
57
37
  "prettier": "ts-builds/prettier",
58
38
  "peerDependencies": {
59
39
  "eslint": "^10.0.3"
60
40
  },
61
41
  "devDependencies": {
62
- "@types/node": "^24.12.0",
63
- "@typescript-eslint/rule-tester": "^8.57.2",
42
+ "@types/node": "^24.12.2",
43
+ "@typescript-eslint/rule-tester": "^8.58.0",
64
44
  "eslint-config-prettier": "^10.1.8",
65
- "functype": "^0.51.0",
66
- "ts-builds": "^2.6.1",
67
- "tsdown": "^0.21.5"
45
+ "functype": "^0.56.0",
46
+ "ts-builds": "^2.6.3",
47
+ "tsdown": "^0.21.7"
68
48
  },
69
49
  "author": {
70
50
  "name": "Jordan Burke",
@@ -82,5 +62,24 @@
82
62
  "homepage": "https://github.com/jordanburke/eslint-functype#readme",
83
63
  "engines": {
84
64
  "node": ">=22.0.0"
65
+ },
66
+ "scripts": {
67
+ "validate": "ts-builds validate",
68
+ "format": "ts-builds format",
69
+ "format:check": "ts-builds format:check",
70
+ "lint": "ts-builds lint",
71
+ "lint:check": "ts-builds lint:check",
72
+ "typecheck": "ts-builds typecheck",
73
+ "test": "ts-builds test",
74
+ "test:watch": "ts-builds test:watch",
75
+ "test:ui": "ts-builds test:ui",
76
+ "test:coverage": "ts-builds test:coverage",
77
+ "build": "ts-builds build",
78
+ "dev": "ts-builds dev",
79
+ "list-rules": "node dist/cli/list-rules.js",
80
+ "list-rules:verbose": "node dist/cli/list-rules.js --verbose",
81
+ "list-rules:usage": "node dist/cli/list-rules.js --usage",
82
+ "check-deps": "node dist/cli/list-rules.js --check-deps",
83
+ "cli:help": "node dist/cli/list-rules.js --help"
85
84
  }
86
- }
85
+ }