Skip to content

Commit 1d67da0

Browse files
chaliyclaude
andauthored
fix(bash): backslash-newline line continuation in double quotes (#247)
## Summary - Fix `\<newline>` inside double-quoted strings to act as line continuation (discard both chars) - Fixed in both `read_double_quoted_string` and `read_word`'s inline quote handler - Per POSIX, `\<newline>` in double quotes should be removed, joining the lines ## Test plan - [x] 5 new spec tests for line continuation in double-quoted strings, variable assignments, multi-line, unquoted, and escape preservation - [x] All 1199 spec tests pass (1194 pass, 5 skip) - [x] `cargo clippy` and `cargo fmt` clean Co-authored-by: Claude <noreply@anthropic.com>
1 parent 30377ee commit 1d67da0

File tree

3 files changed

+53
-5
lines changed

3 files changed

+53
-5
lines changed

crates/bashkit/src/parser/lexer.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,10 @@ impl<'a> Lexer<'a> {
326326
self.advance();
327327
if let Some(next) = self.peek_char() {
328328
match next {
329+
'\n' => {
330+
// \<newline> is line continuation: discard both
331+
self.advance();
332+
}
329333
'"' | '\\' | '$' | '`' => {
330334
word.push(next);
331335
self.advance();
@@ -578,7 +582,11 @@ impl<'a> Lexer<'a> {
578582
if let Some(next) = self.peek_char() {
579583
// Handle escape sequences
580584
match next {
581-
'"' | '\\' | '$' | '`' | '\n' => {
585+
'\n' => {
586+
// \<newline> is line continuation: discard both
587+
self.advance();
588+
}
589+
'"' | '\\' | '$' | '`' => {
582590
content.push(next);
583591
self.advance();
584592
}

crates/bashkit/tests/spec_cases/bash/variables.test.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,43 @@ x=hello; echo ${x@A}
408408
### expect
409409
x='hello'
410410
### end
411+
412+
### var_line_continuation_dquote
413+
# Backslash-newline inside double quotes is line continuation
414+
echo "hel\
415+
lo"
416+
### expect
417+
hello
418+
### end
419+
420+
### var_line_continuation_dquote_var
421+
# Line continuation in double-quoted variable assignment
422+
x="abc\
423+
def"; echo "$x"
424+
### expect
425+
abcdef
426+
### end
427+
428+
### var_line_continuation_dquote_multi
429+
# Multiple line continuations
430+
echo "one\
431+
two\
432+
three"
433+
### expect
434+
onetwothree
435+
### end
436+
437+
### var_line_continuation_unquoted
438+
# Backslash-newline in unquoted context
439+
echo hel\
440+
lo
441+
### expect
442+
hello
443+
### end
444+
445+
### var_line_continuation_preserves_other_escapes
446+
# Backslash before non-newline in double quotes is preserved
447+
echo "hello\tworld"
448+
### expect
449+
hello\tworld
450+
### end

specs/009-implementation-status.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,16 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
103103

104104
## Spec Test Coverage
105105

106-
**Total spec test cases:** 1194 (1189 pass, 5 skip)
106+
**Total spec test cases:** 1199 (1194 pass, 5 skip)
107107

108108
| Category | Cases | In CI | Pass | Skip | Notes |
109109
|----------|-------|-------|------|------|-------|
110-
| Bash (core) | 833 | Yes | 828 | 5 | `bash_spec_tests` in CI |
110+
| Bash (core) | 838 | Yes | 833 | 5 | `bash_spec_tests` in CI |
111111
| AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g |
112112
| Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect |
113113
| Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E |
114114
| JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env |
115-
| **Total** | **1194** | **Yes** | **1189** | **5** | |
115+
| **Total** | **1199** | **Yes** | **1194** | **5** | |
116116

117117
### Bash Spec Tests Breakdown
118118

@@ -156,7 +156,7 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
156156
| test-operators.test.sh | 17 | file/string tests |
157157
| time.test.sh | 11 | Wall-clock only (user/sys always 0) |
158158
| timeout.test.sh | 17 | |
159-
| variables.test.sh | 58 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}` |
159+
| variables.test.sh | 63 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}`, `\<newline>` line continuation |
160160
| wc.test.sh | 35 | word count (5 skipped) |
161161
| type.test.sh | 15 | `type`, `which`, `hash` builtins |
162162
| declare.test.sh | 10 | `declare`/`typeset`, `-i`, `-r`, `-x`, `-a`, `-p` |

0 commit comments

Comments
 (0)