Skip to content

text formats: name the input flags passed to with_text_format_source - #39144

Merged
alii merged 3 commits into
mainfrom
farm/1745f9e8/text-format-input-enums
Aug 15, 2026
Merged

text formats: name the input flags passed to with_text_format_source#39144
alii merged 3 commits into
mainfrom
farm/1745f9e8/text-format-input-enums

Conversation

@robobun

@robobun robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • with_text_format_source (src/runtime/api.rs) takes two positional bools, accept_blob_or_buffer and reject_nullish, and all four callers (Bun.TOML.parse, Bun.JSONC.parse, Bun.JSON5.parse, Bun.YAML.parse) pass bare literals for both. At with_text_format_source(.., b"input.toml", true, true, ..) nothing says which flag is which, and swapping them compiles.
  • with_text_format_source_encoded adds a third bool, string_passthrough, and Bun.XML.parse calls it with true, true, true.
  • These are the two bare_bool_args findings mordant has baselined for src/runtime/api.rs.

Fix

  • Each flag becomes a two-variant enum in api.rs, and every call site spells out what it sets:
    • BlobOrBufferInput::{Bytes, ToString} replaces accept_blob_or_buffer (JSONC is the one ToString caller).
    • NullishInput::{Throw, ToString} replaces reject_nullish (YAML is the one ToString caller).
    • StringInput::{Utf8, AsIs} replaces string_passthrough (XML is the one AsIs caller; the with_text_format_source wrapper passes Utf8).
  • Enums rather than an options struct because the two helpers differ only in the string flag: the wrapper's closure is not told the encoding, so it must force StringInput::Utf8 itself, and a separate parameter keeps that from being settable through the wrapper.
  • The three if tests in the helper body compare against the enum instead of reading the bool; the values every caller passes are unchanged, so there is no behavior change.
  • The "bare_bool_args:src/runtime/api.rs" = 2 line is removed from mordant-baseline.toml.
  • Tests, for the two call sites whose flag values differ from the rest and had no coverage:
    • yaml.test.ts: YAML.parse stringifies undefined, null and a missing argument instead of throwing (NullishInput::ToString).
    • jsonc.test.ts: JSONC.parse throws on nullish input, and stringifies everything else (a Blob included) rather than reading bytes (BlobOrBufferInput::ToString).
    • The other variants were already covered: nullish and Buffer/Blob tests in toml.test.ts, json5.test.ts, xml.test.ts; the xml.test.ts encodings block for StringInput::AsIs; Latin-1 range strings in toml.test.ts for the wrapper's Utf8.
    • This is a refactor, so the new tests pass before and after it; they guard against a call site picking the wrong variant later. What enforces the refactor itself is the removed baseline line: the mordant CI job fails if bare_bool_args fires in api.rs again.
  • Verified:
    • bun bd test on test/js/bun/{toml,jsonc,json5,yaml,xml}/*.test.ts, test/js/bun/resolve/toml/toml-parse.test.ts, test/js/bun/resolve/jsonc.test.ts and the YAML regression tests: 4718 pass. Three stringify/leak-loop tests hit the 5s timeout under the local debug+ASAN build; two of them time out identically on an unmodified build and the third passes when run alone, and all three pass on main's CI (build 98374).
    • A script calling every format's parse with a string, Buffer, Blob, undefined, null, no argument, a number and a toString object, plus XML with Latin-1 and UTF-16 strings and declared-encoding buffers (46 cases, covering both values of all three flags), produces byte-identical output before and after (see below).
    • rustfmt --check on the touched files.
    • bun run rust:mordant with this baseline: nothing over the baseline, locally and in the mordant job on this PR. As a check that the local run was live, deleting the types.rs bare_bool_args line as well makes it report exactly those 4 findings and still nothing for api.rs.
    • bun run rust:mordant:baseline reproduces the committed file (the only other lines it dropped at the time have since been removed on main by their own PRs).

Background

  • Bun.TOML.parse, Bun.JSONC.parse, Bun.JSON5.parse, Bun.YAML.parse and Bun.XML.parse share one scaffold, with_text_format_source: it takes argument 0 of the call, turns it into bytes, and hands the bytes to a per-format closure that runs the parser and converts the result to JS. Outside the closure, the formats differ only in how argument 0 is treated, which is what these flags select.
  • BlobOrBufferInput: whether a Blob, ArrayBuffer, typed array or DataView argument is parsed as bytes. JSONC mirrors JSON.parse and instead runs every non-string argument through JS ToString (so a Buffer still parses via Buffer.prototype.toString, while a Blob becomes "[object Blob]" and fails to parse).
  • NullishInput: whether undefined/null (including a missing argument) throws Expected a string to parse. YAML instead stringifies them, so Bun.YAML.parse() returns the string "undefined" and Bun.YAML.parse(null) returns null.
  • StringInput: whether a JS string is re-encoded to UTF-8 before parsing or handed over as its own Latin-1 bytes or UTF-16 code units along with a SourceEncoding tag. Only XML wants the latter, because a string is already-decoded text whose encoding="..." declaration must not be applied to it.
  • mordant is the dylint pack run by bun run rust:mordant (pinned in Cargo.toml); mordant-baseline.toml records the pre-existing findings per lint and file, so fixing a site means deleting its line.
Before/after probe

Output of the probe script below is byte-identical on main (88a6398) and with this change:

TOML string                                  ok  {"a":1}
TOML Buffer                                  ok  {"a":1}
TOML Blob                                    ok  {"a":1}
TOML undefined                               throws TypeError: Expected a string to parse
TOML null                                    throws TypeError: Expected a string to parse
TOML no args                                 throws TypeError: Expected a string to parse
TOML number                                  throws SyntaxError: TOML Parse error: Expected '=' after a key but found end of file
TOML toString object                         ok  {"a":1}
JSONC string                                 ok  {"a":1}
JSONC Buffer                                 ok  {"a":1}
JSONC Blob                                   throws SyntaxError: JSONC Parse error: Unexpected object
JSONC undefined                              throws TypeError: Expected a string to parse
JSONC null                                   throws TypeError: Expected a string to parse
JSONC no args                                throws TypeError: Expected a string to parse
JSONC number                                 ok  1
JSONC toString object                        ok  {"a":1}
JSON5 string                                 ok  {"a":1}
JSON5 Buffer                                 ok  {"a":1}
JSON5 Blob                                   ok  {"a":1}
JSON5 undefined                              throws TypeError: Expected a string to parse
JSON5 null                                   throws TypeError: Expected a string to parse
JSON5 no args                                throws TypeError: Expected a string to parse
JSON5 number                                 ok  1
JSON5 toString object                        ok  {"a":1}
YAML string                                  ok  {"a":1}
YAML Buffer                                  ok  {"a":1}
YAML Blob                                    ok  {"a":1}
YAML undefined                               ok  "undefined"
YAML null                                    ok  null
YAML no args                                 ok  "undefined"
YAML number                                  ok  1
YAML toString object                         ok  {"a":1}
XML string                                   ok  {"a":"1"}
XML Buffer                                   ok  {"a":"1"}
XML Blob                                     ok  {"a":"1"}
XML undefined                                throws TypeError: Expected a string to parse
XML null                                     throws TypeError: Expected a string to parse
XML no args                                  throws TypeError: Expected a string to parse
XML number                                   throws SyntaxError: XML Parse error: Expected the root element but found '1'
XML toString object                          ok  {"a":"1"}
XML latin1 string                            ok  {"d":{"@a":"café","#text":"naïve"}}
XML utf16 string                             ok  {"d":"🎉 é"}
XML latin1 string needing wider encoding     ok  {"d":"🎉"}
XML latin1 buffer                            ok  {"d":"é"}
XML utf8 buffer declared latin1 mismatch     throws SyntaxError: XML Parse error: Document is not UTF-16 but declares encoding 'UTF-16'
XML non-compact string                       ok  {"name":"a","attributes":{"x":"1"},"children":["t"]}

Probe script:

// Exercises every input-handling flag of the Bun.{TOML,JSONC,JSON5,YAML,XML}.parse scaffold.
const out = {};
function probe(name, fn) {
  try {
    out[name] = { ok: fn() };
  } catch (e) {
    out[name] = { err: `${e.constructor.name}: ${e.message}` };
  }
}

const formats = {
  TOML: { text: 'a = 1', buf: Buffer.from('a = 1') },
  JSONC: { text: '{"a": 1 /* c */}', buf: Buffer.from('{"a": 1}') },
  JSON5: { text: "{a: 1}", buf: Buffer.from("{a: 1}") },
  YAML: { text: "a: 1", buf: Buffer.from("a: 1") },
  XML: { text: "<a>1</a>", buf: Buffer.from("<a>1</a>") },
};

for (const [fmt, { text, buf }] of Object.entries(formats)) {
  const api = Bun[fmt];
  probe(`${fmt} string`, () => api.parse(text));
  probe(`${fmt} Buffer`, () => api.parse(buf));
  probe(`${fmt} Blob`, () => api.parse(new Blob([buf])));
  probe(`${fmt} undefined`, () => api.parse(undefined));
  probe(`${fmt} null`, () => api.parse(null));
  probe(`${fmt} no args`, () => api.parse());
  probe(`${fmt} number`, () => api.parse(1));
  probe(`${fmt} toString object`, () => api.parse({ toString: () => text }));
}

// XML only: a JS string's own encoding declaration is ignored (string passthrough),
// while the same bytes in a Buffer are decoded per the declaration.
probe("XML latin1 string", () => Bun.XML.parse(`<?xml version="1.0" encoding="ISO-8859-1"?><d a="caf\xe9">na\xefve</d>`));
probe("XML utf16 string", () => Bun.XML.parse(`<?xml version="1.0" encoding="UTF-16"?><d>\u{1F389} \u00e9</d>`));
probe("XML latin1 string needing wider encoding", () => Bun.XML.parse(`<d>&#x1F389;</d>`));
probe("XML latin1 buffer", () =>
  Bun.XML.parse(Buffer.from(`<?xml version="1.0" encoding="ISO-8859-1"?><d>\xe9</d>`, "latin1")),
);
probe("XML utf8 buffer declared latin1 mismatch", () =>
  Bun.XML.parse(Buffer.from(`<?xml version="1.0" encoding="UTF-16"?><a/>`)),
);
probe("XML non-compact string", () => Bun.XML.parse("<a x='1'>t</a>", { compact: false }));

console.log(JSON.stringify(out, null, 1));

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 27 minutes

Limit details: You’ve used all 1 included review currently available under your plan. You completed 74 included PR reviews in the past 7 days; at that activity level, included reviews refill at 1 review per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 270b82ad-6e12-4201-9bff-0f10715432da

📥 Commits

Reviewing files that changed from the base of the PR and between 8a09cf7 and 62891dc.

📒 Files selected for processing (9)
  • mordant-baseline.toml
  • src/runtime/api.rs
  • src/runtime/api/JSON5Object.rs
  • src/runtime/api/JSONCObject.rs
  • src/runtime/api/TOMLObject.rs
  • src/runtime/api/XMLObject.rs
  • src/runtime/api/YAMLObject.rs
  • test/js/bun/jsonc/jsonc.test.ts
  • test/js/bun/yaml/yaml.test.ts

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

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:53 PM PT - Aug 15th, 2026

@alii, your commit 62891dc is building: #98849

@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 — mechanical bool-to-enum refactor with no behavior change.

Checked that each enum variant maps to the same branch as the bool it replaces (Bytestrue, Throwtrue, AsIstrue) at all three if sites in with_text_format_source_encoded.
Verified all five callers (TOML/JSONC/JSON5/YAML/XML) plus the with_text_format_source wrapper pass the variant matching their previous literal — JSONC keeps ToString for buffers, YAML keeps ToString for nullish, XML keeps AsIs for strings, wrapper forces Utf8.
Confirmed no other callers of either helper exist in the tree, and the removed mordant-baseline.toml line matches the two sites eliminated.

Extended reasoning...

Overview

Replaces three positional bool parameters of the shared Bun.{TOML,JSONC,JSON5,YAML,XML}.parse scaffold (with_text_format_source / with_text_format_source_encoded in src/runtime/api.rs) with three private two-variant enums (BlobOrBufferInput, NullishInput, StringInput), updates all five call sites plus the wrapper, updates two doc comments referencing the old parameter name, and removes the corresponding bare_bool_args:src/runtime/api.rs = 2 line from mordant-baseline.toml.

Security risks

None. This is a type-level refactor of internal function signatures; no new code paths, no changed input handling, no changed error handling. The three condition checks in the helper body are semantically identical to the bools they replace.

Level of scrutiny

Low. The change is mechanical and the compiler enforces exhaustiveness — a swapped or missed argument would fail to compile. I traced each old literal to its new variant and each condition to confirm polarity is preserved: accept_blob_or_buffer=trueBlobOrBufferInput::Bytes (checked with == Bytes), reject_nullish=trueNullishInput::Throw (checked with == Throw), string_passthrough=trueStringInput::AsIs (checked with == AsIs), and the wrapper's hardcoded falseStringInput::Utf8. Every caller's before/after pair matches.

Other factors

A repo-wide grep confirms the two helpers have no callers outside the five modified files, so nothing is missed. The enums are crate-private and derive only Clone, Copy, PartialEq, Eq — no public surface change. The PR description includes a 46-case before/after probe with byte-identical output and passing test suites for all five formats. No prior reviewer comments to address and no CODEOWNERS entries for the touched paths.

@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 — mechanical bool→enum refactor with no behavior change.

Checked that each enum variant maps to the same branch as the bool it replaces (Bytestrue, Throwtrue, AsIstrue; wrapper's Utf8false), and that all five call sites (TOML/JSONC/JSON5/YAML/XML) pass the variant equivalent to their previous literal. The three if tests in with_text_format_source_encoded compare against the same-polarity variant. New tests cover the two odd-one-out call sites (JSONC's ToString for Blob, YAML's ToString for nullish) that had no prior coverage.

Extended reasoning...

Overview

Replaces three positional bool parameters on with_text_format_source / with_text_format_source_encoded in src/runtime/api.rs with three two-variant enums (BlobOrBufferInput, NullishInput, StringInput), updates the five callers (TOML/JSONC/JSON5/YAML/XML parse), removes the corresponding bare_bool_args line from mordant-baseline.toml, and adds two small test blocks in jsonc.test.ts and yaml.test.ts covering the input-handling variants that differ from the majority.

Security risks

None. This is a type-level rename with no change to input validation, allocation, or control flow. The enum comparisons preserve the exact polarity of the previous bool checks.

Level of scrutiny

Low. The diff is mechanical and verifiable by inspection: each old true/false maps to a named variant, and each if <bool> becomes if <enum> == <variant-for-true>. I traced every call site's old literals to its new variants — TOML (true,true)(Bytes,Throw), JSONC (false,true)(ToString,Throw), JSON5 (true,true)(Bytes,Throw), YAML (true,false)(Bytes,ToString), XML (true,true,true)(Bytes,Throw,AsIs), wrapper's hardcoded falseUtf8 — and the helper body's three conditionals to confirm no polarity was flipped.

Other factors

The PR description includes a 46-case before/after probe showing byte-identical output on main vs. this branch, and the author ran the full text-format test suite plus mordant. The added tests are simple, deterministic, and follow harness conventions (no sleeps, no network, placed in existing test files). No outstanding reviewer comments.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Status: rebased onto main as 57028f5 (the mordant sweep landing on main deleted the baseline lines next to this PR's line, which made it conflict; the resolution is still exactly the one removed line). The rebase also folds two doc comments down to one line each; the code is otherwise the one reviewed above. Rebuilt and re-ran the TOML/JSONC/JSON5/YAML/XML suites and the 46-case probe on the rebased branch: unchanged.

Previous head fe762d8, build 98432: 177 of 179 jobs passed. The other two were the darwin 14 aarch64 - test-bun shards, which never got an agent before the build was canceled 90 minutes later; every test annotation on that build was a retry-passed flake in unrelated areas (napi, child_process, next-pages dev server, inspect-error-leak, template-literal, dlopen, resolve), and none of the text-format suites failed on any lane.

…ffold

with_text_format_source and with_text_format_source_encoded took two and
three bool parameters, and every caller passed bare true/false, so a call
like with_text_format_source(.., true, true, ..) did not say which flag
was which and a swapped call would still compile. Each flag is now a
two-variant enum (BlobOrBufferInput, NullishInput, StringInput) that the
call sites spell out. No behavior change.

Removes the matching bare_bool_args entry from mordant-baseline.toml.
YAML.parse is the one caller that stringifies undefined/null instead of
throwing, and JSONC.parse the one that stringifies a Blob or Buffer
instead of reading its bytes; neither had a test.
@robobun
robobun force-pushed the farm/1745f9e8/text-format-input-enums branch from fe762d8 to 57028f5 Compare August 15, 2026 19:22

@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 — mechanical bool→enum refactor with no behavior change.

What was reviewed:

  • Verified each enum comparison in with_text_format_source_encoded maps to the same polarity as the bool it replaces (Throwtrue, Bytestrue, AsIstrue), and the wrapper's hardcoded StringInput::Utf8 matches the old false.
  • Checked all five call sites (TOML/JSONC/JSON5/YAML/XML) pass the variant matching their prior bool literal.
  • New tests in jsonc.test.ts and yaml.test.ts cover the two divergent call sites (JSONC's ToString blob handling, YAML's ToString nullish handling); assertions are specific and non-flaky.
Extended reasoning...

Overview

This PR replaces three positional bool parameters on with_text_format_source / with_text_format_source_encoded in src/runtime/api.rs with three private two-variant enums (BlobOrBufferInput, NullishInput, StringInput), updates the five call sites in {TOML,JSONC,JSON5,YAML,XML}Object.rs to spell out the variant they pass, removes the corresponding bare_bool_args:src/runtime/api.rs = 2 line from mordant-baseline.toml, and adds two small test blocks covering the call sites whose flag values differ from the majority.

Security risks

None. This is a type-level refactor of an internal helper's parameter list; no user-facing behavior, parsing logic, allocation, or input validation changes. The three if conditions in the helper body are rewritten from if bool_flag to if enum_param == Variant with the same truth value at every call site.

Level of scrutiny

Low. The change is mechanical and self-verifying: swapping the enum variants at a call site would now be a visible semantic choice rather than a silent true, truetrue, false typo, which is the point of the lint being cleared. I traced each of the five call sites' old bool literals against the new enum variant and confirmed the mapping is exact (TOML/JSON5: true,trueBytes,Throw; JSONC: false,trueToString,Throw; YAML: true,falseBytes,ToString; XML: true,true,trueBytes,Throw,AsIs; wrapper's falseUtf8). The SourceEncoding doc comments are updated to reference the new enum name.

Other factors

The PR description includes a 46-case before/after probe showing byte-identical output, and CI (build 98432) passed 177/179 with the two non-passing jobs being darwin agent-unavailable cancellations unrelated to the diff. The mordant job passed with the baseline line removed, confirming the lint no longer fires. The new tests follow harness conventions (specific error messages via toThrow("..."), toBe/toEqual on exact values, no sleeps, no network). No outstanding reviewer comments.

@alii
alii merged commit 22494bc into main Aug 15, 2026
9 of 10 checks passed
@alii
alii deleted the farm/1745f9e8/text-format-input-enums branch August 15, 2026 23:53

@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 — mechanical bool→enum refactor with no behavior change.

What was reviewed:

  • Verified each enum variant maps to the same branch as the old bool: Bytestrue, Throwtrue, AsIstrue, and the wrapper's hard-coded falseStringInput::Utf8.
  • Checked all five call sites (TOML/JSONC/JSON5/YAML/XML) pass the variant matching their previous literal.
  • New tests cover the two divergent call sites (YAML nullish→ToString, JSONC Blob→ToString) with specific error classes/messages.
Extended reasoning...

Overview

Replaces three positional bool parameters on with_text_format_source / with_text_format_source_encoded in src/runtime/api.rs with three two-variant enums (BlobOrBufferInput, NullishInput, StringInput), updates the five call sites in {TOML,JSONC,JSON5,YAML,XML}Object.rs, removes the corresponding bare_bool_args line from mordant-baseline.toml, and adds regression tests to jsonc.test.ts and yaml.test.ts for the two call sites whose flag values differ from the rest.

Security risks

None. This is a type-level refactor of internal helper signatures; no user-facing input handling, validation, or control flow changes. The three if conditions in the helper body compare against the enum variant that corresponds exactly to the old true value, so every branch is taken under the same conditions as before.

Level of scrutiny

Low. This fits the ongoing mordant lint-sweep pattern visible in recent commits on main (#39152, #39166, #39147, #39143 — each replacing bare bools with named enums and dropping a baseline line). The change is mechanical and the type system enforces that every call site was updated. I traced each old literal to its new variant and confirmed the helper body's three comparisons preserve polarity: accept_blob_or_buffer (true→Bytes), reject_nullish (true→Throw), string_passthrough (true→AsIs, wrapper's falseUtf8).

Other factors

The PR description includes a 46-case before/after probe showing byte-identical output across all five formats and both values of every flag. The new tests assert specific error messages ("Expected a string to parse") and error classes (SyntaxError) rather than bare toThrow(), and are placed in the existing test files for their modules. The removed baseline line means CI's mordant job will fail if a bare bool is reintroduced here.

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.

2 participants