From dd6c70fd7a0876ba327c087119f3808b378aa08e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 15:35:41 +0000 Subject: [PATCH] fix(bash): backslash-newline line continuation inside double quotes \ inside double-quoted strings was preserved literally instead of being treated as a line continuation (POSIX requires both chars to be discarded). Fixed in both read_double_quoted_string and read_word's inline quote handler. Add 5 spec tests for line continuation scenarios. --- crates/bashkit/src/parser/lexer.rs | 10 ++++- .../tests/spec_cases/bash/variables.test.sh | 40 +++++++++++++++++++ specs/009-implementation-status.md | 8 ++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/crates/bashkit/src/parser/lexer.rs b/crates/bashkit/src/parser/lexer.rs index f3ae3fe..ba15936 100644 --- a/crates/bashkit/src/parser/lexer.rs +++ b/crates/bashkit/src/parser/lexer.rs @@ -326,6 +326,10 @@ impl<'a> Lexer<'a> { self.advance(); if let Some(next) = self.peek_char() { match next { + '\n' => { + // \ is line continuation: discard both + self.advance(); + } '"' | '\\' | '$' | '`' => { word.push(next); self.advance(); @@ -578,7 +582,11 @@ impl<'a> Lexer<'a> { if let Some(next) = self.peek_char() { // Handle escape sequences match next { - '"' | '\\' | '$' | '`' | '\n' => { + '\n' => { + // \ is line continuation: discard both + self.advance(); + } + '"' | '\\' | '$' | '`' => { content.push(next); self.advance(); } diff --git a/crates/bashkit/tests/spec_cases/bash/variables.test.sh b/crates/bashkit/tests/spec_cases/bash/variables.test.sh index 80da605..7754e4c 100644 --- a/crates/bashkit/tests/spec_cases/bash/variables.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/variables.test.sh @@ -408,3 +408,43 @@ x=hello; echo ${x@A} ### expect x='hello' ### end + +### var_line_continuation_dquote +# Backslash-newline inside double quotes is line continuation +echo "hel\ +lo" +### expect +hello +### end + +### var_line_continuation_dquote_var +# Line continuation in double-quoted variable assignment +x="abc\ +def"; echo "$x" +### expect +abcdef +### end + +### var_line_continuation_dquote_multi +# Multiple line continuations +echo "one\ +two\ +three" +### expect +onetwothree +### end + +### var_line_continuation_unquoted +# Backslash-newline in unquoted context +echo hel\ +lo +### expect +hello +### end + +### var_line_continuation_preserves_other_escapes +# Backslash before non-newline in double quotes is preserved +echo "hello\tworld" +### expect +hello\tworld +### end diff --git a/specs/009-implementation-status.md b/specs/009-implementation-status.md index 977a763..8a76c1c 100644 --- a/specs/009-implementation-status.md +++ b/specs/009-implementation-status.md @@ -103,16 +103,16 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See ## Spec Test Coverage -**Total spec test cases:** 1194 (1189 pass, 5 skip) +**Total spec test cases:** 1199 (1194 pass, 5 skip) | Category | Cases | In CI | Pass | Skip | Notes | |----------|-------|-------|------|------|-------| -| Bash (core) | 833 | Yes | 828 | 5 | `bash_spec_tests` in CI | +| Bash (core) | 838 | Yes | 833 | 5 | `bash_spec_tests` in CI | | AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g | | Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect | | Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E | | JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env | -| **Total** | **1194** | **Yes** | **1189** | **5** | | +| **Total** | **1199** | **Yes** | **1194** | **5** | | ### Bash Spec Tests Breakdown @@ -156,7 +156,7 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See | test-operators.test.sh | 17 | file/string tests | | time.test.sh | 11 | Wall-clock only (user/sys always 0) | | timeout.test.sh | 17 | | -| variables.test.sh | 58 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}` | +| variables.test.sh | 63 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}`, `\` line continuation | | wc.test.sh | 35 | word count (5 skipped) | | type.test.sh | 15 | `type`, `which`, `hash` builtins | | declare.test.sh | 10 | `declare`/`typeset`, `-i`, `-r`, `-x`, `-a`, `-p` |