@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
package/report/cli.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI entry point for complexity-report
5
+ * Parses command-line arguments and runs the report generator
6
+ */
7
+
8
+ import { generateComplexityReport } from './index.js';
9
+
10
+ function getCliFlags() {
11
+ const argv = process.argv;
12
+ return {
13
+ showAllInitially: argv.includes('--show-all') || argv.includes('--all'),
14
+ showAllColumnsInitially: argv.includes('--show-all-columns'),
15
+ hideTableInitially: argv.includes('--hide-table'),
16
+ hideLinesInitially: argv.includes('--no-lines'),
17
+ hideHighlightsInitially: argv.includes('--no-highlights'),
18
+ shouldExport: argv.includes('--export') || argv.includes('--exports'),
19
+ cwd: getCwdFlag(argv),
20
+ outputDir: getOutputDirFlag(argv),
21
+ };
22
+ }
23
+
24
+ function getCwdFlag(argv) {
25
+ const cwdIndex = argv.findIndex(arg => arg === '--cwd');
26
+ if (cwdIndex !== -1 && argv[cwdIndex + 1]) {
27
+ return argv[cwdIndex + 1];
28
+ }
29
+ return process.cwd();
30
+ }
31
+
32
+ function getOutputDirFlag(argv) {
33
+ const outputIndex = argv.findIndex(arg => arg === '--output-dir' || arg === '--output');
34
+ if (outputIndex !== -1 && argv[outputIndex + 1]) {
35
+ return argv[outputIndex + 1];
36
+ }
37
+ return undefined;
38
+ }
39
+
40
+ async function main() {
41
+ const flags = getCliFlags();
42
+
43
+ await generateComplexityReport({
44
+ cwd: flags.cwd,
45
+ outputDir: flags.outputDir,
46
+ showAllInitially: flags.showAllInitially,
47
+ showAllColumnsInitially: flags.showAllColumnsInitially,
48
+ hideTableInitially: flags.hideTableInitially,
49
+ hideLinesInitially: flags.hideLinesInitially,
50
+ hideHighlightsInitially: flags.hideHighlightsInitially,
51
+ shouldExport: flags.shouldExport,
52
+ });
53
+ }
54
+
55
+ main().catch((error) => {
56
+ console.error('Error generating complexity report:', error);
57
+ process.exit(1);
58
+ });
@@ -0,0 +1,559 @@
1
+ import { writeFileSync, mkdirSync, copyFileSync, readFileSync, existsSync } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, resolve } from 'path';
4
+
5
+ import {
6
+ runESLintComplexityCheck,
7
+ findESLintConfig,
8
+ getComplexityVariant,
9
+ } from '../integration/eslint/index.js';
10
+ import {
11
+ extractFunctionsFromESLintResults,
12
+ getComplexityLevel,
13
+ getDirectory,
14
+ getBaseFunctionName,
15
+ } from '../function-extraction/index.js';
16
+ import { findFunctionBoundaries } from '../function-boundaries/index.js';
17
+ import { parseDecisionPointsAST } from '../decision-points/index.js';
18
+ import { calculateComplexityBreakdown } from '../complexity-breakdown.js';
19
+ import {
20
+ formatFunctionHierarchy,
21
+ setEscapeHtml,
22
+ } from '../function-hierarchy.js';
23
+ import {
24
+ escapeHtml,
25
+ generateAboutPageHTML,
26
+ generateMainIndexHTML,
27
+ generateFolderHTML,
28
+ generateFileHTML,
29
+ } from '../html-generators/index.js';
30
+ import { getComplexityThreshold } from '../integration/threshold/index.js';
31
+ import { generateAllExports } from '../export-generators/index.js';
32
+
33
+ /**
34
+ * Calculates decision point totals across all functions
35
+ */
36
+ async function calculateDecisionPointTotals(
37
+ allFunctions,
38
+ projectRoot,
39
+ findFunctionBoundaries,
40
+ parseDecisionPoints
41
+ ) {
42
+ const controlFlowTypes = [
43
+ 'if',
44
+ 'else if',
45
+ 'for',
46
+ 'for...of',
47
+ 'for...in',
48
+ 'while',
49
+ 'do...while',
50
+ 'switch',
51
+ 'case',
52
+ 'catch',
53
+ ];
54
+ const expressionTypes = ['ternary', '&&', '||', '??', '?.'];
55
+ const functionParameterTypes = ['default parameter'];
56
+
57
+ let controlFlowTotal = 0;
58
+ let expressionsTotal = 0;
59
+ let functionParametersTotal = 0;
60
+
61
+ const fileToFunctions = new Map();
62
+ allFunctions.forEach((func) => {
63
+ if (!fileToFunctions.has(func.file)) fileToFunctions.set(func.file, []);
64
+ fileToFunctions.get(func.file).push(func);
65
+ });
66
+
67
+ for (const [filePath, functions] of fileToFunctions.entries()) {
68
+ const fullPath = resolve(projectRoot, filePath);
69
+ if (!existsSync(fullPath)) continue;
70
+ try {
71
+ const sourceCode = readFileSync(fullPath, 'utf-8');
72
+ const functionBoundaries = findFunctionBoundaries(sourceCode, functions);
73
+ const decisionPoints = await parseDecisionPoints(
74
+ sourceCode,
75
+ functionBoundaries,
76
+ functions,
77
+ filePath,
78
+ projectRoot
79
+ );
80
+ const seenLines = new Set();
81
+ functions.forEach((func) => {
82
+ const lineKey = `${filePath}:${func.line}`;
83
+ if (seenLines.has(lineKey)) return;
84
+ seenLines.add(lineKey);
85
+ const breakdown = calculateComplexityBreakdown(func.line, decisionPoints, 1);
86
+ controlFlowTypes.forEach((type) => {
87
+ controlFlowTotal += breakdown.breakdown[type] || 0;
88
+ });
89
+ expressionTypes.forEach((type) => {
90
+ expressionsTotal += breakdown.breakdown[type] || 0;
91
+ });
92
+ functionParameterTypes.forEach((type) => {
93
+ functionParametersTotal += breakdown.breakdown[type] || 0;
94
+ });
95
+ });
96
+ } catch (error) {
97
+ console.warn(
98
+ `Warning: Could not process ${filePath} for decision point totals:`,
99
+ error.message
100
+ );
101
+ }
102
+ }
103
+
104
+ return {
105
+ controlFlow: controlFlowTotal,
106
+ expressions: expressionsTotal,
107
+ functionParameters: functionParametersTotal,
108
+ };
109
+ }
110
+
111
+ function calculateFunctionStatistics(allFunctions, complexityThreshold) {
112
+ const overThreshold = allFunctions.filter(
113
+ (f) => parseInt(f.complexity, 10) > complexityThreshold
114
+ );
115
+ const allFunctionsCount = allFunctions.length;
116
+ const maxComplexity =
117
+ allFunctions.length > 0
118
+ ? Math.max(...allFunctions.map((i) => parseInt(i.complexity, 10)))
119
+ : 0;
120
+ const avgComplexity =
121
+ allFunctions.length > 0
122
+ ? Math.round(
123
+ allFunctions.reduce(
124
+ (sum, i) => sum + parseInt(i.complexity, 10),
125
+ 0
126
+ ) / allFunctions.length
127
+ )
128
+ : 0;
129
+ const withinThreshold = allFunctions.filter(
130
+ (f) => parseInt(f.complexity, 10) <= complexityThreshold
131
+ ).length;
132
+ const withinThresholdPercentage =
133
+ allFunctionsCount > 0
134
+ ? Math.round((withinThreshold / allFunctionsCount) * 100)
135
+ : 100;
136
+ return {
137
+ overThreshold,
138
+ allFunctionsCount,
139
+ maxComplexity,
140
+ avgComplexity,
141
+ withinThreshold,
142
+ withinThresholdPercentage,
143
+ };
144
+ }
145
+
146
+ function groupFunctionsByFolder(allFunctions, complexityThreshold) {
147
+ const folderMap = new Map();
148
+ allFunctions.forEach((func) => {
149
+ const dir = getDirectory(func.file);
150
+ if (!folderMap.has(dir)) folderMap.set(dir, []);
151
+ folderMap.get(dir).push(func);
152
+ });
153
+ return Array.from(folderMap.entries()).map(([dir, functions]) => {
154
+ const totalFunctions = functions.length;
155
+ const withinThreshold = functions.filter(
156
+ (f) => parseInt(f.complexity, 10) <= complexityThreshold
157
+ ).length;
158
+ const percentage =
159
+ totalFunctions > 0
160
+ ? Math.round((withinThreshold / totalFunctions) * 100)
161
+ : 100;
162
+ return {
163
+ directory: dir,
164
+ totalFunctions,
165
+ withinThreshold,
166
+ percentage,
167
+ functions: functions.sort(
168
+ (a, b) => parseInt(b.complexity, 10) - parseInt(a.complexity, 10)
169
+ ),
170
+ };
171
+ }).sort((a, b) => a.directory.localeCompare(b.directory));
172
+ }
173
+
174
+ function groupFunctionsByFile(allFunctions) {
175
+ const fileMap = new Map();
176
+ allFunctions.forEach((func) => {
177
+ if (!fileMap.has(func.file)) fileMap.set(func.file, []);
178
+ fileMap.get(func.file).push(func);
179
+ });
180
+ return fileMap;
181
+ }
182
+
183
+ function copyRequiredFiles(projectRoot, complexityDir, packageRoot) {
184
+ const assetsDir = resolve(packageRoot, 'assets');
185
+ const htmlGeneratorsDir = resolve(packageRoot, 'html-generators');
186
+ const prettifyFiles = [
187
+ { source: resolve(assetsDir, 'prettify.css'), dest: resolve(complexityDir, 'prettify.css') },
188
+ { source: resolve(assetsDir, 'prettify.js'), dest: resolve(complexityDir, 'prettify.js') },
189
+ ];
190
+ prettifyFiles.forEach(({ source, dest }) => {
191
+ try {
192
+ copyFileSync(source, dest);
193
+ } catch (error) {
194
+ console.warn(`Warning: Could not copy ${source}:`, error.message);
195
+ }
196
+ });
197
+ try {
198
+ copyFileSync(
199
+ resolve(assetsDir, 'sort-arrow-sprite.png'),
200
+ resolve(complexityDir, 'sort-arrow-sprite.png')
201
+ );
202
+ } catch (error) {
203
+ console.warn(
204
+ 'Warning: Could not copy sort-arrow-sprite.png:',
205
+ error.message
206
+ );
207
+ }
208
+ try {
209
+ copyFileSync(
210
+ resolve(htmlGeneratorsDir, 'shared.css'),
211
+ resolve(complexityDir, 'shared.css')
212
+ );
213
+ } catch (error) {
214
+ console.warn('Warning: Could not copy shared.css:', error.message);
215
+ }
216
+ try {
217
+ copyFileSync(
218
+ resolve(htmlGeneratorsDir, 'file.css'),
219
+ resolve(complexityDir, 'file.css')
220
+ );
221
+ } catch (error) {
222
+ console.warn('Warning: Could not copy file.css:', error.message);
223
+ }
224
+ }
225
+
226
+ function getCliFlags() {
227
+ const argv = process.argv;
228
+ return {
229
+ showAllInitially: argv.includes('--show-all') || argv.includes('--all'),
230
+ showAllColumnsInitially: argv.includes('--show-all-columns'),
231
+ hideTableInitially: argv.includes('--hide-table'),
232
+ hideLinesInitially: argv.includes('--no-lines'),
233
+ hideHighlightsInitially: argv.includes('--no-highlights'),
234
+ shouldExport: argv.includes('--export') || argv.includes('--exports'),
235
+ };
236
+ }
237
+
238
+ function writeMainReport(projectRoot, complexityDir, packageRoot, html) {
239
+ mkdirSync(complexityDir, { recursive: true });
240
+ copyRequiredFiles(projectRoot, complexityDir, packageRoot);
241
+ writeFileSync(resolve(complexityDir, 'index.html'), html, 'utf-8');
242
+ writeFileSync(
243
+ resolve(complexityDir, 'about.html'),
244
+ generateAboutPageHTML(),
245
+ 'utf-8'
246
+ );
247
+ }
248
+
249
+ async function generateOneFolderHTML(
250
+ folder,
251
+ folders,
252
+ projectRoot,
253
+ complexityDir,
254
+ parseDecisionPointsFn,
255
+ showAllInitially,
256
+ complexityThreshold
257
+ ) {
258
+ if (!folder.directory) return 0;
259
+ const folderPath = folder.directory;
260
+ const folderDir = resolve(complexityDir, ...folderPath.split('/'));
261
+ try {
262
+ mkdirSync(folderDir, { recursive: true });
263
+ const folderDecisionPointTotals = await calculateDecisionPointTotals(
264
+ folder.functions,
265
+ projectRoot,
266
+ findFunctionBoundaries,
267
+ parseDecisionPointsFn
268
+ );
269
+ const folderHTML = generateFolderHTML(
270
+ folder,
271
+ folders,
272
+ showAllInitially,
273
+ getComplexityLevel,
274
+ getBaseFunctionName,
275
+ complexityThreshold,
276
+ folderDecisionPointTotals
277
+ );
278
+ writeFileSync(resolve(folderDir, 'index.html'), folderHTML, 'utf-8');
279
+ return 1;
280
+ } catch (error) {
281
+ console.error(
282
+ `Error generating folder HTML for ${folderPath}:`,
283
+ error.message
284
+ );
285
+ return 0;
286
+ }
287
+ }
288
+
289
+ async function generateOneFileHTML(
290
+ filePath,
291
+ functions,
292
+ projectRoot,
293
+ complexityDir,
294
+ parseDecisionPointsFn,
295
+ showAllColumnsInitially,
296
+ hideTableInitially,
297
+ complexityThreshold,
298
+ hideLinesInitially = false,
299
+ hideHighlightsInitially = false,
300
+ variant = 'classic'
301
+ ) {
302
+ try {
303
+ const fileDir = getDirectory(filePath);
304
+ const fileName = filePath.split('/').pop();
305
+ if (fileDir) {
306
+ mkdirSync(
307
+ resolve(complexityDir, ...fileDir.split('/')),
308
+ { recursive: true }
309
+ );
310
+ }
311
+ const fileHTML = await generateFileHTML(
312
+ filePath,
313
+ functions,
314
+ projectRoot,
315
+ findFunctionBoundaries,
316
+ parseDecisionPointsFn,
317
+ calculateComplexityBreakdown,
318
+ formatFunctionHierarchy,
319
+ getComplexityLevel,
320
+ getDirectory,
321
+ escapeHtml,
322
+ showAllColumnsInitially,
323
+ hideTableInitially,
324
+ complexityThreshold,
325
+ hideLinesInitially,
326
+ hideHighlightsInitially,
327
+ variant
328
+ );
329
+ const fileHTMLPath = fileDir
330
+ ? resolve(complexityDir, ...fileDir.split('/'), `${fileName}.html`)
331
+ : resolve(complexityDir, `${fileName}.html`);
332
+ writeFileSync(fileHTMLPath, fileHTML, 'utf-8');
333
+ return 1;
334
+ } catch (error) {
335
+ console.error(
336
+ `Error generating file HTML for ${filePath}:`,
337
+ error.message
338
+ );
339
+ return 0;
340
+ }
341
+ }
342
+
343
+ function runExportsIfRequested(shouldExport, projectRoot, allFunctions) {
344
+ if (!shouldExport) return;
345
+ try {
346
+ const packageJsonPath = resolve(projectRoot, 'package.json');
347
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
348
+ const exportDirConfig =
349
+ packageJson.complexityReport?.exportDir || 'complexity/reports';
350
+ const exportDir = resolve(projectRoot, exportDirConfig);
351
+ const exportResult = generateAllExports(
352
+ allFunctions,
353
+ projectRoot,
354
+ exportDir
355
+ );
356
+ console.log(`\n✅ Exports generated in: ${exportDirConfig}/`);
357
+ console.log(
358
+ ` Generated ${exportResult.generatedFiles.length} export file(s):`
359
+ );
360
+ exportResult.generatedFiles.forEach((file) =>
361
+ console.log(` - ${file.replace(projectRoot + '/', '')}`)
362
+ );
363
+ } catch (error) {
364
+ console.error('Error generating exports:', error.message);
365
+ console.error(' Exports will be skipped, but HTML report generation will continue.');
366
+ }
367
+ }
368
+
369
+ function printReportSummary(
370
+ stats,
371
+ complexityThreshold,
372
+ foldersGenerated,
373
+ filesGenerated
374
+ ) {
375
+ console.log(`\n✅ Complexity report generated: complexity/index.html`);
376
+ console.log(` About: complexity/about.html`);
377
+ console.log(` Generated ${foldersGenerated} folder HTML file(s)`);
378
+ console.log(` Generated ${filesGenerated} file HTML page(s)`);
379
+ console.log(` Found ${stats.allFunctionsCount} total function(s)`);
380
+ if (stats.overThreshold.length > 0) {
381
+ console.log(
382
+ ` ${stats.overThreshold.length} function(s) with complexity > ${complexityThreshold}`
383
+ );
384
+ console.log('');
385
+ stats.overThreshold.forEach((f) =>
386
+ console.log(
387
+ ` ${f.file}:${f.line} ${f.functionName} (complexity ${f.complexity})`
388
+ )
389
+ );
390
+ }
391
+ if (stats.allFunctionsCount > 0) {
392
+ console.log(
393
+ ` Highest complexity: ${stats.maxComplexity} / Average: ${stats.avgComplexity}`
394
+ );
395
+ }
396
+ console.log(` Using AST-based parser for 100% accuracy`);
397
+ }
398
+
399
+ /**
400
+ * Generate complexity report for a project
401
+ * @param {Object} options - Configuration options
402
+ * @param {string} [options.cwd] - Project root directory (defaults to process.cwd())
403
+ * @param {string} [options.outputDir] - Output directory for reports (defaults to 'complexity' under cwd)
404
+ * @param {boolean} [options.showAllInitially] - Show all functions initially
405
+ * @param {boolean} [options.showAllColumnsInitially] - Show all breakdown columns initially
406
+ * @param {boolean} [options.hideTableInitially] - Hide breakdown table initially
407
+ * @param {boolean} [options.hideLinesInitially] - Hide line numbers initially
408
+ * @param {boolean} [options.hideHighlightsInitially] - Hide highlights initially
409
+ * @param {boolean} [options.shouldExport] - Generate export files
410
+ */
411
+ export async function generateComplexityReport(options = {}) {
412
+ const {
413
+ cwd = process.cwd(),
414
+ outputDir,
415
+ showAllInitially = false,
416
+ showAllColumnsInitially = false,
417
+ hideTableInitially = false,
418
+ hideLinesInitially = false,
419
+ hideHighlightsInitially = false,
420
+ shouldExport = false,
421
+ } = options;
422
+
423
+ // Get package root from this file's location
424
+ const __filename = fileURLToPath(import.meta.url);
425
+ const __dirname = dirname(__filename);
426
+ const packageRoot = resolve(__dirname, '..');
427
+
428
+ // Use provided cwd or default to process.cwd()
429
+ const projectRoot = resolve(cwd);
430
+
431
+ setEscapeHtml(escapeHtml);
432
+
433
+ const complexityThreshold = getComplexityThreshold(projectRoot);
434
+ const configPath = findESLintConfig(projectRoot);
435
+ const variant = configPath ? getComplexityVariant(configPath) : 'classic';
436
+
437
+ const eslintResults = await runESLintComplexityCheck(projectRoot);
438
+ const allFunctions = extractFunctionsFromESLintResults(eslintResults, projectRoot);
439
+ allFunctions.sort((a, b) => parseInt(b.complexity, 10) - parseInt(a.complexity, 10));
440
+
441
+ const stats = calculateFunctionStatistics(allFunctions, complexityThreshold);
442
+ const folders = groupFunctionsByFolder(allFunctions, complexityThreshold);
443
+ const fileMap = groupFunctionsByFile(allFunctions);
444
+
445
+ // Use options passed to function (CLI will override via getCliFlags)
446
+
447
+ const parseDecisionPointsFn = (
448
+ sourceCode,
449
+ functionBoundaries,
450
+ functions,
451
+ filePath,
452
+ projectRoot
453
+ ) =>
454
+ parseDecisionPointsAST(
455
+ sourceCode,
456
+ functionBoundaries,
457
+ functions,
458
+ filePath,
459
+ projectRoot,
460
+ { variant }
461
+ );
462
+
463
+ const decisionPointTotals = await calculateDecisionPointTotals(
464
+ allFunctions,
465
+ projectRoot,
466
+ findFunctionBoundaries,
467
+ parseDecisionPointsFn
468
+ );
469
+
470
+ const html = generateMainIndexHTML(
471
+ folders,
472
+ stats.allFunctionsCount,
473
+ stats.overThreshold,
474
+ stats.maxComplexity,
475
+ stats.avgComplexity,
476
+ showAllInitially,
477
+ complexityThreshold,
478
+ decisionPointTotals,
479
+ stats.withinThreshold,
480
+ stats.withinThresholdPercentage
481
+ );
482
+
483
+ const complexityDir = outputDir
484
+ ? resolve(projectRoot, outputDir)
485
+ : resolve(projectRoot, 'complexity');
486
+ writeMainReport(projectRoot, complexityDir, packageRoot, html);
487
+
488
+ const folderPromises = folders.map((folder) =>
489
+ generateOneFolderHTML(
490
+ folder,
491
+ folders,
492
+ projectRoot,
493
+ complexityDir,
494
+ parseDecisionPointsFn,
495
+ showAllInitially,
496
+ complexityThreshold
497
+ )
498
+ );
499
+ const foldersGenerated = (await Promise.all(folderPromises)).reduce(
500
+ (a, b) => a + b,
501
+ 0
502
+ );
503
+
504
+ const filePromises = Array.from(fileMap.entries()).map(
505
+ ([filePath, functions]) =>
506
+ generateOneFileHTML(
507
+ filePath,
508
+ functions,
509
+ projectRoot,
510
+ complexityDir,
511
+ parseDecisionPointsFn,
512
+ showAllColumnsInitially,
513
+ hideTableInitially,
514
+ complexityThreshold,
515
+ hideLinesInitially,
516
+ hideHighlightsInitially,
517
+ variant
518
+ )
519
+ );
520
+ const filesGenerated = (await Promise.all(filePromises)).reduce(
521
+ (a, b) => a + b,
522
+ 0
523
+ );
524
+
525
+ runExportsIfRequested(shouldExport, projectRoot, allFunctions);
526
+ printReportSummary(stats, complexityThreshold, foldersGenerated, filesGenerated);
527
+
528
+ return {
529
+ stats,
530
+ folders,
531
+ complexityDir,
532
+ };
533
+ }
534
+
535
+ /**
536
+ * Main entry point when run directly (not as a module)
537
+ * Parses CLI flags and calls generateComplexityReport
538
+ */
539
+ async function main() {
540
+ const flags = getCliFlags();
541
+
542
+ await generateComplexityReport({
543
+ cwd: process.cwd(),
544
+ showAllInitially: flags.showAllInitially,
545
+ showAllColumnsInitially: flags.showAllColumnsInitially,
546
+ hideTableInitially: flags.hideTableInitially,
547
+ hideLinesInitially: flags.hideLinesInitially,
548
+ hideHighlightsInitially: flags.hideHighlightsInitially,
549
+ shouldExport: flags.shouldExport,
550
+ });
551
+ }
552
+
553
+ // Only run main if this file is executed directly
554
+ if (import.meta.url === `file://${process.argv[1]}`) {
555
+ main().catch((error) => {
556
+ console.error('Error generating complexity report:', error);
557
+ process.exit(1);
558
+ });
559
+ }