Skip to content

Fix TOML multi-line string trimming - #28682

Closed
robobun wants to merge 9 commits into
mainfrom
farm/5c7ac681/fix-toml-multiline-trim
Closed

Fix TOML multi-line string trimming#28682
robobun wants to merge 9 commits into
mainfrom
farm/5c7ac681/fix-toml-multiline-trim

Conversation

@robobun

@robobun robobun commented Mar 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

Bun's TOML parser does not properly handle multi-line strings per the TOML v1.0.0 spec:

  1. Leading newline not trimmed: A newline immediately following the opening """ or ''' delimiter should be trimmed, but was kept.
  2. Backslash line continuation incomplete: When \ is the last non-whitespace character on a line, it and all subsequent whitespace (including newlines) should be consumed until the next non-whitespace character. Only the immediate newline was skipped.

Reproduction

const toml = `str1 = """
The quick brown \


  fox jumps over \
    the lazy dog."""`

Bun.TOML.parse(toml).str1
// Expected: "The quick brown fox jumps over the lazy dog."
// Got:      "\nThe quick brown \n\n  fox jumps over     the lazy dog."

Fix

  • Added trimMultilineStart helper that skips \n or \r\n immediately after the opening delimiter for both """ and ''' strings.
  • Fixed backslash line continuation in decodeEscapeSequences to consume all subsequent whitespace characters (spaces, tabs, newlines, carriage returns) after a line-ending backslash.

Verification

  • USE_SYSTEM_BUN=1 bun test test/regression/issue/28680.test.ts → 3 fail, 1 pass
  • bun bd test test/regression/issue/28680.test.ts → 4 pass

Closes #28680

@robobun

robobun commented Mar 30, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai

coderabbitai Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 032a90d2-a775-48a8-bb9c-c14713d4c32b

📥 Commits

Reviewing files that changed from the base of the PR and between f49d9cf and 0b59e55.

📒 Files selected for processing (2)
  • src/interchange/toml/lexer.zig
  • test/regression/issue/28680.test.ts

Walkthrough

Trimmed initial newlines after multi-line TOML string delimiters and improved backslash-continuation handling in the lexer: line endings plus following whitespace are skipped and iterator state updated. Added regression tests covering basic and literal multi-line string behaviors and escape sequencings.

Changes

Cohort / File(s) Summary
TOML Lexer Fix
src/interchange/toml/lexer.zig
Added internal trimMultilineStart(contents, content_start) to remove a newline immediately after multi-line string openings. Enhanced escape-sequence decoding to skip \r\n or \n and then consume subsequent whitespace (space, tab, \n, \r), resetting the codepoint iterator width accordingly. Converted several escape-value literals to hex byte constants.
Regression Test Suite
test/regression/issue/28680.test.ts
Added four tests using Bun.TOML.parse to validate: trimming leading newlines for multi-line basic and literal strings, correct concatenation for backslash-continued lines without inserted newlines/spaces, and correct handling of escape sequences like \t and \f.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix TOML multi-line string trimming' directly and clearly describes the main change—fixing multi-line string trimming behavior in the TOML parser to comply with TOML v1.0.0 spec.
Description check ✅ Passed The PR description comprehensively covers both required template sections: it explains the problem with specific TOML spec violations and provides a reproduction case, and it details verification steps showing test results.
Linked Issues check ✅ Passed The changes directly address all requirements from issue #28680: implementing leading newline trimming for multi-line strings and fixing backslash line-continuation to consume all whitespace per TOML v1.0.0 spec.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing TOML multi-line string parsing: the lexer fix in lexer.zig and regression tests in 28680.test.ts are both essential to the stated objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread src/interchange/toml/lexer.zig Outdated
Comment thread test/regression/issue/28680.test.ts Outdated
Comment thread src/interchange/toml/lexer.zig Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three previously flagged bugs have been addressed in 0b59e55. LGTM.

Extended reasoning...

Overview

This PR fixes TOML multi-line string parsing in src/interchange/toml/lexer.zig: adds trimMultilineStart() to strip the leading newline after opening """ / '' delimiters, and rewrites the backslash line-continuation logic in decodeEscapeSequences to consume all subsequent whitespace. A regression test file (test/regression/issue/28680.test.ts) covers the new behavior.

Previous Review Findings — All Addressed

My prior review flagged three issues, all resolved in commit 0b59e55:

  1. Bare CR trimmed incorrectlytrimMultilineStart now returns content_start unchanged for bare CR (not followed by LF), as required by spec.
  2. \t/\f escape values swapped\t now correctly emits 0x09 and \f emits 0x0C; a test case was added.
  3. Outer \r CRLF lookahead used wrong index — now correctly uses iter.i + iter.width to peek at the next byte.

Security Risks

No security-sensitive code paths are touched. This is a TOML parser fix affecting string value decoding only.

Level of Scrutiny

Moderate — parser logic changes require care, but the scope is narrow and well-tested. The fixes follow the established iterator pattern already present in the file. The pre-existing 4-5 closing delimiter bug is not introduced or worsened by this PR.

Other Factors

The CI failure (azure-service-bus SIGILL on asan) is unrelated to these changes. All inline comments are resolved. The fix correctly handles the TOML v1.0.0 spec requirements for multi-line string parsing.

Comment thread src/interchange/toml/lexer.zig
@robobun
robobun force-pushed the farm/5c7ac681/fix-toml-multiline-trim branch from 0b59e55 to a1f5989 Compare May 16, 2026 07:20
Comment thread test/regression/issue/28680.test.ts Outdated
Comment thread src/parsers/toml/lexer.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior feedback is addressed and the new space/tab lookahead arm in f3a477d traces correctly against the Cursor semantics — deferring to a human for final sign-off since this is ~80 lines of manual iterator-state manipulation in the lexer, and build-rust is currently red on the latest commit.

Extended reasoning...

Overview

This PR fixes TOML v1.0.0 multi-line string handling in src/parsers/toml/lexer.rs: a new trim_multiline_start helper strips the leading LF/CRLF after """/''', the line-ending-backslash paths now consume all trailing whitespace, the outer \r CRLF-normalization lookahead is corrected to use iter.i + iter.width, the \t/\f escape values are unswapped, and (in f3a477d) a new ' ' | '\t' arm peeks past trailing whitespace between a backslash and the newline per the mlb-escaped-nl ABNF. Tests were moved from test/regression/issue/ into test/js/bun/resolve/toml/toml.test.js per project convention, with six new it(...) blocks.

Review history

I have reviewed this PR twice before. All five issues I raised (bare-CR trimming, \t/\f swap, broken outer-CRLF lookahead, test-file placement, and backslash-then-trailing-whitespace) have been fixed in 0b59e55 / 2c592a0 / f3a477d, and the 4/5-closing-delimiter pre-existing bug was reasonably declined as out of scope. I re-traced the f3a477d arm against CodepointIterator::next (src/bun_core/string/immutable.rs:315) — iter.i points at the start of the just-read char and next() resumes at iter.i + iter.width, so the peek = iter.i + iter.width lookahead and the iter.width = 0 reset are both correct. The fall-through path (\ + space not followed by a newline) preserves the prior lenient behavior. The bug-hunting system found nothing new this round.

Security risks

None. This is pure lexer string-decoding logic with no I/O, auth, or external-input trust boundaries beyond what already existed; all new index arithmetic is bounds-checked.

Level of scrutiny

Medium-high. The change is well-scoped and well-tested, but it is ~80 net lines of hand-rolled cursor position/width manipulation across four interacting match arms in a user-facing parser (Bun.TOML.parse and .toml imports). Off-by-one errors here are easy to introduce and hard to spot.

Other factors

The robobun CI comment shows build-rust failing on several targets for commit f3a477d (plus an agent-creation failure). I do not see anything in the diff that would obviously fail to compile, so this may be infrastructure, but I will not approve over a red build. Combined with the non-trivial iterator logic, this should get a human pass before merge.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior feedback addressed, cursor arithmetic verified against CodepointIterator::next.

Extended reasoning...

Overview

This PR fixes TOML v1.0.0 multi-line string handling in src/parsers/toml/lexer.rs: it adds trim_multiline_start to skip a leading LF/CRLF after """/''', rewrites the line-ending-backslash arms in decode_escape_sequences to consume all subsequent whitespace, adds a new arm for backslash + trailing spaces/tabs before a newline (per ABNF mlb-escaped-nl), fixes the pre-existing \t/\f value swap, and corrects the outer \r CRLF-normalization lookahead. Six new test cases were added to test/js/bun/resolve/toml/toml.test.js.

Security risks

None. This is pure-function lexing of already-loaded byte slices: no I/O, no allocation outside the existing arena, no unsafe code, and all new index arithmetic is bounds-checked before dereference. No injection, auth, or data-exposure surface.

Level of scrutiny

Moderate — parser code with manual cursor manipulation is error-prone, so I traced every new iter.i / iter.width mutation against NewCodePointIterator::next (unicode.rs:247-288), which sets cursor.i to the start of the just-decoded codepoint and cursor.width to its byte length. The PR's iter.i + iter.width lookahead and iter.width = 0 reset match the canonical pattern already used in the SIMD scan path (unicode.rs:223-225). The \r, \n, and space/tab continuation arms all advance correctly and re-enter the loop at the right byte.

Other factors

This PR has been through three review rounds; every issue I raised (bare-CR trimming, \t/\f swap, outer CRLF lookahead, test-file placement, whitespace-before-newline ABNF gap) has been fixed and is covered by a test. The one item declined as out-of-scope (4/5 closing delimiters) is a pre-existing bug unrelated to the opening-delimiter logic this PR touches. No CODEOWNERS apply to src/parsers/toml/, and the bug-hunting system found nothing new on the latest revision.

@robobun
robobun force-pushed the farm/5c7ac681/fix-toml-multiline-trim branch from 25ec3ac to aba1bc4 Compare May 18, 2026 09:27
Comment thread src/parsers/toml/lexer.rs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior feedback addressed; the remaining inline note is a pre-existing cosmetic offset in error positions and not blocking.

Extended reasoning...

Overview

This PR fixes TOML v1.0.0 spec compliance for multi-line strings in src/parsers/toml/lexer.rs: trimming the leading newline after """/''' delimiters, consuming all whitespace after a line-ending backslash (including the escape ws newline ABNF case), fixing the pre-existing \t/\f value swap, and correcting the CRLF lookahead in the outer \r handler. Six new test cases in test/js/bun/resolve/toml/toml.test.js cover the spec examples directly. The <cassert> include in wtf-bindings.cpp is an unrelated one-line build fix for the existing assert() call in uv__tty_make_raw.

Security risks

None. This is string-content lexing for TOML config/import files — no auth, crypto, permissions, or command execution. The new code only adjusts byte-index cursors within already-bounded slices; all indexing is guarded by < text.len() checks, and trim_multiline_start bounds-checks before reading.

Level of scrutiny

Moderate. The TOML lexer feeds bunfig.toml and TOML imports, so correctness matters, but this PR has now been through four review passes. Earlier rounds caught and fixed a bare-CR over-trim, the \t/\f swap, the broken outer CRLF lookahead, the escape ws newline gap, test-file placement, and the hex-literal consistency oversight — all confirmed resolved in the current diff. The iterator-state manipulation (iter.i/iter.width = 0) is the subtle part and I've re-read it against CodepointIterator::next semantics; it's correct.

Other factors

  • The one finding from this run is a pre-existing 2–4 byte skew in diagnostic column positions for malformed escapes inside """…""" strings — purely cosmetic, root cause predates the PR.
  • CI failures in the robobun comment are all in unrelated files (HiveArrayFallback deprecations, FreeBSD spawn/Futex warnings, musl build) and not caused by this change.
  • No CODEOWNERS for the touched paths.
  • The author explicitly deferred the unrelated 4/5-closing-delimiter spec gap as out-of-scope, which is reasonable.

Comment thread src/parsers/toml/lexer.rs
@robobun
robobun force-pushed the farm/5c7ac681/fix-toml-multiline-trim branch from 0c50110 to 82a86d9 Compare May 24, 2026 18:06

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior feedback addressed, iterator-reposition logic verified against Cursor::next semantics, and the new test cases cover the edge cases.

Extended reasoning...

Overview

This PR fixes TOML v1.0.0 spec compliance for multi-line strings in src/parsers/toml/lexer.rs: (1) a new trim_multiline_start helper trims a leading LF/CRLF after """ / ''', (2) the line-ending-backslash handling in decode_escape_sequences now consumes all subsequent whitespace (and accepts trailing ws between the backslash and the newline per the ABNF), (3) escape values for \t/\f are corrected and all single-char escapes converted to hex literals, and (4) slice_lo is now passed as the diagnostic base so error columns line up with the slice origin. Six new test cases were added to test/js/bun/resolve/toml/toml.test.js.

Security risks

None. This is read-only lexing of TOML input into string values; no auth, crypto, filesystem, or network surface. All new byte indexing is bounds-checked (pos < text.len() / peek < text.len()), and Rust slice indexing panics rather than reading OOB, so there is no memory-safety exposure.

Level of scrutiny

Moderate. The TOML lexer feeds bunfig.toml and .toml imports, so correctness matters, but the change is narrowly scoped to multi-line string slice bounds and a whitespace-consuming loop inside the escape decoder. The only subtle part — repositioning the cursor via iter.i = pos; iter.width = 0; so the next iterator.next() resumes at pos — I verified directly against NewCodePointIterator::next in src/bun_core/string/immutable.rs (which computes pos = cursor.i + cursor.width), and the same reposition pattern is already used elsewhere in that file.

Other factors

This PR has been through four prior review rounds; every issue raised (bare-CR trim, \t/\f swap, CRLF lookahead off-by-one, test-file placement, ws-before-newline ABNF gap, hex-literal consistency, diagnostic-base offset) has been fixed and the threads resolved. The bug-hunting pass on the current revision found nothing. Test coverage includes the no-leading-newline case, pure-continuation strings, ws-between-backslash-and-newline, and the \t/\f escape values. No CODEOWNERS entry covers src/parsers/toml/. Given the accumulated review history and verified correctness, I'm comfortable approving without further human review.

robobun and others added 8 commits June 6, 2026 13:35
Trim the first newline after opening """ and ''' delimiters per TOML
v1.0.0 spec. Fix backslash line continuation to consume all subsequent
whitespace (spaces, tabs, newlines) until the next non-whitespace
character. Fix swapped \t / \f escape values and the outer \r handler's
CRLF lookahead in decode_escape_sequences.
Per test/CLAUDE.md: test/regression/issue/ is only for true regressions
(worked in a previous release, then broke). #28680 is a TOML
spec-compliance gap that was never correct, so the tests belong in
test/js/bun/resolve/toml/toml.test.js.
…rings

Per TOML v1.0.0 ABNF `mlb-escaped-nl = escape ws newline *( wschar / newline )`,
trailing spaces/tabs between a line-ending backslash and the newline are
valid and should be consumed along with the rest of the continuation.
debian-13-x64-asan-test-bun failed with exit status -1 (agent lost)
on autofix commit — infrastructure flake. All build-rust steps green.
Consistency nit: the other escape pushes (\b, \n, \v, \r) use 0x0X
hex, while \f and \t still used decimal 12 and 9.
…nces

Error positions inside decode_escape_sequences are computed as
base + iter.i, where iter.i is an offset into the sliced text. Passing
`start` (position after the first quote) misaligned diagnostics for
multi-line basic strings by 2-4 bytes — the difference between `start`
and the actual slice origin (`start + 2` plus any trimmed LF/CRLF).
Pass `slice_lo` so the column points at the real source byte.
@robobun
robobun force-pushed the farm/5c7ac681/fix-toml-multiline-trim branch from 82a86d9 to cf642c2 Compare June 6, 2026 13:37

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior review feedback addressed; the remaining inline nit is just a CRLF test-coverage suggestion and non-blocking.

Extended reasoning...

Overview

This PR fixes two TOML v1.0.0 spec-compliance gaps in src/parsers/toml/lexer.rs: (1) trimming the leading newline immediately after """ / ''' opening delimiters via a new trim_multiline_start helper, and (2) making line-ending backslashes consume all subsequent whitespace (and, per the ABNF, trailing whitespace between the backslash and the newline). Along the way it also fixes two pre-existing bugs surfaced during review — the swapped \\t/\\f escape values and the broken CRLF lookahead in the outer \\r handler — and corrects the diagnostic base offset passed to decode_escape_sequences. Six new tests in test/js/bun/resolve/toml/toml.test.js cover the spec examples plus the escape-value fix.

Security risks

None. This is pure TOML string-content parsing with no auth, crypto, filesystem, or network surface. All new byte indexing is bounds-checked (pos < text.len(), content_start + 1 < contents.len()), and Rust slice indexing panics rather than reading out of bounds, so there is no memory-safety exposure.

Level of scrutiny

Medium — the iterator-cursor manipulation (iter.i / iter.width resets) is the only subtle part. I verified against bun_core::string::immutable::Cursor::next that next() advances to cursor.i + cursor.width and then sets cursor.i to that position, so the new code's pattern of writing iter.i = pos; iter.width = 0 correctly resumes iteration at pos. The \\r, \\n/U+2028/U+2029, and space/tab line-continuation arms each compute the resume point consistently with those semantics. trim_multiline_start is fully bounds-checked and correctly leaves bare CR untrimmed.

Other factors

This PR has been through four prior review rounds; every issue raised (bare-CR trim, \\t/\\f swap, CRLF double-newline, test-file placement, escape ws newline ABNF gap, hex-literal consistency, diagnostic base offset) has been fixed and the threads resolved. No CODEOWNERS cover these paths. The only finding from this run is a non-blocking nit suggesting explicit CRLF test cases for the new branches — worth adding but not a reason to hold the merge.

Comment thread test/js/bun/resolve/toml/toml.test.js
Template literals normalize to LF so the existing tests never hit the
CRLF branches of trim_multiline_start or the backslash-CR lookahead.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior feedback addressed, CRLF coverage added in a9b01a2.

Extended reasoning...

Overview

This PR fixes two TOML v1.0.0 spec-compliance gaps in Bun's TOML lexer (src/parsers/toml/lexer.rs): trimming the newline immediately after """ / ''' opening delimiters, and making line-ending backslashes consume all subsequent whitespace. Along the way (in response to review), it also fixes a pre-existing \\t/\\f value swap, a CRLF lookahead off-by-one in the outer \\r handler, the diagnostic-column base for multiline escapes, and adds the mlb-escaped-nl trailing-whitespace case. Seven new tests in test/js/bun/resolve/toml/toml.test.js cover LF, CRLF, literal vs basic strings, and escape codepoints.

Security risks

None. This is pure lexer logic over byte slices with explicit bounds checks (pos < text.len(), content_start + 1 < contents.len()) on every read; no FFI, no allocation patterns that could overflow, no untrusted-path or auth surface. Worst-case impact of a defect here is a mis-parsed TOML string value.

Level of scrutiny

Moderate — it's a parser change and TOML backs bunfig.toml, but the change is narrowly scoped to multi-line string content-start/whitespace handling and doesn't touch tokenization of other value types. This PR has been through eight rounds of bot review with every finding (bare-CR trim, \\t/\\f swap, CRLF lookahead, test placement, escape ws newline ABNF gap, hex-literal consistency, slice_lo diagnostic base, CRLF test coverage) addressed and resolved. The remaining flagged item (4–5 closing delimiters) is a pre-existing, orthogonal end-position bug the author reasonably scoped out.

Other factors

The current bug-hunting pass found nothing new. The added tests directly exercise every new branch including the CRLF paths added in a9b01a2. CI failures are unrelated infrastructure (musl LTO data-layout linking) and a flaky bunx.test.ts, neither touched by this diff. No CODEOWNERS cover src/parsers/toml/. Net: well-understood, well-tested spec-compliance fix with all feedback closed out.

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

#32994 also fixes both issues here (the leading-newline trim and the line-ending backslash), validated against the official toml-test 1.0 corpus, and additionally handles the cases this diff does not: a backslash followed by trailing whitespace before the newline (valid per the mlb-escaped-nl ABNF, see toml-test valid/string/ends-in-whitespace-escape), a backslash followed by non-whitespace on the same line (must error), and one or two quotes adjacent to the closing delimiter. It also covers the \UXXXXXXXX escape, date-times, 64-bit integer exactness, and dotted numeric keys from the same lexer. If #32994 lands first this one can be closed; happy to rebase whichever way maintainers prefer.

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

Correction to the above: #32994 is now closed in favor of #32953, a full rewrite of the TOML parser that reaches 708/708 on the official toml-test suite and covers the multi-line trimming and line-ending backslash rules here. #32953 is the PR to track.

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

The TOML parser was rewritten for v1.1.0 conformance in #32953, which landed on main (aca54d5) and closed #28680. It deletes src/parsers/toml/lexer.rs, the file this PR patches, so this PR no longer applies.

The behavior this PR fixes is covered on current main: all 7 tests added here (first-newline trimming for basic and literal multi-line strings, line-ending backslash including whitespace before the newline, CRLF variants, and the \t/\f escapes) pass against main as is, and the official toml-test case valid/string/multiline in test/js/bun/toml/toml-test-suite.test.ts is the same document as the #28680 report. Closing.

@robobun robobun closed this Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TOML multi-line strings not properly trimmed

1 participant