eslint-plugin-maintainability 3.2.12 → 3.2.13

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.12",
3
+ "version": "3.2.13",
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.12"},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.13"},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.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[*]|\\.|\[(?:[^\]\\]|\\.)*\])(?:[^/\\\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 encloses(outer,inner){const o=outer.range;const i=inner.range;if(!o||!i)return false;return o[0]<=i[0]&&i[1]<=o[1]}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}if(encloses(functions[i].node,functions[j].node)||encloses(functions[j].node,functions[i].node)){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[*]|\\.|\[(?:[^\]\\]|\\.)*\])(?:[^/\\\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,offset,whole)=>{if(memberPrefix)return match;if(keySuffix!==void 0){const before=whole.slice(0,offset).trimEnd();const previous=before.at(-1);if(previous==="{"||previous===",")return match}const name=keySuffix===void 0?match:match.slice(0,-keySuffix.length);const renamed=RESERVED_WORDS.has(name)?name:"VAR";return keySuffix===void 0?renamed:`${renamed}${keySuffix}`});let restored=text;for(let pass=0;pass<=literals.length;pass++){if(!restored.includes("\uE000"))break;restored=restored.replace(/\uE000(\d+)\uE000/g,(_match,index)=>literals[Number(index)])}return restored.trim()}function encloses(outer,inner){const o=outer.range;const i=inner.range;if(!o||!i)return false;return o[0]<=i[0]&&i[1]<=o[1]}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}if(encloses(functions[i].node,functions[j].node)||encloses(functions[j].node,functions[i].node)){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}}});