diff --git a/src/tools/grep/cli.test.ts b/src/tools/grep/cli.test.ts new file mode 100644 index 0000000000..45e47b6f56 --- /dev/null +++ b/src/tools/grep/cli.test.ts @@ -0,0 +1,110 @@ +/// + +import { describe, expect, test } from "bun:test" + +import { parseOutput, parseCountOutput } from "./cli" + +describe("parseOutput", () => { + describe("#given LF line endings", () => { + test("#then parses content matches", () => { + const output = "src/foo.ts:10: hello()\nsrc/foo.ts:20: world()\n" + + const matches = parseOutput(output) + + expect(matches).toEqual([ + { file: "src/foo.ts", line: 10, text: " hello()" }, + { file: "src/foo.ts", line: 20, text: " world()" }, + ]) + }) + + test("#then parses files-only matches", () => { + const output = "src/foo.ts\nsrc/bar.ts\n" + + const matches = parseOutput(output, true) + + expect(matches).toEqual([ + { file: "src/foo.ts", line: 0, text: "" }, + { file: "src/bar.ts", line: 0, text: "" }, + ]) + }) + }) + + describe("#given CRLF line endings", () => { + test("#then parses content matches", () => { + const output = "src/foo.ts:10: hello()\r\nsrc/foo.ts:20: world()\r\n" + + const matches = parseOutput(output) + + expect(matches).toEqual([ + { file: "src/foo.ts", line: 10, text: " hello()" }, + { file: "src/foo.ts", line: 20, text: " world()" }, + ]) + }) + + test("#then parses files-only matches", () => { + const output = "src/foo.ts\r\nsrc/bar.ts\r\n" + + const matches = parseOutput(output, true) + + expect(matches).toEqual([ + { file: "src/foo.ts", line: 0, text: "" }, + { file: "src/bar.ts", line: 0, text: "" }, + ]) + }) + + test("#then captured text has no trailing carriage return", () => { + const output = "file.py:10: self.proxy = None\r\nfile.py:20: proxy.start()\r\n" + + const matches = parseOutput(output) + + for (const m of matches) { + expect(m.text.endsWith("\r")).toBe(false) + expect(m.file.endsWith("\r")).toBe(false) + } + expect(matches).toHaveLength(2) + }) + }) + + describe("#given empty output", () => { + test("#then returns empty array", () => { + expect(parseOutput("")).toEqual([]) + expect(parseOutput(" \n ")).toEqual([]) + expect(parseOutput(" \r\n ")).toEqual([]) + }) + }) +}) + +describe("parseCountOutput", () => { + describe("#given LF line endings", () => { + test("#then parses count results", () => { + const output = "src/foo.ts:3\nsrc/bar.ts:1\n" + + const results = parseCountOutput(output) + + expect(results).toEqual([ + { file: "src/foo.ts", count: 3 }, + { file: "src/bar.ts", count: 1 }, + ]) + }) + }) + + describe("#given CRLF line endings", () => { + test("#then parses count results", () => { + const output = "src/foo.ts:3\r\nsrc/bar.ts:1\r\n" + + const results = parseCountOutput(output) + + expect(results).toEqual([ + { file: "src/foo.ts", count: 3 }, + { file: "src/bar.ts", count: 1 }, + ]) + }) + }) + + describe("#given empty output", () => { + test("#then returns empty array", () => { + expect(parseCountOutput("")).toEqual([]) + expect(parseCountOutput(" \r\n ")).toEqual([]) + }) + }) +}) diff --git a/src/tools/grep/cli.ts b/src/tools/grep/cli.ts index 1a6cd89d00..b31098fe4f 100644 --- a/src/tools/grep/cli.ts +++ b/src/tools/grep/cli.ts @@ -96,11 +96,11 @@ function buildArgs(options: GrepOptions, backend: GrepBackend): string[] { return backend === "rg" ? buildRgArgs(options) : buildGrepArgs(options) } -function parseOutput(output: string, filesOnly = false): GrepMatch[] { +export function parseOutput(output: string, filesOnly = false): GrepMatch[] { if (!output.trim()) return [] const matches: GrepMatch[] = [] - const lines = output.split("\n") + const lines = output.split(/\r?\n/) for (const line of lines) { if (!line.trim()) continue @@ -128,11 +128,11 @@ function parseOutput(output: string, filesOnly = false): GrepMatch[] { return matches } -function parseCountOutput(output: string): CountResult[] { +export function parseCountOutput(output: string): CountResult[] { if (!output.trim()) return [] const results: CountResult[] = [] - const lines = output.split("\n") + const lines = output.split(/\r?\n/) for (const line of lines) { if (!line.trim()) continue