@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,133 @@
1
+ /**
2
+ * AST-based function name extraction.
3
+ */
4
+
5
+ import { parseAST, findAllFunctions, findAllNodesByType, findContainingNode, readFileIfExists, getNodeLine } from './ast-utils.js';
6
+ import { identifyCallbackContext } from './extract-callback.js';
7
+
8
+ export function isFirstArgOfDirectCall(funcNode, ast) {
9
+ const callExpressions = findAllNodesByType(ast, 'CallExpression');
10
+ const containingCall = findContainingNode(funcNode, callExpressions);
11
+ if (
12
+ !containingCall ||
13
+ !containingCall.arguments ||
14
+ containingCall.arguments[0] !== funcNode
15
+ ) {
16
+ return false;
17
+ }
18
+ return containingCall.callee.type === 'Identifier';
19
+ }
20
+
21
+ export function findVariableDeclaratorForFunction(funcNode, ast) {
22
+ const variableDeclarators = findAllNodesByType(ast, 'VariableDeclarator');
23
+ for (const declarator of variableDeclarators) {
24
+ if (declarator.init === funcNode) return declarator;
25
+ if (
26
+ funcNode.range &&
27
+ declarator.range &&
28
+ declarator.range[0] <= funcNode.range[0] &&
29
+ declarator.range[1] >= funcNode.range[1]
30
+ ) {
31
+ if (isFirstArgOfDirectCall(funcNode, ast)) return declarator;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+
37
+ export function extractNameFromVariableDeclarator(funcNode, ast) {
38
+ const declarator = findVariableDeclaratorForFunction(funcNode, ast);
39
+ if (declarator && declarator.id) return declarator.id.name;
40
+ return null;
41
+ }
42
+
43
+ export function extractNameFromProperty(funcNode, ast) {
44
+ const properties = findAllNodesByType(ast, 'Property');
45
+ const containingProperty = findContainingNode(funcNode, properties);
46
+ if (containingProperty && containingProperty.key) {
47
+ if (containingProperty.key.type === 'Identifier') return containingProperty.key.name;
48
+ if (containingProperty.key.type === 'Literal') return String(containingProperty.key.value);
49
+ }
50
+ return null;
51
+ }
52
+
53
+ export function extractNameFromMethodDefinition(funcNode, ast) {
54
+ const methodDefinitions = findAllNodesByType(ast, 'MethodDefinition');
55
+ const containingMethod = findContainingNode(funcNode, methodDefinitions);
56
+ if (containingMethod && containingMethod.key && containingMethod.key.type === 'Identifier') {
57
+ return containingMethod.key.name;
58
+ }
59
+ return null;
60
+ }
61
+
62
+ export function extractNameFromASTNode(funcNode, ast) {
63
+ if (funcNode.type === 'FunctionDeclaration' && funcNode.id) return funcNode.id.name;
64
+ const varName = extractNameFromVariableDeclarator(funcNode, ast);
65
+ if (varName) return varName;
66
+ const propName = extractNameFromProperty(funcNode, ast);
67
+ if (propName) return propName;
68
+ const methodName = extractNameFromMethodDefinition(funcNode, ast);
69
+ if (methodName) return methodName;
70
+ return null;
71
+ }
72
+
73
+ export function doNodeTypesMatch(eslintNodeType, astNodeType) {
74
+ return (eslintNodeType === 'FunctionDeclaration' && astNodeType === 'FunctionDeclaration') ||
75
+ (eslintNodeType === 'ArrowFunctionExpression' && astNodeType === 'ArrowFunctionExpression') ||
76
+ (eslintNodeType === 'FunctionExpression' && astNodeType === 'FunctionExpression');
77
+ }
78
+
79
+ export function findArrowFunctionLine(astFunc, sourceCode) {
80
+ if (!astFunc.range) return null;
81
+ const funcCode = sourceCode.substring(astFunc.range[0], astFunc.range[1]);
82
+ const arrowIndex = funcCode.indexOf('=>');
83
+ if (arrowIndex === -1) return null;
84
+ const linesBeforeArrow = sourceCode.substring(0, astFunc.range[0] + arrowIndex).split('\n');
85
+ return linesBeforeArrow.length;
86
+ }
87
+
88
+ export function matchesArrowFunctionLine(astFunc, lineNumber, sourceCode) {
89
+ const arrowLine = findArrowFunctionLine(astFunc, sourceCode);
90
+ return arrowLine !== null && arrowLine === lineNumber;
91
+ }
92
+
93
+ export function findMatchingASTFunction(
94
+ astFunctions,
95
+ lineNumber,
96
+ nodeType,
97
+ sourceCode
98
+ ) {
99
+ for (const astFunc of astFunctions) {
100
+ if (!doNodeTypesMatch(nodeType, astFunc.type)) continue;
101
+ if (astFunc.type === 'ArrowFunctionExpression') {
102
+ if (matchesArrowFunctionLine(astFunc, lineNumber, sourceCode)) return astFunc;
103
+ } else {
104
+ if (getNodeLine(astFunc) === lineNumber) return astFunc;
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+
110
+ export function extractFunctionNameAST(filePath, lineNumber, nodeType, projectRoot) {
111
+ try {
112
+ const sourceCode = readFileIfExists(filePath, projectRoot);
113
+ if (!sourceCode) return null;
114
+ const ast = parseAST(sourceCode, filePath);
115
+ if (!ast) return null;
116
+ const astFunctions = findAllFunctions(ast);
117
+ const matchingFunc = findMatchingASTFunction(
118
+ astFunctions,
119
+ lineNumber,
120
+ nodeType,
121
+ sourceCode
122
+ );
123
+ if (!matchingFunc) return null;
124
+ const functionName = extractNameFromASTNode(matchingFunc, ast);
125
+ if (functionName) return functionName;
126
+ const callbackType = identifyCallbackContext(matchingFunc, ast);
127
+ if (callbackType) return callbackType;
128
+ if (matchingFunc.type === 'ArrowFunctionExpression') return 'anonymous arrow function';
129
+ return 'anonymous';
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Regex-based function name extraction (fallback when AST fails).
3
+ * getFunctionNameForLine is optional; when provided,
4
+ * findParentFunctionWithFallback uses it for recursive lookup.
5
+ */
6
+
7
+ const FUNCTION_PATTERNS = [
8
+ /(?:export\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[<(]/,
9
+ /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:\([^)]*\)\s*)?(?:=>|function)/,
10
+ /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:\([^)]*\)\s*)?(?:=>|function)/,
11
+ ];
12
+
13
+ export function findParentFunction(lines, lineNumber, maxLookBack = 50) {
14
+ let parentFunction = null;
15
+ let closestFunctionLine = -1;
16
+ for (
17
+ let lookBack = 1;
18
+ lookBack <= maxLookBack && lineNumber - lookBack >= 1;
19
+ lookBack += 1
20
+ ) {
21
+ const checkLine = lineNumber - lookBack;
22
+ const checkLineContent = lines[checkLine - 1] || '';
23
+ for (const funcPattern of FUNCTION_PATTERNS) {
24
+ const funcMatch = checkLineContent.match(funcPattern);
25
+ if (funcMatch && funcMatch[1]) {
26
+ if (closestFunctionLine === -1 || checkLine > closestFunctionLine) {
27
+ parentFunction = funcMatch[1];
28
+ closestFunctionLine = checkLine;
29
+ }
30
+ }
31
+ }
32
+ }
33
+ return parentFunction;
34
+ }
35
+
36
+ export function findParentFunctionWithFallback(
37
+ filePath,
38
+ lineNumber,
39
+ projectRoot,
40
+ lines,
41
+ getFunctionNameForLine
42
+ ) {
43
+ const parentFunction = findParentFunction(lines, lineNumber);
44
+ if (parentFunction) return parentFunction;
45
+ if (typeof getFunctionNameForLine === 'function') {
46
+ return getFunctionNameForLine(
47
+ filePath,
48
+ lineNumber - 5,
49
+ 'FunctionDeclaration',
50
+ projectRoot
51
+ );
52
+ }
53
+ return 'anonymous';
54
+ }
55
+
56
+ export function formatCallbackName(callbackType, parentFunction) {
57
+ if (
58
+ parentFunction &&
59
+ parentFunction !== 'anonymous' &&
60
+ parentFunction !== 'unknown'
61
+ ) {
62
+ return `${parentFunction} (${callbackType})`;
63
+ }
64
+ return callbackType;
65
+ }
66
+
67
+ export function checkNamedArrowFunction(prevLine, currentLine) {
68
+ const combinedContext = (prevLine + ' ' + currentLine).trim();
69
+ const namedArrowPattern =
70
+ /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:\([^)]*\)\s*)?=>/;
71
+ const namedMatch = combinedContext.match(namedArrowPattern);
72
+ return namedMatch && namedMatch[1] ? namedMatch[1] : null;
73
+ }
74
+
75
+ export function findMethodCallCallback(
76
+ beforeArrow,
77
+ filePath,
78
+ lineNumber,
79
+ projectRoot,
80
+ lines,
81
+ getFunctionNameForLine
82
+ ) {
83
+ const methodCallMatch = beforeArrow.match(/\.(\w+)\s*\(/);
84
+ if (!methodCallMatch || !methodCallMatch[1]) return null;
85
+ const callbackType = methodCallMatch[1];
86
+ const parentFunction = findParentFunctionWithFallback(
87
+ filePath,
88
+ lineNumber,
89
+ projectRoot,
90
+ lines,
91
+ getFunctionNameForLine
92
+ );
93
+ return formatCallbackName(callbackType, parentFunction);
94
+ }
95
+
96
+ export function findFunctionCallCallback(
97
+ beforeArrow,
98
+ filePath,
99
+ lineNumber,
100
+ projectRoot,
101
+ lines,
102
+ getFunctionNameForLine
103
+ ) {
104
+ const functionCallMatch = beforeArrow.match(
105
+ /(?!if|for|while|switch)\b(\w+)\s*\(/
106
+ );
107
+ if (!functionCallMatch || !functionCallMatch[1]) return null;
108
+ const callbackType = functionCallMatch[1];
109
+ const parentFunction = findParentFunctionWithFallback(
110
+ filePath,
111
+ lineNumber,
112
+ projectRoot,
113
+ lines,
114
+ getFunctionNameForLine
115
+ );
116
+ return formatCallbackName(callbackType, parentFunction);
117
+ }
118
+
119
+ export function findCallbackWithFallbackPatterns(
120
+ combinedContext,
121
+ filePath,
122
+ lineNumber,
123
+ projectRoot,
124
+ lines,
125
+ getFunctionNameForLine
126
+ ) {
127
+ const arrowFunctionPatterns = [
128
+ /\.(\w+)\s*\([^)]*\)\s*=>/,
129
+ /\.(\w+)\s*\([^)]*=>/,
130
+ /(\w+)\s*\([^)]*\)\s*=>/,
131
+ /(\w+)\s*\([^)]*=>/,
132
+ ];
133
+ for (const pattern of arrowFunctionPatterns) {
134
+ const match = combinedContext.match(pattern);
135
+ if (match && match[1]) {
136
+ const parentFunction = findParentFunctionWithFallback(
137
+ filePath,
138
+ lineNumber,
139
+ projectRoot,
140
+ lines,
141
+ getFunctionNameForLine
142
+ );
143
+ return formatCallbackName(match[1], parentFunction);
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+
149
+ export function handleArrowFunctionFinalFallback(
150
+ filePath,
151
+ lineNumber,
152
+ projectRoot,
153
+ lines,
154
+ getFunctionNameForLine
155
+ ) {
156
+ const parentFunction = findParentFunctionWithFallback(
157
+ filePath,
158
+ lineNumber,
159
+ projectRoot,
160
+ lines,
161
+ getFunctionNameForLine
162
+ );
163
+ if (
164
+ parentFunction &&
165
+ parentFunction !== 'anonymous' &&
166
+ parentFunction !== 'unknown'
167
+ ) {
168
+ return `${parentFunction} (arrow function)`;
169
+ }
170
+ return 'anonymous arrow function';
171
+ }
172
+
173
+ export function tryFindCallbackFromCurrentLine(
174
+ currentLine,
175
+ filePath,
176
+ lineNumber,
177
+ projectRoot,
178
+ lines,
179
+ getFunctionNameForLine
180
+ ) {
181
+ const arrowIndex = currentLine.indexOf('=>');
182
+ if (arrowIndex === -1) return null;
183
+ const beforeArrow = currentLine.substring(0, arrowIndex);
184
+ const methodCallback = findMethodCallCallback(
185
+ beforeArrow,
186
+ filePath,
187
+ lineNumber,
188
+ projectRoot,
189
+ lines,
190
+ getFunctionNameForLine
191
+ );
192
+ if (methodCallback) return methodCallback;
193
+ return findFunctionCallCallback(
194
+ beforeArrow,
195
+ filePath,
196
+ lineNumber,
197
+ projectRoot,
198
+ lines,
199
+ getFunctionNameForLine
200
+ );
201
+ }
202
+
203
+ export function handleArrowFunctionExpression(
204
+ lines,
205
+ lineNumber,
206
+ filePath,
207
+ projectRoot,
208
+ getFunctionNameForLine
209
+ ) {
210
+ const lineIndex = lineNumber - 1;
211
+ const currentLine = lines[lineIndex] || '';
212
+ const prevLine = lines[lineIndex - 1] || '';
213
+ const namedArrow = checkNamedArrowFunction(prevLine, currentLine);
214
+ if (namedArrow) return namedArrow;
215
+ const callbackFromLine = tryFindCallbackFromCurrentLine(
216
+ currentLine,
217
+ filePath,
218
+ lineNumber,
219
+ projectRoot,
220
+ lines,
221
+ getFunctionNameForLine
222
+ );
223
+ if (callbackFromLine) return callbackFromLine;
224
+ const combinedContext = (prevLine + ' ' + currentLine).trim();
225
+ const fallbackCallback = findCallbackWithFallbackPatterns(
226
+ combinedContext,
227
+ filePath,
228
+ lineNumber,
229
+ projectRoot,
230
+ lines,
231
+ getFunctionNameForLine
232
+ );
233
+ if (fallbackCallback) return fallbackCallback;
234
+ return handleArrowFunctionFinalFallback(
235
+ filePath,
236
+ lineNumber,
237
+ projectRoot,
238
+ lines,
239
+ getFunctionNameForLine
240
+ );
241
+ }
242
+
243
+ export function handleFunctionDeclaration(lines, lineNumber) {
244
+ const startLine = Math.max(0, lineNumber - 50);
245
+ const context = lines.slice(startLine, lineNumber).join('\n');
246
+ const patterns = [
247
+ /(?:export\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[<(]/,
248
+ /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:\([^)]*\)\s*)?(?:=>|function)/,
249
+ /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:\([^)]*\)\s*)?(?:=>|function)/,
250
+ /export\s+default\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[<(]/,
251
+ /(?:export\s+default\s+|const\s+)([A-Z][a-zA-Z0-9_$]*)\s*[:=]\s*(?:\([^)]*\)\s*)?=>/,
252
+ ];
253
+ let lastMatch = null;
254
+ let lastIndex = -1;
255
+ for (const pattern of patterns) {
256
+ const matches = [...context.matchAll(new RegExp(pattern.source, 'g'))];
257
+ if (matches.length > 0) {
258
+ const match = matches[matches.length - 1];
259
+ const matchIndex = context.lastIndexOf(match[0]);
260
+ if (matchIndex > lastIndex) {
261
+ lastMatch = match[1];
262
+ lastIndex = matchIndex;
263
+ }
264
+ }
265
+ }
266
+ return lastMatch || 'anonymous';
267
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Function extraction module — public API.
3
+ */
4
+
5
+ export { extractFunctionName, extractFunctionsFromESLintResults, processComplexityMessage } from './extract-from-eslint.js';
6
+ export { getComplexityLevel, getDirectory, getBaseFunctionName } from './utils.js';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Complexity and path utilities.
3
+ */
4
+
5
+ export function getComplexityLevel(complexity) {
6
+ const num = parseInt(complexity, 10);
7
+ if (num >= 20) return 'low';
8
+ if (num >= 15) return 'medium';
9
+ if (num > 10) return 'high';
10
+ if (num > 6) return 'acceptable';
11
+ return 'good';
12
+ }
13
+
14
+ export function getDirectory(filePath) {
15
+ const parts = filePath.split('/');
16
+ if (parts.length <= 1) return filePath;
17
+ return parts.slice(0, -1).join('/');
18
+ }
19
+
20
+ export function getBaseFunctionName(name) {
21
+ if (!name) return 'unknown';
22
+ let baseName = String(name).trim();
23
+ let prev = '';
24
+ while (prev !== baseName) {
25
+ prev = baseName;
26
+ baseName = baseName.replace(/\s*\([^)]+\)\s*$/g, '').replace(/\s*→\s*[^→]*$/g, '').trim();
27
+ }
28
+ return baseName || 'unknown';
29
+ }