eslint-plugin-maintainability 3.0.12 → 3.0.14

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.
@@ -1,261 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noUnhandledPromise = void 0;
4
- exports.isPromiseExpression = isPromiseExpression;
5
- exports.isInsidePromiseCallback = isInsidePromiseCallback;
6
- exports.isPromiseHandled = isPromiseHandled;
7
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
8
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
9
- function isPromiseExpression(node) {
10
- if (node.type === 'CallExpression') {
11
- return true;
12
- }
13
- if (node.type === 'AwaitExpression') {
14
- return false;
15
- }
16
- return false;
17
- }
18
- function isInsidePromiseCallback(node) {
19
- let current = node;
20
- let depth = 0;
21
- const maxDepth = 10;
22
- while (current && depth < maxDepth) {
23
- const parent = current
24
- .parent;
25
- if (!parent)
26
- break;
27
- if (parent.type === 'ArrowFunctionExpression' ||
28
- parent.type === 'FunctionExpression') {
29
- const funcParent = parent
30
- .parent;
31
- if (funcParent &&
32
- funcParent.type === 'CallExpression' &&
33
- funcParent.callee &&
34
- funcParent.callee.type === 'MemberExpression') {
35
- const memberExpr = funcParent.callee;
36
- if (memberExpr.property.type === 'Identifier') {
37
- const methodName = memberExpr.property.name;
38
- if (methodName === 'then' ||
39
- methodName === 'catch' ||
40
- methodName === 'finally') {
41
- return true;
42
- }
43
- }
44
- }
45
- }
46
- current = parent;
47
- depth++;
48
- }
49
- return false;
50
- }
51
- function isPromiseHandled(node) {
52
- if (node.type === 'Identifier') {
53
- const parent = node.parent;
54
- if (parent &&
55
- parent.type === 'MemberExpression' &&
56
- parent.object === node) {
57
- if (parent.property.type === 'Identifier') {
58
- const methodName = parent.property.name;
59
- if (methodName === 'catch' ||
60
- methodName === 'then' ||
61
- methodName === 'finally') {
62
- const memberParent = parent.parent;
63
- if (memberParent &&
64
- memberParent.type === 'CallExpression' &&
65
- memberParent.callee === parent) {
66
- return true;
67
- }
68
- }
69
- }
70
- }
71
- }
72
- let current = node;
73
- let depth = 0;
74
- const maxDepth = 10;
75
- while (current && depth < maxDepth) {
76
- const parent = current
77
- .parent;
78
- if (!parent)
79
- break;
80
- if (parent.type === 'MemberExpression' && parent.object === current) {
81
- if (parent.property.type === 'Identifier') {
82
- const methodName = parent.property.name;
83
- if (methodName === 'catch' ||
84
- methodName === 'then' ||
85
- methodName === 'finally') {
86
- const memberParent = parent.parent;
87
- if (memberParent &&
88
- memberParent.type === 'CallExpression' &&
89
- memberParent.callee === parent) {
90
- return true;
91
- }
92
- }
93
- }
94
- }
95
- if (parent.type === 'TryStatement') {
96
- return true;
97
- }
98
- if (parent.type === 'AwaitExpression') {
99
- return true;
100
- }
101
- current = parent;
102
- depth++;
103
- }
104
- return false;
105
- }
106
- exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
107
- name: 'no-unhandled-promise',
108
- meta: {
109
- type: 'problem',
110
- docs: {
111
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-unhandled-promise.md',
112
- description: 'Detects unhandled Promise rejections',
113
- cwe: 'CWE-1024',
114
- cvss: 7.5,
115
- },
116
- hasSuggestions: true,
117
- messages: {
118
- unhandledPromise: (0, eslint_devkit_1.formatLLMMessage)({
119
- icon: eslint_devkit_1.MessageIcons.WARNING,
120
- issueName: 'Unhandled promise',
121
- cwe: 'CWE-1024',
122
- description: 'Unhandled Promise rejection detected',
123
- severity: 'HIGH',
124
- fix: 'Add .catch() handler or use try/catch with await',
125
- documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-4635/',
126
- }),
127
- addCatch: (0, eslint_devkit_1.formatLLMMessage)({
128
- icon: eslint_devkit_1.MessageIcons.INFO,
129
- issueName: 'Add catch handler',
130
- description: 'Add .catch() handler to promise',
131
- severity: 'LOW',
132
- fix: 'promise.catch(error => console.error(error))',
133
- documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch',
134
- }),
135
- useTryCatch: (0, eslint_devkit_1.formatLLMMessage)({
136
- icon: eslint_devkit_1.MessageIcons.INFO,
137
- issueName: 'Use try/catch',
138
- description: 'Use try/catch with await',
139
- severity: 'LOW',
140
- fix: 'try { await promise; } catch (error) { handle(error); }',
141
- documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch',
142
- }),
143
- useAwait: (0, eslint_devkit_1.formatLLMMessage)({
144
- icon: eslint_devkit_1.MessageIcons.INFO,
145
- issueName: 'Use await',
146
- description: 'Use await to handle promise',
147
- severity: 'LOW',
148
- fix: 'await promise;',
149
- documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await',
150
- }),
151
- },
152
- schema: [
153
- {
154
- type: 'object',
155
- properties: {
156
- ignoreInTests: {
157
- type: 'boolean',
158
- default: true,
159
- description: 'Ignore promises in test files',
160
- },
161
- ignoreVoidExpressions: {
162
- type: 'boolean',
163
- default: false,
164
- description: 'Ignore promises in void expressions',
165
- },
166
- },
167
- additionalProperties: false,
168
- },
169
- ],
170
- },
171
- defaultOptions: [
172
- {
173
- ignoreInTests: true,
174
- ignoreVoidExpressions: false,
175
- },
176
- ],
177
- create(context, [options = {}]) {
178
- const { ignoreInTests = true, ignoreVoidExpressions = false } = options || {};
179
- const filename = context.filename;
180
- const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
181
- if (isTestFile) {
182
- return {};
183
- }
184
- function checkCallExpression(node) {
185
- if (isInsidePromiseCallback(node)) {
186
- return;
187
- }
188
- if (node.callee.type === 'MemberExpression' &&
189
- node.callee.property.type === 'Identifier') {
190
- const methodName = node.callee.property.name;
191
- if (methodName === 'then' ||
192
- methodName === 'catch' ||
193
- methodName === 'finally') {
194
- if (node.arguments.length > 0 &&
195
- node.arguments[0].type === 'ArrowFunctionExpression') {
196
- const callback = node.arguments[0];
197
- if (callback.body.type === 'BlockStatement' &&
198
- callback.body.body.length === 0) {
199
- }
200
- else {
201
- return;
202
- }
203
- }
204
- else {
205
- return;
206
- }
207
- }
208
- }
209
- if (!isPromiseExpression(node)) {
210
- return;
211
- }
212
- if (isPromiseHandled(node)) {
213
- return;
214
- }
215
- if (ignoreVoidExpressions) {
216
- const parent = node
217
- .parent;
218
- if (parent &&
219
- parent.type === 'UnaryExpression' &&
220
- parent.operator === 'void') {
221
- return;
222
- }
223
- }
224
- const parent = node
225
- .parent;
226
- if (parent && parent.type === 'CallExpression') {
227
- const grandParent = parent.parent;
228
- if (!(grandParent &&
229
- grandParent.type === 'MemberExpression' &&
230
- grandParent.object === parent &&
231
- grandParent.property.type === 'Identifier' &&
232
- (grandParent.property.name === 'then' ||
233
- grandParent.property.name === 'catch' ||
234
- grandParent.property.name === 'finally'))) {
235
- return;
236
- }
237
- }
238
- context.report({
239
- node,
240
- messageId: 'unhandledPromise',
241
- suggest: [
242
- {
243
- messageId: 'addCatch',
244
- fix: () => null,
245
- },
246
- {
247
- messageId: 'useTryCatch',
248
- fix: () => null,
249
- },
250
- {
251
- messageId: 'useAwait',
252
- fix: () => null,
253
- },
254
- ],
255
- });
256
- }
257
- return {
258
- CallExpression: checkCallExpression,
259
- };
260
- },
261
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnhandledPromise=void 0;exports.isPromiseExpression=isPromiseExpression;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}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;if(memberExpr.property.type==="Identifier"){const methodName=memberExpr.property.name;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"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,ignoreVoidExpressions:false}],create(context,[options={}]){const{ignoreInTests=true,ignoreVoidExpressions=false}=options||{};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==="then"||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(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;if(!(grandParent&&grandParent.type==="MemberExpression"&&grandParent.object===parent&&grandParent.property.type==="Identifier"&&(grandParent.property.name==="then"||grandParent.property.name==="catch"||grandParent.property.name==="finally"))){return}}context.report({node,messageId:"unhandledPromise",suggest:[{messageId:"addCatch",fix:()=>null},{messageId:"useTryCatch",fix:()=>null},{messageId:"useAwait",fix:()=>null}]})}return{CallExpression:checkCallExpression}}});
@@ -1,341 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cognitiveComplexity = 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
- exports.cognitiveComplexity = (0, eslint_devkit_2.createRule)({
8
- name: 'cognitive-complexity',
9
- meta: {
10
- type: 'suggestion',
11
- docs: {
12
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/cognitive-complexity.md',
13
- description: 'Enforces a maximum cognitive complexity threshold with refactoring guidance',
14
- cwe: 'CWE-1104',
15
- cvss: 7.5,
16
- },
17
- messages: {
18
- highCognitiveComplexity: (0, eslint_devkit_1.formatLLMMessage)({
19
- icon: eslint_devkit_1.MessageIcons.COMPLEXITY,
20
- issueName: 'High cognitive complexity',
21
- cwe: 'CWE-1104',
22
- description: '{{functionName}}: {{complexity}}/{{max}} ({{overBy}} over)',
23
- severity: 'HIGH',
24
- fix: 'Extract logic to helpers',
25
- documentationLink: 'https://en.wikipedia.org/wiki/Cognitive_complexity',
26
- }),
27
- extractMethod: (0, eslint_devkit_1.formatLLMMessage)({
28
- icon: eslint_devkit_1.MessageIcons.INFO,
29
- issueName: 'Extract Method',
30
- description: 'Extract nested logic to separate method',
31
- severity: 'LOW',
32
- fix: 'Extract to "{{methodName}}" (reduces complexity by ~{{reduction}})',
33
- documentationLink: 'https://refactoring.guru/extract-method',
34
- }),
35
- simplifyLogic: (0, eslint_devkit_1.formatLLMMessage)({
36
- icon: eslint_devkit_1.MessageIcons.INFO,
37
- issueName: 'Simplify Logic',
38
- description: 'Simplify conditional logic',
39
- severity: 'LOW',
40
- fix: 'Use guard clauses and early returns',
41
- documentationLink: 'https://refactoring.guru/replace-nested-conditional-with-guard-clauses',
42
- }),
43
- useStrategy: (0, eslint_devkit_1.formatLLMMessage)({
44
- icon: eslint_devkit_1.MessageIcons.INFO,
45
- issueName: 'Use Strategy Pattern',
46
- description: 'Apply design pattern to reduce complexity',
47
- severity: 'LOW',
48
- fix: 'Apply {{pattern}} pattern',
49
- documentationLink: 'https://refactoring.guru/design-patterns/strategy',
50
- }),
51
- },
52
- schema: [
53
- {
54
- type: 'object',
55
- properties: {
56
- maxComplexity: {
57
- type: 'number',
58
- default: 15,
59
- minimum: 1,
60
- },
61
- includeMetrics: {
62
- type: 'boolean',
63
- default: true,
64
- },
65
- },
66
- additionalProperties: false,
67
- },
68
- ],
69
- },
70
- defaultOptions: [
71
- {
72
- maxComplexity: 15,
73
- includeMetrics: true,
74
- },
75
- ],
76
- create(context) {
77
- const { maxComplexity = 15 } = context.options[0] || {};
78
- const filename = context.filename;
79
- function calculateCognitiveComplexity(node) {
80
- let complexity = 0;
81
- const breakdown = {
82
- conditionals: 0,
83
- loops: 0,
84
- switches: 0,
85
- nesting: 0,
86
- logicalOperators: 0,
87
- catches: 0,
88
- recursion: 0,
89
- };
90
- const functionName = node.type === 'FunctionDeclaration' && node.id
91
- ? node.id.name
92
- : 'anonymous';
93
- function traverse(n, currentNesting) {
94
- if (n.type === 'IfStatement') {
95
- complexity += 1 + currentNesting;
96
- breakdown.conditionals++;
97
- traverse(n.test, currentNesting);
98
- traverse(n.consequent, currentNesting + 1);
99
- if (n.alternate) {
100
- if (n.alternate.type === 'IfStatement') {
101
- traverse(n.alternate, currentNesting);
102
- }
103
- else {
104
- complexity += 1;
105
- traverse(n.alternate, currentNesting + 1);
106
- }
107
- }
108
- return;
109
- }
110
- if (n.type === 'ForStatement' ||
111
- n.type === 'ForInStatement' ||
112
- n.type === 'ForOfStatement' ||
113
- n.type === 'WhileStatement' ||
114
- n.type === 'DoWhileStatement') {
115
- complexity += 1 + currentNesting;
116
- breakdown.loops++;
117
- if (n.type === 'ForStatement') {
118
- if (n.init)
119
- traverse(n.init, currentNesting);
120
- if (n.test)
121
- traverse(n.test, currentNesting);
122
- if (n.update)
123
- traverse(n.update, currentNesting);
124
- traverse(n.body, currentNesting + 1);
125
- }
126
- else if (n.type === 'WhileStatement' ||
127
- n.type === 'DoWhileStatement') {
128
- traverse(n.test, currentNesting);
129
- traverse(n.body, currentNesting + 1);
130
- }
131
- else {
132
- if ('left' in n)
133
- traverse(n.left, currentNesting);
134
- if ('right' in n)
135
- traverse(n.right, currentNesting);
136
- traverse(n.body, currentNesting + 1);
137
- }
138
- return;
139
- }
140
- if (n.type === 'SwitchStatement') {
141
- complexity += 1 + currentNesting;
142
- breakdown.switches++;
143
- traverse(n.discriminant, currentNesting);
144
- n.cases.forEach((c) => traverse(c, currentNesting + 1));
145
- return;
146
- }
147
- if (n.type === 'LogicalExpression') {
148
- if (n.operator === '&&' ||
149
- n.operator === '||' ||
150
- n.operator === '??') {
151
- complexity += 1;
152
- breakdown.logicalOperators++;
153
- }
154
- }
155
- if (n.type === 'CatchClause') {
156
- complexity += 1 + currentNesting;
157
- breakdown.catches++;
158
- traverse(n.body, currentNesting + 1);
159
- return;
160
- }
161
- if (n.type === 'ConditionalExpression') {
162
- complexity += 1 + currentNesting;
163
- breakdown.conditionals++;
164
- }
165
- if (n.type === 'CallExpression') {
166
- if (n.callee.type === 'Identifier' &&
167
- n.callee.name === functionName) {
168
- complexity += 1;
169
- breakdown.recursion++;
170
- }
171
- }
172
- if (n.type === 'BlockStatement' ||
173
- n.type === 'FunctionDeclaration' ||
174
- n.type === 'FunctionExpression' ||
175
- n.type === 'ArrowFunctionExpression') {
176
- breakdown.nesting = Math.max(breakdown.nesting, currentNesting);
177
- }
178
- const visited = new Set();
179
- function traverseChild(child) {
180
- if (child && typeof child === 'object' && 'type' in child) {
181
- const childNode = child;
182
- if (!visited.has(childNode)) {
183
- visited.add(childNode);
184
- traverse(childNode, currentNesting);
185
- }
186
- }
187
- }
188
- const childKeys = [
189
- 'body',
190
- 'test',
191
- 'consequent',
192
- 'alternate',
193
- 'init',
194
- 'update',
195
- 'left',
196
- 'right',
197
- 'argument',
198
- 'arguments',
199
- 'callee',
200
- 'object',
201
- 'property',
202
- 'elements',
203
- 'properties',
204
- 'expression',
205
- 'expressions',
206
- 'declarations',
207
- 'declaration',
208
- 'specifiers',
209
- 'source',
210
- 'key',
211
- 'value',
212
- 'handler',
213
- 'block',
214
- 'finalizer',
215
- ];
216
- for (const key of childKeys) {
217
- const child = n[key];
218
- if (child) {
219
- if (Array.isArray(child)) {
220
- child.forEach(traverseChild);
221
- }
222
- else {
223
- traverseChild(child);
224
- }
225
- }
226
- }
227
- }
228
- if (node.body) {
229
- traverse(node.body, 0);
230
- }
231
- return { total: complexity, breakdown };
232
- }
233
- function suggestExtractions(node, breakdown) {
234
- const suggestions = [];
235
- if (breakdown.nesting >= 4 && node.loc) {
236
- suggestions.push({
237
- name: 'extractNestedLogic',
238
- reason: `Nesting depth of ${breakdown.nesting} makes code hard to follow`,
239
- lineRange: [node.loc.start.line, node.loc.end.line],
240
- estimatedComplexityReduction: Math.floor(breakdown.nesting * 1.5),
241
- });
242
- }
243
- if (breakdown.switches >= 2 && node.loc) {
244
- suggestions.push({
245
- name: 'refactorSwitchToStrategy',
246
- reason: `${breakdown.switches} switch statements suggest strategy pattern`,
247
- lineRange: [node.loc.start.line, node.loc.end.line],
248
- estimatedComplexityReduction: breakdown.switches * 2,
249
- });
250
- }
251
- if (breakdown.loops >= 3 && node.loc) {
252
- suggestions.push({
253
- name: 'extractLoopLogic',
254
- reason: `${breakdown.loops} loops can be extracted to separate methods`,
255
- lineRange: [node.loc.start.line, node.loc.end.line],
256
- estimatedComplexityReduction: breakdown.loops * 2,
257
- });
258
- }
259
- if (breakdown.conditionals >= 5 && node.loc) {
260
- suggestions.push({
261
- name: 'simplifyConditionals',
262
- reason: `${breakdown.conditionals} conditional branches could use Guard Clauses`,
263
- lineRange: [node.loc.start.line, node.loc.end.line],
264
- estimatedComplexityReduction: Math.floor(breakdown.conditionals * 0.8),
265
- });
266
- }
267
- return suggestions;
268
- }
269
- function suggestPattern(breakdown) {
270
- if (breakdown.switches >= 2)
271
- return 'Strategy Pattern';
272
- if (breakdown.conditionals >= 5)
273
- return 'Guard Clauses + Early Return';
274
- if (breakdown.loops >= 3)
275
- return 'Extract Method + Pipeline';
276
- if (breakdown.nesting >= 4)
277
- return 'Extract Method + Composed Functions';
278
- return 'Extract Method';
279
- }
280
- function estimateRefactoringTime(complexity, breakdown) {
281
- const baseTime = Math.floor((complexity - maxComplexity) * 3);
282
- const nestingPenalty = breakdown.nesting >= 4 ? 15 : 0;
283
- const totalMinutes = baseTime + nestingPenalty;
284
- if (totalMinutes < 30)
285
- return `${totalMinutes} minutes`;
286
- if (totalMinutes < 60)
287
- return '30-60 minutes';
288
- return `${Math.ceil(totalMinutes / 60)} hours`;
289
- }
290
- function checkFunction(node) {
291
- const { total: complexity, breakdown } = calculateCognitiveComplexity(node);
292
- if (complexity <= maxComplexity)
293
- return;
294
- const functionSignature = (0, eslint_devkit_3.extractFunctionSignature)(node);
295
- const suggestions = suggestExtractions(node, breakdown);
296
- const pattern = suggestPattern(breakdown);
297
- const estimatedTime = estimateRefactoringTime(complexity, breakdown);
298
- context.report({
299
- node,
300
- messageId: 'highCognitiveComplexity',
301
- data: {
302
- functionName: functionSignature,
303
- complexity: String(complexity),
304
- max: String(maxComplexity),
305
- overBy: String(complexity - maxComplexity),
306
- current: String(complexity),
307
- filePath: filename,
308
- line: String(node.loc?.start.line ?? 0),
309
- conditionals: String(breakdown.conditionals),
310
- loops: String(breakdown.loops),
311
- nesting: String(breakdown.nesting),
312
- pattern,
313
- estimatedTime,
314
- },
315
- suggest: suggestions.length > 0
316
- ? suggestions.map((suggestion, index) => {
317
- const messageId = index === 0
318
- ? 'extractMethod'
319
- : index === 1
320
- ? 'useStrategy'
321
- : 'simplifyLogic';
322
- return {
323
- messageId,
324
- data: {
325
- methodName: suggestion.name,
326
- pattern,
327
- reduction: String(suggestion.estimatedComplexityReduction),
328
- },
329
- fix: () => null,
330
- };
331
- })
332
- : undefined,
333
- });
334
- }
335
- return {
336
- FunctionDeclaration: checkFunction,
337
- FunctionExpression: checkFunction,
338
- ArrowFunctionExpression: checkFunction,
339
- };
340
- },
341
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.cognitiveComplexity=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");exports.cognitiveComplexity=(0,eslint_devkit_2.createRule)({name:"cognitive-complexity",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/cognitive-complexity.md",description:"Enforces a maximum cognitive complexity threshold with refactoring guidance",cwe:"CWE-1104",cvss:7.5},messages:{highCognitiveComplexity:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.COMPLEXITY,issueName:"High cognitive complexity",cwe:"CWE-1104",description:"{{functionName}}: {{complexity}}/{{max}} ({{overBy}} over)",severity:"HIGH",fix:"Extract logic to helpers",documentationLink:"https://en.wikipedia.org/wiki/Cognitive_complexity"}),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 "{{methodName}}" (reduces complexity by ~{{reduction}})',documentationLink:"https://refactoring.guru/extract-method"}),simplifyLogic:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Simplify Logic",description:"Simplify conditional logic",severity:"LOW",fix:"Use guard clauses and early returns",documentationLink:"https://refactoring.guru/replace-nested-conditional-with-guard-clauses"}),useStrategy:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Strategy Pattern",description:"Apply design pattern to reduce complexity",severity:"LOW",fix:"Apply {{pattern}} pattern",documentationLink:"https://refactoring.guru/design-patterns/strategy"})},schema:[{type:"object",properties:{maxComplexity:{type:"number",default:15,minimum:1},includeMetrics:{type:"boolean",default:true}},additionalProperties:false}]},defaultOptions:[{maxComplexity:15,includeMetrics:true}],create(context){const{maxComplexity=15}=context.options[0]||{};const filename=context.filename;function calculateCognitiveComplexity(node){let complexity=0;const breakdown={conditionals:0,loops:0,switches:0,nesting:0,logicalOperators:0,catches:0,recursion:0};const functionName=node.type==="FunctionDeclaration"&&node.id?node.id.name:"anonymous";function traverse(n,currentNesting){if(n.type==="IfStatement"){complexity+=1+currentNesting;breakdown.conditionals++;traverse(n.test,currentNesting);traverse(n.consequent,currentNesting+1);if(n.alternate){if(n.alternate.type==="IfStatement"){traverse(n.alternate,currentNesting)}else{complexity+=1;traverse(n.alternate,currentNesting+1)}}return}if(n.type==="ForStatement"||n.type==="ForInStatement"||n.type==="ForOfStatement"||n.type==="WhileStatement"||n.type==="DoWhileStatement"){complexity+=1+currentNesting;breakdown.loops++;if(n.type==="ForStatement"){if(n.init)traverse(n.init,currentNesting);if(n.test)traverse(n.test,currentNesting);if(n.update)traverse(n.update,currentNesting);traverse(n.body,currentNesting+1)}else if(n.type==="WhileStatement"||n.type==="DoWhileStatement"){traverse(n.test,currentNesting);traverse(n.body,currentNesting+1)}else{if("left"in n)traverse(n.left,currentNesting);if("right"in n)traverse(n.right,currentNesting);traverse(n.body,currentNesting+1)}return}if(n.type==="SwitchStatement"){complexity+=1+currentNesting;breakdown.switches++;traverse(n.discriminant,currentNesting);n.cases.forEach(c=>traverse(c,currentNesting+1));return}if(n.type==="LogicalExpression"){if(n.operator==="&&"||n.operator==="||"||n.operator==="??"){complexity+=1;breakdown.logicalOperators++}}if(n.type==="CatchClause"){complexity+=1+currentNesting;breakdown.catches++;traverse(n.body,currentNesting+1);return}if(n.type==="ConditionalExpression"){complexity+=1+currentNesting;breakdown.conditionals++}if(n.type==="CallExpression"){if(n.callee.type==="Identifier"&&n.callee.name===functionName){complexity+=1;breakdown.recursion++}}if(n.type==="BlockStatement"||n.type==="FunctionDeclaration"||n.type==="FunctionExpression"||n.type==="ArrowFunctionExpression"){breakdown.nesting=Math.max(breakdown.nesting,currentNesting)}const visited=new Set;function traverseChild(child){if(child&&typeof child==="object"&&"type"in child){const childNode=child;if(!visited.has(childNode)){visited.add(childNode);traverse(childNode,currentNesting)}}}const childKeys=["body","test","consequent","alternate","init","update","left","right","argument","arguments","callee","object","property","elements","properties","expression","expressions","declarations","declaration","specifiers","source","key","value","handler","block","finalizer"];for(const key of childKeys){const child=n[key];if(child){if(Array.isArray(child)){child.forEach(traverseChild)}else{traverseChild(child)}}}}if(node.body){traverse(node.body,0)}return{total:complexity,breakdown}}function suggestExtractions(node,breakdown){const suggestions=[];if(breakdown.nesting>=4&&node.loc){suggestions.push({name:"extractNestedLogic",reason:`Nesting depth of ${breakdown.nesting} makes code hard to follow`,lineRange:[node.loc.start.line,node.loc.end.line],estimatedComplexityReduction:Math.floor(breakdown.nesting*1.5)})}if(breakdown.switches>=2&&node.loc){suggestions.push({name:"refactorSwitchToStrategy",reason:`${breakdown.switches} switch statements suggest strategy pattern`,lineRange:[node.loc.start.line,node.loc.end.line],estimatedComplexityReduction:breakdown.switches*2})}if(breakdown.loops>=3&&node.loc){suggestions.push({name:"extractLoopLogic",reason:`${breakdown.loops} loops can be extracted to separate methods`,lineRange:[node.loc.start.line,node.loc.end.line],estimatedComplexityReduction:breakdown.loops*2})}if(breakdown.conditionals>=5&&node.loc){suggestions.push({name:"simplifyConditionals",reason:`${breakdown.conditionals} conditional branches could use Guard Clauses`,lineRange:[node.loc.start.line,node.loc.end.line],estimatedComplexityReduction:Math.floor(breakdown.conditionals*.8)})}return suggestions}function suggestPattern(breakdown){if(breakdown.switches>=2)return"Strategy Pattern";if(breakdown.conditionals>=5)return"Guard Clauses + Early Return";if(breakdown.loops>=3)return"Extract Method + Pipeline";if(breakdown.nesting>=4)return"Extract Method + Composed Functions";return"Extract Method"}function estimateRefactoringTime(complexity,breakdown){const baseTime=Math.floor((complexity-maxComplexity)*3);const nestingPenalty=breakdown.nesting>=4?15:0;const totalMinutes=baseTime+nestingPenalty;if(totalMinutes<30)return`${totalMinutes} minutes`;if(totalMinutes<60)return"30-60 minutes";return`${Math.ceil(totalMinutes/60)} hours`}function checkFunction(node){const{total:complexity,breakdown}=calculateCognitiveComplexity(node);if(complexity<=maxComplexity)return;const functionSignature=(0,eslint_devkit_3.extractFunctionSignature)(node);const suggestions=suggestExtractions(node,breakdown);const pattern=suggestPattern(breakdown);const estimatedTime=estimateRefactoringTime(complexity,breakdown);context.report({node,messageId:"highCognitiveComplexity",data:{functionName:functionSignature,complexity:String(complexity),max:String(maxComplexity),overBy:String(complexity-maxComplexity),current:String(complexity),filePath:filename,line:String(node.loc?.start.line??0),conditionals:String(breakdown.conditionals),loops:String(breakdown.loops),nesting:String(breakdown.nesting),pattern,estimatedTime},suggest:suggestions.length>0?suggestions.map((suggestion,index)=>{const messageId=index===0?"extractMethod":index===1?"useStrategy":"simplifyLogic";return{messageId,data:{methodName:suggestion.name,pattern,reduction:String(suggestion.estimatedComplexityReduction)},fix:()=>null}}):void 0})}return{FunctionDeclaration:checkFunction,FunctionExpression:checkFunction,ArrowFunctionExpression:checkFunction}}});