Skip to content

Bun.Transpiler: do not throw on a parser warning - #42433

Open
robobun wants to merge 3 commits into
mainfrom
robobun/e80a0006/transpiler-warnings-do-not-throw
Open

robobun wants to merge 3 commits into
mainfrom
robobun/e80a0006/transpiler-warnings-do-not-throw

Conversation

@robobun

@robobun robobun commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Bun.Transpiler throws, or rejects, when the parser logs a warning. The thrown value is a BuildMessage with level: "warn", for example Treating "-->" as the start of a legacy HTML single-line comment. The caller gets no output for a source that bun build transpiles with exit 0.
  • The cause is the check at each entry point in src/runtime/api/JSTranspiler.rs: scan (line 1320), transform_sync (line 1504), scan_imports (line 1714) test warnings + errors > 0, and the async transform tests log.has_any() (line 842).
  • logLevel: "error" hides the warning for transform and transformSync only. scan and scanImports ignore it (Bun.Transpiler: scan() and scanImports() ignore the logLevel option #42426), so no workaround exists for them.

Fix

  • The four sites now test log.has_errors(). A warning-only parse returns the normal result. An error still throws the log as before.
  • Warnings are dropped. This matches the runtime module loader, which gates on errors > 0 only (src/runtime/jsc_hooks.rs:2755). A channel that returns warnings would be a new API and is out of scope.
  • paperclover proposed this same errors > 0 change in the review of fix: jsx key after spread #7464 (fix: jsx key after spread #7464 (comment)). The logLevel option landed instead because that diff missed the async site.
  • Verified: test/bundler/transpiler/transpiler.test.js. The new block "a parser warning does not throw" fails on main (4 of 5 cases) and passes with the fix. The "class static blocks" test asserted six private accessor warnings through the throw. Those cases move to a new test that checks the warnings through Bun.build logs. Also ran all of test/bundler/transpiler/.

Background

  • The parser writes diagnostics to a Log (src/ast/lib.rs). Each message has a kind. warnings and errors count them by kind. has_any() is warnings + errors > 0, has_errors() is errors > 0.
  • Log::to_js turns the log into one BuildMessage, or an AggregateError of them when it holds more than one message. With a warning next to an error the thrown value is still that AggregateError.
  • The constructor checks for tsconfig and define errors (lines 966, 985, 1021) keep the old condition. They report config problems, not parser diagnostics.

Fixes #42427
Related: #7328, #42426, #42430

Notes
  • Inputs that hit this on main: a --> legacy HTML comment at the start of a line, <div {...obj} key="k" />, <div key />, and the private accessor warnings such as Writing to getter-only property "#x" will throw.
  • The scanImports case in the new test uses the JSX key-after-spread warning. That warning makes the file fall back to the classic runtime, so the result also lists react/jsx-dev-runtime and react as require-call imports.
  • test/bundler/transpiler/jsx-production.test.ts timed out in my container when run with the whole directory (32 concurrent spawns, 3.3 s each, 5 s limit). It uses bun run, not Bun.Transpiler, and passes when run alone.

[human-review] gate passed · iteration 1 · 2 files touched

fails on main (without fix)
ASAN without fix: 5 failed, 22 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/transpiler.test.js
bun test v1.4.3 (6a92015fc)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [10.10ms]
(pass) Bun.Transpiler > normalizes \r\n [12.37ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [8.30ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [9.84ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [3.22ms]
(pass) Bun.Transpiler > property access inlining > works [2.75ms]
(pass) Bun.Transpiler > property access inlining > works nested [3.65ms]
(pass) Bun.Transpiler > property access inlining > bails out when the array item is an optional chain [89.04ms]
(pass) Bun.Transpiler > property access inlining > bails out or strips `this` when the index is a call/assignment target [32.54ms]
(pass) Bun.Transpiler > property access inlining > preserves runtime semantics when inlining from a literal index [484.70ms]
(pass) Bun.Transpiler > property access inlining > bails out on optiona
... (truncated)

release without fix: 22 skipped
bun test v1.4.3-canary.1 (a53ef51a4)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [0.16ms]
(pass) Bun.Transpiler > normalizes \r\n [0.23ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [0.10ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [0.16ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [0.04ms]
(pass) Bun.Transpiler > property access inlining > works [0.03ms]
(pass) Bun.Transpiler > property access inlining > works nested [0.03ms]
(pass) Bun.Transpiler > property access inlining > bails out when the array item is an optional chain [1.13ms]
(pass) Bun.Transpiler > property access inlining > bails out or strips `this` when the index is a call/assignment target [0.40ms]
(pass) Bun.Transpiler > property access inlining > preserves runtime semantics when inlining from a literal index [10.16ms]
(pass) Bun.Transpiler > property access inlining > bails out on optional-chain index into enum [0.54ms]
(pass) Bun.Transpiler > property access inlining > template literal around an inlined string enum member > member as the first part of the
... (truncated)
passes on PR (with fix)
ASAN with fix: 22 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/transpiler.test.js
bun test v1.4.3 (6a92015fc)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [8.51ms]
(pass) Bun.Transpiler > normalizes \r\n [10.50ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [7.44ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [13.91ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [5.40ms]
(pass) Bun.Transpiler > property access inlining > works [3.62ms]
(pass) Bun.Transpiler > property access inlining > works nested [4.09ms]
(pass) Bun.Transpiler > property access inlining > bails out when the array item is an optional chain [85.96ms]
(pass) Bun.Transpiler > property access inlining > bails out or strips `this` when the index is a call/assignment target [27.25ms]
(pass) Bun.Transpiler > property access inlining > preserves runtime semantics when inlining from a literal index [461.60ms]
(pass) Bun.Transpiler > property access inlining > bails out on optiona
... (truncated)

release with fix: 22 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 916ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/125] gen generated_host_exports.rs
generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited
[2/125] gen cpp.rs (cppbind)
[3/125] gen JS modules (bundle-modules)
Preprocess modules (13389ms)
Bundle modules (96ms)
Postprocesss modules (198ms)
Bundle Functions (705ms)
Generate Code (52ms)

[14.45s] Bundled "src/js" for production
  2600 kb
  197 internal modules
  13 native modules
  50 internal functions across 16 files
[3/124] cargo bun_runtime → libbun_runtime.a
�[1m�[92m   Compiling�[0m bun_runtime v0.0.0 (/workspace/bun/src/runtime)
�[1m�[92m    Finished�[0m `release` profile [optimized + debuginfo] target(s) in 5m 16s
[120/124] cxx obj/src/jsc/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.cpp.o
[121/124] link bun-profile
[123/124] strip bun
[123/124] bun-profile --revision
1.4.3-canary.1+242d4e0e2
[build] done
bun test v1.4.3-canary.1 (242d4e0e2)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing m
... (truncated)
diff hotspot
src/runtime/api/JSTranspiler.rs            |  8 +--
 test/bundler/transpiler/transpiler.test.js | 95 +++++++++++++++++++++++++++---
 2 files changed, 92 insertions(+), 11 deletions(-)

gate history · 2 passed · 0 rejected · iteration 1

evidence per changed file
file                                        reads  edits  tests
src/runtime/api/JSTranspiler.rs                 2      4     22
test/bundler/transpiler/transpiler.test.js      3      8     22

root cause · written by the author bot

The four Bun.Transpiler entry points (transform, transformSync, scan, scanImports) in src/runtime/api/JSTranspiler.rs threw the parser log whenever it contained any message, so a mere warning such as the legacy HTML comment notice or the JSX key-after-spread deprecation caused valid source to fail with a BuildMessage of level warn and no output. The fix changes each of those guards from a "warnings plus errors" or has_any() check to has_errors(), matching the runtime module loader, so warnings no longer abort the call while genuine errors still surface as an `AggregateEr…

transform(), transformSync(), scan() and scanImports() threw the log
as a BuildMessage when it held warnings only. A valid source that
produced a warning gave the caller no output. Check log.errors at
those four sites, which matches the runtime module loader.
@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

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

Or wait 2 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 5b2f3cee-79b4-4755-a793-dbc03f60d97e

📥 Commits

Reviewing files that changed from the base of the PR and between a749e0a and 242d4e0.

📒 Files selected for processing (2)
  • src/runtime/api/JSTranspiler.rs
  • test/bundler/transpiler/transpiler.test.js

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

@robobun

robobun commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator Author

The automated review hit its rate limit and posted no findings. No change is needed for that. CI is still running on 6af5ccc.

@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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding, I also checked that the moved expectParseError cases are not weakened — the new Bun.build assertions still check the exact message and now additionally assert level: "warn" and the line number. Also confirmed the self.log.has_any() at JSTranspiler.rs:845 is intentionally left as-is: it sits inside the already-gated error branch and only decides whether to synthesize a BuildMessage from self.err when the log is empty.

Extended reasoning...

The four-line Rust change is mechanical and matches the stated intent — each site swaps warnings + errors > 0 / has_any() for has_errors(). I verified the one remaining has_any() in TransformTask::then is inside the rejection branch and governs error-message construction, not the throw gate, so leaving it is correct. On the test side, REVIEW.md flags silent weakening of existing assertions; the six expectParseError calls that relied on the old throw are replaced by a Bun.build check that asserts the same message text plus level and position.line, and a transformSync check that output is still produced — coverage is preserved or stronger. tempDir and join are already imported at the top of the file. Nothing further to add beyond the inline nit about the async transform path in the mixed-error case.

Comment thread test/bundler/transpiler/transpiler.test.js
@robobun

robobun commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the one suggestion: the warning-plus-error test now also covers the async transform() path, in a53ef51.

@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 follow-up commit adds the async transform rejection assertion, so all four changed entry points are now covered for both the warning-only and warning+error cases.

What was reviewed:

  • The four has_any() / warnings + errors > 0 → has_errors() swaps in JSTranspiler.rs; constructor-side config checks are untouched as stated.
  • The reworked private-accessor assertions: coverage moves from expectParseError to Bun.build logs asserting exact [level, line, message], which is at least as strong.
  • Test hermeticity: tempDir + Bun.build({ write: false }), no network, no ports.
Extended reasoning...

Overview

Four call sites in src/runtime/api/JSTranspiler.rs (async TransformTask::then, transformSync, scan, scanImports) switch their throw/reject gate from "any log message" to log.has_errors(), so parser warnings no longer fail the operation. test/bundler/transpiler/transpiler.test.js gains a describe block exercising all four entry points with warning-only input, plus a mixed warning+error case that asserts the AggregateError still surfaces both messages on every entry point (the async path was added in the follow-up commit, addressing the only nit from the earlier review). Six existing expectParseError assertions for private-accessor/method warnings are re-homed into a dedicated test that checks transformSync succeeds and that Bun.build reports each as a warn log with the expected line and message.

Security risks

None. This is a diagnostic-severity gate change in a JS-facing transpiler API; no auth, crypto, network, filesystem, or untrusted-input parsing surface is touched. The parser itself is unchanged — only whether a populated warning log short-circuits into a throw.

Level of scrutiny

Low-to-moderate. The Rust diff is four one-token condition changes with no control-flow restructuring, and the new gate matches the runtime module loader's existing errors > 0 behavior. It is a user-visible behavior change (calls that previously threw now succeed), but the PR description cites a maintainer having proposed exactly this fix in an earlier review, and the constructor-side config checks are deliberately left on the stricter condition. CODEOWNERS does not cover these paths.

Other factors

REVIEW.md's "fix the whole class" and "cover the variant matrix" are satisfied: all four sibling entry points get the same guard and matching tests, and both states (warning-only succeeds; warning+error still throws with the full AggregateError) are asserted. The migrated private-accessor assertions are justified in the PR body and replaced with equal-or-stronger coverage (exact level/line/message via Bun.build logs), so no existing test is silently weakened. The new tests use tempDir from harness and Bun.build({ write: false }), keeping them hermetic.

@robobun

robobun commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:38 AM PT - Sep 12th, 2026

❌ @robobun, your commit 242d4e0 has 1 failures in Build #114617 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 42433

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

bun-42433 --bun

@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.

Code review found no issues

No high-confidence issues detected in this change.

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.Transpiler: a parser warning makes transform(), transformSync(), scan() and scanImports() throw

1 participant