eslint-plugin-maintainability 3.0.13 → 3.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/index.js +1 -50
- package/src/oxlint.js +1 -3
- package/src/rules/error-handling/error-message.js +1 -131
- package/src/rules/error-handling/no-missing-error-context.js +1 -164
- package/src/rules/error-handling/no-silent-errors.js +1 -157
- package/src/rules/error-handling/no-unhandled-promise.js +1 -261
- package/src/rules/maintainability/cognitive-complexity.js +1 -341
- package/src/rules/maintainability/consistent-function-scoping.js +1 -229
- package/src/rules/maintainability/identical-functions.js +1 -259
- package/src/rules/maintainability/max-parameters.js +1 -129
- package/src/rules/maintainability/nested-complexity-hotspots.js +1 -137
- package/src/rules/maintainability/no-lonely-if.js +1 -107
- package/src/rules/maintainability/no-nested-ternary.js +1 -139
- package/src/rules/maintainability/no-unreadable-iife.js +1 -206
- package/src/types/index.js +1 -2
|
@@ -1,229 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.consistentFunctionScoping = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
exports.consistentFunctionScoping = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'consistent-function-scoping',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/consistent-function-scoping.md',
|
|
12
|
-
description: 'Move function definitions to the highest possible scope to improve readability and performance',
|
|
13
|
-
},
|
|
14
|
-
hasSuggestions: true,
|
|
15
|
-
messages: {
|
|
16
|
-
inconsistentFunctionScoping: (0, eslint_devkit_2.formatLLMMessage)({
|
|
17
|
-
icon: eslint_devkit_2.MessageIcons.ARCHITECTURE,
|
|
18
|
-
issueName: 'Inconsistent Function Scoping',
|
|
19
|
-
description: 'Function can be moved to higher scope as it doesn\'t capture outer variables',
|
|
20
|
-
severity: 'MEDIUM',
|
|
21
|
-
fix: 'Move function declaration to module scope',
|
|
22
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md',
|
|
23
|
-
}),
|
|
24
|
-
moveToModuleScope: (0, eslint_devkit_2.formatLLMMessage)({
|
|
25
|
-
icon: eslint_devkit_2.MessageIcons.ARCHITECTURE,
|
|
26
|
-
issueName: 'Function Scoping Optimization',
|
|
27
|
-
description: 'Function does not use variables from its containing scope and can be moved to module level',
|
|
28
|
-
severity: 'MEDIUM',
|
|
29
|
-
fix: 'Move function outside current scope: extract `function helper() { return "value"; }` to module level before the containing function/class',
|
|
30
|
-
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md',
|
|
31
|
-
}),
|
|
32
|
-
},
|
|
33
|
-
schema: [
|
|
34
|
-
{
|
|
35
|
-
type: 'object',
|
|
36
|
-
properties: {
|
|
37
|
-
checkArrowFunctions: {
|
|
38
|
-
type: 'boolean',
|
|
39
|
-
default: true,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
additionalProperties: false,
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
defaultOptions: [{ checkArrowFunctions: true }],
|
|
47
|
-
create(context) {
|
|
48
|
-
const [options] = context.options;
|
|
49
|
-
const { checkArrowFunctions = true } = options || {};
|
|
50
|
-
const scopeStack = [new Set()];
|
|
51
|
-
function enterScope() {
|
|
52
|
-
scopeStack.push(new Set());
|
|
53
|
-
}
|
|
54
|
-
function exitScope() {
|
|
55
|
-
scopeStack.pop();
|
|
56
|
-
}
|
|
57
|
-
function addVariableToCurrentScope(name) {
|
|
58
|
-
const currentScope = scopeStack[scopeStack.length - 1];
|
|
59
|
-
if (currentScope) {
|
|
60
|
-
currentScope.add(name);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function getOuterScopeVariables() {
|
|
64
|
-
const outerScopes = scopeStack.slice(0, -1);
|
|
65
|
-
const outerVars = new Set();
|
|
66
|
-
for (const scope of outerScopes) {
|
|
67
|
-
for (const varName of scope) {
|
|
68
|
-
outerVars.add(varName);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return outerVars;
|
|
72
|
-
}
|
|
73
|
-
function analyzeFunction(node) {
|
|
74
|
-
if (node.parent?.type === 'Program' || node.parent?.type === 'ExportNamedDeclaration' || node.parent?.type === 'ExportDefaultDeclaration') {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const p = node.parent;
|
|
78
|
-
if (p?.type === 'MethodDefinition' || p?.type === 'PropertyDefinition') {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (p?.type === 'CallExpression') {
|
|
82
|
-
const callee = p.callee;
|
|
83
|
-
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
|
|
84
|
-
const HOST_METHODS = new Set([
|
|
85
|
-
'map', 'filter', 'reduce', 'reduceRight', 'forEach', 'find', 'findIndex',
|
|
86
|
-
'some', 'every', 'sort', 'flatMap', 'flat',
|
|
87
|
-
'then', 'catch', 'finally',
|
|
88
|
-
'on', 'once', 'addEventListener', 'removeEventListener', 'subscribe',
|
|
89
|
-
]);
|
|
90
|
-
if (HOST_METHODS.has(callee.property.name))
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (callee.type === 'Identifier') {
|
|
94
|
-
const HOST_FNS = new Set([
|
|
95
|
-
'setTimeout', 'setInterval', 'setImmediate',
|
|
96
|
-
'requestAnimationFrame', 'requestIdleCallback', 'queueMicrotask',
|
|
97
|
-
]);
|
|
98
|
-
if (HOST_FNS.has(callee.name))
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (p?.type === 'NewExpression' &&
|
|
103
|
-
p.callee.type === 'Identifier' &&
|
|
104
|
-
p.callee.name === 'Promise') {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const referencedVars = new Set();
|
|
108
|
-
function collectReferences(astNode, depth = 0, visited = new Set()) {
|
|
109
|
-
if (depth > 10 || visited.has(astNode)) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
visited.add(astNode);
|
|
113
|
-
if (astNode.type === 'Identifier') {
|
|
114
|
-
referencedVars.add(astNode.name);
|
|
115
|
-
}
|
|
116
|
-
if (depth < 10) {
|
|
117
|
-
for (const key in astNode) {
|
|
118
|
-
const child = astNode[key];
|
|
119
|
-
if (child && typeof child === 'object') {
|
|
120
|
-
if (Array.isArray(child)) {
|
|
121
|
-
child.forEach(item => {
|
|
122
|
-
if (item && typeof item === 'object' && 'type' in item) {
|
|
123
|
-
collectReferences(item, depth + 1, visited);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
else if ('type' in child) {
|
|
128
|
-
collectReferences(child, depth + 1, visited);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (node.body.type === 'BlockStatement') {
|
|
135
|
-
node.body.body.forEach((stmt) => collectReferences(stmt));
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
collectReferences(node.body);
|
|
139
|
-
}
|
|
140
|
-
node.params.forEach((param) => {
|
|
141
|
-
if (param.type === 'Identifier') {
|
|
142
|
-
referencedVars.add(param.name);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
const outerVars = getOuterScopeVariables();
|
|
146
|
-
let capturesOuterVar = false;
|
|
147
|
-
for (const ref of referencedVars) {
|
|
148
|
-
if (outerVars.has(ref)) {
|
|
149
|
-
capturesOuterVar = true;
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (!capturesOuterVar) {
|
|
154
|
-
const functionName = node.type === 'FunctionDeclaration' ? node.id?.name : undefined;
|
|
155
|
-
const moduleScope = scopeStack[0];
|
|
156
|
-
if (!functionName || !moduleScope.has(functionName)) {
|
|
157
|
-
context.report({
|
|
158
|
-
node,
|
|
159
|
-
messageId: 'inconsistentFunctionScoping',
|
|
160
|
-
data: {
|
|
161
|
-
functionName: functionName || 'anonymous function',
|
|
162
|
-
},
|
|
163
|
-
suggest: [
|
|
164
|
-
{
|
|
165
|
-
messageId: 'moveToModuleScope',
|
|
166
|
-
fix(fixer) {
|
|
167
|
-
return fixer.insertTextBefore(node, '// TODO: Move this function to module scope - it doesn\'t capture outer variables\n');
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
Program() {
|
|
177
|
-
enterScope();
|
|
178
|
-
},
|
|
179
|
-
'Program:exit'() {
|
|
180
|
-
exitScope();
|
|
181
|
-
},
|
|
182
|
-
FunctionDeclaration(node) {
|
|
183
|
-
enterScope();
|
|
184
|
-
node.params.forEach((param) => {
|
|
185
|
-
if (param.type === 'Identifier') {
|
|
186
|
-
addVariableToCurrentScope(param.name);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
analyzeFunction(node);
|
|
190
|
-
},
|
|
191
|
-
'FunctionDeclaration:exit'() {
|
|
192
|
-
exitScope();
|
|
193
|
-
},
|
|
194
|
-
FunctionExpression(node) {
|
|
195
|
-
enterScope();
|
|
196
|
-
node.params.forEach((param) => {
|
|
197
|
-
if (param.type === 'Identifier') {
|
|
198
|
-
addVariableToCurrentScope(param.name);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
analyzeFunction(node);
|
|
202
|
-
},
|
|
203
|
-
'FunctionExpression:exit'() {
|
|
204
|
-
exitScope();
|
|
205
|
-
},
|
|
206
|
-
ArrowFunctionExpression(node) {
|
|
207
|
-
enterScope();
|
|
208
|
-
node.params.forEach((param) => {
|
|
209
|
-
if (param.type === 'Identifier') {
|
|
210
|
-
addVariableToCurrentScope(param.name);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
if (checkArrowFunctions) {
|
|
214
|
-
analyzeFunction(node);
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
'ArrowFunctionExpression:exit'() {
|
|
218
|
-
exitScope();
|
|
219
|
-
},
|
|
220
|
-
VariableDeclaration(node) {
|
|
221
|
-
node.declarations.forEach((decl) => {
|
|
222
|
-
if (decl.id.type === 'Identifier') {
|
|
223
|
-
addVariableToCurrentScope(decl.id.name);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
},
|
|
227
|
-
};
|
|
228
|
-
},
|
|
229
|
-
});
|
|
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");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){if(node.parent?.type==="Program"||node.parent?.type==="ExportNamedDeclaration"||node.parent?.type==="ExportDefaultDeclaration"){return}const p=node.parent;if(p?.type==="MethodDefinition"||p?.type==="PropertyDefinition"){return}if(p?.type==="CallExpression"){const callee=p.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"){const HOST_METHODS=new Set(["map","filter","reduce","reduceRight","forEach","find","findIndex","some","every","sort","flatMap","flat","then","catch","finally","on","once","addEventListener","removeEventListener","subscribe"]);if(HOST_METHODS.has(callee.property.name))return}if(callee.type==="Identifier"){const HOST_FNS=new Set(["setTimeout","setInterval","setImmediate","requestAnimationFrame","requestIdleCallback","queueMicrotask"]);if(HOST_FNS.has(callee.name))return}}if(p?.type==="NewExpression"&&p.callee.type==="Identifier"&&p.callee.name==="Promise"){return}const referencedVars=new Set;function collectReferences(astNode,depth=0,visited=new Set){if(depth>10||visited.has(astNode)){return}visited.add(astNode);if(astNode.type==="Identifier"){referencedVars.add(astNode.name)}if(depth<10){for(const key in astNode){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,visited)}})}else if("type"in child){collectReferences(child,depth+1,visited)}}}}}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,259 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.identicalFunctions = void 0;
|
|
4
|
-
exports.buildGenericName = buildGenericName;
|
|
5
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
6
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
7
|
-
const eslint_devkit_3 = require("@interlace/eslint-devkit");
|
|
8
|
-
function buildGenericName(firstFunctionName) {
|
|
9
|
-
const baseName = firstFunctionName.replace(/^(handle|process|get|set|create|update|delete)/, '');
|
|
10
|
-
return `handle${baseName || 'Generic'}`;
|
|
11
|
-
}
|
|
12
|
-
exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
13
|
-
name: 'identical-functions',
|
|
14
|
-
meta: {
|
|
15
|
-
type: 'suggestion',
|
|
16
|
-
docs: {
|
|
17
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/identical-functions.md',
|
|
18
|
-
description: 'Detects duplicate function implementations with DRY refactoring suggestions',
|
|
19
|
-
},
|
|
20
|
-
messages: {
|
|
21
|
-
identicalFunctions: (0, eslint_devkit_1.formatLLMMessage)({
|
|
22
|
-
icon: eslint_devkit_1.MessageIcons.DUPLICATION,
|
|
23
|
-
issueName: 'Code duplication',
|
|
24
|
-
description: '{{count}} duplicates ({{similarity}}% similar)',
|
|
25
|
-
severity: 'MEDIUM',
|
|
26
|
-
fix: 'Extract to reusable function',
|
|
27
|
-
documentationLink: 'https://en.wikipedia.org/wiki/Don%27t_repeat_yourself',
|
|
28
|
-
}),
|
|
29
|
-
extractGeneric: (0, eslint_devkit_1.formatLLMMessage)({
|
|
30
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
31
|
-
issueName: 'Extract Generic',
|
|
32
|
-
description: 'Extract to generic function',
|
|
33
|
-
severity: 'LOW',
|
|
34
|
-
fix: 'Create shared function with parameters',
|
|
35
|
-
documentationLink: 'https://en.wikipedia.org/wiki/Don%27t_repeat_yourself',
|
|
36
|
-
}),
|
|
37
|
-
useHigherOrder: (0, eslint_devkit_1.formatLLMMessage)({
|
|
38
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
39
|
-
issueName: 'Use Higher-Order',
|
|
40
|
-
description: 'Use higher-order function pattern',
|
|
41
|
-
severity: 'LOW',
|
|
42
|
-
fix: 'Create factory function that returns specialized functions',
|
|
43
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Glossary/Higher-order_function',
|
|
44
|
-
}),
|
|
45
|
-
applyInheritance: (0, eslint_devkit_1.formatLLMMessage)({
|
|
46
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
47
|
-
issueName: 'Use Composition',
|
|
48
|
-
description: 'Use inheritance/composition',
|
|
49
|
-
severity: 'LOW',
|
|
50
|
-
fix: 'Extract common behavior to base class or mixin',
|
|
51
|
-
documentationLink: 'https://en.wikipedia.org/wiki/Composition_over_inheritance',
|
|
52
|
-
}),
|
|
53
|
-
},
|
|
54
|
-
schema: [
|
|
55
|
-
{
|
|
56
|
-
type: 'object',
|
|
57
|
-
properties: {
|
|
58
|
-
minLines: {
|
|
59
|
-
type: 'number',
|
|
60
|
-
default: 3,
|
|
61
|
-
minimum: 1,
|
|
62
|
-
description: 'Minimum lines to consider for duplication',
|
|
63
|
-
},
|
|
64
|
-
similarityThreshold: {
|
|
65
|
-
type: 'number',
|
|
66
|
-
default: 0.9,
|
|
67
|
-
minimum: 0.5,
|
|
68
|
-
maximum: 1,
|
|
69
|
-
description: 'Similarity threshold (0.5-1.0)',
|
|
70
|
-
},
|
|
71
|
-
ignoreTestFiles: {
|
|
72
|
-
type: 'boolean',
|
|
73
|
-
default: true,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
additionalProperties: false,
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
},
|
|
80
|
-
defaultOptions: [
|
|
81
|
-
{
|
|
82
|
-
minLines: 3,
|
|
83
|
-
similarityThreshold: 0.9,
|
|
84
|
-
ignoreTestFiles: true,
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
create(context) {
|
|
88
|
-
const { minLines = 3, similarityThreshold = 0.9, ignoreTestFiles = true, } = context.options[0] || {};
|
|
89
|
-
const sourceCode = context.sourceCode;
|
|
90
|
-
const filename = context.filename;
|
|
91
|
-
if (ignoreTestFiles && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
92
|
-
return {};
|
|
93
|
-
}
|
|
94
|
-
const functions = [];
|
|
95
|
-
function normalizeBody(body) {
|
|
96
|
-
return (body
|
|
97
|
-
.replace(/\s+/g, ' ')
|
|
98
|
-
.replace(/["'`]/g, '"')
|
|
99
|
-
.replace(/\b[a-z_$][a-zA-Z0-9_$]*\b/g, 'VAR')
|
|
100
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
101
|
-
.replace(/\/\/.*/g, '')
|
|
102
|
-
.trim());
|
|
103
|
-
}
|
|
104
|
-
function calculateSimilarity(str1, str2) {
|
|
105
|
-
if (str1 === str2)
|
|
106
|
-
return 1.0;
|
|
107
|
-
const longer = str1.length > str2.length ? str1 : str2;
|
|
108
|
-
const shorter = str1.length > str2.length ? str2 : str1;
|
|
109
|
-
const editDistance = levenshteinDistance(longer, shorter);
|
|
110
|
-
return (longer.length - editDistance) / longer.length;
|
|
111
|
-
}
|
|
112
|
-
function levenshteinDistance(str1, str2) {
|
|
113
|
-
const matrix = [];
|
|
114
|
-
for (let i = 0; i <= str2.length; i++) {
|
|
115
|
-
matrix[i] = [i];
|
|
116
|
-
}
|
|
117
|
-
for (let j = 0; j <= str1.length; j++) {
|
|
118
|
-
matrix[0][j] = j;
|
|
119
|
-
}
|
|
120
|
-
for (let i = 1; i <= str2.length; i++) {
|
|
121
|
-
for (let j = 1; j <= str1.length; j++) {
|
|
122
|
-
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
123
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return matrix[str2.length][str1.length];
|
|
131
|
-
}
|
|
132
|
-
function findDuplicationGroups() {
|
|
133
|
-
const groups = [];
|
|
134
|
-
const processed = new Set();
|
|
135
|
-
for (let i = 0; i < functions.length; i++) {
|
|
136
|
-
if (processed.has(i))
|
|
137
|
-
continue;
|
|
138
|
-
const group = [functions[i]];
|
|
139
|
-
processed.add(i);
|
|
140
|
-
for (let j = i + 1; j < functions.length; j++) {
|
|
141
|
-
if (processed.has(j))
|
|
142
|
-
continue;
|
|
143
|
-
const similarity = calculateSimilarity(functions[i].normalizedBody, functions[j].normalizedBody);
|
|
144
|
-
if (similarity >= similarityThreshold) {
|
|
145
|
-
group.push(functions[j]);
|
|
146
|
-
processed.add(j);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (group.length >= 2) {
|
|
150
|
-
const avgSimilarity = group.reduce((sum, func, idx) => {
|
|
151
|
-
if (idx === 0)
|
|
152
|
-
return 0;
|
|
153
|
-
return (sum +
|
|
154
|
-
calculateSimilarity(group[0].normalizedBody, func.normalizedBody));
|
|
155
|
-
}, 0) /
|
|
156
|
-
(group.length - 1);
|
|
157
|
-
groups.push({
|
|
158
|
-
functions: group,
|
|
159
|
-
similarityScore: avgSimilarity,
|
|
160
|
-
commonPattern: functions[i].normalizedBody,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return groups;
|
|
165
|
-
}
|
|
166
|
-
function suggestRefactoringApproach(group) {
|
|
167
|
-
const funcNames = group.functions.map((f) => f.name);
|
|
168
|
-
const hasRolePattern = funcNames.some((name) => /user|admin|guest|customer/i.test(name));
|
|
169
|
-
const hasTypePattern = funcNames.some((name) => /payment|shipping|billing|email|sms/i.test(name));
|
|
170
|
-
if (hasRolePattern || hasTypePattern) {
|
|
171
|
-
return {
|
|
172
|
-
approach: 'Parameter Object + Strategy Pattern',
|
|
173
|
-
pattern: 'Extract discriminator as parameter',
|
|
174
|
-
complexity: 'moderate',
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
if (group.functions[0].params.length > 0) {
|
|
178
|
-
return {
|
|
179
|
-
approach: 'Higher-Order Function',
|
|
180
|
-
pattern: 'Extract common logic, inject differences',
|
|
181
|
-
complexity: 'simple',
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
return {
|
|
185
|
-
approach: 'Extract Method',
|
|
186
|
-
pattern: 'DRY - Single source of truth',
|
|
187
|
-
complexity: 'simple',
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
function storeFunctionInfo(node) {
|
|
191
|
-
const body = node.body ? sourceCode.getText(node.body) : '';
|
|
192
|
-
const lines = body.split('\n').length;
|
|
193
|
-
if (lines < minLines)
|
|
194
|
-
return;
|
|
195
|
-
const name = (0, eslint_devkit_3.extractFunctionSignature)(node)
|
|
196
|
-
.split('(')[0]
|
|
197
|
-
.replace('function ', '');
|
|
198
|
-
const params = node.params.map((p) => p.type === 'Identifier' ? p.name : sourceCode.getText(p));
|
|
199
|
-
functions.push({
|
|
200
|
-
node,
|
|
201
|
-
name: name || 'anonymous',
|
|
202
|
-
body,
|
|
203
|
-
normalizedBody: normalizeBody(body),
|
|
204
|
-
lines,
|
|
205
|
-
location: `${filename}:${node.loc?.start.line}`,
|
|
206
|
-
params,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
function reportDuplications() {
|
|
210
|
-
const groups = findDuplicationGroups();
|
|
211
|
-
groups.forEach((group) => {
|
|
212
|
-
const refactoringApproach = suggestRefactoringApproach(group);
|
|
213
|
-
const primaryFunction = group.functions[0];
|
|
214
|
-
const similarityPercent = Math.round(group.similarityScore * 100);
|
|
215
|
-
context.report({
|
|
216
|
-
node: primaryFunction.node,
|
|
217
|
-
messageId: 'identicalFunctions',
|
|
218
|
-
data: {
|
|
219
|
-
count: String(group.functions.length),
|
|
220
|
-
similarity: String(similarityPercent),
|
|
221
|
-
filePath: filename,
|
|
222
|
-
line: String(primaryFunction.node.loc?.start.line ?? 0),
|
|
223
|
-
},
|
|
224
|
-
suggest: [
|
|
225
|
-
{
|
|
226
|
-
messageId: 'extractGeneric',
|
|
227
|
-
data: {
|
|
228
|
-
functionName: buildGenericName(primaryFunction.name),
|
|
229
|
-
},
|
|
230
|
-
fix: () => null,
|
|
231
|
-
},
|
|
232
|
-
...(refactoringApproach.approach.includes('Higher-Order')
|
|
233
|
-
? [
|
|
234
|
-
{
|
|
235
|
-
messageId: 'useHigherOrder',
|
|
236
|
-
fix: () => null,
|
|
237
|
-
},
|
|
238
|
-
]
|
|
239
|
-
: []),
|
|
240
|
-
...(refactoringApproach.approach.includes('Strategy')
|
|
241
|
-
? [
|
|
242
|
-
{
|
|
243
|
-
messageId: 'applyInheritance',
|
|
244
|
-
fix: () => null,
|
|
245
|
-
},
|
|
246
|
-
]
|
|
247
|
-
: []),
|
|
248
|
-
],
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
return {
|
|
253
|
-
FunctionDeclaration: storeFunctionInfo,
|
|
254
|
-
FunctionExpression: storeFunctionInfo,
|
|
255
|
-
ArrowFunctionExpression: storeFunctionInfo,
|
|
256
|
-
'Program:exit': reportDuplications,
|
|
257
|
-
};
|
|
258
|
-
},
|
|
259
|
-
});
|
|
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"}`}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}},additionalProperties:false}]},defaultOptions:[{minLines:3,similarityThreshold:.9,ignoreTestFiles:true}],create(context){const{minLines=3,similarityThreshold=.9,ignoreTestFiles=true}=context.options[0]||{};const sourceCode=context.sourceCode;const filename=context.filename;if(ignoreTestFiles&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const functions=[];function normalizeBody(body){return body.replace(/\s+/g," ").replace(/["'`]/g,'"').replace(/\b[a-z_$][a-zA-Z0-9_$]*\b/g,"VAR").replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*/g,"").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;const similarity=calculateSimilarity(functions[i].normalizedBody,functions[j].normalizedBody,similarityThreshold);if(similarity>=similarityThreshold){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),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,129 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.maxParameters = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
|
-
const eslint_devkit_3 = require("@interlace/eslint-devkit");
|
|
7
|
-
function countParameters(node) {
|
|
8
|
-
return node.params.length;
|
|
9
|
-
}
|
|
10
|
-
exports.maxParameters = (0, eslint_devkit_2.createRule)({
|
|
11
|
-
name: 'max-parameters',
|
|
12
|
-
meta: {
|
|
13
|
-
type: 'suggestion',
|
|
14
|
-
docs: {
|
|
15
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/max-parameters.md',
|
|
16
|
-
description: 'Detects functions with too many parameters',
|
|
17
|
-
},
|
|
18
|
-
messages: {
|
|
19
|
-
tooManyParameters: (0, eslint_devkit_1.formatLLMMessage)({
|
|
20
|
-
icon: eslint_devkit_1.MessageIcons.COMPLEXITY,
|
|
21
|
-
issueName: 'Too many parameters',
|
|
22
|
-
description: '{{functionName}}: {{count}} parameters (max: {{max}})',
|
|
23
|
-
severity: 'MEDIUM',
|
|
24
|
-
fix: 'Refactor to use object parameter or split function',
|
|
25
|
-
documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-107/',
|
|
26
|
-
}),
|
|
27
|
-
useObjectParameter: (0, eslint_devkit_1.formatLLMMessage)({
|
|
28
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
29
|
-
issueName: 'Use Object Parameter',
|
|
30
|
-
description: 'Use object parameter pattern',
|
|
31
|
-
severity: 'LOW',
|
|
32
|
-
fix: 'function({ param1, param2, param3 })',
|
|
33
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment',
|
|
34
|
-
}),
|
|
35
|
-
extractToClass: (0, eslint_devkit_1.formatLLMMessage)({
|
|
36
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
37
|
-
issueName: 'Extract to Class',
|
|
38
|
-
description: 'Extract to class with properties',
|
|
39
|
-
severity: 'LOW',
|
|
40
|
-
fix: 'Create class to hold related parameters',
|
|
41
|
-
documentationLink: 'https://refactoring.guru/introduce-parameter-object',
|
|
42
|
-
}),
|
|
43
|
-
splitFunction: (0, eslint_devkit_1.formatLLMMessage)({
|
|
44
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
45
|
-
issueName: 'Split Function',
|
|
46
|
-
description: 'Split into smaller functions',
|
|
47
|
-
severity: 'LOW',
|
|
48
|
-
fix: 'Extract logic into separate focused functions',
|
|
49
|
-
documentationLink: 'https://refactoring.guru/smells/long-parameter-list',
|
|
50
|
-
}),
|
|
51
|
-
},
|
|
52
|
-
schema: [
|
|
53
|
-
{
|
|
54
|
-
type: 'object',
|
|
55
|
-
properties: {
|
|
56
|
-
max: {
|
|
57
|
-
type: 'number',
|
|
58
|
-
default: 4,
|
|
59
|
-
minimum: 1,
|
|
60
|
-
},
|
|
61
|
-
ignoreConstructors: {
|
|
62
|
-
type: 'boolean',
|
|
63
|
-
default: false,
|
|
64
|
-
},
|
|
65
|
-
ignoreOverriddenMethods: {
|
|
66
|
-
type: 'boolean',
|
|
67
|
-
default: false,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
additionalProperties: false,
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
defaultOptions: [
|
|
75
|
-
{
|
|
76
|
-
max: 4,
|
|
77
|
-
ignoreConstructors: false,
|
|
78
|
-
ignoreOverriddenMethods: false,
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
create(context, [options = {}]) {
|
|
82
|
-
const { max = 4, ignoreConstructors = false, } = options || {};
|
|
83
|
-
function checkFunction(node) {
|
|
84
|
-
if (ignoreConstructors) {
|
|
85
|
-
if (node.type === 'FunctionDeclaration' &&
|
|
86
|
-
node.id &&
|
|
87
|
-
node.id.name &&
|
|
88
|
-
/^[A-Z]/.test(node.id.name)) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const paramCount = countParameters(node);
|
|
93
|
-
if (paramCount <= max) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const functionSignature = (0, eslint_devkit_3.extractFunctionSignature)(node);
|
|
97
|
-
const overBy = paramCount - max;
|
|
98
|
-
context.report({
|
|
99
|
-
node,
|
|
100
|
-
messageId: 'tooManyParameters',
|
|
101
|
-
data: {
|
|
102
|
-
functionName: functionSignature,
|
|
103
|
-
count: String(paramCount),
|
|
104
|
-
max: String(max),
|
|
105
|
-
overBy: String(overBy),
|
|
106
|
-
},
|
|
107
|
-
suggest: [
|
|
108
|
-
{
|
|
109
|
-
messageId: 'useObjectParameter',
|
|
110
|
-
fix: () => null,
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
messageId: 'extractToClass',
|
|
114
|
-
fix: () => null,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
messageId: 'splitFunction',
|
|
118
|
-
fix: () => null,
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
FunctionDeclaration: checkFunction,
|
|
125
|
-
FunctionExpression: checkFunction,
|
|
126
|
-
ArrowFunctionExpression: checkFunction,
|
|
127
|
-
};
|
|
128
|
-
},
|
|
129
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.maxParameters=void 0;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 countParameters(node){return node.params.length}exports.maxParameters=(0,eslint_devkit_2.createRule)({name:"max-parameters",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/max-parameters.md",description:"Detects functions with too many parameters"},hasSuggestions:true,messages:{tooManyParameters:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.COMPLEXITY,issueName:"Too many parameters",description:"{{functionName}}: {{count}} parameters (max: {{max}})",severity:"MEDIUM",fix:"Refactor to use object parameter or split function",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-107/"}),useObjectParameter:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Object Parameter",description:"Use object parameter pattern",severity:"LOW",fix:"function({ param1, param2, param3 })",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment"}),extractToClass:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Extract to Class",description:"Extract to class with properties",severity:"LOW",fix:"Create class to hold related parameters",documentationLink:"https://refactoring.guru/introduce-parameter-object"}),splitFunction:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Split Function",description:"Split into smaller functions",severity:"LOW",fix:"Extract logic into separate focused functions",documentationLink:"https://refactoring.guru/smells/long-parameter-list"})},schema:[{type:"object",properties:{max:{type:"number",default:4,minimum:1},ignoreConstructors:{type:"boolean",default:false},ignoreOverriddenMethods:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{max:4,ignoreConstructors:false,ignoreOverriddenMethods:false}],create(context,[options={}]){const{max=4,ignoreConstructors=false}=options||{};function checkFunction(node){if(ignoreConstructors){if(node.type==="FunctionDeclaration"&&node.id&&node.id.name&&/^[A-Z]/.test(node.id.name)){return}}const paramCount=countParameters(node);if(paramCount<=max){return}const functionSignature=(0,eslint_devkit_3.extractFunctionSignature)(node);const overBy=paramCount-max;context.report({node,messageId:"tooManyParameters",data:{functionName:functionSignature,count:String(paramCount),max:String(max),overBy:String(overBy)},suggest:[{messageId:"useObjectParameter",fix:()=>null},{messageId:"extractToClass",fix:()=>null},{messageId:"splitFunction",fix:()=>null}]})}return{FunctionDeclaration:checkFunction,FunctionExpression:checkFunction,ArrowFunctionExpression:checkFunction}}});
|