Skip to content

Bun.markdown: implement the hardSoftBreaks and collapseWhitespace options - #39495

Open
robobun wants to merge 2 commits into
mainfrom
farm/d8c772e2/md-hardsoftbreaks-collapsews
Open

robobun wants to merge 2 commits into
mainfrom
farm/d8c772e2/md-hardsoftbreaks-collapsews

Conversation

@robobun

@robobun robobun commented Aug 18, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #39491

Problem

  • Bun.markdown.html("a\nb\n", { hardSoftBreaks: true }) returns "<p>a\nb</p>\n" and Bun.markdown.html("a b\n", { collapseWhitespace: true }) returns "<p>a b</p>\n": both options are documented (docs/runtime/markdown.mdx, Bun.markdown.Options) but do nothing.
  • hard_soft_breaks is parsed into Options (src/md/root.rs) but dropped by Options::to_flags(); Flags (src/md/types.rs) had no such field.
  • collapse_whitespace reaches Flags and marks space/tab/CR in the mark char map (src/md/parser.rs:351), but process_inline_content (src/md/inlines.rs) had no branch for those characters, so they fell through as literal text.

Fix

  • Adds hard_soft_breaks to Flags and threads it through to_flags(). The newline handler in process_inline_content now forces a hard break when the flag is set, dropping trailing whitespace before the break, matching md4c's MD_FLAG_HARD_SOFT_BREAKS (first call above now returns "<p>a<br />\nb</p>\n").
  • Adds a whitespace-run branch to process_inline_content: when collapse_whitespace is set, a non-trivial run (longer than one character, or not a plain space) in normal text is emitted as a single space, matching md4c's MD_FLAG_COLLAPSEWHITESPACE (second call above now returns "<p>a b</p>\n"). A run immediately before a newline is left for the existing hard-break detection, so "a \n" still produces <br />. Code spans and code blocks are untouched (they never go through this path).
  • Verified: new tests in test/js/bun/md/md-edge-cases.test.ts fail on current bun and pass with this change; md4c's checked-in spec-hard-soft-breaks.txt is now enabled in test/js/bun/md/md-spec.test.ts and passes; all 1082 tests in test/js/bun/md/ pass (including the full CommonMark spec suite, so no behavior change with the flags off).
  • Intentionally excluded: underline and latexMath are the same bug class but are span-type options handled separately in Bun.markdown: implement the latexMath and underline options #39493, so they are not touched here.

Background

  • Bun.markdown is a Rust port of md4c. Block structure is parsed first; each leaf block's lines are merged into one buffer and walked by process_inline_content, which dispatches on a per-parser "mark char map" (a 256-bit set of characters that need special handling, built once from the flags).
  • In md4c, soft breaks (MD_TEXT_SOFTBR) render as \n and hard breaks (MD_TEXT_BR) as <br />\n. MD_FLAG_HARD_SOFT_BREAKS turns every soft break into a hard break; MD_FLAG_COLLAPSEWHITESPACE replaces each whitespace run that is more than a single space with one space.

[review] gate passed · iteration 0 · 5 files touched

fails on main (without fix)
ASAN without fix: 8 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/md/md-edge-cases.test.ts test/js/bun/md/md-spec.test.ts
bun test v1.4.0 (6e906e468)

test/js/bun/md/md-spec.test.ts:
(pass) CommonMark > example 1 (line 355): Tabs [31.86ms]
(pass) CommonMark > example 2 (line 362): Tabs [6.42ms]
(pass) CommonMark > example 3 (line 369): Tabs [6.49ms]
(pass) CommonMark > example 4 (line 382): Tabs [12.83ms]
(pass) CommonMark > example 5 (line 395): Tabs [15.02ms]
(pass) CommonMark > example 6 (line 418): Tabs [9.48ms]
(pass) CommonMark > example 7 (line 427): Tabs [11.89ms]
(pass) CommonMark > example 8 (line 439): Tabs [6.30ms]
(pass) CommonMark > example 9 (line 448): Tabs [25.98ms]
(pass) CommonMark > example 10 (line 466): Tabs [5.95ms]
(pass) CommonMark > example 11 (line 472): Tabs [3.40ms]
(pass) CommonMark > example 12 (line 489): Backslash escapes [5.15ms]
(pass) CommonMark > example 13 (line 499): Backslash escapes [4.19ms]
(pass) CommonMark > example 14 (line 509): Backslash escapes [6.61ms]
(pass) CommonMark > example 15 (line 534): Backslash escapes [6.59ms]
(pass) CommonMark >
... (truncated)

release without fix: 8 FAILED
bun test v1.4.0-canary.1 (6e906e468)

test/js/bun/md/md-spec.test.ts:
(pass) CommonMark > example 1 (line 355): Tabs [0.76ms]
(pass) CommonMark > example 2 (line 362): Tabs [0.05ms]
(pass) CommonMark > example 3 (line 369): Tabs [0.03ms]
(pass) CommonMark > example 4 (line 382): Tabs [0.13ms]
(pass) CommonMark > example 5 (line 395): Tabs [0.07ms]
(pass) CommonMark > example 6 (line 418): Tabs [0.05ms]
(pass) CommonMark > example 7 (line 427): Tabs [0.06ms]
(pass) CommonMark > example 8 (line 439): Tabs [0.04ms]
(pass) CommonMark > example 9 (line 448): Tabs [0.10ms]
(pass) CommonMark > example 10 (line 466): Tabs [0.04ms]
(pass) CommonMark > example 11 (line 472): Tabs [0.02ms]
(pass) CommonMark > example 12 (line 489): Backslash escapes [0.05ms]
(pass) CommonMark > example 13 (line 499): Backslash escapes [0.03ms]
(pass) CommonMark > example 14 (line 509): Backslash escapes [0.05ms]
(pass) CommonMark > example 15 (line 534): Backslash escapes [0.10ms]
(pass) CommonMark > example 16 (line 543): Backslash escapes [0.05ms]
(pass) CommonMark > example 17 (line 555): Backslash escapes [0.03ms]
(pass) CommonMark > example 18 (line 562): Backslash escapes [0.03ms]
(pass)
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/md/md-edge-cases.test.ts test/js/bun/md/md-spec.test.ts
bun test v1.4.0 (6e906e468)

test/js/bun/md/md-spec.test.ts:
(pass) CommonMark > example 1 (line 355): Tabs [32.12ms]
(pass) CommonMark > example 2 (line 362): Tabs [6.42ms]
(pass) CommonMark > example 3 (line 369): Tabs [6.17ms]
(pass) CommonMark > example 4 (line 382): Tabs [13.01ms]
(pass) CommonMark > example 5 (line 395): Tabs [14.59ms]
(pass) CommonMark > example 6 (line 418): Tabs [9.32ms]
(pass) CommonMark > example 7 (line 427): Tabs [11.27ms]
(pass) CommonMark > example 8 (line 439): Tabs [6.17ms]
(pass) CommonMark > example 9 (line 448): Tabs [24.90ms]
(pass) CommonMark > example 10 (line 466): Tabs [8.24ms]
(pass) CommonMark > example 11 (line 472): Tabs [3.71ms]
(pass) CommonMark > example 12 (line 489): Backslash escapes [5.01ms]
(pass) CommonMark > example 13 (line 499): Backslash escapes [4.50ms]
(pass) CommonMark > example 14 (line 509): Backslash escapes [6.61ms]
(pass) CommonMark > example 15 (line 534): Backslash escapes [6.00ms]
(pass) CommonMark >
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     e63331534b
  features     baseline

23 deps, 129 codegen, 1172 objects in 708ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1244] install /workspace/bun
bun install v1.4.0-canary.1 (6e906e468)

Checked 26 installs across 63 packages (no changes) [8.00ms]
[2/1244] gen ErrorCode+*.h
[3/1244] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (6e906e468)

Checked 1 install across 2 packages (no changes) [3.00ms]
[4/1244] fetch tinycc
[tinycc] up to date
[5/1243] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[6/1216] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (6e906e468)

Checked 111 installs across 104 packages (no changes) [8.00ms]
[7/1216] gen bindgenv2
[8/1216] fetch zlib
[zlib] up to date
[9/1216] gen node-fallbacks/react-refresh.js
Bundled 1 module in 14ms

  react-refresh.js  4.81 KB  (entry point)

[10/1216] gen .bind.ts → GeneratedBindings.cpp
[11/1216] gen bake.{client,server,error}.js
-> bake.client.js, bake.server
... (truncated)
diff hotspot
src/md/inlines.rs                    | 37 ++++++++++++++++++++-
 src/md/root.rs                       |  1 +
 src/md/types.rs                      |  1 +
 test/js/bun/md/md-edge-cases.test.ts | 63 ++++++++++++++++++++++++++++++++++++
 test/js/bun/md/md-spec.test.ts       |  1 +
 5 files changed, 102 insertions(+), 1 deletion(-)

gate history · 2 passed · 0 rejected · iteration 0

evidence per changed file
file                                  reads  edits  tests
src/md/inlines.rs                         4      3      0
src/md/root.rs                            1      1      0
src/md/types.rs                           2      2      0
test/js/bun/md/md-edge-cases.test.ts      2      0      0
test/js/bun/md/md-spec.test.ts            1      1      0

root cause · written by the author bot

The hardSoftBreaks and collapseWhitespace options were parsed from the JavaScript options object but never took effect, because hard_soft_breaks was dropped in Options::to_flags and the inline processing loop had no handling for either behavior, so soft breaks always became Softbr and whitespace runs passed through as plain text. The fix threads both flags through to the parser, makes the newline handling emit a hard break and strip preceding trailing whitespace when hard_soft_breaks is set, and adds a branch that collapses non-trivial whitespace runs into a single space when collapse_white…

@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Your included review limit has been reached.

You’re in a promotional period — use the checkbox below to run this review for free:

  • Run review for free

On-demand reviews are free for the next 31 days. After that, they cost $0.25 per reviewed file.

How can I continue?

Run this review now using the option above, or comment @coderabbitai review --use-credits.

You can also wait for the limit to reset (next review available in 45 seconds), then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 64ae36e5-8d03-41ea-bdd9-32dc5e5f69f1

📥 Commits

Reviewing files that changed from the base of the PR and between 1b88ad3 and e633315.

📒 Files selected for processing (5)
  • src/md/inlines.rs
  • src/md/root.rs
  • src/md/types.rs
  • test/js/bun/md/md-edge-cases.test.ts
  • test/js/bun/md/md-spec.test.ts

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

@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 — the two flags are threaded correctly and gated so flags-off behavior is unchanged. The inline note about underline is informational; it's a heavier change and reasonable to defer.

Extended reasoning...

Overview

Implements two documented-but-no-op Bun.markdown options: hardSoftBreaks (adds a Flags field, threads it through to_flags(), and OR's it into the existing hard-break condition in the newline handler) and collapseWhitespace (adds a whitespace-run branch to process_inline_content and a trailing-whitespace strip before soft breaks). Five files touched: three in src/md/, two test files.

Security risks

None. Pure markdown-rendering logic on already-merged inline content; no new syscalls, allocations, or FFI. All new slice accesses are bounds-checked (end < content.len(), sp > text_start, i > text_start).

Level of scrutiny

Medium — parser hot path, but the change is small and additive. I verified via build_mark_char_map (parser.rs:351-355) that space/tab/CR are only mark chars when collapse_whitespace is set, so with the flag off the new branch is unreachable via the fast-path skip and the CommonMark spec suite is unaffected. Traced the whitespace-run → newline handoff for "a \n" (still a hard break), "a \n" (collapses before soft break), "a\t\n" with hardSoftBreaks (tab stripped, hard break), and single-space (no-op, end - i == 1 && c == ' '). Confirmed the option parser accepts snake_case so md-spec.test.ts's --fhard-soft-breaks → hard_soft_breaks mapping works, and that the spec-hard-soft-breaks.txt fixture exists.

Other factors

Tests are exact-value .toBe() assertions covering both flag states, code-span/code-block preservation, and the hard-break interaction; the md4c spec file is now enabled. The one finding (underline is a third parsed-but-dropped option) is a nit — it needs emph_char threaded into emit_emph_open_tags/close_tags, which is a separate feature implementation, not a mechanical sibling of this fix.

Comment thread src/md/root.rs
…ions

Both options were parsed from the JS options object but never reached the
inline processor, so they were silently ignored.

- hard_soft_breaks now threads through Flags and forces every soft break
  to a hard break, dropping trailing whitespace before it, matching
  md4c's MD_FLAG_HARD_SOFT_BREAKS.
- collapse_whitespace now collapses non-trivial whitespace runs (longer
  than one char, or containing a tab/CR) in normal text into a single
  space, matching md4c's MD_FLAG_COLLAPSEWHITESPACE. Runs immediately
  before a newline are left to the existing hard-break detection.

Enables md4c's spec-hard-soft-breaks.txt in the spec test suite.
@robobun
robobun force-pushed the farm/d8c772e2/md-hardsoftbreaks-collapsews branch from 0469333 to d2d159c Compare August 21, 2026 02:56
Comment thread src/md/inlines.rs Outdated
Comment thread src/md/inlines.rs Outdated
Comment thread src/md/inlines.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 — both flags are correctly threaded through and gated so default behavior is unchanged.

What was reviewed:

  • Traced the new whitespace-collapse branch in process_inline_content: space/tab/CR are only in mark_char_map when collapse_whitespace is set (parser.rs:339-343), so the branch is unreachable with the flag off; the run-before-\n deferral preserves the two-space hard-break rule.
  • Verified hard_soft_breaks reuses the existing trailing-space stripping, and spec-hard-soft-breaks.txt maps via --fhard-soft-breaks → hard_soft_breaks in BOOL_FIELD_SETTERS.
  • Prior feedback (the underline/latexMath sibling exclusion, comment-cop length) is addressed and resolved.
Extended reasoning...

Overview

This PR wires up two documented-but-no-op Bun.markdown options: hardSoftBreaks (adds a hard_soft_breaks field to Flags, threads it through Options::to_flags(), and ORs it into the existing hard-break condition in the newline branch of process_inline_content) and collapseWhitespace (adds a whitespace-run branch to process_inline_content that emits a single space for non-trivial runs, plus a trailing-whitespace strip before soft breaks). ~37 net production lines across three files, plus 63 lines of new tests and one line enabling md4c's checked-in spec-hard-soft-breaks.txt.

Security risks

None. This is a pure text-transformation flag in the markdown inline parser — no allocation, no FFI, no user-controlled sizes, no external data. The new branches only read and re-emit slices of the already-bounded merged-line buffer.

Level of scrutiny

Low-to-medium. Both new code paths are gated on flags that default to false, and build_mark_char_map (parser.rs:339-343) only marks space/tab/CR when collapse_whitespace is set — so with defaults the new collapse branch is provably unreachable and the || self.flags.hard_soft_breaks clause is a no-op. The full CommonMark spec suite (1082 tests) still passes, confirming no behavior change with the flags off. The flag-on paths are covered by exact-output assertions across the variant matrix (single space vs run, tab, inside emphasis/link labels, code spans/blocks untouched, two-trailing-spaces still <br>, disabled-by-default) plus md4c's own spec fixture.

Other factors

I traced the trickier interactions by hand: (1) a whitespace run immediately before \n is left for the newline branch (i = end; continue; without touching text_start), so "a \n" under collapseWhitespace still hits emit_end - sp >= 2 and produces <br>; (2) a single plain space skips the emit (end - i > 1 || c != b' ' is false) and stays part of the enclosing Normal text run; (3) spec-hard-soft-breaks.txt uses --fhard-soft-breaks, which renderMarkdown maps to snake_case hard_soft_breaks, matching BOOL_FIELD_SETTERS. All prior review threads are resolved: my earlier note on underline being a sibling instance was addressed by an intentional-exclusion note in the description linking #39493, and the comment-cop length complaints were fixed in e633315. No CODEOWNERS on src/md/.

@robobun

robobun commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 8:44 PM PT - Aug 20th, 2026

✅ @robobun, your commit e63331534bf334f0a608f96c2e9a9007055be8ae passed in Build #102149! 🎉


🧪   To try this PR locally:

bunx bun-pr 39495

That installs a local version of the PR into your bun-39495 executable, so you can run:

bun-39495 --bun

Jarred-Sumner pushed a commit that referenced this pull request Sep 23, 2026
#43745)

### Problem
- 16 struct fields in 11 crates are never read. Two options are never
set: `BufferWriter.append_null_byte` is never `true`,
`TransposeState.import_record_tag` is never `Some`.
- No lint reports them. rustc's `dead_code` pass counts `x.f = v` as a
use of `f`. hawk counts the initializer as a reference.

### Fix
- Delete each field with its initializers and stores, and the two
branches that the options guard. 18 files, 105 lines removed.
- Candidates come from a compiler probe: `#[deprecated]` on 12,134
struct fields, `cargo check --force-warn deprecated` for Linux, Windows,
and macOS, then a syn pass marks each use as read or write. 134 fields
have no read. Most stay (Notes).
- Verified: `bun bd`, `bun run rust:check-all` (12 of 12 targets),
`cargo check --workspace --all-targets`, and the tests in the Notes.
- Self-reviewed: 4 concerns raised, 2 addressed. Rejected as outside a
deletion: the now vestigial `written_without_trailing_zero()` and an
older stale comment.

### Background
- `MultiArrayList<T>` keeps each field of `T` in its own column. Code
reads a column by name as a string (`items::<"name", _>()`), so the
compiler sees no read. Those fields stay.
- `BufferWriter` is the JS printer's output buffer. `done()` could
append a NUL byte. No caller asks for it.

### Downsides
- `bun_core::output::Source` becomes `Send + Sync`, because its raw
pointer fields are gone. It lives in a `thread_local!` only.
- The `bun_resolver::Result` in `_resolve` now drops when `_resolve`
returns. It has no `Drop` effect: paths are borrowed, fds are `Copy`.

<details><summary>Notes</summary>

#### Removed, one line each
- `bun_core::output::Source`: `buffered_stream`,
`buffered_error_stream`, `stream`, `error_stream` (`*mut io::Writer`).
`init()` cached them, nothing read them. The accessors of the same names
return `*_backing.new_interface()`, which is a pointer cast with no side
effect.
- `bun_bundler`: `InputFileInfo.import_count` (`MetafileBuilder.rs`).
- `bun_install`: `SecurityScanSubprocess.stderr_data` (an empty `Vec`
that is never filled, the scanner's stderr is inherited),
`PackageManager.root_progress_node`. The `progress.start(b"", 0)` call
stays, because it starts the progress root.
- `bun_js_parser`: `PropertyOpts.async_range`,
`ScanPassResult.approximate_newline_count`,
`TransposeState.import_record_tag`. #16624 replaced the only setter of
the tag with `import_loader`, which stays.
- `bun_jsc`: `VirtualMachine.has_terminated` (its readers were debug
panics in `enqueue_task_concurrent`, which #37075 removed),
`ResolveFunctionResult.result` (seven stores, no read). Nothing borrows
from the stored value: `path` and `query_string` point into the
resolver's arena and the specifier.
- `bun_js_printer`: `BufferWriter.append_null_byte`, with the seven `=
false` stores in `bun_jsc` and `bun_runtime`.
- `bun_libarchive`: `BufferReadStream.reading`.
- `bun_runtime`: `WindowsState.is_server` (`ipc.rs`, Windows only). Its
last reader went away in #18688, before the Rust port.
- `bun_csrf`: `GenerateOptions.encoding`. `csrf_jsc.rs` encodes the
token itself, and `VerifyOptions.encoding` stays.
- `bun_resolver`: `DataURL.url` (always `String::EMPTY`).
- `bun_s3_signing`: `S3CredentialsWithOptions.virtual_hosted_style`. No
code names it. Every reader uses `credentials.virtual_hosted_style` on
the inner `S3Credentials`.

#### Passed the probe, kept on purpose
- `MultiArrayList` columns: fields of `JSMeta`, `File`, `InputFile`,
`BundledAst`, `Entry`, `Node`, `WatchItem`, `LineOffsetTable`,
`ServerComponentBoundary`, and others.
- Owners of memory that other fields borrow:
`ParseResult.source_contents_backing`, `LinkerContext.unique_key_buf`,
`OutputFile.owned_src_path_text`, `HTTPResponseMetadata.owned_buf`,
`PackageJSON.source_contents` and `json_tape`,
`MatchedRoute.pathname_backing`, `SignResult.content_md5`,
`KEventWaker.machport_buf`.
- Values whose drop has an effect: `Repl.last_error` (a GC protect),
`SecurityScanSubprocess.process`, `Watcher.thread`.
- Options that are unfinished features or open bugs, not dead code:
`md::Options.hard_soft_breaks` and `underline` (#39495, #39493),
`jsc::virtual_machine::Options.dns_result_order` (#40703),
`P.has_top_level_return` (#40840), `ReactRefresh.last_hook_seen` and
`force_reset`, `DebugOptions.dump_environment_variables` (the
`--dump-environment-variables` flag is parsed and ignored).
- Kept by a comment in the source:
`AllocatorConfiguration.long_running`, `DumpStackTraceOptions.skip_*`,
`NameOfSymbol.has_property_key_comment`.
- Removal needs more than a deletion: `PostgresSQLQuery` `Flags.is_done`
(set by the JS `done()` call), `WorkerPipe.done` (leaves two empty
reader callbacks), `WTFTimer.repeat` (leaves an unused FFI parameter),
`MaxHeapAllocator.len` (leaves an empty `reset()`),
`ReadToEndResult.err` (callers drop read errors, which looks like a
bug), `Subcommand::Pack` with `PACK_PARAMS` (`bun pm pack` runs as
`Subcommand::Pm`, so the pack help text is unreachable).

#### Self-review
Three independent read-only passes tried to prove each deletion wrong
(readers through raw pointers, `offset_of!`, C++ layout mirrors, macros,
other `cfg`s, unit tests, the Zig originals, drop effects). None found a
reader. Concerns raised: the `Send + Sync` change of `Source` (now in
Downsides), the style of the kept `progress.start` call (now `let _ =`,
as in `install_with_manager.rs`),
`BufferWriter::written_without_trailing_zero()` (nine callers, it only
strips NUL bytes that the printer never appends now, a follow-up), and a
comment above `ResolveFunctionResult.path` that names a function which
no longer exists (it predates this change).

#### Other scans of this run, all clean or already in an open pull
request
- A relink of the debug binary with `--gc-sections --print-gc-sections`,
and a zero-mention identifier index over `src/`, `packages/`, `scripts/`
and `build/debug/codegen/`.
- `macro_rules!` without an invocation, Cargo features that nothing
enables, `#if 0` and never-true `#if` blocks in the C++ bindings,
patches that no dependency script applies, unused exports under
`scripts/`, `tsc --noUnusedLocals` over `src/js` and `scripts/`.
- A syn scan for enum variants that no expression constructs. The hits
are FFI code tables, derive-constructed variants, or already claimed.

#### Overlap with the open dead-code pull requests
- Every removed line was checked per file against the diffs of the 36
open dead-code pull requests. None of them deletes these lines. Several
touch the same files in other places (`output.rs`, `VirtualMachine.rs`,
`parser.rs`, `p.rs`, `js_printer/lib.rs`, `jsc_hooks.rs`,
`PackageManager.rs`, `data_url.rs`).

#### Tests run with the debug build
`test/js/bun/util/csrf.test.ts` (31 pass),
`test/bundler/metafile.test.ts` (65 pass), `test/js/bun/archive.test.ts`
(108 pass), `test/bundler/transpiler/transpiler.test.js` (222 pass),
`test/js/bun/resolve/import-empty.test.js`,
`test/js/bun/resolve/esModule.test.ts`,
`test/cli/install/bun-install-security-provider.test.ts` (43 pass),
`test/internal/source-lints/` (195 pass). Also a `data:` URL import, and
`bun install` under a pty so that the progress root starts.

No test is added. The change deletes fields that nothing reads, so there
is no behavior to assert, and `test/internal/source-lints/CLAUDE.md`
asks for no tests that pin dead symbols.

</details>

This branch has not been deployed

No deployments
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.

Bun.markdown: hardSoftBreaks and collapseWhitespace options are documented but do nothing

1 participant