From 25f5045f9d0afc79aeab726542237ad3913dbded Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 03:47:05 +0000 Subject: [PATCH] fix(parser): expand $'\n' ANSI-C quoting in concatenated function args In read_continuation_into(), $'...' sequences were not recognized as ANSI-C quoting. The $ was consumed as a word character and the following '...' was treated as a regular single-quoted string, producing literal $\n instead of a newline. Add explicit $' detection in read_continuation_into() that delegates to read_dollar_single_quoted_content() for proper escape expansion. Closes #862 --- crates/bashkit/src/parser/lexer.rs | 13 +++++ .../tests/spec_cases/bash/quote.test.sh | 57 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/crates/bashkit/src/parser/lexer.rs b/crates/bashkit/src/parser/lexer.rs index 57f9f6aa..bcaa6bb8 100644 --- a/crates/bashkit/src/parser/lexer.rs +++ b/crates/bashkit/src/parser/lexer.rs @@ -954,6 +954,19 @@ impl<'a> Lexer<'a> { self.advance(); } } + Some('$') => { + // Check for $'...' ANSI-C quoting in continuation + let mut lookahead = self.chars.clone(); + lookahead.next(); // skip $ + if lookahead.next() == Some('\'') { + self.advance(); // consume $ + self.advance(); // consume opening ' + content.push_str(&self.read_dollar_single_quoted_content()); + } else { + content.push('$'); + self.advance(); + } + } Some(ch) if self.is_word_char(ch) => { content.push(ch); self.advance(); diff --git a/crates/bashkit/tests/spec_cases/bash/quote.test.sh b/crates/bashkit/tests/spec_cases/bash/quote.test.sh index 67060f21..a28ce6d1 100644 --- a/crates/bashkit/tests/spec_cases/bash/quote.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/quote.test.sh @@ -274,3 +274,60 @@ echo 'say "hi"' ### expect say "hi" ### end + +### quote_ansi_c_concat_func_arg +# Issue #862: $'\n' concatenated in function argument position +show() { printf '%q\n' "$1"; } +show "a"$'\n'"b" +### expect +$'a\nb' +### end + +### quote_ansi_c_tab_concat_func_arg +# $'\t' concatenated in function argument position +show() { printf '%q\n' "$1"; } +show "a"$'\t'"b" +### expect +$'a\tb' +### end + +### quote_ansi_c_sole_arg +# $'\n' as sole function argument +show() { printf '%q\n' "$1"; } +show $'\n' +### expect +$'\n' +### end + +### quote_ansi_c_at_start +# $'\n' at start of concatenation +show() { printf '%q\n' "$1"; } +show $'\n'"after" +### expect +$'\nafter' +### end + +### quote_ansi_c_at_end +# $'\n' at end of concatenation +show() { printf '%q\n' "$1"; } +show "before"$'\n' +### expect +$'before\n' +### end + +### quote_ansi_c_multiple_segments +# Multiple ANSI-C segments concatenated +show() { printf '%q\n' "$1"; } +show "a"$'\n'"b"$'\t'"c" +### expect +$'a\nb\tc' +### end + +### quote_ansi_c_via_variable_baseline +# Baseline: ANSI-C via variable works +show() { printf '%q\n' "$1"; } +x="a"$'\n'"b" +show "${x}" +### expect +$'a\nb' +### end