@pythonidaer/complexity-report 1.0.2

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -0
  4. package/assets/prettify.css +1 -0
  5. package/assets/prettify.js +2 -0
  6. package/assets/sort-arrow-sprite.png +0 -0
  7. package/complexity-breakdown.js +53 -0
  8. package/decision-points/ast-utils.js +127 -0
  9. package/decision-points/decision-type.js +92 -0
  10. package/decision-points/function-matching.js +185 -0
  11. package/decision-points/in-params.js +262 -0
  12. package/decision-points/index.js +6 -0
  13. package/decision-points/node-helpers.js +89 -0
  14. package/decision-points/parent-map.js +62 -0
  15. package/decision-points/parse-main.js +101 -0
  16. package/decision-points/ternary-multiline.js +86 -0
  17. package/export-generators/helpers.js +309 -0
  18. package/export-generators/index.js +143 -0
  19. package/export-generators/md-exports.js +160 -0
  20. package/export-generators/txt-exports.js +262 -0
  21. package/function-boundaries/arrow-brace-body.js +302 -0
  22. package/function-boundaries/arrow-helpers.js +93 -0
  23. package/function-boundaries/arrow-jsx.js +73 -0
  24. package/function-boundaries/arrow-object-literal.js +65 -0
  25. package/function-boundaries/arrow-single-expr.js +72 -0
  26. package/function-boundaries/brace-scanning.js +151 -0
  27. package/function-boundaries/index.js +67 -0
  28. package/function-boundaries/named-helpers.js +227 -0
  29. package/function-boundaries/parse-utils.js +456 -0
  30. package/function-extraction/ast-utils.js +112 -0
  31. package/function-extraction/extract-callback.js +65 -0
  32. package/function-extraction/extract-from-eslint.js +91 -0
  33. package/function-extraction/extract-name-ast.js +133 -0
  34. package/function-extraction/extract-name-regex.js +267 -0
  35. package/function-extraction/index.js +6 -0
  36. package/function-extraction/utils.js +29 -0
  37. package/function-hierarchy.js +427 -0
  38. package/html-generators/about.js +75 -0
  39. package/html-generators/file-boundary-builders.js +36 -0
  40. package/html-generators/file-breakdown.js +412 -0
  41. package/html-generators/file-data.js +50 -0
  42. package/html-generators/file-helpers.js +100 -0
  43. package/html-generators/file-javascript.js +430 -0
  44. package/html-generators/file-line-render.js +160 -0
  45. package/html-generators/file.css +370 -0
  46. package/html-generators/file.js +207 -0
  47. package/html-generators/folder.js +424 -0
  48. package/html-generators/index.js +6 -0
  49. package/html-generators/main-index.js +346 -0
  50. package/html-generators/shared.css +471 -0
  51. package/html-generators/utils.js +15 -0
  52. package/index.js +36 -0
  53. package/integration/eslint/index.js +94 -0
  54. package/integration/threshold/index.js +45 -0
  55. package/package.json +64 -0
  56. package/report/cli.js +58 -0
  57. package/report/index.js +559 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Character-level parsing: comments, strings, regex, brace counting.
3
+ */
4
+
5
+ /**
6
+ * Handles escape sequences in strings
7
+ */
8
+ export function handleEscapeSequence(char, inString, escapeNext) {
9
+ if (escapeNext) return { escapeNext: false, shouldContinue: true };
10
+ if (char === '\\' && inString) return { escapeNext: true, shouldContinue: true };
11
+ return { escapeNext: false, shouldContinue: false };
12
+ }
13
+
14
+ /**
15
+ * Checks if a single-line comment starts
16
+ */
17
+ export function isSingleLineCommentStart(
18
+ char,
19
+ nextChar,
20
+ inString,
21
+ inRegex,
22
+ inMultiLineComment
23
+ ) {
24
+ return (
25
+ char === '/' &&
26
+ nextChar === '/' &&
27
+ !inString &&
28
+ !inRegex &&
29
+ !inMultiLineComment
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Checks if a multi-line comment starts
35
+ */
36
+ export function isMultiLineCommentStart(
37
+ char,
38
+ nextChar,
39
+ inString,
40
+ inRegex,
41
+ inSingleLineComment
42
+ ) {
43
+ return (
44
+ char === '/' &&
45
+ nextChar === '*' &&
46
+ !inString &&
47
+ !inRegex &&
48
+ !inSingleLineComment
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Checks if a multi-line comment ends
54
+ */
55
+ export function isMultiLineCommentEnd(char, nextChar, inMultiLineComment) {
56
+ return char === '*' && nextChar === '/' && inMultiLineComment;
57
+ }
58
+
59
+ /**
60
+ * Handles comment detection and state updates
61
+ */
62
+ export function handleComments(
63
+ char,
64
+ nextChar,
65
+ inString,
66
+ inRegex,
67
+ inSingleLineComment,
68
+ inMultiLineComment
69
+ ) {
70
+ if (
71
+ isSingleLineCommentStart(
72
+ char,
73
+ nextChar,
74
+ inString,
75
+ inRegex,
76
+ inMultiLineComment
77
+ )
78
+ ) {
79
+ return {
80
+ inSingleLineComment: true,
81
+ inMultiLineComment: false,
82
+ shouldBreak: true,
83
+ shouldContinue: false,
84
+ skipNext: false,
85
+ };
86
+ }
87
+ if (
88
+ isMultiLineCommentStart(
89
+ char,
90
+ nextChar,
91
+ inString,
92
+ inRegex,
93
+ inSingleLineComment
94
+ )
95
+ ) {
96
+ return {
97
+ inSingleLineComment: false,
98
+ inMultiLineComment: true,
99
+ shouldBreak: false,
100
+ shouldContinue: true,
101
+ skipNext: true,
102
+ };
103
+ }
104
+ if (isMultiLineCommentEnd(char, nextChar, inMultiLineComment)) {
105
+ return {
106
+ inSingleLineComment: false,
107
+ inMultiLineComment: false,
108
+ shouldBreak: false,
109
+ shouldContinue: true,
110
+ skipNext: true,
111
+ };
112
+ }
113
+ if (inSingleLineComment || inMultiLineComment) {
114
+ return {
115
+ inSingleLineComment,
116
+ inMultiLineComment,
117
+ shouldBreak: false,
118
+ shouldContinue: true,
119
+ skipNext: false,
120
+ };
121
+ }
122
+ return {
123
+ inSingleLineComment: false,
124
+ inMultiLineComment: false,
125
+ shouldBreak: false,
126
+ shouldContinue: false,
127
+ skipNext: false,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Handles string literal detection
133
+ */
134
+ export function handleStringLiterals(char, inRegex, inString, stringChar) {
135
+ if ((char === '"' || char === "'") && !inRegex) {
136
+ if (!inString) return { inString: true, stringChar: char };
137
+ if (char === stringChar) return { inString: false, stringChar: null };
138
+ }
139
+ return { inString, stringChar };
140
+ }
141
+
142
+ /**
143
+ * Detects if a slash character is the start of a regex pattern
144
+ */
145
+ export function isRegexStart(line, j, _prevChar) {
146
+ const beforeSlash = line.substring(Math.max(0, j - 2), j).trim();
147
+ return (
148
+ beforeSlash === '' ||
149
+ beforeSlash.endsWith('=') ||
150
+ beforeSlash.endsWith('(') ||
151
+ beforeSlash.endsWith('[') ||
152
+ beforeSlash.endsWith(',') ||
153
+ /^\s*$/.test(beforeSlash)
154
+ );
155
+ }
156
+
157
+ export function couldBeRegexStart(char, prevChar, inRegex, inString) {
158
+ return (
159
+ char === '/' &&
160
+ prevChar !== '/' &&
161
+ prevChar !== '*' &&
162
+ !inRegex &&
163
+ !inString
164
+ );
165
+ }
166
+
167
+ export function couldBeRegexEnd(char, nextChar, inRegex) {
168
+ return char === '/' && inRegex && nextChar !== '/' && nextChar !== '*';
169
+ }
170
+
171
+ /**
172
+ * Handles regex detection
173
+ */
174
+ export function handleRegexDetection(
175
+ char,
176
+ prevChar,
177
+ nextChar,
178
+ line,
179
+ j,
180
+ inRegex,
181
+ inString
182
+ ) {
183
+ if (couldBeRegexStart(char, prevChar, inRegex, inString)) {
184
+ if (isRegexStart(line, j, prevChar)) return true;
185
+ }
186
+ if (couldBeRegexEnd(char, nextChar, inRegex)) return false;
187
+ return inRegex;
188
+ }
189
+
190
+ /**
191
+ * Creates a result object with updated state
192
+ */
193
+ export function createBracesResult(
194
+ openBraces,
195
+ closeBraces,
196
+ state,
197
+ updatedState,
198
+ shouldBreak,
199
+ shouldContinue,
200
+ skipNext
201
+ ) {
202
+ return {
203
+ openBraces,
204
+ closeBraces,
205
+ state: { ...state, ...updatedState },
206
+ shouldBreak,
207
+ shouldContinue,
208
+ skipNext
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Processes escape sequence
214
+ */
215
+ export function processEscapeSequence(
216
+ char,
217
+ inString,
218
+ escapeNext,
219
+ state,
220
+ openBraces,
221
+ closeBraces
222
+ ) {
223
+ const escapeResult = handleEscapeSequence(char, inString, escapeNext);
224
+ if (escapeResult.shouldContinue) {
225
+ return {
226
+ result: createBracesResult(
227
+ openBraces,
228
+ closeBraces,
229
+ state,
230
+ { escapeNext: escapeResult.escapeNext },
231
+ false,
232
+ true,
233
+ false
234
+ ),
235
+ escapeNext: escapeResult.escapeNext
236
+ };
237
+ }
238
+ return { result: null, escapeNext: escapeResult.escapeNext };
239
+ }
240
+
241
+ /**
242
+ * Handles comment processing
243
+ */
244
+ export function processCommentHandling(
245
+ char,
246
+ nextChar,
247
+ inString,
248
+ inRegex,
249
+ inSingleLineComment,
250
+ inMultiLineComment,
251
+ escapeNext,
252
+ state,
253
+ openBraces,
254
+ closeBraces
255
+ ) {
256
+ const commentResult = handleComments(
257
+ char,
258
+ nextChar,
259
+ inString,
260
+ inRegex,
261
+ inSingleLineComment,
262
+ inMultiLineComment
263
+ );
264
+ if (commentResult.shouldBreak) {
265
+ return {
266
+ result: createBracesResult(
267
+ openBraces,
268
+ closeBraces,
269
+ state,
270
+ {
271
+ inSingleLineComment: commentResult.inSingleLineComment,
272
+ inMultiLineComment: commentResult.inMultiLineComment,
273
+ escapeNext
274
+ },
275
+ true,
276
+ false,
277
+ false
278
+ ),
279
+ inSingleLineComment: commentResult.inSingleLineComment,
280
+ inMultiLineComment: commentResult.inMultiLineComment
281
+ };
282
+ }
283
+ if (commentResult.shouldContinue) {
284
+ return {
285
+ result: createBracesResult(
286
+ openBraces,
287
+ closeBraces,
288
+ state,
289
+ {
290
+ inSingleLineComment: commentResult.inSingleLineComment,
291
+ inMultiLineComment: commentResult.inMultiLineComment,
292
+ escapeNext
293
+ },
294
+ false,
295
+ true,
296
+ commentResult.skipNext
297
+ ),
298
+ inSingleLineComment: commentResult.inSingleLineComment,
299
+ inMultiLineComment: commentResult.inMultiLineComment
300
+ };
301
+ }
302
+ return {
303
+ result: null,
304
+ inSingleLineComment: commentResult.inSingleLineComment,
305
+ inMultiLineComment: commentResult.inMultiLineComment
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Handles string literal processing
311
+ */
312
+ export function processStringLiteralHandling(
313
+ char,
314
+ inRegex,
315
+ inString,
316
+ stringChar,
317
+ escapeNext,
318
+ state,
319
+ openBraces,
320
+ closeBraces
321
+ ) {
322
+ const stringResult = handleStringLiterals(char, inRegex, inString, stringChar);
323
+ if (
324
+ stringResult.inString !== inString ||
325
+ stringResult.stringChar !== stringChar
326
+ ) {
327
+ return {
328
+ result: createBracesResult(
329
+ openBraces,
330
+ closeBraces,
331
+ state,
332
+ {
333
+ inString: stringResult.inString,
334
+ stringChar: stringResult.stringChar,
335
+ escapeNext
336
+ },
337
+ false,
338
+ true,
339
+ false
340
+ ),
341
+ inString: stringResult.inString,
342
+ stringChar: stringResult.stringChar
343
+ };
344
+ }
345
+ return {
346
+ result: null,
347
+ inString: stringResult.inString,
348
+ stringChar: stringResult.stringChar,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Processes a single character in the line to count braces
354
+ */
355
+ export function processCharacterForBraces(
356
+ char,
357
+ prevChar,
358
+ nextChar,
359
+ line,
360
+ j,
361
+ state,
362
+ openBraces,
363
+ closeBraces
364
+ ) {
365
+ let {
366
+ inRegex,
367
+ inString,
368
+ inSingleLineComment,
369
+ inMultiLineComment,
370
+ stringChar,
371
+ escapeNext,
372
+ } = state;
373
+
374
+ const escapeHandling = processEscapeSequence(
375
+ char,
376
+ inString,
377
+ escapeNext,
378
+ state,
379
+ openBraces,
380
+ closeBraces
381
+ );
382
+ if (escapeHandling.result) return escapeHandling.result;
383
+ escapeNext = escapeHandling.escapeNext;
384
+
385
+ const commentHandling = processCommentHandling(
386
+ char,
387
+ nextChar,
388
+ inString,
389
+ inRegex,
390
+ inSingleLineComment,
391
+ inMultiLineComment,
392
+ escapeNext,
393
+ state,
394
+ openBraces,
395
+ closeBraces
396
+ );
397
+ if (commentHandling.result) return commentHandling.result;
398
+ inSingleLineComment = commentHandling.inSingleLineComment;
399
+ inMultiLineComment = commentHandling.inMultiLineComment;
400
+
401
+ const stringHandling = processStringLiteralHandling(
402
+ char,
403
+ inRegex,
404
+ inString,
405
+ stringChar,
406
+ escapeNext,
407
+ state,
408
+ openBraces,
409
+ closeBraces
410
+ );
411
+ if (stringHandling.result) return stringHandling.result;
412
+ inString = stringHandling.inString;
413
+ stringChar = stringHandling.stringChar;
414
+
415
+ const newRegexState = handleRegexDetection(
416
+ char,
417
+ prevChar,
418
+ nextChar,
419
+ line,
420
+ j,
421
+ inRegex,
422
+ inString
423
+ );
424
+ if (newRegexState !== inRegex) {
425
+ return createBracesResult(
426
+ openBraces,
427
+ closeBraces,
428
+ state,
429
+ {
430
+ inRegex: newRegexState,
431
+ inString,
432
+ stringChar,
433
+ escapeNext
434
+ },
435
+ false,
436
+ true,
437
+ false
438
+ );
439
+ }
440
+ inRegex = newRegexState;
441
+
442
+ if (!inRegex && !inString) {
443
+ if (char === '{') openBraces += 1;
444
+ if (char === '}') closeBraces += 1;
445
+ }
446
+
447
+ return createBracesResult(
448
+ openBraces,
449
+ closeBraces,
450
+ state,
451
+ { inRegex, inString, stringChar, escapeNext },
452
+ false,
453
+ false,
454
+ false
455
+ );
456
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * AST parsing and traversal for function extraction.
3
+ */
4
+
5
+ import { readFileSync, existsSync } from 'fs';
6
+ import { resolve } from 'path';
7
+ import { parse } from '@typescript-eslint/typescript-estree';
8
+
9
+ /**
10
+ * Parses source code into AST
11
+ */
12
+ export function parseAST(sourceCode, filePath) {
13
+ try {
14
+ const isTSX = filePath.endsWith('.tsx') || filePath.endsWith('.jsx');
15
+ return parse(sourceCode, {
16
+ sourceType: 'module',
17
+ ecmaVersion: 2020,
18
+ jsx: isTSX,
19
+ filePath: filePath,
20
+ comment: true,
21
+ loc: true,
22
+ range: true,
23
+ });
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ export function getNodeLine(node) {
30
+ if (node.loc && node.loc.start) return node.loc.start.line;
31
+ return 1;
32
+ }
33
+
34
+ export function shouldSkipKey(key) {
35
+ return key === 'parent' || key === 'range' || key === 'loc' ||
36
+ key === 'leadingComments' || key === 'trailingComments';
37
+ }
38
+
39
+ function processArrayChildren(array, visit) {
40
+ array.forEach(item => {
41
+ if (item && typeof item === 'object' && item.type) visit(item);
42
+ });
43
+ }
44
+
45
+ function processChildNode(child, visit) {
46
+ if (child && typeof child === 'object' && child.type) visit(child);
47
+ }
48
+
49
+ export function traverseAST(node, visit) {
50
+ if (!node || typeof node !== 'object') return;
51
+ visit(node);
52
+ for (const key in node) {
53
+ if (shouldSkipKey(key)) continue;
54
+ const child = node[key];
55
+ if (Array.isArray(child)) {
56
+ processArrayChildren(child, (item) => traverseAST(item, visit));
57
+ } else {
58
+ processChildNode(child, (item) => traverseAST(item, visit));
59
+ }
60
+ }
61
+ }
62
+
63
+ export function findAllFunctions(ast) {
64
+ const functions = [];
65
+ const functionTypes = new Set([
66
+ 'FunctionDeclaration',
67
+ 'FunctionExpression',
68
+ 'ArrowFunctionExpression',
69
+ 'MethodDefinition',
70
+ ]);
71
+ traverseAST(ast, (node) => {
72
+ if (functionTypes.has(node.type)) functions.push(node);
73
+ });
74
+ return functions;
75
+ }
76
+
77
+ export function findAllNodesByType(ast, nodeType) {
78
+ const results = [];
79
+ traverseAST(ast, (node) => {
80
+ if (node.type === nodeType) results.push(node);
81
+ });
82
+ return results;
83
+ }
84
+
85
+ /**
86
+ * Finds the containing node that wraps a function node
87
+ */
88
+ export function findContainingNode(funcNode, candidateNodes) {
89
+ if (!funcNode.range) return null;
90
+ let containingNode = null;
91
+ let smallestSize = Infinity;
92
+ for (const candidate of candidateNodes) {
93
+ if (!candidate.range) continue;
94
+ if (
95
+ candidate.range[0] < funcNode.range[0] &&
96
+ candidate.range[1] > funcNode.range[1]
97
+ ) {
98
+ const size = candidate.range[1] - candidate.range[0];
99
+ if (size < smallestSize) {
100
+ containingNode = candidate;
101
+ smallestSize = size;
102
+ }
103
+ }
104
+ }
105
+ return containingNode;
106
+ }
107
+
108
+ export function readFileIfExists(filePath, projectRoot) {
109
+ const fullPath = resolve(projectRoot, filePath);
110
+ if (!existsSync(fullPath)) return null;
111
+ return readFileSync(fullPath, 'utf-8');
112
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Callback context identification (CallExpression, JSXAttribute, ReturnStatement).
3
+ */
4
+
5
+ import { findAllNodesByType, findAllFunctions, findContainingNode } from './ast-utils.js';
6
+
7
+ export function getCalleeCallbackName(callee) {
8
+ if (!callee) return null;
9
+ if (callee.type === 'MemberExpression' && callee.property) return callee.property.name || null;
10
+ if (callee.type === 'Identifier') return callee.name || null;
11
+ return null;
12
+ }
13
+
14
+ export function checkCallExpressionCallback(funcNode, ast) {
15
+ const callExpressions = findAllNodesByType(ast, 'CallExpression');
16
+ const newExpressions = findAllNodesByType(ast, 'NewExpression');
17
+ const containingNode = findContainingNode(funcNode, [
18
+ ...callExpressions,
19
+ ...newExpressions,
20
+ ]);
21
+ if (containingNode && containingNode.callee) {
22
+ return getCalleeCallbackName(containingNode.callee);
23
+ }
24
+ return null;
25
+ }
26
+
27
+ export function checkJSXAttributeCallback(funcNode, ast) {
28
+ const jsxAttributes = findAllNodesByType(ast, 'JSXAttribute');
29
+ const containingAttr = findContainingNode(funcNode, jsxAttributes);
30
+ if (containingAttr && containingAttr.name) {
31
+ const attrName = containingAttr.name.name;
32
+ if (attrName.startsWith('on') || attrName === 'ref') {
33
+ return attrName === 'ref' ? 'ref' : `${attrName} handler`;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ export function checkReturnCallback(funcNode, ast) {
40
+ const returnStatements = findAllNodesByType(ast, 'ReturnStatement');
41
+ const containingReturn = findContainingNode(funcNode, returnStatements);
42
+ if (containingReturn) {
43
+ const isDirectReturnValue = containingReturn.argument === funcNode ||
44
+ (containingReturn.argument &&
45
+ containingReturn.argument.type === 'ArrowFunctionExpression' &&
46
+ containingReturn.argument === funcNode);
47
+ if (isDirectReturnValue) {
48
+ const allFunctions = findAllFunctions(ast);
49
+ const returnParent = findContainingNode(containingReturn, allFunctions);
50
+ if (returnParent) return 'return';
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+
56
+ export function identifyCallbackContext(funcNode, ast) {
57
+ if (!funcNode.range) return null;
58
+ const returnCallback = checkReturnCallback(funcNode, ast);
59
+ if (returnCallback) return returnCallback;
60
+ const jsxCallback = checkJSXAttributeCallback(funcNode, ast);
61
+ if (jsxCallback) return jsxCallback;
62
+ const callCallback = checkCallExpressionCallback(funcNode, ast);
63
+ if (callCallback) return callCallback;
64
+ return null;
65
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * ESLint result processing and main extractFunctionName (AST + regex fallback).
3
+ */
4
+
5
+ import { readFileIfExists } from './ast-utils.js';
6
+ import { extractFunctionNameAST } from './extract-name-ast.js';
7
+ import { handleArrowFunctionExpression, handleFunctionDeclaration } from './extract-name-regex.js';
8
+
9
+ /**
10
+ * Extracts function name: AST first, then regex fallback.
11
+ * Uses self-reference for regex fallback.
12
+ */
13
+ export function extractFunctionName(
14
+ filePath,
15
+ lineNumber,
16
+ nodeType,
17
+ projectRoot
18
+ ) {
19
+ const astName = extractFunctionNameAST(
20
+ filePath,
21
+ lineNumber,
22
+ nodeType,
23
+ projectRoot
24
+ );
25
+ if (astName) return astName;
26
+ try {
27
+ const fileContent = readFileIfExists(filePath, projectRoot);
28
+ if (!fileContent) return 'unknown';
29
+ const lines = fileContent.split('\n');
30
+ if (nodeType === 'ArrowFunctionExpression') {
31
+ return handleArrowFunctionExpression(
32
+ lines,
33
+ lineNumber,
34
+ filePath,
35
+ projectRoot,
36
+ extractFunctionName
37
+ );
38
+ }
39
+ return handleFunctionDeclaration(lines, lineNumber);
40
+ } catch {
41
+ return 'unknown';
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Processes a single complexity message and upserts into functionMap.
47
+ */
48
+ export function processComplexityMessage(message, file, projectRoot, functionMap) {
49
+ if (message.ruleId !== 'complexity' || message.severity !== 1) return;
50
+ const complexityMatch = message.message.match(/complexity of (\d+)/i);
51
+ if (!complexityMatch) return;
52
+ const filePath = file.filePath.replace(projectRoot + '/', '');
53
+ const nodeType = message.nodeType || 'FunctionDeclaration';
54
+ const functionName = extractFunctionName(
55
+ filePath,
56
+ message.line,
57
+ nodeType,
58
+ projectRoot
59
+ );
60
+ const complexity = complexityMatch[1];
61
+ const key = `${filePath}:${functionName}:${message.line}`;
62
+ const existing = functionMap.get(key);
63
+ if (existing && complexity <= parseInt(existing.complexity, 10)) return;
64
+ functionMap.set(key, {
65
+ file: filePath,
66
+ line: message.line,
67
+ column: message.column || 1,
68
+ message: message.message,
69
+ complexity: complexity,
70
+ functionName: functionName,
71
+ nodeType: nodeType,
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Processes ESLint results and extracts function complexity data
77
+ */
78
+ export function extractFunctionsFromESLintResults(eslintResults, projectRoot) {
79
+ const functionMap = new Map();
80
+ eslintResults.forEach((file) => {
81
+ if (!file.messages) return;
82
+ file.messages.forEach((message) =>
83
+ processComplexityMessage(message, file, projectRoot, functionMap)
84
+ );
85
+ });
86
+ const allFunctions = [...functionMap.values()];
87
+ allFunctions.sort(
88
+ (a, b) => parseInt(b.complexity, 10) - parseInt(a.complexity, 10)
89
+ );
90
+ return allFunctions;
91
+ }