Skip to content

pretty_fmt: keep rewriting markup after an escaped {{ - #37855

Open
robobun wants to merge 1 commit into
mainfrom
farm/f32136a7/pretty-fmt-escaped-braces
Open

robobun wants to merge 1 commit into
mainfrom
farm/f32136a7/pretty-fmt-escaped-braces

Conversation

@robobun

@robobun robobun commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • bun install prints raw markup in its "expects a map of specifiers" error, with colors on or off: the example line comes out as <green>"bun"<r>: <green>"latest"<r>.
  • Same output for optionalDependencies and for a non-string specifier; the install snapshot was asserting the leaked text.
  • Cause: the markup rewriter shields format specs by copying from a { to the next } verbatim, and it treated the {{ escape as the start of a spec, so the example body up to its first } was copied through unrewritten.

Fix

  • On a doubled {{ the rewriter emits it unchanged and keeps rewriting what follows; only a single { starts the verbatim copy. A lone } was already passed through, so }} needs nothing. Applied to all three copies of the rewriter.
  • This is format_args!'s own left to right reading of the template ({{ is a literal brace, what follows is ordinary text), so the emitted template means what the source template says.
  • Of the 97 string literals under src/ containing {{, only the three Package.rs templates change their expansion; the rest expand byte for byte as before, and the workspace still compiles.
  • Verification: new unit tests for the rewriters (run under miri), plus four install tests, one with FORCE_COLOR=1 asserting real ANSI codes, that fail against the released binary and pass with the fix.

Background

  • bun's Rust code formats user-facing text through pretty_fmt! templates: <green>...<r> style tags are rewritten into ANSI codes (colors on) or dropped (colors off), and the result becomes a format_args! template.
  • In a format_args! template {...} is a placeholder and {{ / }} are escapes for literal braces. The rewriter must leave placeholders such as {:<16} alone, since a < inside one is not a tag.
  • The rewrite exists three times: a proc macro for compile-time templates, a runtime copy for templates built at runtime, and a copy in the clap macros that produces the plain form of help text. A parity test checks the macro and runtime copies agree.
Original description

What

bun install prints raw markup tags in the "expects a map of specifiers" error, with colors on or off:

$ printf '{"name":"foo","version":"0.0.1","dependencies":[]}' > package.json && bun install
error: dependencies expects a map of specifiers, e.g.
  "dependencies": {
    <green>"bun"<r>: <green>"latest"<r>
  }

Same output for a non-object value ("optionalDependencies": "bar") and for an object with a non-string specifier ("dependencies": {"bun": 1}); all three share the template in src/install/lockfile/Package.rs. The tags on the first line of the example (<r><green>"{0}"<r>) are rewritten, only the ones after the {{ survive. test/cli/install/__snapshots__/bun-install.test.ts.snap and the optionalDependencies test were asserting the leaked output.

Why

The templates go through pretty_fmt! (via add_error_pretty!), whose output is a format_args! template. The rewriter's { arm exists to shield format specs such as {:<16} from the tag parser, and it does that by copying everything from a { up to the next } verbatim. It did not know about format_args!'s {{ / }} escapes, so for

<r><green>"{0}"<r>: {{\n    <green>"bun"<r>: <green>"latest"<r>\n  }}

the scan starting at {{ runs to the first } of the closing }} and emits the whole example body untouched. {{ is a literal brace in the template language the macro emits (and in the one pretty_fmt_args / substitute_template consume at runtime, which already unescape {{), so the text after it is ordinary text and has to be rewritten like any other text. Matching format_args!'s own left to right pairing ({{ is an escape, any other { opens a spec) is the only interpretation under which the emitted template means the same thing as the source template. A lone } was already copied through, so }} needs no special case.

Fix

  • src/bun_core_macros/lib.rs (rewrite): on {{, emit {{ and continue rewriting; only an undoubled { starts the verbatim spec copy.
  • src/bun_core/output.rs (pretty_fmt_runtime): same change, so the runtime rewriter and the proc-macro keep producing identical output (the macro_matches_runtime test now covers brace escapes too).
  • src/clap_macros/lib.rs (pretty_rewrite): same change. It is documented as a 1:1 copy of pretty_fmt_runtime and produces the plain form of a help description while pretty_fmt_runtime produces the colored form of the same string, so the two have to agree.

I scanned every string literal under src/ containing {{ (97 of them) through the old and new algorithm; the only ones whose expansion changes are the three Package.rs templates. The other {{ templates that reach a pretty macro (Timings.rs, unlink_command.rs, S3File.rs, S3Client.rs, JSBundler.rs) have no markup after the escape and expand byte for byte as before. The workspace also still compiles, which rules out any template that had been using {{ ... }} to hide a literal <...> from the tag parser (that would now be a compile error).

Tests

  • src/bun_core/output.rs: markup_after_escaped_brace_is_rewritten and runtime_markup_after_escaped_brace_is_rewritten (escape followed by tags, escape abutting a spec, spec with < after an escape, pretty_fmt_args end to end), plus two brace templates in the macro/runtime parity check. cargo miri test -p bun_core -- pretty_fmt: 11 pass (the bun_core test binary does not link on its own, so miri is how these run).
  • test/cli/install/bun-install.test.ts: updated the dependencies snapshot and the optionalDependencies expectation, added a test for the third path (object with a non-string specifier) and one with FORCE_COLOR=1 asserting the example is rendered as \x1b[32m"bun"\x1b[0m: \x1b[32m"latest"\x1b[0m. The four affected tests fail against the released binary (USE_SYSTEM_BUN=1) and pass with bun bd test.

The `{` arm of the `<tag>` rewriter copied everything up to the next `}`
verbatim, so a template containing a `{{` brace escape shielded all of the
markup between it and the next `}` from the rewrite. Those tags were then
neither converted to ANSI nor stripped, which is how `bun install` printed
`<green>"bun"<r>: <green>"latest"<r>` in the "expects a map of specifiers"
error. Treat `{{` as the literal brace it is in the proc-macro, in
`pretty_fmt_runtime`, and in clap's copy of the same state machine.
@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on the released binary (printf '{"name":"foo","version":"0.0.1","dependencies":[]}' > package.json && bun install prints <green>"bun"<r>: <green>"latest"<r>), root cause is the { arm of the pretty_fmt! rewriter not knowing about {{ escapes. Fix and tests are in this PR; the updated install tests fail on the released binary and pass with the debug build. Waiting on CI.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Escaped double opening braces now remain literal in compile-time, runtime, and CLI formatting paths. Markup processing continues after escaped braces. Regression tests cover placeholders, format specifications, color handling, macro/runtime parity, and install diagnostics.

Changes

Escaped brace formatting

Layer / File(s) Summary
Brace escape handling
src/bun_core/output.rs, src/bun_core_macros/lib.rs, src/clap_macros/lib.rs
The runtime and macro rewrite paths preserve {{ as an escaped literal pair and continue processing later markup.
Formatting regression coverage
src/bun_core/output.rs
Tests cover escaped-brace color rewriting, color stripping, placeholders, format specifications, and macro/runtime parity.
CLI diagnostic validation
test/cli/install/bun-install.test.ts
Install tests validate numeric dependency specifiers and ANSI-colored error examples with normalized snapshots.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description explains the problem, fix, scope, background, and verification, although it uses different headings than the template.
Title check ✅ Passed The title clearly and concisely describes the main change: continuing markup rewriting after escaped double braces.

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

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/cli/install/bun-install.test.ts`:
- Around line 4991-5002: Remove the stdout content assertion following the
concurrent Promise.all in the install test, while retaining proc.stdout.text()
in that Promise.all to drain stdout. Keep the normalized stderr snapshot and
exitCode assertions unchanged.
🪄 Autofix

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: 5aad6f5b-bafd-446c-b663-408c9d9ba61c

📥 Commits

Reviewing files that changed from the base of the PR and between 9a543cc and b3f516f.

⛔ Files ignored due to path filters (1)
  • test/cli/install/__snapshots__/bun-install.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • src/bun_core/output.rs
  • src/bun_core_macros/lib.rs
  • src/clap_macros/lib.rs
  • test/cli/install/bun-install.test.ts

Comment on lines +4991 to +5002
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(normalizeBunSnapshot(stderr, String(dir))).toMatchInlineSnapshot(`
"1 | {"name":"foo","version":"0.0.1","dependencies":{"bun":1}}
^
error: dependencies expects a map of specifiers, e.g.
"dependencies": {
"bun": "latest"
}
at <dir>/package.json:1:55"
`);
expect(stdout).toContain("bun install v1.");

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Remove the version-prefix assertion.

The bun install version header is not part of this regression and can change independently. Keep proc.stdout.text() in the concurrent Promise.all, but do not assert its contents. The normalized stderr snapshot and exitCode already verify the invalid-specifier path.

Suggested change
-    const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
+    const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

     expect(normalizeBunSnapshot(stderr, String(dir))).toMatchInlineSnapshot(`
...
-    expect(stdout).toContain("bun install v1.");
     expect(exitCode).toBe(1);

As per coding guidelines: “Subprocess tests must drain stdout, stderr, and process exit concurrently.”
Based on learnings: “Do not assert stdout for commands like bun install whose output can vary between runs.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(normalizeBunSnapshot(stderr, String(dir))).toMatchInlineSnapshot(`
"1 | {"name":"foo","version":"0.0.1","dependencies":{"bun":1}}
^
error: dependencies expects a map of specifiers, e.g.
"dependencies": {
"bun": "latest"
}
at <dir>/package.json:1:55"
`);
expect(stdout).toContain("bun install v1.");
const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(normalizeBunSnapshot(stderr, String(dir))).toMatchInlineSnapshot(`
"1 | {"name":"foo","version":"0.0.1","dependencies":{"bun":1}}
^
error: dependencies expects a map of specifiers, e.g.
"dependencies": {
"bun": "latest"
}
at <dir>/package.json:1:55"
`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/cli/install/bun-install.test.ts` around lines 4991 - 5002, Remove the
stdout content assertion following the concurrent Promise.all in the install
test, while retaining proc.stdout.text() in that Promise.all to drain stdout.
Keep the normalized stderr snapshot and exitCode assertions unchanged.

Sources: Coding guidelines, Learnings

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Taking the stdout suggestion: the new test will only read stderr and the exit code (stdout ignored, same as the FORCE_COLOR test). It goes out with the next push, which I am holding until a review pass on the diff finishes so there is a single CI run.

@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 — small, well-scoped fix applied consistently across all three rewriter copies.

What was reviewed:

  • Traced the {{ handling through the state machine in all three sites; verified {{{{, {{{spec}}}, and {{...{:<4}...}} cases behave correctly (all covered by new unit tests).
  • Confirmed }} needs no special case since lone } already falls to the default arm.
  • Checked the new install tests follow harness conventions (tempDir, {...env, FORCE_COLOR}, concurrent pipe drain, exitCode last); snapshot updates were previously certifying the bug.
Extended reasoning...

Overview

Fixes a bug in the pretty_fmt! markup rewriter (and its two documented copies in pretty_fmt_runtime and clap_macros::pretty_rewrite) where the { arm — which exists to shield format specs like {:<16} from the <tag> parser — did not recognize {{ as format_args!'s literal-brace escape. It scanned from {{ to the next }, copying the whole span verbatim and leaving any <green>...<r> markup inside it unrewritten. The user-visible symptom was raw <green>"bun"<r> tags leaking into three bun install error messages from Package.rs.

The fix is a 5-line early-out in each of the three state machines: on {{, emit {{ and continue so the text after it is still subject to markup rewriting; only an undoubled { starts the verbatim spec copy. Tests are added at the unit level (proc-macro output, runtime rewriter output, macro/runtime parity, pretty_fmt_args end-to-end) and at the integration level (updated snapshots + a new FORCE_COLOR test asserting the actual ANSI escapes).

Security risks

None. This is a compile-time / display-time string formatter for Bun's own error-message templates; no untrusted input reaches these paths, and the change only affects how {{ is tokenized relative to the existing <tag> rewriter.

Level of scrutiny

Low-to-medium. The change is mechanical and identical across the three sibling implementations, which are already documented as intentional near-copies. The author enumerated the input space carefully (escape abutting a spec, quadruple braces, spec-with-< after an escape), ran the old vs new algorithm over all 97 {{ literals under src/ to confirm only the three intended templates change output, and noted the workspace still compiles — ruling out any template that had been relying on the old behavior to hide a literal <...>. The snapshots being updated were asserting the buggy output, which is exactly the case where a snapshot update is warranted.

Other factors

  • All three copies of the state machine are updated in the same PR (fixing the whole class, per repo review guidance).
  • New tests follow harness conventions: tempDir, bunEnv spread, Promise.all pipe draining, normalizeBunSnapshot, exitCode asserted last.
  • Author verified the tests fail with USE_SYSTEM_BUN=1 and pass with bun bd test.
  • The macro_matches_runtime parity test now covers brace escapes, guarding against future divergence between the proc-macro and runtime paths.

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Nothing to change from this review. The only pending edit is the stdout assertion removal from the thread above; it goes out in the next push.

@robobun

robobun commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:05 PM PT - Aug 12th, 2026

❌ @robobun, your commit b3f516f has 1 failures in Build #93294 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 37855

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

bun-37855 --bun

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.

1 participant