From 7eb455840965d8c2d3abb97d887ade7d58c085b8 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Tue, 6 Oct 2026 19:41:06 +0000 Subject: [PATCH] fix(parser): run commands before a later syntax error; quote/heredoc-aware $( ) end Bash reads and runs a script line by line, so commands before a syntax error execute and the error is reported afterwards (exit 2). Bashkit rejected the whole script, losing all earlier work. Also find the end of $(...) with a shared scanner aware of quotes, escapes, backticks, comments and heredoc bodies; $(cat <<'E' ... ) ... E) and $(echo ")") were cut at the inner paren. Claude-Session: https://claude.ai/code/session_019aFikmptPc91Fj4N2iDXQA --- crates/bashkit/src/interpreter/mod.rs | 42 +- crates/bashkit/src/lib.rs | 38 +- crates/bashkit/src/parser/ast.rs | 7 + crates/bashkit/src/parser/budget.rs | 1 + crates/bashkit/src/parser/lexer.rs | 157 +------ crates/bashkit/src/parser/mod.rs | 106 +++-- crates/bashkit/src/parser/subst_scan.rs | 382 ++++++++++++++++++ crates/bashkit/tests/integration/main.rs | 1 + .../tests/integration/partial_parse_tests.rs | 93 +++++ .../spec_cases/bash/cmdsub-body-end.test.sh | 84 ++++ knowledge/foundations/parser.md | 17 + 11 files changed, 750 insertions(+), 178 deletions(-) create mode 100644 crates/bashkit/src/parser/subst_scan.rs create mode 100644 crates/bashkit/tests/integration/partial_parse_tests.rs create mode 100644 crates/bashkit/tests/spec_cases/bash/cmdsub-body-end.test.sh diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index f68c7ce04..2420d5d6a 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -640,6 +640,24 @@ fn decode_file_bytes_for_path(_path: &Path, bytes: &[u8]) -> String { String::from_utf8_lossy(bytes).into_owned() } +/// Child-shell counterpart of `recover_partial_parse` in `lib.rs`: commands +/// before a syntax error run, then the error is reported (exit 2). `bash -n` +/// keeps whole-script rejection since it only checks syntax. +fn nested_partial_parse( + (mut script, error): (Script, Option), + noexec: bool, + shell_name: &str, +) -> Result