From 657ee659f4835e85ab262fcf78ba7c3deb9b564c Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Mon, 18 May 2026 15:41:45 +0100 Subject: [PATCH 1/4] fmt: decode multi-byte code points in the identifier formatter --- src/bun_core/lib.rs | 31 +++++++++++++++++++++++++---- test/bundler/bundler_naming.test.ts | 22 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/bun_core/lib.rs b/src/bun_core/lib.rs index 8849b07f1352..da57477b6972 100644 --- a/src/bun_core/lib.rs +++ b/src/bun_core/lib.rs @@ -2434,11 +2434,34 @@ pub(crate) mod strings_impl { return false; } let b = self.bytes[i]; - // TODO(port): full UTF-8 decode — bun_str owns the table-driven impl. - let (cp, w) = if b < 0x80 { - (b as i32, 1u8) + // Decode the WTF-8 sequence so multi-byte code points report their + // true width (Zig sets `width = cp_len`); fmt.rs collapses any + // `width > 1` code point to a single `_`. Invalid / truncated + // sequences fall back to a 1-byte step. + let seq = wtf8_byte_sequence_length(b) as usize; + let valid = (2..=4).contains(&seq) + && i + seq <= self.bytes.len() + && self.bytes[i + 1..i + seq] + .iter() + .all(|&c| (c & 0xC0) == 0x80); + let (cp, w): (i32, u8) = if !valid { + (b as i32, 1) } else { - (b as i32, 1u8) + let cp = match seq { + 2 => ((b as i32 & 0x1F) << 6) | (self.bytes[i + 1] as i32 & 0x3F), + 3 => { + ((b as i32 & 0x0F) << 12) + | ((self.bytes[i + 1] as i32 & 0x3F) << 6) + | (self.bytes[i + 2] as i32 & 0x3F) + } + _ => { + ((b as i32 & 0x07) << 18) + | ((self.bytes[i + 1] as i32 & 0x3F) << 12) + | ((self.bytes[i + 2] as i32 & 0x3F) << 6) + | (self.bytes[i + 3] as i32 & 0x3F) + } + }; + (cp, seq as u8) }; cursor.i = i; cursor.c = cp; diff --git a/test/bundler/bundler_naming.test.ts b/test/bundler/bundler_naming.test.ts index e2870b399b3d..678baed2cd62 100644 --- a/test/bundler/bundler_naming.test.ts +++ b/test/bundler/bundler_naming.test.ts @@ -302,4 +302,26 @@ describe("bundler", () => { }, ], }); + // A non-ASCII basename char must collapse to a single "_" in the generated + // CommonJS wrapper symbol, not one "_" per UTF-8 byte. Regressed to + // `require_caf__utils` (the 2 bytes of "é" walked individually) because the + // identifier formatter resolved to a stub that never decoded multi-byte + // sequences (width always 1). + itBundled("naming/NonAsciiSourceFilenameSymbol", { + files: { + "/entry.js": /* js */ ` + const u = require("./café-utils.js"); + console.log(u.hi); + `, + "/café-utils.js": /* js */ ` + module.exports = { hi: 1 }; + `, + }, + target: "bun", + onAfterBundle(api) { + api.expectFile("/out.js").toContain("require_caf_utils"); + api.expectFile("/out.js").not.toContain("require_caf__utils"); + }, + run: { stdout: "1" }, + }); }); From 038103e0ba7f6d8c3fa39e3fb1d545c27fb6a938 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 18 May 2026 21:58:25 +0000 Subject: [PATCH 2/4] route strings_impl::CodepointIterator through decode_wtf8_rune_t Replaces the inline multi-byte WTF-8 decode with a call to the canonical decode_wtf8_rune_t used by strings::CodepointIterator::next, so there is a single decode path to maintain. --- src/bun_core/lib.rs | 53 ++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/bun_core/lib.rs b/src/bun_core/lib.rs index da57477b6972..e159a67b365d 100644 --- a/src/bun_core/lib.rs +++ b/src/bun_core/lib.rs @@ -2433,39 +2433,28 @@ pub(crate) mod strings_impl { if i >= self.bytes.len() { return false; } - let b = self.bytes[i]; - // Decode the WTF-8 sequence so multi-byte code points report their - // true width (Zig sets `width = cp_len`); fmt.rs collapses any - // `width > 1` code point to a single `_`. Invalid / truncated - // sequences fall back to a 1-byte step. - let seq = wtf8_byte_sequence_length(b) as usize; - let valid = (2..=4).contains(&seq) - && i + seq <= self.bytes.len() - && self.bytes[i + 1..i + seq] - .iter() - .all(|&c| (c & 0xC0) == 0x80); - let (cp, w): (i32, u8) = if !valid { - (b as i32, 1) - } else { - let cp = match seq { - 2 => ((b as i32 & 0x1F) << 6) | (self.bytes[i + 1] as i32 & 0x3F), - 3 => { - ((b as i32 & 0x0F) << 12) - | ((self.bytes[i + 1] as i32 & 0x3F) << 6) - | (self.bytes[i + 2] as i32 & 0x3F) - } - _ => { - ((b as i32 & 0x07) << 18) - | ((self.bytes[i + 1] as i32 & 0x3F) << 12) - | ((self.bytes[i + 2] as i32 & 0x3F) << 6) - | (self.bytes[i + 3] as i32 & 0x3F) - } - }; - (cp, seq as u8) - }; + let tail = &self.bytes[i..]; + let b = tail[0]; cursor.i = i; - cursor.c = cp; - cursor.width = w; + if b < 0x80 { + cursor.c = b as i32; + cursor.width = 1; + return true; + } + // Multi-byte: defer to the canonical WTF-8 decoder so this stub + // stays in lockstep with `strings::CodepointIterator::next`. + let len = wtf8_byte_sequence_length(b); + let take = (len as usize).min(tail.len()); + let mut buf = [0u8; 4]; + buf[..take].copy_from_slice(&tail[..take]); + let cp = crate::string::immutable::decode_wtf8_rune_t::(&buf, len, -1); + if cp == -1 { + cursor.c = crate::string::immutable::UNICODE_REPLACEMENT as i32; + cursor.width = 1; + } else { + cursor.c = cp; + cursor.width = len; + } true } } From 95b15b7a9d99d2d2f71d4aec92aab708fba87878 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 18 May 2026 23:07:47 +0000 Subject: [PATCH 3/4] ci: retrigger From f4e1f16e6844551d5169805cd1d295cdc2bf8dc4 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 18 May 2026 23:24:33 +0000 Subject: [PATCH 4/4] wtf-bindings: include for uv__tty_make_raw The libuv-derived uv__tty_make_raw() calls assert() but the header was only pulled in transitively by other files in the same unified source group; when the grouping shifts the build fails with 'use of undeclared identifier assert'. --- src/jsc/bindings/wtf-bindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jsc/bindings/wtf-bindings.cpp b/src/jsc/bindings/wtf-bindings.cpp index 0d4968f805fc..a8986324943e 100644 --- a/src/jsc/bindings/wtf-bindings.cpp +++ b/src/jsc/bindings/wtf-bindings.cpp @@ -13,6 +13,7 @@ #endif #if !OS(WINDOWS) +#include #include #include