From c806a35e499ff104bff09584aa845cfa94282bf0 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 22 Feb 2026 15:19:26 +0900 Subject: [PATCH] fix(grep): format files_with_matches output as clean file paths --- src/tools/grep/result-formatter.test.ts | 123 ++++++++++++++++++++++++ src/tools/grep/result-formatter.ts | 11 ++- 2 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/tools/grep/result-formatter.test.ts diff --git a/src/tools/grep/result-formatter.test.ts b/src/tools/grep/result-formatter.test.ts new file mode 100644 index 000000000..2ef8fcb33 --- /dev/null +++ b/src/tools/grep/result-formatter.test.ts @@ -0,0 +1,123 @@ +/// + +import { describe, expect, test } from "bun:test" + +import { formatGrepResult } from "./result-formatter" +import type { GrepResult } from "./types" + +describe("formatGrepResult", () => { + describe("#given grep result has error", () => { + describe("#when formatting result", () => { + test("#then returns error message", () => { + const result: GrepResult = { + matches: [], + totalMatches: 0, + filesSearched: 0, + truncated: false, + error: "ripgrep failed", + } + + const formatted = formatGrepResult(result) + + expect(formatted).toBe("Error: ripgrep failed") + }) + }) + }) + + describe("#given grep result has no matches", () => { + describe("#when formatting result", () => { + test("#then returns no matches message", () => { + const result: GrepResult = { + matches: [], + totalMatches: 0, + filesSearched: 0, + truncated: false, + } + + const formatted = formatGrepResult(result) + + expect(formatted).toBe("No matches found") + }) + }) + }) + + describe("#given grep result is files-with-matches mode", () => { + describe("#when formatting result", () => { + test("#then prints only file paths", () => { + const result: GrepResult = { + matches: [ + { file: "src/foo.ts", line: 0, text: "" }, + { file: "src/bar.ts", line: 0, text: "" }, + { file: "src/baz.ts", line: 0, text: "" }, + ], + totalMatches: 3, + filesSearched: 3, + truncated: false, + } + + const formatted = formatGrepResult(result) + + expect(formatted).toBe( + "Found 3 match(es) in 3 file(s)\n\n" + + "src/foo.ts\n\n" + + "src/bar.ts\n\n" + + "src/baz.ts\n", + ) + }) + }) + }) + + describe("#given grep result is content mode", () => { + describe("#when formatting result", () => { + test("#then prints line numbers and content", () => { + const result: GrepResult = { + matches: [ + { file: "src/foo.ts", line: 10, text: " function hello() {" }, + { file: "src/foo.ts", line: 25, text: " function world() {" }, + { file: "src/bar.ts", line: 5, text: ' import { hello } from "./foo"' }, + ], + totalMatches: 3, + filesSearched: 2, + truncated: false, + } + + const formatted = formatGrepResult(result) + + expect(formatted).toBe( + "Found 3 match(es) in 2 file(s)\n\n" + + "src/foo.ts\n" + + " 10: function hello() {\n" + + " 25: function world() {\n\n" + + "src/bar.ts\n" + + ' 5: import { hello } from "./foo"\n', + ) + }) + }) + }) + + describe("#given grep result has mixed file-only and content matches", () => { + describe("#when formatting result", () => { + test("#then skips file-only placeholders and prints valid content matches", () => { + const result: GrepResult = { + matches: [ + { file: "src/foo.ts", line: 0, text: "" }, + { file: "src/foo.ts", line: 10, text: " function hello() {" }, + { file: "src/bar.ts", line: 0, text: "" }, + ], + totalMatches: 3, + filesSearched: 2, + truncated: false, + } + + const formatted = formatGrepResult(result) + + expect(formatted).toBe( + "Found 3 match(es) in 2 file(s)\n\n" + + "src/foo.ts\n" + + " 10: function hello() {\n\n" + + "src/bar.ts\n", + ) + }) + }) + }) +}) diff --git a/src/tools/grep/result-formatter.ts b/src/tools/grep/result-formatter.ts index 2cfc408a8..3068f4026 100644 --- a/src/tools/grep/result-formatter.ts +++ b/src/tools/grep/result-formatter.ts @@ -10,6 +10,7 @@ export function formatGrepResult(result: GrepResult): string { } const lines: string[] = [] + const isFilesOnlyMode = result.matches.every((match) => match.line === 0 && match.text.trim() === "") lines.push(`Found ${result.totalMatches} match(es) in ${result.filesSearched} file(s)`) if (result.truncated) { @@ -26,8 +27,14 @@ export function formatGrepResult(result: GrepResult): string { for (const [file, matches] of byFile) { lines.push(file) - for (const match of matches) { - lines.push(` ${match.line}: ${match.text.trim()}`) + if (!isFilesOnlyMode) { + for (const match of matches) { + const trimmedText = match.text.trim() + if (match.line === 0 && trimmedText === "") { + continue + } + lines.push(` ${match.line}: ${trimmedText}`) + } } lines.push("") }