@optave/codegraph 3.1.1 → 3.1.3

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 (72) hide show
  1. package/README.md +6 -6
  2. package/package.json +7 -7
  3. package/src/ast-analysis/engine.js +365 -0
  4. package/src/ast-analysis/metrics.js +118 -0
  5. package/src/ast-analysis/visitor-utils.js +176 -0
  6. package/src/ast-analysis/visitor.js +162 -0
  7. package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
  8. package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
  9. package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
  10. package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
  11. package/src/ast.js +13 -140
  12. package/src/audit.js +2 -87
  13. package/src/batch.js +0 -25
  14. package/src/boundaries.js +1 -1
  15. package/src/branch-compare.js +1 -96
  16. package/src/builder.js +60 -178
  17. package/src/cfg.js +89 -883
  18. package/src/check.js +1 -84
  19. package/src/cli.js +31 -22
  20. package/src/cochange.js +1 -39
  21. package/src/commands/audit.js +88 -0
  22. package/src/commands/batch.js +26 -0
  23. package/src/commands/branch-compare.js +97 -0
  24. package/src/commands/cfg.js +55 -0
  25. package/src/commands/check.js +82 -0
  26. package/src/commands/cochange.js +37 -0
  27. package/src/commands/communities.js +69 -0
  28. package/src/commands/complexity.js +77 -0
  29. package/src/commands/dataflow.js +110 -0
  30. package/src/commands/flow.js +70 -0
  31. package/src/commands/manifesto.js +77 -0
  32. package/src/commands/owners.js +52 -0
  33. package/src/commands/query.js +21 -0
  34. package/src/commands/sequence.js +33 -0
  35. package/src/commands/structure.js +64 -0
  36. package/src/commands/triage.js +49 -0
  37. package/src/communities.js +12 -83
  38. package/src/complexity.js +43 -357
  39. package/src/cycles.js +1 -1
  40. package/src/dataflow.js +12 -665
  41. package/src/db/repository/build-stmts.js +104 -0
  42. package/src/db/repository/cached-stmt.js +19 -0
  43. package/src/db/repository/cfg.js +72 -0
  44. package/src/db/repository/cochange.js +54 -0
  45. package/src/db/repository/complexity.js +20 -0
  46. package/src/db/repository/dataflow.js +17 -0
  47. package/src/db/repository/edges.js +281 -0
  48. package/src/db/repository/embeddings.js +51 -0
  49. package/src/db/repository/graph-read.js +59 -0
  50. package/src/db/repository/index.js +43 -0
  51. package/src/db/repository/nodes.js +247 -0
  52. package/src/db.js +40 -1
  53. package/src/embedder.js +14 -34
  54. package/src/export.js +1 -1
  55. package/src/extractors/javascript.js +130 -5
  56. package/src/flow.js +2 -70
  57. package/src/index.js +30 -20
  58. package/src/{result-formatter.js → infrastructure/result-formatter.js} +1 -1
  59. package/src/kinds.js +1 -0
  60. package/src/manifesto.js +0 -76
  61. package/src/native.js +31 -9
  62. package/src/owners.js +1 -56
  63. package/src/parser.js +53 -2
  64. package/src/queries-cli.js +1 -1
  65. package/src/queries.js +79 -280
  66. package/src/sequence.js +5 -44
  67. package/src/structure.js +16 -75
  68. package/src/triage.js +1 -54
  69. package/src/viewer.js +1 -1
  70. package/src/watcher.js +7 -4
  71. package/src/db/repository.js +0 -134
  72. /package/src/{test-filter.js → infrastructure/test-filter.js} +0 -0
package/src/complexity.js CHANGED
@@ -1,19 +1,23 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import {
4
+ computeLOCMetrics as _computeLOCMetrics,
5
+ computeMaintainabilityIndex as _computeMaintainabilityIndex,
6
+ } from './ast-analysis/metrics.js';
3
7
  import { COMPLEXITY_RULES, HALSTEAD_RULES } from './ast-analysis/rules/index.js';
4
8
  import {
5
9
  findFunctionNode as _findFunctionNode,
6
10
  buildExtensionSet,
7
11
  buildExtToLangMap,
8
12
  } from './ast-analysis/shared.js';
13
+ import { walkWithVisitors } from './ast-analysis/visitor.js';
14
+ import { createComplexityVisitor } from './ast-analysis/visitors/complexity-visitor.js';
9
15
  import { loadConfig } from './config.js';
10
- import { openReadonlyOrFail } from './db.js';
16
+ import { getFunctionNodeId, openReadonlyOrFail } from './db.js';
17
+ import { isTestFile } from './infrastructure/test-filter.js';
11
18
  import { info } from './logger.js';
12
19
  import { paginateResult } from './paginate.js';
13
20
 
14
- import { outputResult } from './result-formatter.js';
15
- import { isTestFile } from './test-filter.js';
16
-
17
21
  // Re-export rules for backward compatibility
18
22
  export { COMPLEXITY_RULES, HALSTEAD_RULES };
19
23
 
@@ -95,80 +99,12 @@ export function computeHalsteadMetrics(functionNode, language) {
95
99
  }
96
100
 
97
101
  // ─── LOC Metrics Computation ──────────────────────────────────────────────
98
-
99
- const C_STYLE_PREFIXES = ['//', '/*', '*', '*/'];
100
-
101
- const COMMENT_PREFIXES = new Map([
102
- ['javascript', C_STYLE_PREFIXES],
103
- ['typescript', C_STYLE_PREFIXES],
104
- ['tsx', C_STYLE_PREFIXES],
105
- ['go', C_STYLE_PREFIXES],
106
- ['rust', C_STYLE_PREFIXES],
107
- ['java', C_STYLE_PREFIXES],
108
- ['csharp', C_STYLE_PREFIXES],
109
- ['python', ['#']],
110
- ['ruby', ['#']],
111
- ['php', ['//', '#', '/*', '*', '*/']],
112
- ]);
113
-
114
- /**
115
- * Compute LOC metrics from a function node's source text.
116
- *
117
- * @param {object} functionNode - tree-sitter node
118
- * @param {string} [language] - Language ID (falls back to C-style prefixes)
119
- * @returns {{ loc: number, sloc: number, commentLines: number }}
120
- */
121
- export function computeLOCMetrics(functionNode, language) {
122
- const text = functionNode.text;
123
- const lines = text.split('\n');
124
- const loc = lines.length;
125
- const prefixes = (language && COMMENT_PREFIXES.get(language)) || C_STYLE_PREFIXES;
126
-
127
- let commentLines = 0;
128
- let blankLines = 0;
129
-
130
- for (const line of lines) {
131
- const trimmed = line.trim();
132
- if (trimmed === '') {
133
- blankLines++;
134
- } else if (prefixes.some((p) => trimmed.startsWith(p))) {
135
- commentLines++;
136
- }
137
- }
138
-
139
- const sloc = Math.max(1, loc - blankLines - commentLines);
140
- return { loc, sloc, commentLines };
141
- }
102
+ // Delegated to ast-analysis/metrics.js; re-exported for backward compatibility.
103
+ export const computeLOCMetrics = _computeLOCMetrics;
142
104
 
143
105
  // ─── Maintainability Index ────────────────────────────────────────────────
144
-
145
- /**
146
- * Compute normalized Maintainability Index (0-100 scale).
147
- *
148
- * Original SEI formula: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
149
- * Microsoft normalization: max(0, min(100, MI * 100/171))
150
- *
151
- * @param {number} volume - Halstead volume
152
- * @param {number} cyclomatic - Cyclomatic complexity
153
- * @param {number} sloc - Source lines of code
154
- * @param {number} [commentRatio] - Comment ratio (0-1), optional
155
- * @returns {number} Normalized MI (0-100)
156
- */
157
- export function computeMaintainabilityIndex(volume, cyclomatic, sloc, commentRatio) {
158
- // Guard against zero/negative values in logarithms
159
- const safeVolume = Math.max(volume, 1);
160
- const safeSLOC = Math.max(sloc, 1);
161
-
162
- let mi = 171 - 5.2 * Math.log(safeVolume) - 0.23 * cyclomatic - 16.2 * Math.log(safeSLOC);
163
-
164
- if (commentRatio != null && commentRatio > 0) {
165
- mi += 50 * Math.sin(Math.sqrt(2.4 * commentRatio));
166
- }
167
-
168
- // Microsoft normalization: 0-100 scale
169
- const normalized = Math.max(0, Math.min(100, (mi * 100) / 171));
170
- return +normalized.toFixed(1);
171
- }
106
+ // Delegated to ast-analysis/metrics.js; re-exported for backward compatibility.
107
+ export const computeMaintainabilityIndex = _computeMaintainabilityIndex;
172
108
 
173
109
  // ─── Algorithm: Single-Traversal DFS ──────────────────────────────────────
174
110
 
@@ -346,6 +282,8 @@ export function computeFunctionComplexity(functionNode, language) {
346
282
  * traversal, avoiding two separate DFS walks per function node at build time.
347
283
  * LOC is text-based (not tree-based) and computed separately (very cheap).
348
284
  *
285
+ * Now delegates to the complexity visitor via the unified walker.
286
+ *
349
287
  * @param {object} functionNode - tree-sitter node for the function
350
288
  * @param {string} langId - Language ID (e.g. 'javascript', 'python')
351
289
  * @returns {{ cognitive: number, cyclomatic: number, maxNesting: number, halstead: object|null, loc: object, mi: number } | null}
@@ -355,207 +293,34 @@ export function computeAllMetrics(functionNode, langId) {
355
293
  if (!cRules) return null;
356
294
  const hRules = HALSTEAD_RULES.get(langId);
357
295
 
358
- // ── Complexity state ──
359
- let cognitive = 0;
360
- let cyclomatic = 1; // McCabe starts at 1
361
- let maxNesting = 0;
362
-
363
- // ── Halstead state ──
364
- const operators = hRules ? new Map() : null;
365
- const operands = hRules ? new Map() : null;
366
-
367
- function walk(node, nestingLevel, isTopFunction, halsteadSkip) {
368
- if (!node) return;
369
-
370
- const type = node.type;
371
-
372
- // ── Halstead classification ──
373
- // Propagate skip through type-annotation subtrees (e.g. TS generics, Java type params)
374
- const skipH = halsteadSkip || (hRules ? hRules.skipTypes.has(type) : false);
375
- if (hRules && !skipH) {
376
- // Compound operators (non-leaf): count node type as operator
377
- if (hRules.compoundOperators.has(type)) {
378
- operators.set(type, (operators.get(type) || 0) + 1);
379
- }
380
- // Leaf nodes: classify as operator or operand
381
- if (node.childCount === 0) {
382
- if (hRules.operatorLeafTypes.has(type)) {
383
- operators.set(type, (operators.get(type) || 0) + 1);
384
- } else if (hRules.operandLeafTypes.has(type)) {
385
- const text = node.text;
386
- operands.set(text, (operands.get(text) || 0) + 1);
387
- }
388
- }
389
- }
390
-
391
- // ── Complexity: track nesting depth ──
392
- if (nestingLevel > maxNesting) maxNesting = nestingLevel;
393
-
394
- // Handle logical operators in binary expressions
395
- if (type === cRules.logicalNodeType) {
396
- const op = node.child(1)?.type;
397
- if (op && cRules.logicalOperators.has(op)) {
398
- cyclomatic++;
399
- const parent = node.parent;
400
- let sameSequence = false;
401
- if (parent && parent.type === cRules.logicalNodeType) {
402
- const parentOp = parent.child(1)?.type;
403
- if (parentOp === op) sameSequence = true;
404
- }
405
- if (!sameSequence) cognitive++;
406
- for (let i = 0; i < node.childCount; i++) {
407
- walk(node.child(i), nestingLevel, false, skipH);
408
- }
409
- return;
410
- }
411
- }
412
-
413
- // Handle optional chaining (cyclomatic only)
414
- if (type === cRules.optionalChainType) {
415
- cyclomatic++;
416
- }
417
-
418
- // Handle branch/control flow nodes (skip keyword leaf tokens like Ruby's `if`)
419
- if (cRules.branchNodes.has(type) && node.childCount > 0) {
420
- // Pattern A: else clause wraps if (JS/C#/Rust)
421
- if (cRules.elseNodeType && type === cRules.elseNodeType) {
422
- const firstChild = node.namedChild(0);
423
- if (firstChild && firstChild.type === cRules.ifNodeType) {
424
- for (let i = 0; i < node.childCount; i++) {
425
- walk(node.child(i), nestingLevel, false, skipH);
426
- }
427
- return;
428
- }
429
- cognitive++;
430
- for (let i = 0; i < node.childCount; i++) {
431
- walk(node.child(i), nestingLevel, false, skipH);
432
- }
433
- return;
434
- }
435
-
436
- // Pattern B: explicit elif node (Python/Ruby/PHP)
437
- if (cRules.elifNodeType && type === cRules.elifNodeType) {
438
- cognitive++;
439
- cyclomatic++;
440
- for (let i = 0; i < node.childCount; i++) {
441
- walk(node.child(i), nestingLevel, false, skipH);
442
- }
443
- return;
444
- }
445
-
446
- // Detect else-if via Pattern A or C
447
- let isElseIf = false;
448
- if (type === cRules.ifNodeType) {
449
- if (cRules.elseViaAlternative) {
450
- isElseIf =
451
- node.parent?.type === cRules.ifNodeType &&
452
- node.parent.childForFieldName('alternative')?.id === node.id;
453
- } else if (cRules.elseNodeType) {
454
- isElseIf = node.parent?.type === cRules.elseNodeType;
455
- }
456
- }
457
-
458
- if (isElseIf) {
459
- cognitive++;
460
- cyclomatic++;
461
- for (let i = 0; i < node.childCount; i++) {
462
- walk(node.child(i), nestingLevel, false, skipH);
463
- }
464
- return;
465
- }
466
-
467
- // Regular branch node
468
- cognitive += 1 + nestingLevel;
469
- cyclomatic++;
470
-
471
- // Switch-like nodes don't add cyclomatic themselves (cases do)
472
- if (cRules.switchLikeNodes?.has(type)) {
473
- cyclomatic--;
474
- }
475
-
476
- if (cRules.nestingNodes.has(type)) {
477
- for (let i = 0; i < node.childCount; i++) {
478
- walk(node.child(i), nestingLevel + 1, false, skipH);
479
- }
480
- return;
481
- }
482
- }
483
-
484
- // Pattern C plain else: block that is the alternative of an if_statement (Go/Java)
485
- if (
486
- cRules.elseViaAlternative &&
487
- type !== cRules.ifNodeType &&
488
- node.parent?.type === cRules.ifNodeType &&
489
- node.parent.childForFieldName('alternative')?.id === node.id
490
- ) {
491
- cognitive++;
492
- for (let i = 0; i < node.childCount; i++) {
493
- walk(node.child(i), nestingLevel, false, skipH);
494
- }
495
- return;
496
- }
497
-
498
- // Handle case nodes (cyclomatic only, skip keyword leaves)
499
- if (cRules.caseNodes.has(type) && node.childCount > 0) {
500
- cyclomatic++;
501
- }
502
-
503
- // Handle nested function definitions (increase nesting)
504
- if (!isTopFunction && cRules.functionNodes.has(type)) {
505
- for (let i = 0; i < node.childCount; i++) {
506
- walk(node.child(i), nestingLevel + 1, false, skipH);
507
- }
508
- return;
509
- }
296
+ const visitor = createComplexityVisitor(cRules, hRules, { langId });
510
297
 
511
- // Walk children
512
- for (let i = 0; i < node.childCount; i++) {
513
- walk(node.child(i), nestingLevel, false, skipH);
514
- }
515
- }
298
+ const nestingNodes = new Set(cRules.nestingNodes);
299
+ // NOTE: do NOT add functionNodes here in function-level mode the walker
300
+ // walks a single function node, and adding it to nestingNodeTypes would
301
+ // inflate context.nestingLevel by +1 for the entire body.
516
302
 
517
- walk(functionNode, 0, true, false);
518
-
519
- // ── Compute Halstead derived metrics ──
520
- let halstead = null;
521
- if (hRules && operators && operands) {
522
- const n1 = operators.size;
523
- const n2 = operands.size;
524
- let bigN1 = 0;
525
- for (const c of operators.values()) bigN1 += c;
526
- let bigN2 = 0;
527
- for (const c of operands.values()) bigN2 += c;
528
-
529
- const vocabulary = n1 + n2;
530
- const length = bigN1 + bigN2;
531
- const volume = vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
532
- const difficulty = n2 > 0 ? (n1 / 2) * (bigN2 / n2) : 0;
533
- const effort = difficulty * volume;
534
- const bugs = volume / 3000;
535
-
536
- halstead = {
537
- n1,
538
- n2,
539
- bigN1,
540
- bigN2,
541
- vocabulary,
542
- length,
543
- volume: +volume.toFixed(2),
544
- difficulty: +difficulty.toFixed(2),
545
- effort: +effort.toFixed(2),
546
- bugs: +bugs.toFixed(4),
547
- };
548
- }
303
+ const results = walkWithVisitors(functionNode, [visitor], langId, {
304
+ nestingNodeTypes: nestingNodes,
305
+ });
549
306
 
550
- // ── LOC metrics (text-based, cheap) ──
551
- const loc = computeLOCMetrics(functionNode, langId);
307
+ const rawResult = results.complexity;
552
308
 
553
- // ── Maintainability Index ──
554
- const volume = halstead ? halstead.volume : 0;
309
+ // The visitor's finish() in function-level mode returns the raw metrics
310
+ // but without LOC (needs the functionNode text). Compute LOC + MI here.
311
+ const loc = _computeLOCMetrics(functionNode, langId);
312
+ const volume = rawResult.halstead ? rawResult.halstead.volume : 0;
555
313
  const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
556
- const mi = computeMaintainabilityIndex(volume, cyclomatic, loc.sloc, commentRatio);
314
+ const mi = _computeMaintainabilityIndex(volume, rawResult.cyclomatic, loc.sloc, commentRatio);
557
315
 
558
- return { cognitive, cyclomatic, maxNesting, halstead, loc, mi };
316
+ return {
317
+ cognitive: rawResult.cognitive,
318
+ cyclomatic: rawResult.cyclomatic,
319
+ maxNesting: rawResult.maxNesting,
320
+ halstead: rawResult.halstead,
321
+ loc,
322
+ mi,
323
+ };
559
324
  }
560
325
 
561
326
  // ─── Build-Time: Compute Metrics for Changed Files ────────────────────────
@@ -612,10 +377,6 @@ export async function buildComplexityMetrics(db, fileSymbols, rootDir, _engineOp
612
377
  maintainability_index)
613
378
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
614
379
  );
615
- const getNodeId = db.prepare(
616
- "SELECT id FROM nodes WHERE name = ? AND kind IN ('function','method') AND file = ? AND line = ?",
617
- );
618
-
619
380
  let analyzed = 0;
620
381
 
621
382
  const tx = db.transaction(() => {
@@ -662,12 +423,12 @@ export async function buildComplexityMetrics(db, fileSymbols, rootDir, _engineOp
662
423
 
663
424
  // Use pre-computed complexity from native engine if available
664
425
  if (def.complexity) {
665
- const row = getNodeId.get(def.name, relPath, def.line);
666
- if (!row) continue;
426
+ const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
427
+ if (!nodeId) continue;
667
428
  const ch = def.complexity.halstead;
668
429
  const cl = def.complexity.loc;
669
430
  upsert.run(
670
- row.id,
431
+ nodeId,
671
432
  def.complexity.cognitive,
672
433
  def.complexity.cyclomatic,
673
434
  def.complexity.maxNesting ?? 0,
@@ -693,19 +454,19 @@ export async function buildComplexityMetrics(db, fileSymbols, rootDir, _engineOp
693
454
  // Fallback: compute from AST tree
694
455
  if (!tree || !rules) continue;
695
456
 
696
- const funcNode = findFunctionNode(tree.rootNode, def.line, def.endLine, rules);
457
+ const funcNode = _findFunctionNode(tree.rootNode, def.line, def.endLine, rules);
697
458
  if (!funcNode) continue;
698
459
 
699
460
  // Single-pass: complexity + Halstead + LOC + MI in one DFS walk
700
461
  const metrics = computeAllMetrics(funcNode, langId);
701
462
  if (!metrics) continue;
702
463
 
703
- const row = getNodeId.get(def.name, relPath, def.line);
704
- if (!row) continue;
464
+ const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
465
+ if (!nodeId) continue;
705
466
 
706
467
  const h = metrics.halstead;
707
468
  upsert.run(
708
- row.id,
469
+ nodeId,
709
470
  metrics.cognitive,
710
471
  metrics.cyclomatic,
711
472
  metrics.maxNesting,
@@ -1040,78 +801,3 @@ export function* iterComplexity(customDbPath, opts = {}) {
1040
801
  db.close();
1041
802
  }
1042
803
  }
1043
-
1044
- /**
1045
- * Format complexity output for CLI display.
1046
- */
1047
- export function complexity(customDbPath, opts = {}) {
1048
- const data = complexityData(customDbPath, opts);
1049
-
1050
- if (outputResult(data, 'functions', opts)) return;
1051
-
1052
- if (data.functions.length === 0) {
1053
- if (data.summary === null) {
1054
- if (data.hasGraph) {
1055
- console.log(
1056
- '\nNo complexity data found, but a graph exists. Run "codegraph build --no-incremental" to populate complexity metrics.\n',
1057
- );
1058
- } else {
1059
- console.log(
1060
- '\nNo complexity data found. Run "codegraph build" first to analyze your codebase.\n',
1061
- );
1062
- }
1063
- } else {
1064
- console.log('\nNo functions match the given filters.\n');
1065
- }
1066
- return;
1067
- }
1068
-
1069
- const header = opts.aboveThreshold ? 'Functions Above Threshold' : 'Function Complexity';
1070
- console.log(`\n# ${header}\n`);
1071
-
1072
- if (opts.health) {
1073
- // Health-focused view with Halstead + MI columns
1074
- console.log(
1075
- ` ${'Function'.padEnd(35)} ${'File'.padEnd(25)} ${'MI'.padStart(5)} ${'Vol'.padStart(7)} ${'Diff'.padStart(6)} ${'Effort'.padStart(9)} ${'Bugs'.padStart(6)} ${'LOC'.padStart(5)} ${'SLOC'.padStart(5)}`,
1076
- );
1077
- console.log(
1078
- ` ${'─'.repeat(35)} ${'─'.repeat(25)} ${'─'.repeat(5)} ${'─'.repeat(7)} ${'─'.repeat(6)} ${'─'.repeat(9)} ${'─'.repeat(6)} ${'─'.repeat(5)} ${'─'.repeat(5)}`,
1079
- );
1080
-
1081
- for (const fn of data.functions) {
1082
- const name = fn.name.length > 33 ? `${fn.name.slice(0, 32)}…` : fn.name;
1083
- const file = fn.file.length > 23 ? `…${fn.file.slice(-22)}` : fn.file;
1084
- const miWarn = fn.exceeds?.includes('maintainabilityIndex') ? '!' : ' ';
1085
- console.log(
1086
- ` ${name.padEnd(35)} ${file.padEnd(25)} ${String(fn.maintainabilityIndex).padStart(5)}${miWarn}${String(fn.halstead.volume).padStart(7)} ${String(fn.halstead.difficulty).padStart(6)} ${String(fn.halstead.effort).padStart(9)} ${String(fn.halstead.bugs).padStart(6)} ${String(fn.loc).padStart(5)} ${String(fn.sloc).padStart(5)}`,
1087
- );
1088
- }
1089
- } else {
1090
- // Default view with MI column appended
1091
- console.log(
1092
- ` ${'Function'.padEnd(40)} ${'File'.padEnd(30)} ${'Cog'.padStart(4)} ${'Cyc'.padStart(4)} ${'Nest'.padStart(5)} ${'MI'.padStart(5)}`,
1093
- );
1094
- console.log(
1095
- ` ${'─'.repeat(40)} ${'─'.repeat(30)} ${'─'.repeat(4)} ${'─'.repeat(4)} ${'─'.repeat(5)} ${'─'.repeat(5)}`,
1096
- );
1097
-
1098
- for (const fn of data.functions) {
1099
- const name = fn.name.length > 38 ? `${fn.name.slice(0, 37)}…` : fn.name;
1100
- const file = fn.file.length > 28 ? `…${fn.file.slice(-27)}` : fn.file;
1101
- const warn = fn.exceeds ? ' !' : '';
1102
- const mi = fn.maintainabilityIndex > 0 ? String(fn.maintainabilityIndex) : '-';
1103
- console.log(
1104
- ` ${name.padEnd(40)} ${file.padEnd(30)} ${String(fn.cognitive).padStart(4)} ${String(fn.cyclomatic).padStart(4)} ${String(fn.maxNesting).padStart(5)} ${mi.padStart(5)}${warn}`,
1105
- );
1106
- }
1107
- }
1108
-
1109
- if (data.summary) {
1110
- const s = data.summary;
1111
- const miPart = s.avgMI != null ? ` | avg MI: ${s.avgMI}` : '';
1112
- console.log(
1113
- `\n ${s.analyzed} functions analyzed | avg cognitive: ${s.avgCognitive} | avg cyclomatic: ${s.avgCyclomatic}${miPart} | ${s.aboveWarn} above threshold`,
1114
- );
1115
- }
1116
- console.log();
1117
- }
package/src/cycles.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { isTestFile } from './infrastructure/test-filter.js';
1
2
  import { loadNative } from './native.js';
2
- import { isTestFile } from './test-filter.js';
3
3
 
4
4
  /**
5
5
  * Detect circular dependencies in the codebase using Tarjan's SCC algorithm.