Skip to content

Commit 30c491a

Browse files
committed
refactor: unify call-ref and tests rendering format between audit and inspect commands
Extracts the duplicated "Calls"/"Called by"/"Tests" section rendering from presentation/audit.ts's renderCallRefs/renderRelatedTests and presentation/queries-cli/inspect.ts's renderExplainEdges into shared helpers in the new presentation/call-ref-sections.ts (renderCallRefsSection, renderNoCallEdgesFallback, renderRelatedTestsSection), following the same pattern established by #1756's impact-levels.ts unification. inspect.ts's richer format (indent-aware, "no call edges" fallback, singular/plural "Tests" label) is adopted as canonical in both commands. BEHAVIOR CHANGE: `codegraph audit`'s plain-text output (not --json/--ndjson) now matches `codegraph audit --quick`/`codegraph explain` exactly for these sections: - Prints "(no call edges found -- may be invoked dynamically or via re-exports)" when a function has neither callers nor callees. Previously audit printed nothing in this case. - "Tests (N):" is now "Tests (N file):"/"Tests (N files):" (singular/plural), matching explain's existing label. No changes to `codegraph audit --quick`/`codegraph explain` output — inspect.ts keeps its exact current format, just via the shared helper. docs check acknowledged: this is an internal presentation-layer refactor (no new command, language, or architecture change). CLAUDE.md's presentation/*.ts catch-all row already covers the new shared helper file, matching the precedent set by #1756's impact-levels.ts, which likewise did not get its own table row. Fixes #1772 Impact: 14 functions changed, 6 affected
1 parent 6515186 commit 30c491a

3 files changed

Lines changed: 123 additions & 51 deletions

File tree

src/presentation/audit.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { kindIcon } from '../domain/queries.js';
22
import { auditData } from '../features/audit.js';
33
import { outputResult } from '../infrastructure/result-formatter.js';
44
import type { AuditFunctionEntry, AuditResult, CodegraphConfig } from '../types.js';
5+
import {
6+
renderCallRefsSection,
7+
renderNoCallEdgesFallback,
8+
renderRelatedTestsSection,
9+
} from './call-ref-sections.js';
510
import { renderImpactLevels } from './impact-levels.js';
611

712
interface AuditOpts {
@@ -17,9 +22,6 @@ interface AuditOpts {
1722
config?: CodegraphConfig;
1823
}
1924

20-
/** A caller/callee reference as rendered under the "Calls"/"Called by" sections. */
21-
type CallRef = AuditFunctionEntry['callees'][number];
22-
2325
/** Render health metrics for a single audit function. */
2426
function renderHealthMetrics(fn: AuditFunctionEntry): void {
2527
if (fn.health.cognitive == null) return;
@@ -66,38 +68,21 @@ function renderThresholdBreaches(fn: AuditFunctionEntry): void {
6668
/** Render the transitive-dependent impact summary, one block per BFS level. */
6769
function renderImpactSection(fn: AuditFunctionEntry): void {
6870
console.log(`\n Impact: ${fn.impact.totalDependents} transitive dependent(s)`);
69-
// No "0 found" message here -- the count above already conveys it, matching this
70-
// file's other sections (e.g. renderCallRefs), which print nothing when empty.
71+
// No "0 found" message here -- the count above already conveys it, matching the
72+
// shared call-ref-sections helpers below, which print nothing when empty.
7173
renderImpactLevels(fn.impact.levels, { emptyMessage: null });
7274
}
7375

74-
/** Render a labeled list of call references (used for both "Calls" and "Called by"). */
75-
function renderCallRefs(label: string, refs: CallRef[]): void {
76-
if (refs.length === 0) return;
77-
console.log(`\n ${label} (${refs.length}):`);
78-
for (const c of refs) {
79-
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
80-
}
81-
}
82-
83-
/** Render the related-test-file list for an audited function. */
84-
function renderRelatedTests(fn: AuditFunctionEntry): void {
85-
if (fn.relatedTests.length === 0) return;
86-
console.log(`\n Tests (${fn.relatedTests.length}):`);
87-
for (const t of fn.relatedTests) {
88-
console.log(` ${t.file}`);
89-
}
90-
}
91-
9276
/** Render a single audited function with all its sections. */
9377
function renderAuditFunction(fn: AuditFunctionEntry): void {
9478
renderFunctionHeader(fn);
9579
renderHealthMetrics(fn);
9680
renderThresholdBreaches(fn);
9781
renderImpactSection(fn);
98-
renderCallRefs('Calls', fn.callees);
99-
renderCallRefs('Called by', fn.callers);
100-
renderRelatedTests(fn);
82+
renderCallRefsSection('Calls', fn.callees);
83+
renderCallRefsSection('Called by', fn.callers);
84+
renderRelatedTestsSection(fn.relatedTests);
85+
renderNoCallEdgesFallback(fn.callees.length, fn.callers.length);
10186

10287
console.log();
10388
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Shared renderers for the "Calls" / "Called by" / "Tests" sections rendered by
3+
* both `codegraph audit` (presentation/audit.js) and `codegraph explain`/`codegraph query`
4+
* (presentation/queries-cli/inspect.js — `audit --quick` is an alias for `explain`).
5+
*
6+
* Both commands render these sections from the same underlying shape and must stay
7+
* visually identical — this module is the single source of truth for that format.
8+
*/
9+
10+
import { kindIcon } from '../domain/queries.js';
11+
12+
/** A single call reference rendered under the "Calls"/"Called by" sections. */
13+
export interface CallRefLike {
14+
name: string;
15+
kind: string;
16+
file: string;
17+
line: number;
18+
}
19+
20+
/** A single related-test-file reference rendered under the "Tests" section. */
21+
export interface RelatedTestRefLike {
22+
file: string;
23+
}
24+
25+
export interface RenderCallRefsSectionOpts {
26+
/**
27+
* Indent prefix applied to every printed line. Used by `explain`'s recursive
28+
* dependency rendering; top-level callers (e.g. `codegraph audit`) omit this
29+
* and get the sensible default of no indent.
30+
* Default: ''.
31+
*/
32+
indent?: string;
33+
}
34+
35+
/**
36+
* Render a single labeled call-reference list — used for both the "Calls" and
37+
* "Called by" sections (same shape, different label):
38+
*
39+
* {indent} Calls (2):
40+
* {indent} f parse src/p.ts:1
41+
* {indent} m Parser.run src/p.ts:20
42+
*
43+
* Prints nothing when `refs` is empty (see `renderNoCallEdgesFallback` for the
44+
* combined "no edges at all" message).
45+
*/
46+
export function renderCallRefsSection(
47+
label: string,
48+
refs: CallRefLike[],
49+
opts: RenderCallRefsSectionOpts = {},
50+
): void {
51+
if (refs.length === 0) return;
52+
const { indent = '' } = opts;
53+
console.log(`\n${indent} ${label} (${refs.length}):`);
54+
for (const c of refs) {
55+
console.log(`${indent} ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
56+
}
57+
}
58+
59+
export interface RenderNoCallEdgesFallbackOpts {
60+
/** Indent prefix applied to the printed line. Default: ''. */
61+
indent?: string;
62+
}
63+
64+
/**
65+
* Print the "no call edges" fallback line when a function has neither callers
66+
* nor callees. Call this once, after rendering both the "Calls" and "Called by"
67+
* sections (which print nothing individually when empty) — it no-ops unless
68+
* both counts are zero.
69+
*/
70+
export function renderNoCallEdgesFallback(
71+
calleeCount: number,
72+
callerCount: number,
73+
opts: RenderNoCallEdgesFallbackOpts = {},
74+
): void {
75+
if (calleeCount > 0 || callerCount > 0) return;
76+
const { indent = '' } = opts;
77+
console.log(`${indent} (no call edges found -- may be invoked dynamically or via re-exports)`);
78+
}
79+
80+
export interface RenderRelatedTestsSectionOpts {
81+
/** Indent prefix applied to every printed line. Default: ''. */
82+
indent?: string;
83+
}
84+
85+
/**
86+
* Render the related-test-file list for an audited/explained function, with a
87+
* singular/plural "file"/"files" label:
88+
*
89+
* {indent} Tests (1 file):
90+
* {indent} tests/parse.test.ts
91+
*/
92+
export function renderRelatedTestsSection(
93+
tests: RelatedTestRefLike[],
94+
opts: RenderRelatedTestsSectionOpts = {},
95+
): void {
96+
if (tests.length === 0) return;
97+
const { indent = '' } = opts;
98+
const label = tests.length === 1 ? 'file' : 'files';
99+
console.log(`\n${indent} Tests (${tests.length} ${label}):`);
100+
for (const t of tests) {
101+
console.log(`${indent} ${t.file}`);
102+
}
103+
}

src/presentation/queries-cli/inspect.ts

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import {
99
whereData,
1010
} from '../../domain/queries.js';
1111
import { outputResult } from '../../infrastructure/result-formatter.js';
12+
import {
13+
renderCallRefsSection,
14+
renderNoCallEdgesFallback,
15+
renderRelatedTestsSection,
16+
} from '../call-ref-sections.js';
1217

1318
interface SymbolRef {
1419
kind: string;
@@ -477,31 +482,10 @@ function renderExplainComplexity(
477482
}
478483

479484
function renderExplainEdges(r: FunctionExplainResult, indent: string): void {
480-
if (r.callees.length > 0) {
481-
console.log(`\n${indent} Calls (${r.callees.length}):`);
482-
for (const c of r.callees) {
483-
console.log(`${indent} ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
484-
}
485-
}
486-
487-
if (r.callers.length > 0) {
488-
console.log(`\n${indent} Called by (${r.callers.length}):`);
489-
for (const c of r.callers) {
490-
console.log(`${indent} ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
491-
}
492-
}
493-
494-
if (r.relatedTests.length > 0) {
495-
const label = r.relatedTests.length === 1 ? 'file' : 'files';
496-
console.log(`\n${indent} Tests (${r.relatedTests.length} ${label}):`);
497-
for (const t of r.relatedTests) {
498-
console.log(`${indent} ${t.file}`);
499-
}
500-
}
501-
502-
if (r.callees.length === 0 && r.callers.length === 0) {
503-
console.log(`${indent} (no call edges found -- may be invoked dynamically or via re-exports)`);
504-
}
485+
renderCallRefsSection('Calls', r.callees, { indent });
486+
renderCallRefsSection('Called by', r.callers, { indent });
487+
renderRelatedTestsSection(r.relatedTests, { indent });
488+
renderNoCallEdgesFallback(r.callees.length, r.callers.length, { indent });
505489
}
506490

507491
function renderFunctionExplain(r: FunctionExplainResult, indent = ''): void {

0 commit comments

Comments
 (0)