Skip to content

fix(util): quote top-level strings in util.format %o/%O (Node parity)#5106

Merged
proggeramlug merged 1 commit into
mainfrom
fix/util-format-o-string-quote
Jun 14, 2026
Merged

fix(util): quote top-level strings in util.format %o/%O (Node parity)#5106
proggeramlug merged 1 commit into
mainfrom
fix/util-format-o-string-quote

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Problem

util.format("%o", "hello") and console.log("%o", str) printed the string unquoted (hello), but Node renders %o/%O arguments through util.inspect, which quotes strings: 'hello'.

# node v26.3.0           # perry (before)
util.format("%o","hi") → 'hi'        util.format("%o","hi") → hi
util.format("%O","hi") → 'hi'        util.format("%O","hi") → hi

Root cause

The %o/%O arms called format_jsvalue(val, 0), which mirrors the bare console.log(str) behavior of leaving a top-level string unquoted. But %o/%O should inspect the value, and util.inspect quotes strings at every depth (nested strings already came out right — %o of {a:"x"}{ a: 'x' }). js_util_inspect already special-cases top-level strings (format!("'{}'", escape_string(&s))); the %o/%O path just didn't.

Fix

Route the %o/%O argument through a new inspect_format_arg helper that mirrors js_util_inspect's top-level handling: a primitive string becomes '<escaped>', everything else goes through format_jsvalue.

Verification (vs Node v26.3.0)

%o "hello" → 'hello'      %O "hello" → 'hello'     %o 42 → 42
%o {a:"x"} → { a: 'x' }   %o null → null           %o true → true
%s "hello" → hello   (unchanged — %s coerces, no quotes)

Existing perry-runtime formatting + inspect unit tests pass (12). Found via a util.format/util.inspect differential sweep against Node; relates to the console/util-formatting parity gap noted in CLAUDE.md and the parity roadmap (#4315).

Out of scope: Node's %o also sets showHidden, so an array prints [ 1, 'two', [length]: 2 ]; matching that internal-slot rendering is a separate showHidden-array feature, left as-is.

No version bump / changelog per maintainer instruction.

Summary by CodeRabbit

  • Bug Fixes
    • Improved formatting for %o and %O so string arguments are now rendered with quotes and proper escaping, matching util.inspect-style output.
    • Non-string values keep the same formatting behavior as before to ensure consistency across all other types.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5df8dcc8-7899-46be-9cc0-7db336dbb254

📥 Commits

Reviewing files that changed from the base of the PR and between 61f62a8 and 0d172c4.

📒 Files selected for processing (1)
  • crates/perry-runtime/src/builtins/formatting/util_format.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-runtime/src/builtins/formatting/util_format.rs

📝 Walkthrough

Walkthrough

A new internal helper inspect_format_arg is added to util_format.rs. It quotes and escapes top-level string values (matching util.inspect semantics) and delegates non-strings to format_jsvalue(val, 0). The %o and %O placeholder branches in js_util_format are updated to call this helper instead of calling format_jsvalue directly.

Changes

util.inspect string quoting for %o/%O

Layer / File(s) Summary
inspect_format_arg helper and %o/%O wiring
crates/perry-runtime/src/builtins/formatting/util_format.rs
Adds inspect_format_arg (lines 115–130) to quote and escape top-level strings, then wires it into the %o and %O branches (lines 343–348) of js_util_format replacing the direct format_jsvalue(val, 0) calls.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Poem

🐇 A string walked in wearing %o's disguise,
And found itself quoted — what a surprise!
With backslashes neatly placed just so,
inspect_format_arg puts on a show.
The rabbit hops on, the output shines bright! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing util.format to quote top-level strings in %o/%O format specifiers to match Node.js behavior.
Description check ✅ Passed The description is comprehensive with problem statement, root cause analysis, fix explanation, and verification against Node v26.3.0. All required template sections are addressed with concrete details.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/util-format-o-string-quote

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

@proggeramlug proggeramlug force-pushed the fix/util-format-o-string-quote branch from 7f4365f to 61f62a8 Compare June 14, 2026 06:06
`util.format("%o", "hello")` / `console.log("%o", str)` printed the
string unquoted (`hello`) where Node renders it through `util.inspect`
(`'hello'`). The `%o`/`%O` arms called `format_jsvalue(val, 0)`, which
mirrors the bare `console.log(str)` behavior of leaving a *top-level*
string unquoted — but `%o`/`%O` should inspect the value, and inspect
quotes strings at every depth (nested strings already quoted correctly,
e.g. `%o` of `{a:"x"}` → `{ a: 'x' }`).

Route the `%o`/`%O` argument through a new `inspect_format_arg` helper
that mirrors `js_util_inspect`'s top-level handling: a primitive string
becomes `'<escaped>'`; everything else goes through `format_jsvalue`.

Verified vs Node v26.3.0: `%o`/`%O` of a string now yield `'hello'`;
numbers/objects/arrays/null/bool unchanged; `%s` still coerces without
quotes. Existing formatting/inspect unit tests pass (12).

(Out of scope: Node's `%o` also sets `showHidden`, so an array prints
`[ 1, 'two', [length]: 2 ]`; matching the `[length]` internal-slot
rendering is a separate showHidden-array feature and left as-is.)
@proggeramlug proggeramlug force-pushed the fix/util-format-o-string-quote branch from 61f62a8 to 0d172c4 Compare June 14, 2026 07:33
@proggeramlug proggeramlug merged commit 6eb6ebd into main Jun 14, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/util-format-o-string-quote branch June 14, 2026 08:45
proggeramlug pushed a commit that referenced this pull request Jun 14, 2026
Rolls up the issue-fix batch merged on top of 0.5.1165 (#5102, #5103,
#5105, #5106, #5107, #5108, #5109, #5110, #5112, #5117). See CHANGELOG
for the per-PR breakdown.
proggeramlug added a commit that referenced this pull request Jun 15, 2026
Refresh floors from a clean node-26 full run (2810/2863, 98.1%):
- object 23/23, util 86/86, tty 32/32, events 69/69 -> full (#5144/#5106-7/#5099)
- stream 770, globals 111, diagnostics_channel 66, fs-promises 77 -> ratcheted up with small flake margins
- http floor 19 -> 17: verified 19/19 in isolation but the full-suite harness
  flakes to 17 under port contention; a real http regression is a far larger
  drop (link break / behavior break), which still trips the guard. Stops the
  false-alarm seen on prior runs.

Co-authored-by: Ralph Küpper <ralph2@skelpo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant