diff --git a/src/workflow-test-helpers.test.ts b/src/workflow-test-helpers.test.ts index b871bbca..c4190b8d 100644 --- a/src/workflow-test-helpers.test.ts +++ b/src/workflow-test-helpers.test.ts @@ -38,6 +38,14 @@ test('stepRunsCommand ignores separators inside quotes and inline comments', () assert.equal(stepRunsCommand({ run: 'gh release view "$V" 2>&1 | tee log' }, /^tee\b/), true); }); +test('stepRunsCommand treats backslash-escaped separators as literal text', () => { + assert.equal(runs(String.raw`echo foo \; bun run verify --flag "$VALUE"`), false); + assert.equal(runs(String.raw`echo foo \| bun run verify --flag "$VALUE"`), false); + assert.equal(runs(String.raw`find . -exec bun run verify --flag "$VALUE" \;`), false); + // An escape does not swallow a following real separator. + assert.equal(runs(String.raw`echo a\b; bun run verify --flag "$VALUE"`), true); +}); + test('commandPositions splits on separators and strips control-flow prefixes', () => { assert.deepEqual( commandPositions({ run: 'if gh release view "$V"; then\ngh release edit "$V"\nfi' }), diff --git a/src/workflow-test-helpers.ts b/src/workflow-test-helpers.ts index babd996b..9e74fae2 100644 --- a/src/workflow-test-helpers.ts +++ b/src/workflow-test-helpers.ts @@ -75,6 +75,14 @@ function splitCommandSeparators(line: string): string[] { continue; } + // An unquoted backslash escapes the next character, so `\;` is literal text + // rather than a separator. Checked before comments and separators. + if (char === '\\' && index + 1 < line.length) { + current += char + line[index + 1]!; + index += 1; + continue; + } + if (char === "'" || char === '"') { quote = char; current += char;