From e829c67e88e427a649f0c1afd6158c1fc55caa88 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Thu, 12 Mar 2026 15:22:38 -0700 Subject: [PATCH] Support escaped `$ ` in expected output lines --- src/parse.test.ts | 7 +++++++ src/parse.ts | 5 ++++- src/update.ts | 12 ++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/parse.test.ts b/src/parse.test.ts index fae3931..4e3c500 100644 --- a/src/parse.test.ts +++ b/src/parse.test.ts @@ -105,6 +105,13 @@ describe("parse", () => { expect(result.commands[0]!.expected).toEqual(["@env PORT=3000"]) }) + test("escaped dollar sign in expected output", () => { + const content = "$ echo '$ hello'\n\\$ hello\n" + const result = parse("test.shout", content) + expect(result.commands).toHaveLength(1) + expect(result.commands[0]!.expected).toEqual(["$ hello"]) + }) + test("no directives returns empty array", () => { const result = parse("test.shout", "$ echo hi\nhi\n") expect(result.directives).toEqual([]) diff --git a/src/parse.ts b/src/parse.ts index 490b9f6..9f271b7 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -134,7 +134,10 @@ export function parse(path: string, content: string): ShoutFile { continue } - if (line.startsWith("$ ")) { + if (line.startsWith("\\$ ") && current) { + // Escaped dollar-space: literal expected output starting with "$ " + current.expected.push(line.slice(1)) + } else if (line.startsWith("$ ")) { seenCommand = true if (current) { const trimmed = trimTrailingEmpty(current.expected) diff --git a/src/update.ts b/src/update.ts index e1ac545..faeaa93 100644 --- a/src/update.ts +++ b/src/update.ts @@ -15,7 +15,7 @@ export function rewriteFile( for (let i = 0; i < lines.length; i++) { const line = lines[i]! - if (line.startsWith("$ ")) { + if (line.startsWith("$ ") && !line.startsWith("\\$ ")) { // Emit the command line as-is output.push(line) @@ -28,7 +28,7 @@ export function rewriteFile( // Skip past old expected output lines in the original let j = i + 1 - while (j < lines.length && !lines[j]!.startsWith("$ ")) { + while (j < lines.length && !(lines[j]!.startsWith("$ ") && !lines[j]!.startsWith("\\$ "))) { j++ } // Collect old expected lines (before trimming trailing blanks for separator) @@ -56,8 +56,8 @@ export function rewriteFile( // Output original lines as-is for (const ol of oldExpectedRaw) output.push(ol) } else { - // Replace with actual output - for (const al of result.actual) output.push(al) + // Replace with actual output (escape lines starting with "$ ") + for (const al of result.actual) output.push(escapeDollar(al)) // Re-add exit code marker if it existed if (oldExitMarker) output.push(oldExitMarker) // Preserve trailing blank lines as separators @@ -76,6 +76,10 @@ export function rewriteFile( return output.join("\n") } +function escapeDollar(line: string): string { + return line.startsWith("$ ") ? "\\" + line : line +} + function trimTrailingEmpty(lines: string[]): string[] { let end = lines.length while (end > 0 && lines[end - 1] === "") end--