Skip to content

console: apply group indent to count/time/assert and no-args log paths - #36470

Open
robobun wants to merge 5 commits into
mainfrom
farm/e61d167a/console-group-indent-count-time
Open

console: apply group indent to count/time/assert and no-args log paths#36470
robobun wants to merge 5 commits into
mainfrom
farm/e61d167a/console-group-indent-count-time

Conversation

@robobun

@robobun robobun commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Repro

console.group('G');
console.log('log-line');
console.count('cnt');
console.assert(false);
console.log();
console.warn();
console.time('t'); console.timeLog('t'); console.timeEnd('t');
console.groupEnd();

Node v26:

G
  log-line
  cnt: 1
                        (stdout: indented blank from log())
  t: 0.011ms
  t: 0.965ms

stderr: Assertion failed, (indented blank from warn()).

Bun (before): cnt: 1, Assertion failed, [0.00ms] t all at column 0; bare console.warn()/console.error() wrote their newline to stdout instead of stderr.

Cause

Several console output paths in src/jsc/ConsoleObject.rs write directly to a writer, bypassing the default_indent prefix and the level-based stdout/stderr selection that the log-family path applies via Formatter::write_indent:

  • Bun__ConsoleObject__count
  • Bun__ConsoleObject__timeLog / Bun__ConsoleObject__timeEnd
  • the MessageType::Assert && len == 0 early return (Assertion failed\n)
  • the print_length == 0 fallthroughs: bare console.log() / info() / warn() / error() wrote \n and also re-derived (*console).writer() (always stdout) instead of reusing the level-selected writer, so no-args warn()/error() landed on stdout

The time* output landing on stderr in bracket format is a separate pre-existing divergence and not changed here; console.table indentation is tracked separately in #34236.

Fix

Expose formatter::write_indent_n as pub(super) and call it with the current ConsoleObject.default_indent before each of these writes. The print_length == 0 && Log arm now reuses the already level-selected writer so bare warn()/error() route to stderr.

Verification

New test in test/js/web/console/console-log.test.ts exercises nested groups with count, assert(false), bare log/info/warn/error, and the time family at two indent levels plus back at level 0, asserting both stream and indentation.

  • USE_SYSTEM_BUN=1 bun test test/js/web/console/console-log.test.ts -t 'console.group indents count' fails
  • bun bd test test/js/web/console/console-log.test.ts passes (5 tests)
  • bun bd test test/js/web/console/console-timeLog.test.ts passes (3 tests)
  • bun bd test test/js/node/console/console.test.ts passes (7 tests)

Fixes #30017


[review] gate passed · iteration 2 · 2 files touched

fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/console/console-log.test.ts
bun test v1.4.0 (0ecfecacc)

test/js/web/console/console-log.test.ts:
(pass) should log to console correctly [786.87ms]
(pass) long arrays get cutoff [3.31ms]
(pass) console.group [303.50ms]
173 |     stdout: "pipe",
174 |     stderr: "pipe",
175 |   });
176 |   const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
177 | 
178 |   expect(stdout.replaceAll("\r\n", "\n")).toBe(
                                                ^
error: expect(received).toBe(expected)

  "G1
-   cnt: 1
-   cnt: 2
-   
-   
+ cnt: 1
+ cnt: 2
+ 
+ 
+ 
+ 
    G2
-     cnt: 3
-   cnt: 4
+ cnt: 3
+ cnt: 4
  cnt: 5
  
  "

- Expected  - 6
+ Received  + 8

      at <anonymous> (/workspace/bun/test/js/web/console/console-log.test.ts:178:43)
(fail) console.group indents count/time/assert and the no-args log paths [292.14ms]
(pass) console.log with SharedArrayBuffer [4.41ms]

 4 pass
 1 fail
 2 snapshots, 10 expect() calls
Ran 5 tests across 1 file. [3.41s]
error: script "bd
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (c68b37c0a)

test/js/web/console/console-log.test.ts:
(pass) should log to console correctly [17.82ms]
(pass) long arrays get cutoff [0.10ms]
(pass) console.group [7.35ms]
173 |     stdout: "pipe",
174 |     stderr: "pipe",
175 |   });
176 |   const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
177 | 
178 |   expect(stdout.replaceAll("\r\n", "\n")).toBe(
                                                ^
error: expect(received).toBe(expected)

  "G1
    cnt: 1
    cnt: 2
    
    
+   
+   
    G2
      cnt: 3
    cnt: 4
  cnt: 5
  
  "

- Expected  - 0
+ Received  + 2

      at <anonymous> (/workspace/bun/test/js/web/console/console-log.test.ts:178:43)
(fail) console.group indents count/time/assert and the no-args log paths [6.38ms]
(pass) console.log with SharedArrayBuffer [0.07ms]

 4 pass
 1 fail
 2 snapshots, 10 expect() calls
Ran 5 tests across 1 file. [177.00ms]
__F:1:S:0
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/web/console/console-log.test.ts
bun test v1.4.0 (0ecfecacc)

test/js/web/console/console-log.test.ts:
(pass) should log to console correctly [808.34ms]
(pass) long arrays get cutoff [3.43ms]
(pass) console.group [391.59ms]
(pass) console.group indents count/time/assert and the no-args log paths [298.99ms]
(pass) console.log with SharedArrayBuffer [4.67ms]

 5 pass
 0 fail
 2 snapshots, 19 expect() calls
Ran 5 tests across 1 file. [3.50s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 678ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/6] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 239 extern-C blocks audited
[1/6] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_collecti
... (truncated)
diff hotspot
src/jsc/ConsoleObject.rs                | 37 +++++++++++++--------
 test/js/web/console/console-log.test.ts | 57 +++++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 13 deletions(-)

gate history · 3 passed · 0 rejected · iteration 2

evidence per changed file
file                                     reads  edits  tests
src/jsc/ConsoleObject.rs                     7      8      0
test/js/web/console/console-log.test.ts      3      3      0

console.count, console.timeLog, and console.timeEnd wrote their output
at column 0 regardless of the current console.group nesting level. The
log/info/warn/error/dir/assert/trace paths already prefix each line
with ConsoleObject.default_indent * 2 spaces via Formatter.write_indent;
the count and time* host-call paths write directly and skipped it.

Expose formatter::write_indent_n as pub(super) and call it with the
current default_indent before each of these writes, matching Node.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Console indentation

Layer / File(s) Summary
Message indentation paths
src/jsc/ConsoleObject.rs
The shared indentation helper is made available within the module and used for assertion and log output paths.
Console shims and formatting coverage
src/jsc/ConsoleObject.rs, test/js/web/console/console-log.test.ts
count, timeLog, and timeEnd now indent output on their selected writers, with a test covering grouped console output and timing/assertion formatting.

Possibly related PRs

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fixes the linked issue by indenting console.timeEnd() output inside active console.group() contexts.
Out of Scope Changes check ✅ Passed The additional console paths touched are part of the stated PR objective, with no unrelated changes evident.
Title check ✅ Passed The title clearly summarizes the main change: applying group indentation to console count/time/assert and no-argument log paths.
Description check ✅ Passed The description covers what changed and how it was verified, though it uses custom headings instead of the template's exact sections.

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

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. console.timeEnd() does not apply console.group() indentation as required by the WHATWG Console spec #30017 - Reports that console.timeEnd() does not respect console.group() indentation per the WHATWG Console spec, which is exactly what this PR fixes (along with console.timeLog() and console.count())

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #30017

🤖 Generated with Claude Code

Comment thread src/jsc/ConsoleObject.rs
Same bug class as count/timeLog/timeEnd: the Assert-with-no-message
early return and the print_length==0 fallthroughs wrote directly to the
writer without the default_indent prefix.
@robobun robobun changed the title console: apply group indent to count/timeLog/timeEnd console: apply group indent to count/time/assert and no-args log paths Jul 30, 2026

@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 sibling-site feedback from the earlier pass is now addressed in c68b37c.

What was reviewed:

  • Verified all six touched output paths (count, timeLog, timeEnd, bare assert, bare log→\n, other→undefined\n) read default_indent before borrowing the writer, and that write_indent_n(0, ...) is a no-op so level-0 output is unchanged.
  • Checked Output::error_writer() in the timeLog/timeEnd path is the same stream print_elapsed writes to, so the indent and […ms] land in order.
  • Test exercises two nesting levels plus level-0 for each path and asserts exact stdout / regex-matched stderr.
Extended reasoning...

Overview

Cosmetic Node-compat fix in src/jsc/ConsoleObject.rs: six console output paths that previously wrote directly to a writer now prepend formatter::write_indent_n(default_indent, …) so console.group() indentation applies. Paths covered: Bun__ConsoleObject__count, Bun__ConsoleObject__timeLog, Bun__ConsoleObject__timeEnd, the MessageType::Assert && len == 0 early return, and both print_length == 0 fallthroughs (\n and undefined\n). write_indent_n visibility widened from private to pub(super) so the extern-C host calls can reach it. One new test added to test/js/web/console/console-log.test.ts.

Security risks

None. Output-formatting only; no new inputs parsed, no allocation sizing, no unsafe blocks beyond the pre-existing vm_console_mut pattern already used at every neighboring call site with the same SAFETY justification.

Level of scrutiny

Low. The change is mechanical — the same one-line write_indent_n call inserted at each site — and write_indent_n(0, …) short-circuits, so behavior at group depth 0 is byte-identical to before. Borrow ordering was checked at each site: default_indent (a u16 copy) is read before writer()/error_writer() takes the mutable borrow, matching the existing snapshot-then-borrow pattern at line 448. For timeEnd/timeLog, the indent goes to Output::error_writer(), which is the same stream print_elapsed/print_errorln write to, so ordering is preserved.

Other factors

The previous review round flagged the bare-assert and print_length == 0 sibling paths; c68b37c covers all three and extends the test to assert them at two nesting depths plus level 0 (both stdout and stderr). The stderr assertions use a regex over the timing value rather than a fixed string, so they won't flake on duration. The _globalglobal_this rename in timeEnd is required to reach vm_console_mut. The PR description explicitly scopes out the pre-existing stderr/bracket-format divergence for time* and the separately-tracked console.table case (#34236).

@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: 2

🤖 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 `@src/jsc/ConsoleObject.rs`:
- Around line 550-553: Update the empty-message logging arm in ConsoleObject to
reuse the already level-selected writer variable for indentation, newline, and
flush operations. Remove the unsafe re-derivation via writer() so no-argument
console.warn() and console.error() output remains routed to the selected error
writer.

In `@test/js/web/console/console-log.test.ts`:
- Around line 146-195: Expand the src scenario in the console.group test to
invoke no-argument console.info(), console.warn(), and console.error() at
appropriate group nesting levels. Update stdout and stderr assertions to verify
each method’s expected indentation and stream, while preserving the existing
count, assert, and timing checks in the test.
🪄 Autofix (Beta)

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: 222e02b8-c160-4c79-8ebb-eb91ab39039e

📥 Commits

Reviewing files that changed from the base of the PR and between c08f665 and b1f79f0.

📒 Files selected for processing (2)
  • src/jsc/ConsoleObject.rs
  • test/js/web/console/console-log.test.ts

Comment thread src/jsc/ConsoleObject.rs Outdated
Comment thread test/js/web/console/console-log.test.ts
The print_length==0 Log arm re-derived (*console).writer() (always
stdout) instead of using the level-selected writer, so bare
console.warn()/console.error() wrote their newline to stdout. Use the
already-selected writer so the indented blank line lands on stderr,
matching Node. Test now covers info/warn/error with no args.

@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 sibling sites I flagged earlier and CodeRabbit's writer/coverage points are all addressed.

What was reviewed:

  • Each of the six direct-write paths (count, timeLog, timeEnd, bare assert, and both print_length == 0 arms) now prepends write_indent_n(default_indent, …); the empty-Log arm reuses the level-selected writer so bare warn()/error() land on stderr.
  • Checked that Output::error_writer() is the same stream print_elapsed writes to, so the indent lands on the same line as […ms] label.
  • Removed SAFETY comment was only about the now-deleted re-derive; raw_writer is already a raw-deref borrow so no aliasing concern remains.
  • Test covers two nesting levels + level 0, both streams, and the timing regex accommodates ms/s under NO_COLOR=1 from bunEnv.
Extended reasoning...

Overview

Cosmetic Node-compat fix in src/jsc/ConsoleObject.rs: six console output paths that wrote directly to a writer now prepend the current default_indent (group nesting) via formatter::write_indent_n, which is widened to pub(super). The print_length == 0 && Log arm additionally switches from re-deriving (*console).writer() (always stdout) to the already level-selected writer, so no-arg console.warn()/console.error() route to stderr. A new subprocess test in test/js/web/console/console-log.test.ts asserts indentation and stream at levels 0/1/2 for count, bare assert(false), bare log/info/warn/error, and timeLog/timeEnd.

Security risks

None. Pure output-formatting change; no parsing of untrusted input, no allocation sizing from external data, no new unsafe patterns beyond the existing vm_console_mut(global) idiom already used at neighboring sites with the same SAFETY justification.

Level of scrutiny

Low. This is a narrow cosmetic Node-compat divergence (WHATWG console group indentation) with no control-flow or lifetime changes beyond dropping one raw-pointer re-deref that is no longer needed. The removed SAFETY comment described why the re-derive was safe; with the re-derive gone, writer (derived once via raw *console) is simply reused — no new aliasing. Output::error_writer() is the same stderr stream that print_elapsed/print_errorln write to, so the indent prefix and the timing text land on one line without an intervening flush.

Other factors

All three prior review threads are resolved: my earlier comment on the Assert && len == 0 and print_length == 0 sibling sites was fixed in c68b37c; CodeRabbit's writer-routing and variant-coverage comments were fixed in 0ecfeca. The test follows harness conventions (bunEnv/bunExe, await using, concurrent drain of stdout/stderr/exited, exitCode asserted last, it.concurrent). The timing regex [mnµ]?s is loose enough for both print_elapsed branches (ms/s), and bunEnv sets NO_COLOR=1 so ANSI codes won't break it. PR description explicitly scopes out the pre-existing time*-on-stderr divergence and console.table (#34236).

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Triage note: #37128 (the console output rework) does not indent count / timeLog / timeEnd / bare assert output inside console.group. On its current head cnt: 1, t: 0.053ms and Assertion failed still print at column 0 inside a group, so #30017 stays with this PR, and #36471 was closed in its favor. The bare console.warn() / console.error() stream routing part of this PR is covered by #37128.

This will need a rebase once #37128 lands: count and time* are emitted through a new path there, so the write_indent_n calls move into those closures.

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.

console.timeEnd() does not apply console.group() indentation as required by the WHATWG Console spec

2 participants