@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,73 @@
1
+ /**
2
+ * Arrow function end detection for JSX return and JSX attribute patterns.
3
+ */
4
+
5
+ import { scanForJSXReturnClosingParens } from './arrow-helpers.js';
6
+
7
+ /**
8
+ * Finds the end line for an arrow function returning JSX (=> ( ... ))
9
+ * @param {Array<string>} lines - Array of source code lines
10
+ * @param {number} startLine - Start line index (0-based)
11
+ * @param {number} arrowIndex - Index of => in the line
12
+ * @param {number} functionLine - Reported line number (1-based)
13
+ * @returns {{end: number, found: boolean}} End line (1-based) and whether end was found
14
+ */
15
+ export function findArrowFunctionEndJSXReturn(
16
+ lines,
17
+ startLine,
18
+ arrowIndex,
19
+ functionLine
20
+ ) {
21
+ const line = lines[startLine];
22
+ const afterArrow = line.substring(arrowIndex + 2).trim();
23
+ if (!afterArrow.startsWith('(')) {
24
+ return { end: functionLine, found: false };
25
+ }
26
+ const arrowEndPos = arrowIndex + 2;
27
+ const parenPos = line.indexOf('(', arrowEndPos);
28
+ const scanIndex = parenPos + 1;
29
+ const endLine = scanForJSXReturnClosingParens(
30
+ lines,
31
+ startLine,
32
+ line,
33
+ scanIndex
34
+ );
35
+ if (endLine !== null) {
36
+ return { end: endLine, found: true };
37
+ }
38
+ return { end: functionLine, found: false };
39
+ }
40
+
41
+ /**
42
+ * Finds the end line for an arrow function inside JSX attribute
43
+ * (onChange={...})
44
+ * @param {Array<string>} lines - Array of source code lines
45
+ * @param {number} startLine - Start line index (0-based)
46
+ * @param {number} arrowIndex - Index of => in the line
47
+ * @param {number} functionLine - Reported line number (1-based)
48
+ * @returns {{end: number, found: boolean}} End line and whether found
49
+ */
50
+ export function findArrowFunctionEndJSXAttribute(
51
+ lines,
52
+ startLine,
53
+ arrowIndex,
54
+ functionLine
55
+ ) {
56
+ const line = lines[startLine];
57
+ const isInJSXAttribute = line.includes('{') && line.indexOf('{') < arrowIndex;
58
+ if (!isInJSXAttribute) {
59
+ return { end: functionLine, found: false };
60
+ }
61
+ const afterArrow = line.substring(arrowIndex + 2);
62
+ let parenCount = 0;
63
+ for (let k = 0; k < afterArrow.length; k += 1) {
64
+ if (afterArrow[k] === '(') parenCount += 1;
65
+ else if (afterArrow[k] === ')') {
66
+ parenCount -= 1;
67
+ if (parenCount === 0) {
68
+ return { end: startLine + 1, found: true };
69
+ }
70
+ }
71
+ }
72
+ return { end: startLine + 1, found: true };
73
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Arrow function end detection for object literal return (=> ({ ... })).
3
+ */
4
+
5
+ /**
6
+ * Checks if a brace is part of an object literal pattern
7
+ * @param {string} line - Line content
8
+ * @param {number} arrowIndex - Index of => in the line
9
+ * @param {number} braceIndex - Index of { in the line
10
+ * @returns {boolean} True if object literal pattern
11
+ */
12
+ export function isObjectLiteralPattern(line, arrowIndex, braceIndex) {
13
+ if (braceIndex === -1) return false;
14
+ const betweenArrowAndBrace = line.substring(arrowIndex + 2, braceIndex).trim();
15
+ return /^\(/.test(betweenArrowAndBrace) ||
16
+ (betweenArrowAndBrace === '' && braceIndex > 0 && line[braceIndex - 1] === '(');
17
+ }
18
+
19
+ /**
20
+ * Finds the closing paren of an object literal and checks if expression ends
21
+ * @param {string} line - Line content
22
+ * @param {number} braceIndex - Index of { in the line
23
+ * @returns {boolean} True if closing paren found and expression ends
24
+ */
25
+ export function findObjectLiteralClosingParen(line, braceIndex) {
26
+ let parenCount = 1;
27
+ for (let k = braceIndex + 1; k < line.length; k += 1) {
28
+ if (line[k] === '(') parenCount += 1;
29
+ else if (line[k] === ')') {
30
+ parenCount -= 1;
31
+ if (parenCount === 0) {
32
+ const restOfLine = line.substring(k + 1).trim();
33
+ return restOfLine.startsWith(';') || restOfLine.startsWith(')') || restOfLine === '';
34
+ }
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Finds the end line for an arrow function returning object literal
42
+ * (=> ({ ... }))
43
+ * @param {Array<string>} lines - Array of source code lines
44
+ * @param {number} startLine - Start line index (0-based)
45
+ * @param {number} arrowIndex - Index of => in the line
46
+ * @param {number} braceIndex - Index of { in the line
47
+ * @param {number} functionLine - Reported line number (1-based)
48
+ * @returns {{end: number, found: boolean}} End line and whether found
49
+ */
50
+ export function findArrowFunctionEndObjectLiteral(
51
+ lines,
52
+ startLine,
53
+ arrowIndex,
54
+ braceIndex,
55
+ functionLine
56
+ ) {
57
+ const line = lines[startLine];
58
+ if (!isObjectLiteralPattern(line, arrowIndex, braceIndex)) {
59
+ return { end: functionLine, found: false };
60
+ }
61
+ if (findObjectLiteralClosingParen(line, braceIndex)) {
62
+ return { end: startLine + 1, found: true };
63
+ }
64
+ return { end: startLine + 1, found: true };
65
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Arrow function end detection for single-expression bodies.
3
+ */
4
+
5
+ /**
6
+ * Checks if arrow function ends on the same line
7
+ * @param {string} lineAfterArrow - Content after => on the line
8
+ * @returns {boolean} True if ends on same line
9
+ */
10
+ export function endsOnSameLine(lineAfterArrow) {
11
+ return lineAfterArrow.includes(';') ||
12
+ lineAfterArrow.includes(',') ||
13
+ lineAfterArrow.includes(')');
14
+ }
15
+
16
+ /**
17
+ * Calculates initial paren depth before arrow function
18
+ * @param {string} lineBeforeArrow - Content before => on the line
19
+ * @returns {number} Initial paren depth
20
+ */
21
+ export function calculateInitialParenDepth(lineBeforeArrow) {
22
+ const openParens = (lineBeforeArrow.match(/\(/g) || []).length;
23
+ const closeParens = (lineBeforeArrow.match(/\)/g) || []).length;
24
+ return openParens - closeParens;
25
+ }
26
+
27
+ /**
28
+ * Scans forward to find end of single-expression arrow function
29
+ * @param {Array<string>} lines - Array of source code lines
30
+ * @param {number} startLine - Start line index (0-based)
31
+ * @param {number} parenDepth - Initial paren depth
32
+ * @returns {number} End line number (1-based)
33
+ */
34
+ export function scanForSingleExpressionEnd(lines, startLine, parenDepth) {
35
+ for (let j = startLine + 1; j < lines.length; j += 1) {
36
+ const scanLine = lines[j];
37
+ for (let k = 0; k < scanLine.length; k += 1) {
38
+ if (scanLine[k] === '(') parenDepth += 1;
39
+ else if (scanLine[k] === ')') {
40
+ parenDepth -= 1;
41
+ if (parenDepth <= 0) return j + 1;
42
+ }
43
+ }
44
+ if (scanLine.trim().match(/^[;},]/)) return j + 1;
45
+ }
46
+ return lines.length;
47
+ }
48
+
49
+ /**
50
+ * Finds the end line for a single-expression arrow function
51
+ * @param {Array<string>} lines - Array of source code lines
52
+ * @param {number} startLine - Start line index (0-based)
53
+ * @param {number} arrowIndex - Index of => in the line
54
+ * @param {number} _functionLine - Reported line number (1-based)
55
+ * @returns {{end: number, found: boolean}} End line (1-based) and whether end was found
56
+ */
57
+ export function findArrowFunctionEndSingleExpression(
58
+ lines,
59
+ startLine,
60
+ arrowIndex,
61
+ _functionLine
62
+ ) {
63
+ const line = lines[startLine];
64
+ const lineAfterArrow = line.substring(arrowIndex + 2);
65
+ if (endsOnSameLine(lineAfterArrow)) {
66
+ return { end: startLine + 1, found: true };
67
+ }
68
+ const lineBeforeArrow = line.substring(0, arrowIndex);
69
+ const parenDepth = calculateInitialParenDepth(lineBeforeArrow);
70
+ const endLine = scanForSingleExpressionEnd(lines, startLine, parenDepth);
71
+ return { end: endLine, found: true };
72
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Brace counting and function body end detection.
3
+ */
4
+
5
+ import { processCharacterForBraces } from './parse-utils.js';
6
+
7
+ /**
8
+ * Finds end line for dependency array pattern (}, [deps])
9
+ */
10
+ export function findDependencyArrayEnd(lines, i) {
11
+ for (let k = i; k < Math.min(i + 3, lines.length); k += 1) {
12
+ if (lines[k].includes(']')) return k + 1;
13
+ }
14
+ return null;
15
+ }
16
+
17
+ /**
18
+ * Finds end line for setTimeout callback pattern (}, delay)
19
+ */
20
+ export function findSetTimeoutCallbackEnd(lines, i, _functionLine) {
21
+ for (let k = i; k < Math.min(i + 3, lines.length); k += 1) {
22
+ const checkLine = lines[k];
23
+ if (checkLine.includes(')') && (checkLine.includes(';') || k === i + 1)) return k + 1;
24
+ }
25
+ return null;
26
+ }
27
+
28
+ /**
29
+ * Checks if closing brace is followed by dependency array or callback
30
+ */
31
+ export function checkCallbackPatterns(line, i, lines) {
32
+ const firstBraceIndex = line.indexOf('}');
33
+ if (firstBraceIndex === -1) {
34
+ return { hasDependencyArray: false, hasCallbackParam: false };
35
+ }
36
+ const restOfLine = line.substring(firstBraceIndex);
37
+ const nextLine = i + 1 < lines.length ? lines[i + 1] : '';
38
+ const combined = restOfLine + ' ' + nextLine;
39
+ return {
40
+ hasDependencyArray: /}\s*,\s*\[/.test(combined),
41
+ hasCallbackParam: /}\s*,\s*\d+/.test(combined)
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Handles function body end detection (when braces balance)
47
+ */
48
+ export function handleFunctionBodyEnd(line, i, functionLine, lines) {
49
+ const { hasDependencyArray, hasCallbackParam } = checkCallbackPatterns(
50
+ line,
51
+ i,
52
+ lines
53
+ );
54
+ if (hasDependencyArray) return findDependencyArrayEnd(lines, i);
55
+ if (hasCallbackParam) return findSetTimeoutCallbackEnd(lines, i, functionLine);
56
+ return i + 1;
57
+ }
58
+
59
+ /**
60
+ * Checks if braces are balanced and function ends
61
+ */
62
+ export function checkFunctionEnd(
63
+ updatedBraceCount,
64
+ closeBraces,
65
+ line,
66
+ i,
67
+ functionLine,
68
+ lines,
69
+ nodeType
70
+ ) {
71
+ if (updatedBraceCount === 0 && closeBraces > 0) {
72
+ if (nodeType === 'FunctionDeclaration') {
73
+ const { hasDependencyArray } = checkCallbackPatterns(line, i, lines);
74
+ if (hasDependencyArray) return null;
75
+ }
76
+ const trimmed = line.trim();
77
+ if (trimmed === '};' || trimmed.endsWith('};')) {
78
+ if (i > 0) {
79
+ const prevLines = lines.slice(Math.max(0, i - 10), i);
80
+ const hasAssignmentPattern = prevLines.some(prevLine =>
81
+ /^\s*(const|let|var)\s+\w+\s*=.*=>/.test(prevLine.trim())
82
+ );
83
+ if (hasAssignmentPattern) return i + 1;
84
+ }
85
+ }
86
+ const endLine = handleFunctionBodyEnd(line, i, functionLine, lines);
87
+ if (endLine !== null) return endLine;
88
+ }
89
+ return null;
90
+ }
91
+
92
+ /**
93
+ * Processes a line within function body
94
+ * (brace counting, comment/string/regex aware)
95
+ */
96
+ export function processLineInFunctionBody(
97
+ line,
98
+ i,
99
+ functionLine,
100
+ braceCount,
101
+ lines,
102
+ nodeType
103
+ ) {
104
+ let openBraces = 0;
105
+ let closeBraces = 0;
106
+ let state = {
107
+ inRegex: false,
108
+ inString: false,
109
+ inSingleLineComment: false,
110
+ inMultiLineComment: false,
111
+ stringChar: null,
112
+ escapeNext: false
113
+ };
114
+
115
+ for (let j = 0; j < line.length; j += 1) {
116
+ const char = line[j];
117
+ const prevChar = j > 0 ? line[j - 1] : '';
118
+ const nextChar = j + 1 < line.length ? line[j + 1] : '';
119
+ const result = processCharacterForBraces(
120
+ char,
121
+ prevChar,
122
+ nextChar,
123
+ line,
124
+ j,
125
+ state,
126
+ openBraces,
127
+ closeBraces
128
+ );
129
+ openBraces = result.openBraces;
130
+ closeBraces = result.closeBraces;
131
+ state = result.state;
132
+ if (result.shouldBreak) break;
133
+ if (result.shouldContinue) {
134
+ if (result.skipNext) j += 1;
135
+ continue;
136
+ }
137
+ }
138
+
139
+ const updatedBraceCount = braceCount + openBraces - closeBraces;
140
+ const endLine = checkFunctionEnd(
141
+ updatedBraceCount,
142
+ closeBraces,
143
+ line,
144
+ i,
145
+ functionLine,
146
+ lines,
147
+ nodeType
148
+ );
149
+ if (endLine !== null) return { braceCount: updatedBraceCount, end: endLine };
150
+ return { braceCount: updatedBraceCount, end: null };
151
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Function boundaries module — finds start/end lines for each function.
3
+ * Main export: findFunctionBoundaries.
4
+ */
5
+
6
+ import { findArrowFunctionStart } from './arrow-helpers.js';
7
+ import { findArrowFunctionEnd } from './arrow-brace-body.js';
8
+ import { findNamedFunctionStart, findNamedFunctionEnd, findFunctionEndFallback } from './named-helpers.js';
9
+
10
+ /**
11
+ * Finds function boundaries (start and end lines) for each function
12
+ * @param {string} sourceCode - Full source code
13
+ * @param {Array} functions - Array of function objects with line numbers
14
+ * @returns {Map} Map of functionLine -> { start, end }
15
+ */
16
+ export function findFunctionBoundaries(sourceCode, functions) {
17
+ const boundaries = new Map();
18
+ const lines = sourceCode.split('\n');
19
+
20
+ functions.forEach(func => {
21
+ const functionLine = func.line;
22
+ const nodeType = func.nodeType || 'FunctionDeclaration';
23
+
24
+ const start = nodeType === 'ArrowFunctionExpression'
25
+ ? findArrowFunctionStart(lines, functionLine)
26
+ : findNamedFunctionStart(lines, functionLine, func.functionName);
27
+
28
+ let end = functionLine;
29
+ let braceCount = 0;
30
+ let inFunctionBody = false;
31
+ let arrowFunctionEndSet = false;
32
+ let arrowFunctionHandled = false;
33
+
34
+ if (nodeType === 'ArrowFunctionExpression') {
35
+ const arrowResult = findArrowFunctionEnd(lines, start, functionLine, boundaries);
36
+ end = arrowResult.end;
37
+ arrowFunctionHandled = arrowResult.arrowFunctionHandled;
38
+ arrowFunctionEndSet = arrowResult.arrowFunctionEndSet;
39
+ inFunctionBody = arrowResult.inFunctionBody;
40
+ braceCount = arrowResult.braceCount;
41
+
42
+ if (arrowFunctionEndSet) return;
43
+ }
44
+
45
+ if (!arrowFunctionEndSet) {
46
+ end = findNamedFunctionEnd(
47
+ lines,
48
+ start,
49
+ functionLine,
50
+ arrowFunctionHandled,
51
+ inFunctionBody,
52
+ braceCount,
53
+ nodeType
54
+ );
55
+ }
56
+
57
+ if (end === functionLine || !inFunctionBody) {
58
+ end = findFunctionEndFallback(lines, start, functionLine);
59
+ }
60
+
61
+ if (!boundaries.has(functionLine)) {
62
+ boundaries.set(functionLine, { start, end });
63
+ }
64
+ });
65
+
66
+ return boundaries;
67
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Named function start/end and body detection helpers.
3
+ */
4
+
5
+ import { processLineInFunctionBody } from './brace-scanning.js';
6
+
7
+ /**
8
+ * Checks if a line matches function declaration pattern
9
+ */
10
+ export function isFunctionDeclarationPattern(line) {
11
+ return /^\s*(?:export\s+)?function\s+\w+/.test(line) &&
12
+ line.includes('(') &&
13
+ line.includes('{') &&
14
+ !line.includes('=>');
15
+ }
16
+
17
+ /**
18
+ * Calculates initial brace count for function body
19
+ */
20
+ export function calculateFunctionBodyBraceCount(line) {
21
+ const functionBodyBraceIndex = line.lastIndexOf('{');
22
+ if (functionBodyBraceIndex === -1) return 1;
23
+ const paramCloseIndex = line.lastIndexOf(')');
24
+ if (paramCloseIndex !== -1 && functionBodyBraceIndex > paramCloseIndex) {
25
+ const afterParams = line.substring(paramCloseIndex);
26
+ return (afterParams.match(/{/g) || []).length;
27
+ }
28
+ return (line.match(/{/g) || []).length;
29
+ }
30
+
31
+ /**
32
+ * Handles arrow function without braces case
33
+ */
34
+ export function handleArrowFunctionWithoutBraces(lines, i) {
35
+ let j = i + 1;
36
+ while (j < lines.length && !lines[j].trim().match(/^[;}]/)) j += 1;
37
+ return j + 1;
38
+ }
39
+
40
+ /**
41
+ * Tracks type definition braces (before function body is found)
42
+ */
43
+ export function trackTypeBraces(line, typeBraceCount) {
44
+ if (!line.includes('{')) return typeBraceCount;
45
+ const openBraces = (line.match(/{/g) || []).length;
46
+ const closeBraces = (line.match(/}/g) || []).length;
47
+ return typeBraceCount + openBraces - closeBraces;
48
+ }
49
+
50
+ /**
51
+ * Handles function body start detection
52
+ */
53
+ export function handleFunctionBodyStart(line, i, lines) {
54
+ const hasFunctionBodyPattern = /\)\s*[:\w\s<>[\]|'"]*\s*\{/.test(line);
55
+ const isFunctionDeclaration = isFunctionDeclarationPattern(line);
56
+ const hasArrowFunction = line.includes('=>') && !line.includes('{');
57
+ if (hasFunctionBodyPattern || isFunctionDeclaration) {
58
+ return {
59
+ inFunctionBody: true,
60
+ braceCount: calculateFunctionBodyBraceCount(line),
61
+ end: null,
62
+ };
63
+ }
64
+ if (hasArrowFunction) {
65
+ return {
66
+ inFunctionBody: true,
67
+ braceCount: 0,
68
+ end: handleArrowFunctionWithoutBraces(lines, i),
69
+ };
70
+ }
71
+ return { inFunctionBody: false, braceCount: 0, end: null };
72
+ }
73
+
74
+ /**
75
+ * Handles processing a line before function body is found
76
+ */
77
+ export function processLineBeforeFunctionBody(line, i, lines, typeBraceCount) {
78
+ const bodyStartResult = handleFunctionBodyStart(line, i, lines);
79
+ if (bodyStartResult.end !== null) {
80
+ return {
81
+ inFunctionBody: true,
82
+ braceCount: 0,
83
+ end: bodyStartResult.end,
84
+ typeBraceCount,
85
+ };
86
+ }
87
+ if (bodyStartResult.inFunctionBody) {
88
+ return {
89
+ inFunctionBody: true,
90
+ braceCount: bodyStartResult.braceCount,
91
+ end: null,
92
+ typeBraceCount,
93
+ };
94
+ }
95
+ return {
96
+ inFunctionBody: false,
97
+ braceCount: 0,
98
+ end: null,
99
+ typeBraceCount: trackTypeBraces(line, typeBraceCount)
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Checks if a line contains the function body pattern
105
+ */
106
+ export function hasFunctionBodyPattern(line) {
107
+ return /\)\s*[:\w\s<>[\]|'"]*\s*\{/.test(line);
108
+ }
109
+
110
+ /**
111
+ * Scans lines to find function end using brace counting
112
+ */
113
+ export function scanForFunctionEndWithBraces(lines, start, functionLine) {
114
+ let fallbackBraceCount = 0;
115
+ let foundFunctionBody = false;
116
+ for (let i = start - 1; i < lines.length; i += 1) {
117
+ const line = lines[i];
118
+ if (!foundFunctionBody && hasFunctionBodyPattern(line)) {
119
+ foundFunctionBody = true;
120
+ const result = processLineInFunctionBody(
121
+ line,
122
+ i,
123
+ functionLine,
124
+ 0,
125
+ lines,
126
+ undefined
127
+ );
128
+ fallbackBraceCount = result.braceCount;
129
+ } else if (foundFunctionBody) {
130
+ const result = processLineInFunctionBody(
131
+ line,
132
+ i,
133
+ functionLine,
134
+ fallbackBraceCount,
135
+ lines,
136
+ undefined
137
+ );
138
+ fallbackBraceCount = result.braceCount;
139
+ if (fallbackBraceCount === 0 && result.end !== null) {
140
+ return result.end;
141
+ }
142
+ }
143
+ }
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Fallback logic to find function end when normal detection fails
149
+ */
150
+ export function findFunctionEndFallback(lines, start, functionLine) {
151
+ const end = scanForFunctionEndWithBraces(lines, start, functionLine);
152
+ if (end !== null) return end;
153
+ return Math.min(start + 500, lines.length);
154
+ }
155
+
156
+ /**
157
+ * Finds the end line for a named function by tracking braces
158
+ */
159
+ export function findNamedFunctionEnd(
160
+ lines,
161
+ start,
162
+ functionLine,
163
+ arrowFunctionHandled,
164
+ inFunctionBody,
165
+ braceCount,
166
+ nodeType
167
+ ) {
168
+ let end = functionLine;
169
+ let typeBraceCount = 0;
170
+ const loopStart = start - 1;
171
+ const skipFirstLine = (arrowFunctionHandled && inFunctionBody);
172
+
173
+ for (let i = loopStart; i < lines.length; i += 1) {
174
+ const line = lines[i];
175
+ if (!inFunctionBody) {
176
+ const result = processLineBeforeFunctionBody(line, i, lines, typeBraceCount);
177
+ if (result.end !== null) return result.end;
178
+ if (result.inFunctionBody) {
179
+ inFunctionBody = true;
180
+ braceCount = result.braceCount;
181
+ typeBraceCount = result.typeBraceCount;
182
+ continue;
183
+ }
184
+ typeBraceCount = result.typeBraceCount;
185
+ } else {
186
+ if (skipFirstLine && i + 1 === start) continue;
187
+ const result = processLineInFunctionBody(
188
+ line,
189
+ i,
190
+ functionLine,
191
+ braceCount,
192
+ lines,
193
+ nodeType
194
+ );
195
+ if (result.end !== null) return result.end;
196
+ braceCount = result.braceCount;
197
+ }
198
+ }
199
+ return end;
200
+ }
201
+
202
+ /**
203
+ * Finds the start line for a named function
204
+ */
205
+ export function findNamedFunctionStart(lines, functionLine, functionName) {
206
+ const startLine = Math.max(0, functionLine - 50);
207
+ const patterns = [
208
+ /(?:export\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[<(]/,
209
+ /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:\([^)]*\)\s*)?(?:=>|function)/,
210
+ /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:\([^)]*\)\s*)?(?:=>|function)/,
211
+ /export\s+default\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[<(]/,
212
+ /(?:export\s+default\s+|const\s+)([A-Z][a-zA-Z0-9_$]*)\s*[:=]\s*(?:\([^)]*\)\s*)?=>/,
213
+ ];
214
+ let start = functionLine;
215
+ for (let i = functionLine - 1; i >= startLine; i -= 1) {
216
+ const line = lines[i];
217
+ for (const pattern of patterns) {
218
+ const match = line.match(pattern);
219
+ if (match && match[1] === functionName) {
220
+ start = i + 1;
221
+ break;
222
+ }
223
+ }
224
+ if (start !== functionLine) break;
225
+ }
226
+ return start;
227
+ }