Skip to content

bun-types: fix the family emoji width in the stringWidth example - #38388

Open
robobun wants to merge 1 commit into
mainfrom
farm/0ced755a/stringwidth-dts-example
Open

robobun wants to merge 1 commit into
mainfrom
farm/0ced755a/stringwidth-dts-example

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • The @example on Bun.stringWidth in packages/bun-types/bun.d.ts (line 588 on main) reads console.log(stringWidth("👩‍👩‍👧‍👦")); // 1.
  • The runtime prints 2, on the 1.4.0 release and on a debug build of main.
  • The value dates from the original stringWidth commit (5147c0b, 2024) and was never updated when the implementation moved to grapheme clusters. The perCodePoint docs a few lines above it already say a family sequence measures 2 by default, so the file contradicted itself.

Fix

  • Change the example to // 2, and add the same string measured with { perCodePoint: true } (// 8) on the next line, so the reader sees why a four-emoji sequence is 2 columns and how to get the per-code-point figure.
  • 2 is the correct value to document: the runtime returns it, test/js/bun/util/stringWidth.test.ts already pins it ("👩‍👩‍👧‍👦" is 2, line 1251 on main), the string-width npm package the JSDoc says this API matches returns 2 as well (checked against string-width@7.0.0), and the perCodePoint docs in the same file describe it that way. 8 is what the runtime returns with perCodePoint: true (four emoji at 2 columns each, ZWJs at 0), matching that option's own docs.
  • New test, test/js/bun/util/stringWidth.test.ts ("bun.d.ts @example for stringWidth prints the widths its comments claim"): extracts the example block from bun.d.ts, runs it, and compares what it prints with the // N comments. Fails on main on the // 1 line, passes with this change, and covers every value in the example rather than just the family emoji, so a future width change has to update the example too.
  • bun bd test test/js/bun/util/stringWidth.test.ts: 174 pass (173 pass, 1 fail with packages/ stashed).
  • bun test test/integration/bun-types/bun-types.test.ts: 15 pass.

Background

  • Bun.stringWidth returns the number of terminal columns a string occupies. By default it measures grapheme clusters: a user-perceived character such as an emoji joined out of several code points with U+200D ZERO WIDTH JOINER counts once, and emoji are 2 columns wide, so the family sequence is 2.
  • perCodePoint: true switches to the algorithm Node uses for util.inspect and console.table alignment, which measures each code point separately: the four emoji in the family sequence are 2 columns each and the three joiners are 0, hence 8.
  • docs/runtime/utils.mdx does not contain this example; its stringWidth values were checked and are correct, so only the .d.ts changes here.
Runtime check
$ bun -e 'console.log(Bun.stringWidth("👩‍👩‍👧‍👦"), Bun.stringWidth("👩‍👩‍👧‍👦", { perCodePoint: true }))'
2 8

Failure output of the new test on main:

  [
    "console.log(stringWidth("abc")); // 3",
-   "console.log(stringWidth("👩‍👩‍👧‍👦")); // 1",
+   "console.log(stringWidth("👩‍👩‍👧‍👦")); // 2",
    "console.log(stringWidth("\u001b[31mhello\u001b[39m")); // 5",
    ...
  ]

The @example on Bun.stringWidth said a family ZWJ sequence measures 1.
The runtime (and the string-width package the API is documented to
match) measure it as one 2-column grapheme cluster, and the perCodePoint
option documented a few lines above already describes it that way.
Correct the value and show the perCodePoint: true reading (8) next to it.

Add a test that runs the example block from bun.d.ts and compares what
it prints against the values in its comments, so the example cannot
drift from the implementation again.
@robobun
robobun requested a review from alii as a code owner August 14, 2026 06:24
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

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: 6f10ed87-92bb-4452-8608-c6efe00b09a6

📥 Commits

Reviewing files that changed from the base of the PR and between a0921e1 and 2555b86.

📒 Files selected for processing (2)
  • packages/bun-types/bun.d.ts
  • test/js/bun/util/stringWidth.test.ts

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

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status: fix and test pushed, waiting on CI.

Reproduced with bun -e 'console.log(Bun.stringWidth("👩‍👩‍👧‍👦"))', which prints 2 on the 1.4.0 release and on a debug build of main, while the @example in bun.d.ts said 1. The new test in test/js/bun/util/stringWidth.test.ts runs that example block and fails on main on exactly that line; it passes with this branch.

@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 — doc-only correction of a stale JSDoc example value, with a regression test.

What was reviewed:

  • Confirmed Bun.stringWidth("👩‍👩‍👧‍👦") is 2 (already pinned elsewhere in this test file) and 8 with perCodePoint: true, matching the option's own docs a few lines above.
  • Traced the new test's extraction: lastIndexOf("/**") → code-fence regex → strip * prefixes → run via bunRun; bunRun trims stdout so the 6 printed lines line up with the 6 console.log claims.
  • Checked join(tempDir(...), ...) — Bun's path.join coerces the DisposableString, so no TypeError.
Extended reasoning...

Overview

Two files: packages/bun-types/bun.d.ts changes one JSDoc @example comment value (// 1 → // 2) and adds one adjacent example line demonstrating { perCodePoint: true } returning 8. test/js/bun/util/stringWidth.test.ts gains one test that reads the JSDoc block from bun.d.ts, extracts the fenced code, runs it in a temp dir with bunRun, and asserts each printed value matches its trailing // N comment. No runtime or type-signature changes.

Security risks

None. The .d.ts edit is inside a comment; the test only reads a repo-local file, writes to a tempDir, and spawns the debug bun via bunRun/bunExe. No network, no untrusted input.

Level of scrutiny

Low. This is a documentation typo fix. The corrected values are already asserted elsewhere in the same test file ("👩‍👩‍👧‍👦" → 2 in the "emoji widths" block) and are described identically in the perCodePoint option docs directly above the changed example, so the file was internally inconsistent before this PR and is now self-consistent.

Other factors

  • Verified the test mechanics: bunRun returns stdout.trim(), so splitting on \n yields exactly one entry per console.log line, and the length assertion holds.
  • tempDir returns a DisposableString (String subclass with both Symbol.dispose and Symbol.asyncDispose); await using is valid, and Bun's path.join accepts String objects (confirmed empirically), so join(dir, "example.ts") works without explicit coercion.
  • The expect(claims).not.toBeEmpty() guard prevents the test from vacuously passing if the JSDoc extraction regex ever stops matching.
  • PR description states 174/174 pass on debug build and the new test fails against main, satisfying the fails-on-main / passes-with-fix requirement. No prior human review comments to address; only a CodeRabbit rate-limit notice.

@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:05 AM PT - Aug 14th, 2026

❌ @robobun, your commit 2555b86 has some failures in Build #95578 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 38388

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

bun-38388 --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