diff --git a/src/cli/index.ts b/src/cli/index.ts index 6043f23..883caf0 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -39,6 +39,7 @@ program.command("scan") .option("--config ", "path to debtlens.config.json") .option("--cwd ", "working directory", process.cwd()) .option("--no-color", "disable ANSI color in terminal output") + .option("-q, --quiet", "print only the summary line, suppress individual findings") .action(async (target: string, rawOptions: Record) => { try { const format = parseFormat(String(rawOptions.format ?? "terminal")); @@ -88,7 +89,7 @@ program.command("scan") ? applyBaseline(result, loadBaseline(cwd, String(rawOptions.baseline))) : result; - const report = renderReport(reported, format, { color: rawOptions.color !== false && format === "terminal" && process.stdout.isTTY }); + const report = renderReport(reported, format, { color: rawOptions.color !== false && format === "terminal" && process.stdout.isTTY, quiet: rawOptions.quiet === true }); if (rawOptions.output) { const outputPath = resolve(cwd, String(rawOptions.output)); diff --git a/src/reporters/index.ts b/src/reporters/index.ts index b8716b0..86a39a9 100644 --- a/src/reporters/index.ts +++ b/src/reporters/index.ts @@ -4,9 +4,9 @@ import { renderMarkdown } from "./markdownReporter.js"; import { renderSarif } from "./sarifReporter.js"; import { renderTerminal } from "./terminalReporter.js"; -export function renderReport(result: ScanResult, format: OutputFormat, options: { color?: boolean } = {}): string { +export function renderReport(result: ScanResult, format: OutputFormat, options: { color?: boolean; quiet?: boolean } = {}): string { if (format === "json") return renderJson(result); if (format === "markdown") return renderMarkdown(result); if (format === "sarif") return renderSarif(result); - return renderTerminal(result, { color: options.color ?? true }); + return renderTerminal(result, { color: options.color ?? true, quiet: options.quiet }); } diff --git a/src/reporters/terminalReporter.ts b/src/reporters/terminalReporter.ts index 1ced779..8a6bfea 100644 --- a/src/reporters/terminalReporter.ts +++ b/src/reporters/terminalReporter.ts @@ -3,7 +3,7 @@ import { createColorizer } from "../utils/color.js"; const severityOrder: Severity[] = ["high", "medium", "low", "info"]; -export function renderTerminal(result: ScanResult, options: { color: boolean } = { color: true }): string { +export function renderTerminal(result: ScanResult, options: { color: boolean; quiet?: boolean } = { color: true }): string { const color = createColorizer(options.color); const lines: string[] = []; @@ -11,6 +11,10 @@ export function renderTerminal(result: ScanResult, options: { color: boolean } = lines.push(`Scanned ${result.summary.filesScanned} files with ${result.summary.rulesRun} rules in ${result.summary.elapsedMs}ms.`); lines.push(`Issues: ${result.summary.totalIssues} | high ${result.summary.bySeverity.high} | medium ${result.summary.bySeverity.medium} | low ${result.summary.bySeverity.low} | info ${result.summary.bySeverity.info}`); + if (options.quiet) { + return `${lines.join("\n")}\n`; + } + if (result.issues.length === 0) { lines.push(""); lines.push("No maintainability debt found at the configured severity level."); diff --git a/tests/reporters/terminalReporter.test.ts b/tests/reporters/terminalReporter.test.ts new file mode 100644 index 0000000..b10c653 --- /dev/null +++ b/tests/reporters/terminalReporter.test.ts @@ -0,0 +1,59 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import type { DebtIssue, ScanResult, Severity } from "../../src/core/types.js"; +import { renderTerminal } from "../../src/reporters/terminalReporter.js"; + +function makeResult(issues: DebtIssue[]): ScanResult { + const bySeverity: Record = { info: 0, low: 0, medium: 0, high: 0 }; + for (const issue of issues) bySeverity[issue.severity] += 1; + return { + issues, + summary: { + totalIssues: issues.length, + bySeverity, + byRule: {}, + filesScanned: 3, + rulesRun: 8, + elapsedMs: 42, + }, + options: { target: ".", include: [], exclude: [], minSeverity: "info", rules: undefined }, + }; +} + +const issue: DebtIssue = { + id: "dl_test", + ruleId: "prop-drilling", + ruleName: "Prop drilling", + severity: "high", + confidence: 0.9, + message: "Parent forwards 5 props across 2 components.", + file: "src/Parent.tsx", + location: { startLine: 10, endLine: 20 }, + tags: [], +}; + +describe("renderTerminal", () => { + it("includes summary line", () => { + const out = renderTerminal(makeResult([issue]), { color: false }); + assert.ok(out.includes("Issues: 1 | high 1")); + }); + + it("includes individual findings by default", () => { + const out = renderTerminal(makeResult([issue]), { color: false }); + assert.ok(out.includes("Prop drilling")); + assert.ok(out.includes("src/Parent.tsx")); + }); + + it("quiet mode omits individual findings", () => { + const out = renderTerminal(makeResult([issue]), { color: false, quiet: true }); + assert.ok(out.includes("Issues: 1 | high 1"), "summary should be present"); + assert.ok(!out.includes("src/Parent.tsx"), "file path should not appear in quiet mode"); + assert.ok(!out.includes("Prop drilling"), "rule name should not appear in quiet mode"); + }); + + it("quiet mode still shows summary when no issues", () => { + const out = renderTerminal(makeResult([]), { color: false, quiet: true }); + assert.ok(out.includes("Issues: 0")); + assert.ok(!out.includes("No maintainability debt found")); + }); +});