@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.
- package/CHANGELOG.md +122 -0
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/assets/prettify.css +1 -0
- package/assets/prettify.js +2 -0
- package/assets/sort-arrow-sprite.png +0 -0
- package/complexity-breakdown.js +53 -0
- package/decision-points/ast-utils.js +127 -0
- package/decision-points/decision-type.js +92 -0
- package/decision-points/function-matching.js +185 -0
- package/decision-points/in-params.js +262 -0
- package/decision-points/index.js +6 -0
- package/decision-points/node-helpers.js +89 -0
- package/decision-points/parent-map.js +62 -0
- package/decision-points/parse-main.js +101 -0
- package/decision-points/ternary-multiline.js +86 -0
- package/export-generators/helpers.js +309 -0
- package/export-generators/index.js +143 -0
- package/export-generators/md-exports.js +160 -0
- package/export-generators/txt-exports.js +262 -0
- package/function-boundaries/arrow-brace-body.js +302 -0
- package/function-boundaries/arrow-helpers.js +93 -0
- package/function-boundaries/arrow-jsx.js +73 -0
- package/function-boundaries/arrow-object-literal.js +65 -0
- package/function-boundaries/arrow-single-expr.js +72 -0
- package/function-boundaries/brace-scanning.js +151 -0
- package/function-boundaries/index.js +67 -0
- package/function-boundaries/named-helpers.js +227 -0
- package/function-boundaries/parse-utils.js +456 -0
- package/function-extraction/ast-utils.js +112 -0
- package/function-extraction/extract-callback.js +65 -0
- package/function-extraction/extract-from-eslint.js +91 -0
- package/function-extraction/extract-name-ast.js +133 -0
- package/function-extraction/extract-name-regex.js +267 -0
- package/function-extraction/index.js +6 -0
- package/function-extraction/utils.js +29 -0
- package/function-hierarchy.js +427 -0
- package/html-generators/about.js +75 -0
- package/html-generators/file-boundary-builders.js +36 -0
- package/html-generators/file-breakdown.js +412 -0
- package/html-generators/file-data.js +50 -0
- package/html-generators/file-helpers.js +100 -0
- package/html-generators/file-javascript.js +430 -0
- package/html-generators/file-line-render.js +160 -0
- package/html-generators/file.css +370 -0
- package/html-generators/file.js +207 -0
- package/html-generators/folder.js +424 -0
- package/html-generators/index.js +6 -0
- package/html-generators/main-index.js +346 -0
- package/html-generators/shared.css +471 -0
- package/html-generators/utils.js +15 -0
- package/index.js +36 -0
- package/integration/eslint/index.js +94 -0
- package/integration/threshold/index.js +45 -0
- package/package.json +64 -0
- package/report/cli.js +58 -0
- 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
|
+
}
|