eslint-plugin-maintainability 3.2.4 → 3.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-maintainability",
3
- "version": "3.2.4",
3
+ "version": "3.2.6",
4
4
  "description": "ESLint plugin for maintainable code — limits cognitive complexity, nesting depth, parameter counts, duplicate functions, and unhandled or silent errors.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.rules={get"cognitive-complexity"(){return require("./rules/maintainability/cognitive-complexity").cognitiveComplexity},get"nested-complexity-hotspots"(){return require("./rules/maintainability/nested-complexity-hotspots").nestedComplexityHotspots},get"identical-functions"(){return require("./rules/maintainability/identical-functions").identicalFunctions},get"max-parameters"(){return require("./rules/maintainability/max-parameters").maxParameters},get"no-lonely-if"(){return require("./rules/maintainability/no-lonely-if").noLonelyIf},get"no-nested-ternary"(){return require("./rules/maintainability/no-nested-ternary").noNestedTernary},get"consistent-function-scoping"(){return require("./rules/maintainability/consistent-function-scoping").consistentFunctionScoping},get"no-unreadable-iife"(){return require("./rules/maintainability/no-unreadable-iife").noUnreadableIife},get"error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"no-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise},get"maintainability/cognitive-complexity"(){return require("./rules/maintainability/cognitive-complexity").cognitiveComplexity},get"maintainability/nested-complexity-hotspots"(){return require("./rules/maintainability/nested-complexity-hotspots").nestedComplexityHotspots},get"maintainability/identical-functions"(){return require("./rules/maintainability/identical-functions").identicalFunctions},get"maintainability/max-parameters"(){return require("./rules/maintainability/max-parameters").maxParameters},get"maintainability/no-lonely-if"(){return require("./rules/maintainability/no-lonely-if").noLonelyIf},get"maintainability/no-nested-ternary"(){return require("./rules/maintainability/no-nested-ternary").noNestedTernary},get"maintainability/consistent-function-scoping"(){return require("./rules/maintainability/consistent-function-scoping").consistentFunctionScoping},get"maintainability/no-unreadable-iife"(){return require("./rules/maintainability/no-unreadable-iife").noUnreadableIife},get"maintainability/error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"maintainability/no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"maintainability/no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"maintainability/no-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-maintainability",exports.rules);exports.plugin={meta:{name:"eslint-plugin-maintainability",version:"3.2.4"},rules:exports.rules};exports.configs={recommended:{plugins:{"maintainability":exports.plugin},rules:{"maintainability/cognitive-complexity":"warn","maintainability/identical-functions":"warn","maintainability/max-parameters":"warn"}}};exports.default=exports.plugin;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.rules={get"cognitive-complexity"(){return require("./rules/maintainability/cognitive-complexity").cognitiveComplexity},get"nested-complexity-hotspots"(){return require("./rules/maintainability/nested-complexity-hotspots").nestedComplexityHotspots},get"identical-functions"(){return require("./rules/maintainability/identical-functions").identicalFunctions},get"max-parameters"(){return require("./rules/maintainability/max-parameters").maxParameters},get"no-lonely-if"(){return require("./rules/maintainability/no-lonely-if").noLonelyIf},get"no-nested-ternary"(){return require("./rules/maintainability/no-nested-ternary").noNestedTernary},get"consistent-function-scoping"(){return require("./rules/maintainability/consistent-function-scoping").consistentFunctionScoping},get"no-unreadable-iife"(){return require("./rules/maintainability/no-unreadable-iife").noUnreadableIife},get"error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"no-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise},get"maintainability/cognitive-complexity"(){return require("./rules/maintainability/cognitive-complexity").cognitiveComplexity},get"maintainability/nested-complexity-hotspots"(){return require("./rules/maintainability/nested-complexity-hotspots").nestedComplexityHotspots},get"maintainability/identical-functions"(){return require("./rules/maintainability/identical-functions").identicalFunctions},get"maintainability/max-parameters"(){return require("./rules/maintainability/max-parameters").maxParameters},get"maintainability/no-lonely-if"(){return require("./rules/maintainability/no-lonely-if").noLonelyIf},get"maintainability/no-nested-ternary"(){return require("./rules/maintainability/no-nested-ternary").noNestedTernary},get"maintainability/consistent-function-scoping"(){return require("./rules/maintainability/consistent-function-scoping").consistentFunctionScoping},get"maintainability/no-unreadable-iife"(){return require("./rules/maintainability/no-unreadable-iife").noUnreadableIife},get"maintainability/error-message"(){return require("./rules/error-handling/error-message").errorMessage},get"maintainability/no-missing-error-context"(){return require("./rules/error-handling/no-missing-error-context").noMissingErrorContext},get"maintainability/no-silent-errors"(){return require("./rules/error-handling/no-silent-errors").noSilentErrors},get"maintainability/no-unhandled-promise"(){return require("./rules/error-handling/no-unhandled-promise").noUnhandledPromise}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-maintainability",exports.rules);exports.plugin={meta:{name:"eslint-plugin-maintainability",version:"3.2.6"},rules:exports.rules};exports.configs={recommended:{plugins:{"maintainability":exports.plugin},rules:{"maintainability/cognitive-complexity":"warn","maintainability/identical-functions":"warn","maintainability/max-parameters":"warn"}}};exports.default=exports.plugin;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMissingErrorContext=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function hasErrorMessage(node){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}if(node.argument.type==="NewExpression"&&node.argument.callee.type==="Identifier"&&(node.argument.callee.name==="Error"||node.argument.callee.name.endsWith("Error"))){if(node.argument.arguments.length>0){const firstArg=node.argument.arguments[0];const staticText=(0,eslint_devkit_1.staticString)(firstArg);if(staticText!==null){return staticText.length>0}if(firstArg.type==="TemplateLiteral"){return true}if(node.argument.callee.name!=="Error"&&node.argument.callee.name.endsWith("Error")){return true}}}if(node.argument.type==="Literal"&&typeof node.argument.value==="string"){return node.argument.value.length>0}if(node.argument.type==="TemplateLiteral"){return true}return false}function hasErrorStack(node){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}if(node.argument.type==="NewExpression"&&node.argument.callee.type==="Identifier"){const calleeName=node.argument.callee.name;if(calleeName==="Error"||calleeName.endsWith("Error")){return true}}return false}exports.noMissingErrorContext=(0,eslint_devkit_2.createRule)({name:"no-missing-error-context",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-missing-error-context.md",description:"Detects thrown errors without context"},hasSuggestions:true,messages:{missingErrorContext:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing error context",description:"Thrown error missing {{missing}}",severity:"MEDIUM",fix:"Add error message and use Error class for stack trace",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1128/"}),addErrorMessage:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Message",description:"Add descriptive error message",severity:"LOW",fix:'throw new Error("descriptive message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),addErrorStack:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Error Class",description:"Use Error class for stack trace",severity:"LOW",fix:"throw new Error(message) instead of throw message",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),useErrorClass:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Specific Error",description:"Use specific error class",severity:"LOW",fix:'throw new TypeError("message") or throw new RangeError("message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError"})},schema:[{type:"object",properties:{requireMessage:{type:"boolean",default:true,description:"Require error message"},requireStackTrace:{type:"boolean",default:false,description:"Require stack trace"},ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"}},additionalProperties:false}]},defaultOptions:[{requireMessage:true,requireStackTrace:false,ignoreInTests:true}],create(context,[options={}]){const{requireMessage=true,requireStackTrace=false,ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}function checkThrowStatement(node){const missing=[];if(requireMessage&&!hasErrorMessage(node)){missing.push("message")}if(requireStackTrace&&!hasErrorStack(node)){missing.push("stack trace")}if(missing.length===0){return}context.report({node,messageId:"missingErrorContext",data:{missing:missing.join(" and ")},suggest:[{messageId:"addErrorMessage",fix:()=>null},{messageId:"addErrorStack",fix:()=>null},{messageId:"useErrorClass",fix:()=>null}]})}return{ThrowStatement:checkThrowStatement}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMissingErrorContext=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const BUILTIN_ERROR_CONSTRUCTORS=new Set(["Error","TypeError","RangeError","SyntaxError","ReferenceError","EvalError","URIError","AggregateError"]);function isProvablyString(node){if(node.type==="TemplateLiteral")return true;if(node.type==="Literal"){return typeof node.value==="string"&&node.value.length>0}if(node.type==="LogicalExpression"){return node.operator==="&&"?isProvablyString(node.right):isProvablyString(node.left)||isProvablyString(node.right)}if(node.type==="ConditionalExpression"){return isProvablyString(node.consequent)||isProvablyString(node.alternate)}if(node.type==="BinaryExpression"&&node.operator==="+"){return isProvablyString(node.left)||isProvablyString(node.right)}return false}function hasErrorMessage(node){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}const constructed=node.argument.type==="NewExpression"||node.argument.type==="CallExpression"?node.argument:null;if(constructed!==null&&constructed.callee.type==="Identifier"){if(constructed.type==="CallExpression"&&!BUILTIN_ERROR_CONSTRUCTORS.has(constructed.callee.name)){return false}if(constructed.arguments.length===0){return false}const firstArg=constructed.arguments[0];const staticText=(0,eslint_devkit_1.staticString)(firstArg);if(staticText!==null){return staticText.length>0}if(!BUILTIN_ERROR_CONSTRUCTORS.has(constructed.callee.name)){return true}return isProvablyString(firstArg)}if(node.argument.type==="Literal"&&typeof node.argument.value==="string"){return node.argument.value.length>0}if(node.argument.type==="TemplateLiteral"){return true}return false}function hasErrorStack(node){if(!node.argument){return false}if(node.argument.type==="Identifier"&&node.argument.name!=="undefined"&&node.argument.name!=="NaN"&&node.argument.name!=="Infinity"){return true}if((node.argument.type==="NewExpression"||node.argument.type==="CallExpression")&&node.argument.callee.type==="Identifier"){const calleeName=node.argument.callee.name;if(calleeName==="Error"||calleeName.endsWith("Error")){return true}}return false}exports.noMissingErrorContext=(0,eslint_devkit_2.createRule)({name:"no-missing-error-context",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-missing-error-context.md",description:"Detects thrown errors without context"},hasSuggestions:true,messages:{missingErrorContext:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing error context",description:"Thrown error missing {{missing}}",severity:"MEDIUM",fix:"Add error message and use Error class for stack trace",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-1128/"}),addErrorMessage:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Error Message",description:"Add descriptive error message",severity:"LOW",fix:'throw new Error("descriptive message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),addErrorStack:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Error Class",description:"Use Error class for stack trace",severity:"LOW",fix:"throw new Error(message) instead of throw message",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}),useErrorClass:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Specific Error",description:"Use specific error class",severity:"LOW",fix:'throw new TypeError("message") or throw new RangeError("message")',documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError"})},schema:[{type:"object",properties:{requireMessage:{type:"boolean",default:true,description:"Require error message"},requireStackTrace:{type:"boolean",default:false,description:"Require stack trace"},ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"}},additionalProperties:false}]},defaultOptions:[{requireMessage:true,requireStackTrace:false,ignoreInTests:true}],create(context,[options={}]){const{requireMessage=true,requireStackTrace=false,ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}function checkThrowStatement(node){const missing=[];if(requireMessage&&!hasErrorMessage(node)){missing.push("message")}if(requireStackTrace&&!hasErrorStack(node)){missing.push("stack trace")}if(missing.length===0){return}context.report({node,messageId:"missingErrorContext",data:{missing:missing.join(" and ")},suggest:[{messageId:"addErrorMessage",fix:()=>null},{messageId:"addErrorStack",fix:()=>null},{messageId:"useErrorClass",fix:()=>null}]})}return{ThrowStatement:checkThrowStatement}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnhandledPromise=void 0;exports.isPromiseExpression=isPromiseExpression;exports.hasPromiseEvidence=hasPromiseEvidence;exports.isInsidePromiseCallback=isInsidePromiseCallback;exports.isPromiseHandled=isPromiseHandled;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function isPromiseExpression(node){if(node.type==="CallExpression"){return true}if(node.type==="AwaitExpression"){return false}return false}const PROMISE_STATICS=new Set(["all","allSettled","any","race","resolve","reject"]);function isAsyncFunctionNode(node){if(!node)return false;return(node.type==="FunctionDeclaration"||node.type==="FunctionExpression"||node.type==="ArrowFunctionExpression")&&node.async===true}function hasPromiseEvidence(node,promiseReturning,resolveBinding){if(isAsyncFunctionNode(node.callee))return true;if(node.callee.type==="MemberExpression"){const{object}=node.callee;if(object.type==="Identifier"&&object.name==="Promise"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(node.callee),PROMISE_STATICS)){return true}if((0,eslint_devkit_1.propertyName)(node.callee)==="then")return true;return object.type==="Identifier"&&promiseReturning.has(object.name)}if(node.callee.type==="Identifier"){if(promiseReturning.has(node.callee.name))return true;const bound=resolveBinding(node.callee.name);return isAsyncFunctionNode(bound)||isPromiseReturningAnnotation(bound)}return false}function isPromiseReturningAnnotation(node){if(node?.type!=="Identifier")return false;const annotation=node.typeAnnotation?.typeAnnotation;if(annotation?.type!=="TSFunctionType")return false;const returned=annotation.returnType?.typeAnnotation;return returned?.type==="TSTypeReference"&&returned.typeName.type==="Identifier"&&returned.typeName.name==="Promise"}function isInsidePromiseCallback(node){let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="ArrowFunctionExpression"||parent.type==="FunctionExpression"){const funcParent=parent.parent;if(funcParent&&funcParent.type==="CallExpression"&&funcParent.callee&&funcParent.callee.type==="MemberExpression"){const memberExpr=funcParent.callee;const settled=(0,eslint_devkit_1.propertyName)(memberExpr);if(settled!==null){const methodName=settled;if(methodName==="then"||methodName==="catch"||methodName==="finally"){return true}}}}current=parent;depth++}return false}function isPromiseHandled(node){if(node.type==="Identifier"){const parent=node.parent;if(parent&&parent.type==="MemberExpression"&&parent.object===node){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}}let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="MemberExpression"&&parent.object===current){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}if(parent.type==="TryStatement"){return true}if(parent.type==="AwaitExpression"){return true}current=parent;depth++}return false}exports.noUnhandledPromise=(0,eslint_devkit_2.createRule)({name:"no-unhandled-promise",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-unhandled-promise.md",description:"Detects unhandled Promise rejections",cwe:"CWE-1024",cvss:7.5},hasSuggestions:true,messages:{unhandledPromise:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled promise",cwe:"CWE-1024",description:"Unhandled Promise rejection detected",severity:"HIGH",fix:"Add .catch() handler or use try/catch with await",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-4635/"}),addCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add catch handler",description:"Add .catch() handler to promise",severity:"LOW",fix:"promise.catch(error => console.error(error))",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch"}),useTryCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use try/catch",description:"Use try/catch with await",severity:"LOW",fix:"try { await promise; } catch (error) { handle(error); }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"}),useAwait:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use await",description:"Use await to handle promise",severity:"LOW",fix:"await promise;",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore promises in test files"},ignoreVoidExpressions:{type:"boolean",default:false,description:"Ignore promises in void expressions"},requirePromiseEvidence:{type:"boolean",default:true,description:"Report only calls the file itself shows to be promise-producing. False restores the pre-2026-08 behaviour of treating every call as a promise."},promiseReturning:{type:"array",items:{type:"string"},default:["fetch"],description:"Names that return a promise and cannot be resolved from the file \u2014 platform APIs and project conventions."}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,ignoreVoidExpressions:false,requirePromiseEvidence:true,promiseReturning:["fetch"]}],create(context,[options={}]){const{ignoreInTests=true,ignoreVoidExpressions=false,requirePromiseEvidence=true,promiseReturning=["fetch"]}=options||{};const promiseNames=new Set(promiseReturning);function resolveBinding(name,from){let scope=context.sourceCode.getScope(from);while(scope){const variable=scope.variables.find(v=>v.name===name);if(variable){const def=variable.defs[0];if(!def)return null;if(def.type==="Parameter"){const pattern=def.name.parent;if(pattern?.type==="AssignmentPattern"&&pattern.left===def.name){return pattern.right}return def.name}if(def.node.type==="FunctionDeclaration")return def.node;if(def.node.type==="VariableDeclarator"){return def.node.init??null}return null}scope=scope.upper}return null}const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}function checkCallExpression(node){if(isInsidePromiseCallback(node)){return}if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(methodName==="catch"||methodName==="finally"){if(node.arguments.length>0&&node.arguments[0].type==="ArrowFunctionExpression"){const callback=node.arguments[0];if(callback.body.type==="BlockStatement"&&callback.body.body.length===0){}else{return}}else{return}}}if(!isPromiseExpression(node)){return}if(requirePromiseEvidence&&!hasPromiseEvidence(node,promiseNames,name=>resolveBinding(name,node))){return}if(isPromiseHandled(node)){return}if(ignoreVoidExpressions){const parent2=node.parent;if(parent2&&parent2.type==="UnaryExpression"&&parent2.operator==="void"){return}}const parent=node.parent;if(parent&&parent.type==="CallExpression"){const grandParent=parent.parent;const chainOn=grandParent?.type==="MemberExpression"&&grandParent.object===parent?grandParent:null;const outerChainMethod=chainOn===null?null:(0,eslint_devkit_1.propertyName)(chainOn);const inPromiseChain=chainOn!==null&&chainOn.property.type==="Identifier"&&outerChainMethod==="then";const isArgument=parent.arguments.some(a=>a===node);const outerIsPromise=outerChainMethod==="then"||outerChainMethod==="catch"||outerChainMethod==="finally"||hasPromiseEvidence(parent,promiseNames,name=>resolveBinding(name,parent));if(!inPromiseChain&&(!isArgument||outerIsPromise)){return}}context.report({node,messageId:"unhandledPromise",suggest:[{messageId:"addCatch",fix:()=>null},{messageId:"useTryCatch",fix:()=>null},{messageId:"useAwait",fix:()=>null}]})}function checkStatementPromise(node){const parent=node.parent;if(parent?.type!=="ExpressionStatement")return;context.report({node,messageId:"unhandledPromise"})}return{CallExpression:checkCallExpression,ImportExpression:checkStatementPromise,NewExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="Promise"){checkStatementPromise(node)}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnhandledPromise=void 0;exports.isPromiseExpression=isPromiseExpression;exports.hasPromiseEvidence=hasPromiseEvidence;exports.isInsidePromiseCallback=isInsidePromiseCallback;exports.isPromiseHandled=isPromiseHandled;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");function isPromiseExpression(node){if(node.type==="CallExpression"){return true}if(node.type==="AwaitExpression"){return false}return false}const PROMISE_STATICS=new Set(["all","allSettled","any","race","resolve","reject"]);function isAsyncFunctionNode(node){if(!node)return false;return(node.type==="FunctionDeclaration"||node.type==="FunctionExpression"||node.type==="ArrowFunctionExpression")&&node.async===true}function hasPromiseEvidence(node,promiseReturning,resolveBinding){if(isAsyncFunctionNode(node.callee))return true;if(node.callee.type==="MemberExpression"){const{object}=node.callee;if(object.type==="Identifier"&&object.name==="Promise"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(node.callee),PROMISE_STATICS)){return true}if((0,eslint_devkit_1.propertyName)(node.callee)==="then")return true;return object.type==="Identifier"&&promiseReturning.has(object.name)}if(node.callee.type==="Identifier"){if(promiseReturning.has(node.callee.name))return true;const bound=resolveBinding(node.callee.name);return isAsyncFunctionNode(bound)||isPromiseReturningAnnotation(bound)}return false}function isPromiseReturningAnnotation(node){if(node?.type!=="Identifier")return false;const annotation=node.typeAnnotation?.typeAnnotation;if(annotation?.type!=="TSFunctionType")return false;const returned=annotation.returnType?.typeAnnotation;return returned?.type==="TSTypeReference"&&returned.typeName.type==="Identifier"&&returned.typeName.name==="Promise"}function isInsidePromiseCallback(node){let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="ArrowFunctionExpression"||parent.type==="FunctionExpression"){const funcParent=parent.parent;if(funcParent&&funcParent.type==="CallExpression"&&funcParent.callee&&funcParent.callee.type==="MemberExpression"){const memberExpr=funcParent.callee;const settled=(0,eslint_devkit_1.propertyName)(memberExpr);if(settled!==null){const methodName=settled;if(methodName==="then"||methodName==="catch"||methodName==="finally"){return true}}}}current=parent;depth++}return false}const PROMISE_COMBINATORS=["all","allSettled","any","race"];function isPromiseCombinatorCall(node){const{callee}=node;return callee.type==="MemberExpression"&&callee.object.type==="Identifier"&&callee.object.name==="Promise"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),PROMISE_COMBINATORS)}function isValueConsumed(node){let current=node;for(;;){const parent=current.parent;if(!parent)return false;switch(parent.type){case"MemberExpression":return false;case"CallExpression":if(parent.callee!==current){return current.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression&&isPromiseCombinatorCall(parent)}break;case"ArrayExpression":break;case"TSAsExpression":case"TSNonNullExpression":break;case"UnaryExpression":if(parent.operator!=="void")return true;break;case"ExpressionStatement":return false;case"SequenceExpression":return parent.expressions[parent.expressions.length-1]===current;default:return true}current=parent}}function isThenWithRejectionHandler(node){if(node.callee.type!=="MemberExpression")return false;if((0,eslint_devkit_1.propertyName)(node.callee)!=="then")return false;const onRejected=node.arguments[1];if(onRejected===void 0)return false;let candidate=onRejected;while(candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSAsExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression||candidate.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeAssertion){candidate=candidate.expression}switch(candidate.type){case eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.FunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return true;case eslint_devkit_1.AST_NODE_TYPES.Identifier:return candidate.name!=="undefined";default:return false}}function isPromiseHandled(node){if(node.type==="Identifier"){const parent=node.parent;if(parent&&parent.type==="MemberExpression"&&parent.object===node){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}}let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(!parent)break;if(parent.type==="MemberExpression"&&parent.object===current){if(parent.property.type==="Identifier"){const methodName=parent.property.name;if(methodName==="catch"||methodName==="then"||methodName==="finally"){const memberParent=parent.parent;if(memberParent&&memberParent.type==="CallExpression"&&memberParent.callee===parent){return true}}}}if(parent.type==="TryStatement"){return true}if(parent.type==="AwaitExpression"){return true}current=parent;depth++}return false}exports.noUnhandledPromise=(0,eslint_devkit_2.createRule)({name:"no-unhandled-promise",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-unhandled-promise.md",description:"Detects unhandled Promise rejections",cwe:"CWE-1024",cvss:7.5},hasSuggestions:true,messages:{unhandledPromise:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled promise",cwe:"CWE-1024",description:"Unhandled Promise rejection detected",severity:"HIGH",fix:"Add .catch() handler or use try/catch with await",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-4635/"}),addCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add catch handler",description:"Add .catch() handler to promise",severity:"LOW",fix:"promise.catch(error => console.error(error))",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch"}),useTryCatch:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use try/catch",description:"Use try/catch with await",severity:"LOW",fix:"try { await promise; } catch (error) { handle(error); }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch"}),useAwait:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use await",description:"Use await to handle promise",severity:"LOW",fix:"await promise;",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore promises in test files"},ignoreVoidExpressions:{type:"boolean",default:false,description:"Ignore promises in void expressions"},requirePromiseEvidence:{type:"boolean",default:true,description:"Report only calls the file itself shows to be promise-producing. False restores the pre-2026-08 behaviour of treating every call as a promise."},promiseReturning:{type:"array",items:{type:"string"},default:["fetch"],description:"Names that return a promise and cannot be resolved from the file \u2014 platform APIs and project conventions."}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,ignoreVoidExpressions:false,requirePromiseEvidence:true,promiseReturning:["fetch"]}],create(context,[options={}]){const{ignoreInTests=true,ignoreVoidExpressions=false,requirePromiseEvidence=true,promiseReturning=["fetch"]}=options||{};const promiseNames=new Set(promiseReturning);function resolveBinding(name,from){let scope=context.sourceCode.getScope(from);while(scope){const variable=scope.variables.find(v=>v.name===name);if(variable){const def=variable.defs[0];if(!def)return null;if(def.type==="Parameter"){const pattern=def.name.parent;if(pattern?.type==="AssignmentPattern"&&pattern.left===def.name){return pattern.right}return def.name}if(def.node.type==="FunctionDeclaration")return def.node;if(def.node.type==="VariableDeclarator"){return def.node.init??null}return null}scope=scope.upper}return null}const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}function checkCallExpression(node){if(isInsidePromiseCallback(node)){return}if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(methodName==="catch"||methodName==="finally"){if(node.arguments.length>0&&node.arguments[0].type==="ArrowFunctionExpression"){const callback=node.arguments[0];if(callback.body.type==="BlockStatement"&&callback.body.body.length===0){}else{return}}else{return}}}if(!isPromiseExpression(node)){return}if(requirePromiseEvidence&&!hasPromiseEvidence(node,promiseNames,name=>resolveBinding(name,node))){return}if(isPromiseHandled(node)){return}if(isValueConsumed(node)){return}if(isThenWithRejectionHandler(node)){return}if(ignoreVoidExpressions){const parent2=node.parent;if(parent2&&parent2.type==="UnaryExpression"&&parent2.operator==="void"){return}}const parent=node.parent;if(parent&&parent.type==="CallExpression"){const grandParent=parent.parent;const chainOn=grandParent?.type==="MemberExpression"&&grandParent.object===parent?grandParent:null;const outerChainMethod=chainOn===null?null:(0,eslint_devkit_1.propertyName)(chainOn);const inPromiseChain=chainOn!==null&&chainOn.property.type==="Identifier"&&outerChainMethod==="then";const isArgument=parent.arguments.some(a=>a===node);const outerIsPromise=outerChainMethod==="then"||outerChainMethod==="catch"||outerChainMethod==="finally"||hasPromiseEvidence(parent,promiseNames,name=>resolveBinding(name,parent));if(!inPromiseChain&&(!isArgument||outerIsPromise)){return}}context.report({node,messageId:"unhandledPromise",suggest:[{messageId:"addCatch",fix:()=>null},{messageId:"useTryCatch",fix:()=>null},{messageId:"useAwait",fix:()=>null}]})}function checkStatementPromise(node){const parent=node.parent;if(parent?.type!=="ExpressionStatement")return;context.report({node,messageId:"unhandledPromise"})}return{CallExpression:checkCallExpression,ImportExpression:checkStatementPromise,NewExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="Promise"){checkStatementPromise(node)}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.consistentFunctionScoping=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const BINDING_WRAPPERS=new Set(["VariableDeclarator","VariableDeclaration","TSAsExpression","TSSatisfiesExpression","TSNonNullExpression","TSTypeAssertion"]);exports.consistentFunctionScoping=(0,eslint_devkit_1.createRule)({name:"consistent-function-scoping",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/consistent-function-scoping.md",description:"Move function definitions to the highest possible scope to improve readability and performance"},hasSuggestions:true,messages:{inconsistentFunctionScoping:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.ARCHITECTURE,issueName:"Inconsistent Function Scoping",description:"Function can be moved to higher scope as it doesn't capture outer variables",severity:"MEDIUM",fix:"Move function declaration to module scope",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md"}),moveToModuleScope:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.ARCHITECTURE,issueName:"Function Scoping Optimization",description:"Function does not use variables from its containing scope and can be moved to module level",severity:"MEDIUM",fix:'Move function outside current scope: extract `function helper() { return "value"; }` to module level before the containing function/class',documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md"})},schema:[{type:"object",properties:{checkArrowFunctions:{type:"boolean",default:true}},additionalProperties:false}]},defaultOptions:[{checkArrowFunctions:true}],create(context){const[options]=context.options;const{checkArrowFunctions=true}=options||{};const scopeStack=[new Set];function enterScope(){scopeStack.push(new Set)}function exitScope(){scopeStack.pop()}function addVariableToCurrentScope(name){const currentScope=scopeStack[scopeStack.length-1];if(currentScope){currentScope.add(name)}}function getOuterScopeVariables(){const outerScopes=scopeStack.slice(0,-1);const outerVars=new Set;for(const scope of outerScopes){for(const varName of scope){outerVars.add(varName)}}return outerVars}function analyzeFunction(node){let ancestor=node.parent;while(ancestor&&BINDING_WRAPPERS.has(ancestor.type)){ancestor=ancestor.parent}if(ancestor?.type==="Program"||ancestor?.type==="ExportNamedDeclaration"||ancestor?.type==="ExportDefaultDeclaration"){return}const p=node.parent;if(p?.type==="MethodDefinition"||p?.type==="PropertyDefinition"){return}if(p?.type==="CallExpression"||p?.type==="NewExpression"){return}if(p?.type==="Property"){return}const referencedVars=new Set;function collectReferences(astNode,depth=0){if(astNode.type==="Identifier"){referencedVars.add(astNode.name)}if(depth<10){for(const key in astNode){if(key==="parent")continue;const child=astNode[key];if(child&&typeof child==="object"){if(Array.isArray(child)){child.forEach(item=>{if(item&&typeof item==="object"&&"type"in item){collectReferences(item,depth+1)}})}else if("type"in child){collectReferences(child,depth+1)}}}}}if(node.body.type==="BlockStatement"){node.body.body.forEach(stmt=>collectReferences(stmt))}else{collectReferences(node.body)}node.params.forEach(param=>{if(param.type==="Identifier"){referencedVars.add(param.name)}});const outerVars=getOuterScopeVariables();let capturesOuterVar=false;for(const ref of referencedVars){if(outerVars.has(ref)){capturesOuterVar=true;break}}if(!capturesOuterVar){const functionName=node.type==="FunctionDeclaration"?node.id?.name:void 0;const moduleScope=scopeStack[0];if(!functionName||!moduleScope.has(functionName)){context.report({node,messageId:"inconsistentFunctionScoping",data:{functionName:functionName||"anonymous function"},suggest:[{messageId:"moveToModuleScope",fix(fixer){return fixer.insertTextBefore(node,"// TODO: Move this function to module scope - it doesn't capture outer variables\n")}}]})}}}return{Program(){enterScope()},"Program:exit"(){exitScope()},FunctionDeclaration(node){enterScope();node.params.forEach(param=>{if(param.type==="Identifier"){addVariableToCurrentScope(param.name)}});analyzeFunction(node)},"FunctionDeclaration:exit"(){exitScope()},FunctionExpression(node){enterScope();node.params.forEach(param=>{if(param.type==="Identifier"){addVariableToCurrentScope(param.name)}});analyzeFunction(node)},"FunctionExpression:exit"(){exitScope()},ArrowFunctionExpression(node){enterScope();node.params.forEach(param=>{if(param.type==="Identifier"){addVariableToCurrentScope(param.name)}});if(checkArrowFunctions){analyzeFunction(node)}},"ArrowFunctionExpression:exit"(){exitScope()},VariableDeclaration(node){node.declarations.forEach(decl=>{if(decl.id.type==="Identifier"){addVariableToCurrentScope(decl.id.name)}})}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.consistentFunctionScoping=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const BINDING_WRAPPERS=new Set(["VariableDeclarator","VariableDeclaration","TSAsExpression","TSSatisfiesExpression","TSNonNullExpression","TSTypeAssertion"]);exports.consistentFunctionScoping=(0,eslint_devkit_1.createRule)({name:"consistent-function-scoping",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/consistent-function-scoping.md",description:"Move function definitions to the highest possible scope to improve readability and performance"},hasSuggestions:true,messages:{inconsistentFunctionScoping:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.ARCHITECTURE,issueName:"Inconsistent Function Scoping",description:"Function can be moved to higher scope as it doesn't capture outer variables",severity:"MEDIUM",fix:"Move function declaration to module scope",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md"}),moveToModuleScope:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.ARCHITECTURE,issueName:"Function Scoping Optimization",description:"Function does not use variables from its containing scope and can be moved to module level",severity:"MEDIUM",fix:'Move function outside current scope: extract `function helper() { return "value"; }` to module level before the containing function/class',documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md"})},schema:[{type:"object",properties:{checkArrowFunctions:{type:"boolean",default:true}},additionalProperties:false}]},defaultOptions:[{checkArrowFunctions:true}],create(context){const[options]=context.options;const{checkArrowFunctions=true}=options||{};const scopeStack=[new Set];function enterScope(){scopeStack.push(new Set)}function exitScope(){scopeStack.pop()}function addVariableToCurrentScope(name){const currentScope=scopeStack[scopeStack.length-1];if(currentScope){currentScope.add(name)}}function addBindingToCurrentScope(target){switch(target.type){case"Identifier":addVariableToCurrentScope(target.name);break;case"ObjectPattern":for(const prop of target.properties){addBindingToCurrentScope(prop.type==="RestElement"?prop.argument:prop.value)}break;case"ArrayPattern":for(const element of target.elements){if(element)addBindingToCurrentScope(element)}break;case"AssignmentPattern":addBindingToCurrentScope(target.left);break;case"RestElement":addBindingToCurrentScope(target.argument);break;case"TSParameterProperty":addBindingToCurrentScope(target.parameter);break}}function getOuterScopeVariables(){const outerScopes=scopeStack.slice(0,-1);const outerVars=new Set;for(const scope of outerScopes){for(const varName of scope){outerVars.add(varName)}}return outerVars}function capturesThis(node){if(node.type==="ThisExpression")return true;for(const key in node){if(key==="parent")continue;const value=node[key];const children=Array.isArray(value)?value:[value];for(const child of children){if(!child||typeof child!=="object")continue;const childNode=child;if(typeof childNode.type!=="string")continue;if(childNode.type==="ClassDeclaration"||childNode.type==="ClassExpression"){if(childNode.superClass&&capturesThis(childNode.superClass)){return true}for(const member of childNode.body.body){if("computed"in member&&member.computed&&capturesThis(member.key)){return true}}continue}if(childNode.type==="FunctionDeclaration"||childNode.type==="FunctionExpression"){continue}if(capturesThis(childNode))return true}}return false}function analyzeFunction(node){let ancestor=node.parent;while(ancestor&&BINDING_WRAPPERS.has(ancestor.type)){ancestor=ancestor.parent}if(ancestor?.type==="Program"||ancestor?.type==="ExportNamedDeclaration"||ancestor?.type==="ExportDefaultDeclaration"){return}const p=node.parent;if(p?.type==="MethodDefinition"||p?.type==="PropertyDefinition"){return}if(node.type==="ArrowFunctionExpression"&&capturesThis(node)){return}if(p?.type==="CallExpression"||p?.type==="NewExpression"){return}if(p?.type==="Property"){return}const referencedVars=new Set;function collectReferences(astNode,depth=0){if(astNode.type==="Identifier"){referencedVars.add(astNode.name)}if(depth<10){for(const key in astNode){if(key==="parent")continue;const child=astNode[key];if(child&&typeof child==="object"){if(Array.isArray(child)){child.forEach(item=>{if(item&&typeof item==="object"&&"type"in item){collectReferences(item,depth+1)}})}else if("type"in child){collectReferences(child,depth+1)}}}}}if(node.body.type==="BlockStatement"){node.body.body.forEach(stmt=>collectReferences(stmt))}else{collectReferences(node.body)}node.params.forEach(param=>{collectReferences(param)});const outerVars=getOuterScopeVariables();const boundNames=new Set(context.sourceCode.getDeclaredVariables(node).map(v=>v.name));let capturesOuterVar=false;for(const ref of referencedVars){if(!boundNames.has(ref)&&outerVars.has(ref)){capturesOuterVar=true;break}}if(!capturesOuterVar){const functionName=node.type==="FunctionDeclaration"?node.id?.name:void 0;const moduleScope=scopeStack[0];if(!functionName||!moduleScope.has(functionName)){context.report({node,messageId:"inconsistentFunctionScoping",data:{functionName:functionName||"anonymous function"},suggest:[{messageId:"moveToModuleScope",fix(fixer){return fixer.insertTextBefore(node,"// TODO: Move this function to module scope - it doesn't capture outer variables\n")}}]})}}}return{Program(){enterScope()},"Program:exit"(){exitScope()},FunctionDeclaration(node){enterScope();node.params.forEach(param=>{addBindingToCurrentScope(param)});analyzeFunction(node)},"FunctionDeclaration:exit"(){exitScope()},FunctionExpression(node){enterScope();node.params.forEach(param=>{addBindingToCurrentScope(param)});analyzeFunction(node)},"FunctionExpression:exit"(){exitScope()},ArrowFunctionExpression(node){enterScope();node.params.forEach(param=>{addBindingToCurrentScope(param)});if(checkArrowFunctions){analyzeFunction(node)}},"ArrowFunctionExpression:exit"(){exitScope()},VariableDeclaration(node){node.declarations.forEach(decl=>{addBindingToCurrentScope(decl.id)})}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.identicalFunctions=void 0;exports.buildGenericName=buildGenericName;exports.calculateSimilarity=calculateSimilarity;exports.levenshteinDistance=levenshteinDistance;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const eslint_devkit_3=require("@interlace/eslint-devkit");function buildGenericName(firstFunctionName){const baseName=firstFunctionName.replace(/^(handle|process|get|set|create|update|delete)/,"");return`handle${baseName||"Generic"}`}const RESERVED_WORDS=new Set(["await","break","case","catch","class","const","continue","debugger","default","delete","do","else","export","extends","finally","for","function","if","import","in","instanceof","let","new","of","return","super","switch","this","throw","try","typeof","var","void","while","with","yield","true","false","null","undefined"]);function calculateSimilarity(str1,str2,similarityThreshold){if(str1===str2)return 1;const longer=str1.length>str2.length?str1:str2;const shorter=str1.length>str2.length?str2:str1;if(shorter.length/longer.length<similarityThreshold)return 0;const budget=longer.length-Math.ceil(longer.length*similarityThreshold);const editDistance=levenshteinDistance(longer,shorter,budget);if(editDistance<0)return 0;return(longer.length-editDistance)/longer.length}function levenshteinDistance(str1,str2,budget){let previous=[];let current=[];for(let j=0;j<=str1.length;j++)previous[j]=j;for(let i=1;i<=str2.length;i++){current[0]=i;let rowMin=current[0];for(let j=1;j<=str1.length;j++){current[j]=str2.charAt(i-1)===str1.charAt(j-1)?previous[j-1]:Math.min(previous[j-1]+1,current[j-1]+1,previous[j]+1);if(current[j]<rowMin)rowMin=current[j]}if(rowMin>budget)return-1;const swap=previous;previous=current;current=swap}return previous[str1.length]}exports.identicalFunctions=(0,eslint_devkit_2.createRule)({name:"identical-functions",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/identical-functions.md",description:"Detects duplicate function implementations with DRY refactoring suggestions"},hasSuggestions:true,messages:{identicalFunctions:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.DUPLICATION,issueName:"Code duplication",description:"{{count}} duplicates ({{similarity}}% similar)",severity:"MEDIUM",fix:"Extract to reusable function",documentationLink:"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"}),extractGeneric:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Extract Generic",description:"Extract to generic function",severity:"LOW",fix:"Create shared function with parameters",documentationLink:"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"}),useHigherOrder:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Higher-Order",description:"Use higher-order function pattern",severity:"LOW",fix:"Create factory function that returns specialized functions",documentationLink:"https://developer.mozilla.org/en-US/docs/Glossary/Higher-order_function"}),applyInheritance:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Composition",description:"Use inheritance/composition",severity:"LOW",fix:"Extract common behavior to base class or mixin",documentationLink:"https://en.wikipedia.org/wiki/Composition_over_inheritance"})},schema:[{type:"object",properties:{minLines:{type:"number",default:3,minimum:1,description:"Minimum lines to consider for duplication"},similarityThreshold:{type:"number",default:.9,minimum:.5,maximum:1,description:"Similarity threshold (0.5-1.0)"},ignoreTestFiles:{type:"boolean",default:true},ignoreGeneratedFiles:{type:"boolean",default:true}},additionalProperties:false}]},defaultOptions:[{minLines:3,similarityThreshold:.9,ignoreTestFiles:true,ignoreGeneratedFiles:true}],create(context){const{minLines=3,similarityThreshold=.9,ignoreTestFiles=true,ignoreGeneratedFiles=true}=context.options[0]||{};const sourceCode=context.sourceCode;const filename=context.filename;if(ignoreTestFiles&&(0,eslint_devkit_3.isTestFilePath)(filename)){return{}}if(ignoreGeneratedFiles&&(0,eslint_devkit_3.isGeneratedFile)(sourceCode)){return{}}const functions=[];function normalizeBody(body){const literals=[];const stash=match=>{literals.push(`"${match.slice(1,-1)}"`);return`\uE000${literals.length-1}\uE000`};let text=body.replace(/`(?:[^\\`]|\\[\s\S])*`/g,stash).replace(/(["'])(?:[^\\\n]|\\.)*?\1/g,stash).replace(/(^|[=(,:[!&|?{};+\-*%<>~^]\s*)(\/(?:[^/\\\n[]|\\.|\[(?:[^\]\\]|\\.)*\])+\/[dgimsuvy]*)/g,(_match,prefix,pattern)=>{literals.push(pattern);return`${prefix}\uE000${literals.length-1}\uE000`});text=text.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/[^\n]*/g,"").replace(/\s+/g," ").replace(/(\.\s*)?\b[a-z_$][a-zA-Z0-9_$]*\b(\s*:)?/g,(match,memberPrefix,keySuffix)=>{if(memberPrefix||keySuffix)return match;return RESERVED_WORDS.has(match)?match:"VAR"});return text.replace(/\uE000(\d+)\uE000/g,(_match,index)=>literals[Number(index)]).trim()}function findDuplicationGroups(){const groups=[];const processed=new Set;for(let i=0;i<functions.length;i++){if(processed.has(i))continue;const group=[functions[i]];processed.add(i);for(let j=i+1;j<functions.length;j++){if(processed.has(j))continue;if(functions[i].isAsync!==functions[j].isAsync||functions[i].isGenerator!==functions[j].isGenerator){continue}const similarity=calculateSimilarity(functions[i].normalizedBody,functions[j].normalizedBody,similarityThreshold);const SHORT_BODY_CHARS=120;const longer=Math.max(functions[i].normalizedBody.length,functions[j].normalizedBody.length);const required=longer<SHORT_BODY_CHARS?1:similarityThreshold;if(similarity>=required){group.push(functions[j]);processed.add(j)}}if(group.length>=2){const avgSimilarity=group.reduce((sum,func,idx)=>{if(idx===0)return 0;return sum+calculateSimilarity(group[0].normalizedBody,func.normalizedBody,similarityThreshold)},0)/(group.length-1);groups.push({functions:group,similarityScore:avgSimilarity,commonPattern:functions[i].normalizedBody})}}return groups}function suggestRefactoringApproach(group){const funcNames=group.functions.map(f=>f.name);const hasRolePattern=funcNames.some(name=>/user|admin|guest|customer/i.test(name));const hasTypePattern=funcNames.some(name=>/payment|shipping|billing|email|sms/i.test(name));if(hasRolePattern||hasTypePattern){return{approach:"Parameter Object + Strategy Pattern",pattern:"Extract discriminator as parameter",complexity:"moderate"}}if(group.functions[0].params.length>0){return{approach:"Higher-Order Function",pattern:"Extract common logic, inject differences",complexity:"simple"}}return{approach:"Extract Method",pattern:"DRY - Single source of truth",complexity:"simple"}}function storeFunctionInfo(node){const body=node.body?sourceCode.getText(node.body):"";const lines=body.split("\n").length;if(lines<minLines)return;const name=(0,eslint_devkit_3.extractFunctionSignature)(node).split("(")[0].replace("function ","");const params=node.params.map(p=>p.type==="Identifier"?p.name:sourceCode.getText(p));functions.push({node,name:name||"anonymous",body,normalizedBody:normalizeBody(body),isAsync:Boolean(node.async),isGenerator:"generator"in node&&Boolean(node.generator),lines,location:`${filename}:${node.loc?.start.line}`,params})}function reportDuplications(){const groups=findDuplicationGroups();groups.forEach(group=>{const refactoringApproach=suggestRefactoringApproach(group);const primaryFunction=group.functions[0];const similarityPercent=Math.round(group.similarityScore*100);context.report({node:primaryFunction.node,messageId:"identicalFunctions",data:{count:String(group.functions.length),similarity:String(similarityPercent),filePath:filename,line:String(primaryFunction.node.loc?.start.line??0)},suggest:[{messageId:"extractGeneric",data:{functionName:buildGenericName(primaryFunction.name)},fix:()=>null},...refactoringApproach.approach.includes("Higher-Order")?[{messageId:"useHigherOrder",fix:()=>null}]:[],...refactoringApproach.approach.includes("Strategy")?[{messageId:"applyInheritance",fix:()=>null}]:[]]})})}return{FunctionDeclaration:storeFunctionInfo,FunctionExpression:storeFunctionInfo,ArrowFunctionExpression:storeFunctionInfo,"Program:exit":reportDuplications}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.identicalFunctions=void 0;exports.buildGenericName=buildGenericName;exports.calculateSimilarity=calculateSimilarity;exports.levenshteinDistance=levenshteinDistance;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const eslint_devkit_3=require("@interlace/eslint-devkit");function buildGenericName(firstFunctionName){const baseName=firstFunctionName.replace(/^(handle|process|get|set|create|update|delete)/,"");return`handle${baseName||"Generic"}`}const RESERVED_WORDS=new Set(["await","break","case","catch","class","const","continue","debugger","default","delete","do","else","export","extends","finally","for","function","if","import","in","instanceof","let","new","of","return","super","switch","this","throw","try","typeof","var","void","while","with","yield","true","false","null","undefined"]);function calculateSimilarity(str1,str2,similarityThreshold){if(str1===str2)return 1;const longer=str1.length>str2.length?str1:str2;const shorter=str1.length>str2.length?str2:str1;if(shorter.length/longer.length<similarityThreshold)return 0;const budget=longer.length-Math.ceil(longer.length*similarityThreshold);const editDistance=levenshteinDistance(longer,shorter,budget);if(editDistance<0)return 0;return(longer.length-editDistance)/longer.length}function levenshteinDistance(str1,str2,budget){let previous=[];let current=[];for(let j=0;j<=str1.length;j++)previous[j]=j;for(let i=1;i<=str2.length;i++){current[0]=i;let rowMin=current[0];for(let j=1;j<=str1.length;j++){current[j]=str2.charAt(i-1)===str1.charAt(j-1)?previous[j-1]:Math.min(previous[j-1]+1,current[j-1]+1,previous[j]+1);if(current[j]<rowMin)rowMin=current[j]}if(rowMin>budget)return-1;const swap=previous;previous=current;current=swap}return previous[str1.length]}exports.identicalFunctions=(0,eslint_devkit_2.createRule)({name:"identical-functions",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/identical-functions.md",description:"Detects duplicate function implementations with DRY refactoring suggestions"},hasSuggestions:true,messages:{identicalFunctions:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.DUPLICATION,issueName:"Code duplication",description:"{{count}} duplicates ({{similarity}}% similar)",severity:"MEDIUM",fix:"Extract to reusable function",documentationLink:"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"}),extractGeneric:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Extract Generic",description:"Extract to generic function",severity:"LOW",fix:"Create shared function with parameters",documentationLink:"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"}),useHigherOrder:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Higher-Order",description:"Use higher-order function pattern",severity:"LOW",fix:"Create factory function that returns specialized functions",documentationLink:"https://developer.mozilla.org/en-US/docs/Glossary/Higher-order_function"}),applyInheritance:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Composition",description:"Use inheritance/composition",severity:"LOW",fix:"Extract common behavior to base class or mixin",documentationLink:"https://en.wikipedia.org/wiki/Composition_over_inheritance"})},schema:[{type:"object",properties:{minLines:{type:"number",default:3,minimum:1,description:"Minimum lines to consider for duplication"},similarityThreshold:{type:"number",default:.9,minimum:.5,maximum:1,description:"Similarity threshold (0.5-1.0)"},ignoreTestFiles:{type:"boolean",default:true},ignoreGeneratedFiles:{type:"boolean",default:true}},additionalProperties:false}]},defaultOptions:[{minLines:3,similarityThreshold:.9,ignoreTestFiles:true,ignoreGeneratedFiles:true}],create(context){const{minLines=3,similarityThreshold=.9,ignoreTestFiles=true,ignoreGeneratedFiles=true}=context.options[0]||{};const sourceCode=context.sourceCode;const filename=context.filename;if(ignoreTestFiles&&(0,eslint_devkit_3.isTestFilePath)(filename)){return{}}if(ignoreGeneratedFiles&&(0,eslint_devkit_3.isGeneratedFile)(sourceCode)){return{}}const functions=[];function normalizeBody(body){const literals=[];const stash=match=>{literals.push(`"${match.slice(1,-1)}"`);return`\uE000${literals.length-1}\uE000`};let text=body.replace(/`(?:[^\\`]|\\[\s\S])*`/g,stash).replace(/(["'])(?:[^\\\n]|\\.)*?\1/g,stash).replace(/(^|[=(,:[!&|?{};+\-*%<>~^]\s*)(\/(?:[^/\\\n[]|\\.|\[(?:[^\]\\]|\\.)*\])+\/[dgimsuvy]*)/g,(_match,prefix,pattern)=>{literals.push(pattern);return`${prefix}\uE000${literals.length-1}\uE000`});text=text.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/[^\n]*/g,"").replace(/\s+/g," ").replace(/(\.\s*#?|#)?\b[a-z_$][a-zA-Z0-9_$]*\b(\s*:)?/g,(match,memberPrefix,keySuffix)=>{if(memberPrefix||keySuffix)return match;return RESERVED_WORDS.has(match)?match:"VAR"});return text.replace(/\uE000(\d+)\uE000/g,(_match,index)=>literals[Number(index)]).trim()}function findDuplicationGroups(){const groups=[];const processed=new Set;for(let i=0;i<functions.length;i++){if(processed.has(i))continue;const group=[functions[i]];processed.add(i);for(let j=i+1;j<functions.length;j++){if(processed.has(j))continue;if(functions[i].isAsync!==functions[j].isAsync||functions[i].isGenerator!==functions[j].isGenerator){continue}const similarity=calculateSimilarity(functions[i].normalizedBody,functions[j].normalizedBody,similarityThreshold);const SHORT_BODY_CHARS=120;const longer=Math.max(functions[i].normalizedBody.length,functions[j].normalizedBody.length);const required=longer<SHORT_BODY_CHARS?1:similarityThreshold;if(similarity>=required){group.push(functions[j]);processed.add(j)}}if(group.length>=2){const avgSimilarity=group.reduce((sum,func,idx)=>{if(idx===0)return 0;return sum+calculateSimilarity(group[0].normalizedBody,func.normalizedBody,similarityThreshold)},0)/(group.length-1);groups.push({functions:group,similarityScore:avgSimilarity,commonPattern:functions[i].normalizedBody})}}return groups}function suggestRefactoringApproach(group){const funcNames=group.functions.map(f=>f.name);const hasRolePattern=funcNames.some(name=>/user|admin|guest|customer/i.test(name));const hasTypePattern=funcNames.some(name=>/payment|shipping|billing|email|sms/i.test(name));if(hasRolePattern||hasTypePattern){return{approach:"Parameter Object + Strategy Pattern",pattern:"Extract discriminator as parameter",complexity:"moderate"}}if(group.functions[0].params.length>0){return{approach:"Higher-Order Function",pattern:"Extract common logic, inject differences",complexity:"simple"}}return{approach:"Extract Method",pattern:"DRY - Single source of truth",complexity:"simple"}}function storeFunctionInfo(node){const body=node.body?sourceCode.getText(node.body):"";const lines=body.split("\n").length;if(lines<minLines)return;const name=(0,eslint_devkit_3.extractFunctionSignature)(node).split("(")[0].replace("function ","");const params=node.params.map(p=>p.type==="Identifier"?p.name:sourceCode.getText(p));functions.push({node,name:name||"anonymous",body,normalizedBody:normalizeBody(body),isAsync:Boolean(node.async),isGenerator:"generator"in node&&Boolean(node.generator),lines,location:`${filename}:${node.loc?.start.line}`,params})}function reportDuplications(){const groups=findDuplicationGroups();groups.forEach(group=>{const refactoringApproach=suggestRefactoringApproach(group);const primaryFunction=group.functions[0];const similarityPercent=Math.round(group.similarityScore*100);context.report({node:primaryFunction.node,messageId:"identicalFunctions",data:{count:String(group.functions.length),similarity:String(similarityPercent),filePath:filename,line:String(primaryFunction.node.loc?.start.line??0)},suggest:[{messageId:"extractGeneric",data:{functionName:buildGenericName(primaryFunction.name)},fix:()=>null},...refactoringApproach.approach.includes("Higher-Order")?[{messageId:"useHigherOrder",fix:()=>null}]:[],...refactoringApproach.approach.includes("Strategy")?[{messageId:"applyInheritance",fix:()=>null}]:[]]})})}return{FunctionDeclaration:storeFunctionInfo,FunctionExpression:storeFunctionInfo,ArrowFunctionExpression:storeFunctionInfo,"Program:exit":reportDuplications}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.nestedComplexityHotspots=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.nestedComplexityHotspots=(0,eslint_devkit_2.createRule)({name:"nested-complexity-hotspots",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/nested-complexity-hotspots.md",description:"Identifies nested control structures that harm readability"},hasSuggestions:true,messages:{nestedComplexity:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.COMPLEXITY,issueName:"Nested complexity hotspot",description:"Nesting depth {{depth}} exceeds maximum {{max}}",severity:"MEDIUM",fix:"Use early returns, guard clauses, or extract methods",documentationLink:"https://en.wikipedia.org/wiki/Cyclomatic_complexity"}),useEarlyReturn:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Early Return",description:"Use early return to reduce nesting",severity:"LOW",fix:"if (!condition) return; // Continue with main logic",documentationLink:"https://refactoring.guru/smells/long-method"}),useGuardClauses:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Guard Clauses",description:"Use guard clauses for validation",severity:"LOW",fix:"if (!isValid(input)) throw new Error()",documentationLink:"https://refactoring.guru/replace-nested-conditional-with-guard-clauses"}),extractMethod:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Extract Method",description:"Extract nested logic to separate method",severity:"LOW",fix:"Extract to private method",documentationLink:"https://refactoring.guru/extract-method"})},schema:[{type:"object",properties:{maxDepth:{type:"number",default:4,minimum:1,description:"Maximum nesting depth"},countConditionals:{type:"boolean",default:true,description:"Count nested conditionals"},countLoops:{type:"boolean",default:true,description:"Count nested loops"}},additionalProperties:false}]},defaultOptions:[{maxDepth:4,countConditionals:true,countLoops:true}],create(context,[options={}]){const{maxDepth=4,countConditionals=true,countLoops=true}=options||{};function checkControlStructure(node){let depth=0;let current=node;const maxDepthCheck=20;while(current&&depth<maxDepthCheck){const parent=current.parent;if(!parent)break;if(parent.type==="IfStatement"||parent.type==="ForStatement"||parent.type==="ForInStatement"||parent.type==="ForOfStatement"||parent.type==="WhileStatement"||parent.type==="DoWhileStatement"||parent.type==="SwitchStatement"||parent.type==="TryStatement"){depth++}current=parent}if(depth>=maxDepth){context.report({node,messageId:"nestedComplexity",data:{depth:String(depth+1),max:String(maxDepth)},suggest:[{messageId:"useEarlyReturn",fix:()=>null},{messageId:"useGuardClauses",fix:()=>null},{messageId:"extractMethod",fix:()=>null}]})}}return{IfStatement:countConditionals?checkControlStructure:void 0,ForStatement:countLoops?checkControlStructure:void 0,ForInStatement:countLoops?checkControlStructure:void 0,ForOfStatement:countLoops?checkControlStructure:void 0,WhileStatement:countLoops?checkControlStructure:void 0,SwitchStatement:countConditionals?checkControlStructure:void 0}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.nestedComplexityHotspots=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.nestedComplexityHotspots=(0,eslint_devkit_2.createRule)({name:"nested-complexity-hotspots",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/nested-complexity-hotspots.md",description:"Identifies nested control structures that harm readability"},hasSuggestions:true,messages:{nestedComplexity:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.COMPLEXITY,issueName:"Nested complexity hotspot",description:"Nesting depth {{depth}} exceeds maximum {{max}}",severity:"MEDIUM",fix:"Use early returns, guard clauses, or extract methods",documentationLink:"https://en.wikipedia.org/wiki/Cyclomatic_complexity"}),useEarlyReturn:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Early Return",description:"Use early return to reduce nesting",severity:"LOW",fix:"if (!condition) return; // Continue with main logic",documentationLink:"https://refactoring.guru/smells/long-method"}),useGuardClauses:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Guard Clauses",description:"Use guard clauses for validation",severity:"LOW",fix:"if (!isValid(input)) throw new Error()",documentationLink:"https://refactoring.guru/replace-nested-conditional-with-guard-clauses"}),extractMethod:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Extract Method",description:"Extract nested logic to separate method",severity:"LOW",fix:"Extract to private method",documentationLink:"https://refactoring.guru/extract-method"})},schema:[{type:"object",properties:{maxDepth:{type:"number",default:4,minimum:1,description:"Maximum nesting depth"},countConditionals:{type:"boolean",default:true,description:"Count nested conditionals"},countLoops:{type:"boolean",default:true,description:"Count nested loops"}},additionalProperties:false}]},defaultOptions:[{maxDepth:4,countConditionals:true,countLoops:true}],create(context,[options={}]){const{maxDepth=4,countConditionals=true,countLoops=true}=options||{};function checkControlStructure(node){let depth=0;let current=node;const maxDepthCheck=20;while(current&&depth<maxDepthCheck){const parent=current.parent;if(!parent)break;const isElseIf=parent.type==="IfStatement"&&parent.alternate===current;if(!isElseIf&&(parent.type==="IfStatement"||parent.type==="ForStatement"||parent.type==="ForInStatement"||parent.type==="ForOfStatement"||parent.type==="WhileStatement"||parent.type==="DoWhileStatement"||parent.type==="SwitchStatement"||parent.type==="TryStatement")){depth++}current=parent}if(depth>=maxDepth){context.report({node,messageId:"nestedComplexity",data:{depth:String(depth+1),max:String(maxDepth)},suggest:[{messageId:"useEarlyReturn",fix:()=>null},{messageId:"useGuardClauses",fix:()=>null},{messageId:"extractMethod",fix:()=>null}]})}}return{IfStatement:countConditionals?checkControlStructure:void 0,ForStatement:countLoops?checkControlStructure:void 0,ForInStatement:countLoops?checkControlStructure:void 0,ForOfStatement:countLoops?checkControlStructure:void 0,WhileStatement:countLoops?checkControlStructure:void 0,SwitchStatement:countConditionals?checkControlStructure:void 0}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noLonelyIf=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noLonelyIf=(0,eslint_devkit_1.createRule)({name:"no-lonely-if",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-lonely-if.md",description:"Prevent lone if statements inside else blocks - use else if instead"},hasSuggestions:true,messages:{noLonelyIf:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Lonely If",description:"Unexpected if statement inside else block",severity:"MEDIUM",fix:"Replace with else if for better readability",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-lonely-if.md"})},schema:[{type:"object",properties:{allow:{type:"array",items:{type:"string"},default:[]}},additionalProperties:false}]},defaultOptions:[{allow:[]}],create(context){const[options]=context.options;const{allow=[]}=options||{};const allowedContexts=new Set(allow);function isInAllowedContext(){for(const allowedContext of allowedContexts){const sourceCode=context.sourceCode;const sourceText=sourceCode.getText();if(sourceText.includes(allowedContext)){return true}}return false}function isLonelyIf(node){const parent=node.parent;if(parent?.type==="BlockStatement"){const grandParent=parent.parent;return grandParent?.type==="IfStatement"&&grandParent.alternate===parent}return false}return{IfStatement(node){if(isLonelyIf(node)&&!isInAllowedContext()){context.report({node,messageId:"noLonelyIf",data:{current:"if statement in else block",fix:"else if"},suggest:[{messageId:"noLonelyIf",fix(fixer){const sourceCode=context.sourceCode;let elseToken=null;const tokens=sourceCode.getTokensBefore(node,10);for(let i=tokens.length-1;i>=0;i--){if(tokens[i].value==="else"){elseToken=tokens[i];break}}if(elseToken){const ifToken=sourceCode.getTokenAfter(elseToken);if(ifToken&&ifToken.value==="if"){return[fixer.removeRange([elseToken.range[0],ifToken.range[1]]),fixer.insertTextBefore(ifToken,"else ")]}}return null}}]})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noLonelyIf=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noLonelyIf=(0,eslint_devkit_1.createRule)({name:"no-lonely-if",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-lonely-if.md",description:"Prevent lone if statements inside else blocks - use else if instead"},hasSuggestions:true,messages:{noLonelyIf:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Lonely If",description:"Unexpected if statement inside else block",severity:"MEDIUM",fix:"Replace with else if for better readability",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-lonely-if.md"})},schema:[{type:"object",properties:{allow:{type:"array",items:{type:"string"},default:[]}},additionalProperties:false}]},defaultOptions:[{allow:[]}],create(context){const[options]=context.options;const{allow=[]}=options||{};const allowedContexts=new Set(allow);function isInAllowedContext(){for(const allowedContext of allowedContexts){const sourceCode=context.sourceCode;const sourceText=sourceCode.getText();if(sourceText.includes(allowedContext)){return true}}return false}function isLonelyIf(node){const parent=node.parent;if(parent?.type==="BlockStatement"){const grandParent=parent.parent;return grandParent?.type==="IfStatement"&&grandParent.alternate===parent&&parent.body.length===1}return false}return{IfStatement(node){if(isLonelyIf(node)&&!isInAllowedContext()){context.report({node,messageId:"noLonelyIf",data:{current:"if statement in else block",fix:"else if"},suggest:[{messageId:"noLonelyIf",fix(fixer){const sourceCode=context.sourceCode;let elseToken=null;const tokens=sourceCode.getTokensBefore(node,10);for(let i=tokens.length-1;i>=0;i--){if(tokens[i].value==="else"){elseToken=tokens[i];break}}if(elseToken){const ifToken=sourceCode.getTokenAfter(elseToken);if(ifToken&&ifToken.value==="if"){return[fixer.removeRange([elseToken.range[0],ifToken.range[1]]),fixer.insertTextBefore(ifToken,"else ")]}}return null}}]})}}}}});