Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/shell_parser/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3753,8 +3753,7 @@ impl<'a, const ENCODING: StringEncoding> ShellCharIter<'a, ENCODING> {
|| c == u32::from(b'`')
|| c == u32::from(b'"')
|| c == u32::from(b'\\')
|| c == u32::from(b'\n')
|| c == u32::from(b'#') =>
|| c == u32::from(b'\n') =>
{
char = peeked;
}
Expand Down
21 changes: 21 additions & 0 deletions test/js/bun/shell/bunshell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,27 @@ ${temp_dir}`
TestBuilder.command`echo "${'"'}"`.stdout('"\n').runAsTest("double_quote_in_dquotes");
TestBuilder.command`echo "${"\\"}"`.stdout("\\\n").runAsTest("backslash_in_dquotes");

// Inside double quotes a backslash escapes only $ ` " \ and newline (POSIX 2.2.3).
// Before any other character the backslash stays, as in bash and dash.
TestBuilder.command`echo "a\#b"`.stdout("a\\#b\n").runAsTest("backslash_hash_in_dquotes");
TestBuilder.command`echo "\# Title"`.stdout("\\# Title\n").runAsTest("leading_backslash_hash_in_dquotes");
// A template literal passes `\u00E9` for `é` (#8745). `{ raw }` passes the `é`, so the non-ASCII lexer runs.
TestBuilder.command`echo ${{ raw: '"é\\#b"' }}`.stdout("é\\#b\n").runAsTest("backslash_hash_in_dquotes_non_ascii");
TestBuilder.command`echo "$(echo "a\#b")"`.stdout("a\\#b\n").runAsTest("backslash_hash_in_dquotes_in_cmdsubst");
TestBuilder.command`echo a\#b`.stdout("a#b\n").runAsTest("backslash_hash_unquoted");
TestBuilder.command`echo 'a\#b'`.stdout("a\\#b\n").runAsTest("backslash_hash_in_squotes");

test("backslash_before_each_printable_ascii_in_dquotes", async () => {
const chars = Array.from({ length: 0x7f - 0x20 }, (_, i) => String.fromCharCode(0x20 + i));
const script = chars.map(c => `echo "a\\${c}b"`).join("\n");
const { stdout, stderr, exitCode } = await $`${{ raw: script }}`.quiet();
expect({ stdout: stdout.toString().split("\n"), stderr: stderr.toString(), exitCode }).toEqual({
stdout: [...chars.map(c => ('$`"\\'.includes(c) ? `a${c}b` : `a\\${c}b`)), ""],
stderr: "",
exitCode: 0,
});
});

// Testing characters that need to be escaped within single quotes
TestBuilder.command`echo '${"$"}'`.stdout("$\n").runAsTest("dollar_in_squotes");
TestBuilder.command`echo '${'"'}'`.stdout('"\n').runAsTest("double_quote_in_squotes");
Expand Down
Loading