Repository navigation
Conversation
|
Updated 1:36 PM PT - Sep 25th, 2026
✅ @robobun, your commit e93b4d76890e4654f327bcafbfeae336bd0e7390 passed in 🧪 To try this PR locally: bunx bun-pr 29670That installs a local version of the PR into your bun-29670 --bun |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughLexer and shell-escaping logic were changed to handle Windows CRLF: unescaped Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/cli/run/run-shell.test.ts`:
- Around line 56-59: The test currently asserts stderr is exactly empty after
collecting subprocess outputs (variables proc, stdout/stderr/exitCode in
run-shell.test.ts); change this to either remove the strict
expect(stderr).toBe("") check or filter out ASAN noise before asserting
emptiness by splitting stderr into lines and removing any lines that start with
"WARNING: ASAN interferes" (or similar ASAN prefix) and then asserting the
remaining joined string is empty; apply this same change to the other assertions
mentioned (the blocks around the usages at lines ~78-81, ~100-103, and ~122-125)
so any bunExe()/bunEnv spawned subprocesses ignore ASAN startup warnings when
checking stderr.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: eeaad6e3-063d-4cc5-954c-a2e278b1e488
📥 Commits
Reviewing files that changed from the base of the PR and between d89cd8b6722e418709d451b7d9d55b238e26460f and b946e9ac5c10deec48767c76f6640c562308003c.
📒 Files selected for processing (2)
src/shell/shell.zigtest/cli/run/run-shell.test.ts
There was a problem hiding this comment.
Thanks — b946e9a addresses the last bare-CR-in-double-quotes edge case and all four scenarios now have tests; I don't see any remaining issues. Given how subtle the lexer state interactions turned out to be (three follow-up edge cases), I'd still like a human familiar with the shell lexer to give the final shape a once-over before merge.
Extended reasoning...
Overview
This PR fixes #29669 by teaching the Bun shell lexer (src/shell/shell.zig) to normalize CRLF line endings. It touches four spots: (1) a new unescaped '\r' case in the main dispatch that drops \r before \n in Normal state, (2) a new escaped-\r handler so \<CR><LF> works as a line continuation, (3) \r added to the Double-state backslash escape list so the continuation also works inside double quotes, with a compensating re-emit of the backslash when the CR is bare, and (4) \r added to SPECIAL_CHARS (required by assertSpecialChar and correct for the needsEscape* interpolation heuristics). Four new tests in test/cli/run/run-shell.test.ts cover each scenario.
Security risks
None identified. This is purely lexer tokenization of \r bytes; no auth, crypto, filesystem, or network surface is touched. The SPECIAL_CHARS addition only makes the interpolation-escaping heuristic slightly more conservative (values containing bare \r now get quoted), which is safe.
Level of scrutiny
Moderate-to-high. The shell lexer is a critical path — it runs for every bun run *.sh and every Bun.$ template literal — and the change is a state-machine modification with non-obvious interactions between read_char() escape handling, the !escaped dispatch block, and the escaped fall-through path. The fact that this took three follow-up commits (escaped-CR continuation, Double-state escape list, bare-CR backslash preservation) to reach a correct state confirms the area is subtle. Each of those was caught in review and is now covered by a regression test, but a maintainer who owns this lexer should confirm the final approach is the one they want (vs., say, normalizing CRLF→LF in a pre-pass before lexing).
Other factors
All four of my prior inline comments are resolved with code + tests. CodeRabbit had no actionable findings. There is no CODEOWNERS entry for src/shell/. The PR description reports lex.test.ts/parse.test.ts pass unchanged and bunshell.test.ts has identical pass/fail counts. I am deferring rather than approving solely because this is non-trivial lexer logic in a hot path, not because of any known defect.
There was a problem hiding this comment.
All earlier feedback is addressed and I didn't find further issues — deferring final sign-off to a human since this touches the shell lexer's escape/quoting semantics and took a few iterations to get the edge cases right.
Extended reasoning...
Overview
This PR teaches the Bun shell lexer to normalize CRLF line endings: an unescaped \r immediately before \n in Normal state is dropped, \<CR><LF> is treated as a line continuation (both in Normal and Double-quoted state), and a backslash before a bare CR inside double quotes is re-emitted to preserve POSIX behaviour. \r is also added to SPECIAL_CHARS so the comptime assertSpecialChar check passes and shell-source generation quotes it. Four new integration tests in test/cli/run/run-shell.test.ts cover the basic CRLF case, backslash continuation, double-quoted continuation, and the bare-CR-in-double-quotes regression guard.
Security risks
None identified. This is purely lexer tokenization of CR bytes; it does not touch auth, network, filesystem permissions, or process spawning. The only behavioural change is that previously-broken CRLF scripts now tokenize the same as their LF equivalents.
Level of scrutiny
Moderate-to-high. src/shell/shell.zig's lexer is a hand-written state machine that backs both bun run *.sh and the Bun.$ tagged-template API, so tokenization changes have broad reach. The PR went through three correction rounds during review (escaped-CR continuation, dead Double-state guard, bare-CR backslash regression) — each was a real edge case, and each is now covered by a dedicated test. The final logic reads correctly to me and the bug-hunting pass on the current revision found nothing, but the iteration history suggests a maintainer familiar with the lexer should confirm the chosen semantics (particularly adding \r to the Double-state escape list at line 3715 and then compensating by re-emitting the backslash at 2893–2895, rather than peeking two bytes in read_char()).
Other factors
- All four of my prior inline comments and CodeRabbit's ASAN-stderr note are resolved in the current diff.
- CI failures on the latest build (
socket.test.ts,bun-install-lifecycle-scripts.test.tson Windows) are unrelated to the shell lexer. - No CODEOWNERS entry for
src/shell/. - Tests use
tempDirper repo convention and filter ASAN noise from stderr.
bf909fe to
862e141
Compare
There was a problem hiding this comment.
All earlier feedback (escaped-CR continuation, Double-state escape list, bare-CR backslash preservation, tempDir, ASAN filter) is addressed and the four new tests cover each case — I didn't find anything further, but the final shape touches escape handling across Normal/Double lexer states in four places, so a maintainer glance at the shell.zig changes is worth it before merge.
Extended reasoning...
Overview
This PR makes Bun's shell lexer normalize CRLF line endings so Windows-edited .sh files run correctly. It touches src/shell/shell.zig in four places: (1) a new unescaped '\r' case in the main char dispatch that drops \r before \n in Normal state, (2) a new escaped-'\r' handler so \<CR><LF> acts as a line continuation, (3) adding '\r' to the Double-state backslash escape list in read_char(), and (4) adding '\r' to SPECIAL_CHARS. Four new tests in test/cli/run/run-shell.test.ts cover plain CRLF, backslash continuation, continuation inside double quotes, and the bare-CR-in-double-quotes edge case.
Security risks
None identified. This is pure tokenizer line-ending handling with no auth, crypto, filesystem-path, or external-input-trust implications. The change only affects how \r bytes are treated relative to adjacent \n and backslash bytes.
Level of scrutiny
Moderate-to-high. The shell lexer backs both bun run *.sh and the Bun.$ API, and escape/quote-state handling is exactly the kind of code where subtle interactions hide. The change grew over several review iterations from a single switch arm into coordinated logic across the unescaped path, the escaped path, and read_char()'s Double-state escape list — each iteration fixing a real edge case I (or CodeRabbit) raised, all now covered by tests. I believe the current logic is correct, but lexer state-machine changes of this shape are worth a maintainer's eyes rather than a bot-only sign-off.
Other factors
All four of my prior inline comments and the CodeRabbit ASAN-stderr comment are resolved with corresponding commits and tests. No CODEOWNERS entry covers these files. The PR description reports existing lex.test.ts/parse.test.ts pass unchanged and bunshell.test.ts pass/fail split is identical.
There was a problem hiding this comment.
All four rounds of prior feedback are addressed and the current lexer logic traces correctly for every CRLF/bare-CR/escaped/quoted combination I checked — but this is core shell-lexer state-machine code that needed three correction rounds to converge, so a maintainer familiar with shell.zig should give it a final look.
Extended reasoning...
Overview
This PR teaches Bun's shell lexer (src/shell/shell.zig) to normalize CRLF line endings so Windows-edited .sh files run correctly. It touches four spots: (1) a new unescaped-\r case in the main char dispatch that drops \r before \n in Normal state, (2) a new escaped-\r handler so \\<CR><LF> works as line-continuation, (3) \r added to the Double-state backslash escape list so the continuation also works inside double quotes, and (4) \r added to SPECIAL_CHARS so the comptime assertion passes and interpolated values containing CR get JS-ref-wrapped rather than inlined. Four new integration tests in test/cli/run/run-shell.test.ts cover the basic CRLF case plus each edge case raised during review.
Security risks
None identified. This is lexer tokenization for line endings; it doesn't touch auth, crypto, filesystem permissions, or network. The SPECIAL_CHARS addition makes interpolation strictly more conservative (more values get escaped via JS-ref), not less.
Level of scrutiny
Moderate-to-high. shell.zig is core runtime — the lexer feeds every .sh execution and every Bun.$ template. The change itself is small (~45 LoC of logic) but the state-machine interactions are subtle: getting it fully correct took three review rounds (escaped-path coverage, Double-state escape list, then the bare-CR-in-double-quotes backslash regression). Each was a real bug, each was fixed, and the final revision now handles every combination I traced (Normal/Double × escaped/unescaped × CRLF/bare-CR). That iteration history is exactly why a human maintainer should confirm the final shape rather than rely on bot sign-off alone.
Other factors
All four of my prior inline comments are resolved, the CodeRabbit ASAN-stderr flake concern is addressed via stripAsanWarning, the bug-hunting system found nothing new on the latest revision, and the new tests use the repo-preferred tempDir helper. The PR description's claim that bare \r is preserved is now actually true in all states. I'm deferring rather than approving solely because of the code path's criticality and the demonstrated subtlety, not because of any outstanding concern.
…isting) The integration test `test/integration/build-prefetch/prefetch.test.ts` spawns `bunExe()` and asserts `stderr` is empty. Under ASAN builds (`ASSERT_ENABLED`), JSC's `@assert(!dependency.isAsync)` in `vendor/WebKit/Source/JavaScriptCore/builtins/ModuleLoader.js:544` fires during module linking and prints an "ASSERTION FAILED" line to stderr, flipping all four tests to red. The assertion is a pre-existing JSC builtins issue in async-module linking — reproducible on `main` with no local changes, and hitting other unrelated PRs on shards that pick up this test (e.g. #29670). Add it to `test/expectations.txt` under `[ ASAN ]` so it joins the other tests that are skipped on ASAN lanes. Unblocks CI for #29678 without mixing unrelated fixes into that PR's code changes. Separate commit so it can be reverted independently once the JSC assertion is fixed.
…isting) The integration test `test/integration/build-prefetch/prefetch.test.ts` spawns `bunExe()` and asserts `stderr` is empty. Under ASAN builds (`ASSERT_ENABLED`), JSC's `@assert(!dependency.isAsync)` in `vendor/WebKit/Source/JavaScriptCore/builtins/ModuleLoader.js:544` fires during module linking and prints an "ASSERTION FAILED" line to stderr, flipping all four tests to red. The assertion is a pre-existing JSC builtins issue in async-module linking — reproducible on `main` with no local changes, and hitting other unrelated PRs on shards that pick up this test (e.g. #29670). Add it to `test/expectations.txt` under `[ ASAN ]` so it joins the other tests that are skipped on ASAN lanes. Unblocks CI for #29678 without mixing unrelated fixes into that PR's code changes. Separate commit so it can be reverted independently once the JSC assertion is fixed.
…isting) The integration test `test/integration/build-prefetch/prefetch.test.ts` spawns `bunExe()` and asserts `stderr` is empty. Under ASAN builds (`ASSERT_ENABLED`), JSC's `@assert(!dependency.isAsync)` in `vendor/WebKit/Source/JavaScriptCore/builtins/ModuleLoader.js:544` fires during module linking and prints an "ASSERTION FAILED" line to stderr, flipping all four tests to red. The assertion is a pre-existing JSC builtins issue in async-module linking — reproducible on `main` with no local changes, and hitting other unrelated PRs on shards that pick up this test (e.g. #29670). Add it to `test/expectations.txt` under `[ ASAN ]` so it joins the other tests that are skipped on ASAN lanes. Unblocks CI for #29678 without mixing unrelated fixes into that PR's code changes. Separate commit so it can be reverted independently once the JSC assertion is fixed.
661fb0e to
8bc4823
Compare
8bc4823 to
65f2e8e
Compare
There was a problem hiding this comment.
All three rounds of my earlier feedback are addressed and I found nothing new on this revision — deferring only because lexer state-machine changes across two parallel implementations (shell.zig + shell_parser/parse.rs) plus the SPECIAL_CHARS table addition feel worth one human pass before merge.
Extended reasoning...
Overview
The substantive change is CRLF normalization in Bun's shell lexer, implemented in parallel in src/runtime/shell/shell.zig and the Rust port src/shell_parser/parse.rs: an unescaped-\r case that swallows the CR when followed by LF, an escaped-\r handler so \\<CR><LF> acts as line-continuation (with a Double-state special case to re-emit the backslash for bare CR), \r added to the Double-state backslash escape list, and \r added to the SPECIAL_CHARS escaping table. Four new tests in test/cli/run/run-shell.test.ts cover the base case plus each edge case raised in earlier review rounds. The remaining ten .rs files are pure autofix.ci rustfmt reflows of #[cfg(...)] / #[cfg_attr(...)] attributes with zero semantic change.
Security risks
The shell lexer tokenizes user-supplied script content, so changes here are adjacent to command-injection surface. That said, this change only narrows what reaches appendCharToStrPool (dropping a CR that would otherwise be glued onto a token) and adds \r to the escaper's special-char set — neither widens the set of characters that can terminate a quote or start a substitution. I don't see an injection vector introduced here.
Level of scrutiny
Moderate. The fix itself is conceptually simple, but it touches a hand-rolled lexer state machine in two implementations and went through three review iterations on this PR, each of which surfaced a real correctness edge case (escaped-CR continuation, dead Double-state guard, bare-CR backslash regression). That history says the area is subtle enough that a maintainer familiar with the shell subsystem should confirm the final shape — particularly the SPECIAL_CHARS table addition, which also affects ShellSrcBuilder output escaping, and the Rust port's fell_through control-flow mapping of Zig's continue.
Other factors
All prior inline comments (mine and CodeRabbit's) are resolved in the current diff, and each fix has a dedicated regression test. The robobun build-status comment still shows linker/ci.ts failures on 65f2e8e, but the ci: retrigger after release-build infra flake commit and the -no-pie warning text indicate those are infrastructure flakes unrelated to this change.
1f003f8 to
e2957e8
Compare
fbd86ba to
0380528
Compare
There was a problem hiding this comment.
All earlier feedback is addressed (escaped-CR continuation, double-quote escape list, bare-CR backslash preservation, ASAN filter, and the .zig revert) and I didn't find anything new — but since this touches the shell lexer's main char dispatch and the SPECIAL_CHARS set used for template-literal escaping, it's worth a human pass before merging.
Extended reasoning...
Overview
The PR now modifies exactly two files: src/shell_parser/parse.rs (the shipped Rust shell lexer) and test/cli/run/run-shell.test.ts. In parse.rs it (1) adds a '\r' arm to the unescaped char dispatch that discards a CR immediately preceding an unescaped LF in Normal state while leaving CR literal inside quotes and for bare CR; (2) adds an escaped-\r handler so \\<CR><LF> behaves as a line continuation in both Normal and Double state, with the bare-CR-in-double-quotes case re-emitting the swallowed backslash to preserve POSIX semantics; (3) adds '\r' to the Double-state backslash escape list in read_char(); and (4) adds '\r' to SPECIAL_CHARS (now [u8; 35]). The test file adds a stripAsanWarning helper and four integration tests covering each of those paths. The earlier shell.zig edits were reverted in 0380528, so the diff no longer touches reference-only code.
Security risks
Shell lexing determines word boundaries and therefore what gets executed, so any change here is security-adjacent. The specific change is narrowing — CR before LF is dropped rather than glued onto the preceding token — which reduces the surface for surprising tokenization rather than expanding it. Adding \r to SPECIAL_CHARS also flows into SPECIAL_CHARS_TABLE, which (a) gates the lexer's ASCII fast-path scan at parse.rs:2628 (necessary for the new arm to be reachable) and (b) feeds needs_escape_* so a \r in a JS value interpolated into a Bun.$ template now routes through the JS-string-ref path instead of being inlined raw. That second effect is a behavior change outside the .sh-file path and is the part most worth a human glance, though it appears strictly safer (matches how \n is already handled).
Level of scrutiny
Medium-high. The diff is small and well-commented, every edge case I raised over four prior review rounds now has both a code fix and a dedicated regression test, and I traced fell_through / break 'escaped semantics against the surrounding cases to confirm the new arm is consistent. But this is the production shell lexer's hot loop, and lexer edge cases are subtle enough that a maintainer familiar with parse.rs should sign off rather than relying solely on automated review.
Other factors
All six of my earlier inline comments are resolved and reflected in the current diff; CodeRabbit's ASAN-stderr concern is addressed via stripAsanWarning; no CODEOWNERS entry matches these paths; the bug-hunting pass on the current revision found nothing. Given the critical code path I'm deferring rather than approving.
|
here, i are executing scp'd js bin script from windows to linux |
|
Thanks for the report, but this is a different layer than what this PR touches, and it's not something Bun can fix. When you execute a script directly ( Bun is never launched here, so it has no chance to strip the This PR (#29670) fixes a related-but-separate case: Bun's own shell interpreter choking on CRLF when it reads a The fix is to give the file LF line endings: dos2unix script.js # or: sed -i 's/\r$//' script.jsAnd to keep it that way via |
0380528 to
3018ad9
Compare
|
Rebased onto current
History was collapsed into a single commit since the intermediate Zig commits net to zero against the new base. Verified locally: all 7 |
There was a problem hiding this comment.
LGTM — all prior review rounds addressed, each edge case has a dedicated test; the remaining comment-length nit is non-blocking.
Extended reasoning...
Overview
This PR teaches the Bun shell lexer (src/shell_parser/parse.rs) to normalize CRLF line endings in .sh files so Windows-edited scripts run correctly. Three hunks: an unescaped-\r case in the main char dispatch (drops CR before LF in Normal state, preserves it in quotes and when standalone), an escaped-\r handler mirroring the existing escaped-\n line-continuation (with a Double-state backslash re-emit for bare CR), and \r added to the Double-state backslash escape list. Four new subprocess tests in test/cli/run/run-shell.test.ts cover basic CRLF, \<CR><LF> line-continuation, continuation inside double quotes, and the bare-\<CR>-in-double-quotes case that must not drop the backslash.
Security risks
None identified. This is a lexer whitespace/line-ending normalization change. It does not touch auth, crypto, network, filesystem paths, or command dispatch — it only affects whether a \r byte is treated as part of a newline vs. glued onto the preceding word. No new user-controlled data is interpreted; the input space is the same shell script bytes as before.
Level of scrutiny
Moderate. Lexer state-machine changes are subtle, and this PR went through three prior rounds of correctness feedback (escaped-CR path missing → dead Double-state guard → bare-CR backslash regression), each of which was fixed and given a dedicated test. I re-traced all six state combinations (unescaped/escaped × Normal/Double/bare-CR) against the current code and confirmed fell_through correctly gates the continue vs. fall-through-to-append at parse.rs:3143-3147, and that \r is present in SPECIAL_CHARS (parse.rs:4208) so the const assert and ASCII fast-path scanner are consistent. The .zig reference-file hunks were dropped per prior feedback; only the shipped Rust and tests remain.
Other factors
All previously-raised inline comments (mine and CodeRabbit's) are marked resolved and verified addressed in the current diff. The only new finding is a style nit on comment length (CLAUDE.md rule 13) with zero functional impact — it will land as a non-blocking inline suggestion. Test coverage is thorough: each review-round bug is now a permanent regression test, tests use tempDir/bunEnv/concurrent pipe drains per harness conventions, and stderr assertions filter the known ASAN warning. The change is narrowly scoped (~46 lines in one lexer function) and intentionally conservative — bare CR and quoted CR are left untouched, so the only behaviour change is exactly the CRLF-as-LF case the linked issue describes.
There was a problem hiding this comment.
LGTM — all prior feedback addressed and the comment trim in 5c41af4 covers the last nit.
Extended reasoning...
Overview
This PR adds CRLF handling to Bun's shell lexer (src/shell_parser/parse.rs) so Windows-edited .sh files work under bun run. Three targeted additions: (1) an unescaped-\r match arm that drops the CR when immediately followed by LF in Normal state (falls through to the existing \n handler), preserving it literally inside quotes and for bare CR; (2) an escaped-\r handler mirroring the escaped-\n line-continuation, with a Double-state guard that re-emits the backslash for bare \<CR> so POSIX semantics are preserved; (3) \r added to the Double-state backslash-escape list so \<CR><LF> inside double quotes reaches the handler. Four new tests in test/cli/run/run-shell.test.ts cover each case. Net ~40 lines of Rust plus ~85 lines of tests.
Security risks
None. This is line-ending normalization in a lexer — no auth, crypto, permissions, path traversal, or untrusted-size arithmetic. The change only affects how \r bytes tokenize; it cannot introduce injection since it strictly narrows what reaches append_char_to_str_pool (previously \r was always appended; now it's sometimes dropped).
Level of scrutiny
Medium — the shell lexer runs for every .sh file and Bun.$ template. However: the change activates only on \r bytes, which are absent from LF-terminated scripts (the overwhelming majority), and CRLF scripts were completely broken before (\r glued onto every token). The blast radius is therefore "previously-broken input now works" rather than "working input might change." I traced the control flow for all state × escaped × next-char combinations against the surrounding \n and fell_through machinery in parse.rs:2664-3174 and found no path that alters LF-only tokenization.
Other factors
This PR has been through five review rounds over three months; each concern I raised (escaped-CR continuation, dead Double-state guard, bare-CR backslash regression, .zig reference-file edits, over-length comments) was addressed with a follow-up commit and a corresponding test. The final commit 5c41af4 trims comments to the ≤3-line house rule per my last nit. The bug hunter found nothing on the current revision. SPECIAL_CHARS on main already contains \r (line 4196), so the compile-time assert holds. Tests use tempDir, drain pipes concurrently, filter ASAN stderr noise, and assert exact stdout — all matching repo conventions. The design choice to normalize CRLF (deviating from bash, which chokes on it) is the obviously correct product decision for a cross-platform shell and is stated plainly in the PR description.
|
Pushed 7f3b097 with three changes:
Also added the token-level lex.test.ts cases for CRLF, lone CR, continuations in each quote state, tab, and two |
The POSIX grammar allows a linebreak after these operators and bash and dash accept it. The parser went straight into the next command and reported 'expected a command or assignment' on the Newline token. A trailing '# comment' after the operator used to work only because the lexer dropped the newline with the comment.
eat_comment skipped every escaped char, so '# note \' followed by a newline swallowed the next line in LF files but not in CRLF files. bash and dash end a comment at the newline. Now eat_comment stops at any newline.
eat_var ate an escaped identifier char as part of the name, so $HOME\foo expanded the variable HOMEfoo. bash and dash read $HOME and then the word foo.
|
Pushed f4c6412 with three more changes:
Each change has a fail-before test. Token cases are in lex.test.ts, script cases in run-shell.test.ts. bunshell.test.ts: 436 pass, 0 fail. The PR body is updated. |
…ble name The lexer now continues a variable name across an escaped newline, so the interpolation guard in outbuf_ends_with_var_ref must look past trailing backslash-newline sequences. Otherwise a plain value after $FOO\<LF> was appended verbatim and lexed as part of the name.
The # arm only checked for preceding whitespace, so 'echo a;# note' ran a command named #. POSIX 2.3 rule 9 starts a comment at the start of any token, which includes the position after an operator. A # glued to a word, a closing quote, a variable, or a backslash-newline stays literal. The word-part token check moves out of break_word into last_token_is_word_part so both sites share it.
|
Pushed 2faef9e with two more changes:
Both have fail-before tests in lex.test.ts. Every script in the tests prints the same output under bash, dash, and this branch. The PR body is updated. |
The # arm now asks at_token_start: the word is empty and either the previous char is unescaped whitespace or the last token is a separator or an operator. That keeps # literal after ** and after a JS object reference, which are not in the word-part set break_word uses. The break_word refactor from the previous commit is reverted.
POSIX 2.6.3: the first unescaped backtick closes the substitution. bash and dash print hi for echo `echo hi # note`. eat_comment ate the backtick and the script failed with Unclosed command substitution.
|
Pushed e93b4d7 with two more changes:
Both have fail-before tests in lex.test.ts. bunshell.test.ts: 436 pass, 0 fail. The PR body was briefly overwritten by an API error during an edit and is restored. |
|
This PR breaks one form that main runs correctly. It needs a fix here, or #34901 or #38479 merged first. A backtick substitution whose last line ends in a comment, with the closing backtick on the next line, no longer parses: import { $ } from "bun";
await $`${{ raw: "echo `echo a # c\n`" }}`;
Cause: a comment now emits the Nine of 30 inputs of this shape regress: the comment after a command, after a pipeline, after |
|
This head fixes a report about where a
The cause on main is I compared 81 shell lines with bash. The canary matches bash on 49 and this head on 77. The four that still differ are not about where a comment starts: The tests in this PR cover the operator half ( |
This change is the word loop again: words after a redirection stay in the command, an fd duplication takes no target, and a second redirection is an error. The rule that a statement holds one command turns every place where the lexer emits no line end into a parse error. A comment did that, and so does a CRLF line that ends in `)` or `]]`: main runs such a script and prints one `command not found` line, the rule stopped the whole script. The rule belongs on top of the lexer change that ends comment, CRLF and tab lines with a Newline token (#29670). So this commit removes from this branch: the rule in parse_stmt, the comment change merged from #41468, the newline after `&&`, `||` and `|`, the shared closer of command substitutions, and their tests. Tests: an interpolated array after `>` only asserts that its tail does not run, so the case holds when #45780 makes that input an ambiguous redirect. A quoted word that touches an fd duplication has a case.
&&, ||, | and inside [[ ]]
#41468
Fixes #29669
Problem
.shfile with CRLF line endings fails underbun run ./repro.sh:bun: command not found: exportanderror: Script not found "build\r". Every Windows-edited script has this shape.src/shell_parser/parse.rs) has arms for\n, space, and#, but none for\ror\t. A\rfell through toappend_char_to_str_pooland was glued onto the word before it.Fix
\rarm: in Normal state,\r\nis one newline. Inside quotes the byte stays literal, as in bash and dash. A lone\rstays literal too.\<CR><LF>like\<LF>: a line continuation in Normal and Double state, removed before tokenizing (POSIX 2.2.1). It joins the halves of a word and of a variable name ($FOO\<LF>lsis$FOOls, as in bash). Double state now lets a backslash escape\r. A bare\<CR>in double quotes keeps its backslash.# commentends at its newline (a trailing backslash does not continue it) and emits theNewlinetoken, so the next line is a new command. The parser skips newlines after|,&&, and||(POSIXlinebreak), socmd |on one line and the next command on the following line parse. Any escaped char ends a variable name:$HOME\foois${HOME}foo. A#starts a comment wherever a new token starts, soecho a;# noteno longer runs a command named#. Inside backticks a comment ends at the closing backtick.test/cli/run/run-shell.test.ts(10 new tests, all fail on main) andtest/js/bun/shell/lex.test.ts(token-level cases for CRLF, lone CR, continuations, tab, comments, plus sixBun.$cases). Also ranparse.test.tsandbunshell.test.ts.Background
matchon the next char. An arm either doesbreak 'escaped(append the char to the current word) or setsfell_through(skip it).break_wordflushes the current word as aTexttoken.NewlineandSemicolontokens separate statements in the parser.read_charmarks a char asescapedwhen a backslash precedes it. In Double state only a short list of chars can be escaped ($, backtick,",\,\n,#).\rjoins that list so\<CR><LF>can be a continuation in double quotes.eat_varscans a variable name after$. It now skips an escaped LF or CRLF and keeps scanning, and stops at any other escaped char, so the name matches what bash reads after it removes\<newline>. TheBun.$interpolation guard (outbuf_ends_with_var_refinshell_body.rs) looks past a trailing\<newline>for the same reason, so$FOO\<LF>${x}expandsFOOand keepsxa separate value.run_command.rsreads the file: that leavesBun.$template strings and quoted CR bytes wrong. The lexer is the one place every shell source passes through.Downsides
Bun.$template or script that relied on\tbeing part of a word, on$FOO\<LF>lsexpanding as${FOO}ls, or on$HOME\fooexpandingHOMEfoo, changes meaning. All now match bash and dash.\ror\tbeyond one table lookup per such byte: both were already inSPECIAL_CHARS, so the fast-path scanner stopped on them before this change too.Notes
Behaviour compared against bash 5 and dash for every new test input.
FOO=x; FOOls=y; echo "[$FOO\<LF>ls]" [$FOO\<LF>ls] [$\<LF>FOO]prints[y] [y] [x]in bash, dash, and this branch.Earlier iterations of this PR patched the Zig lexer. Main replaced that file with the Rust lexer in
src/shell_parser/parse.rsand the fix was ported.The continuation-join test uses the
echobuiltin instead ofprintf: Windows has noprintf, and the external one on CI printednfor\n.Pre-existing and left alone:
echo ** foogluesfooonto**(break_word pushes no Delimit afterDoubleAsterisk). Stock bun reportsno matches found: **foo.Operator continuation checked with
echo a | # c,cat,echo b &&,echo c,false || # d,echo eon separate lines: bash, dash, and this branch printa b c e. Stock bun fails withexpected a command or assignment.no test proof · iteration 17 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/run/run-shell.test.ts