diff --git a/src/jsc/ConsoleObject.rs b/src/jsc/ConsoleObject.rs index a9270cf88662..5494ae561148 100644 --- a/src/jsc/ConsoleObject.rs +++ b/src/jsc/ConsoleObject.rs @@ -3597,7 +3597,15 @@ pub mod formatter { }; writer.add_for_new_line(str.length()); - if self.quote_strings && js_type != jsc::JSType::RegExpObject { + // A String wrapper keeps its `[String: "..."]` rendering below when + // nested (like `[Number: 1]`) instead of being quoted like a + // primitive string. + if self.quote_strings + && !matches!( + js_type, + jsc::JSType::StringObject | jsc::JSType::RegExpObject + ) + { if str.is_empty() { writer.write_all(b"\"\""); if writer.failed { @@ -3634,6 +3642,7 @@ pub mod formatter { } if js_type == jsc::JSType::StringObject { + writer.add_for_new_line("[String: \"\"]".len()); if C { writer.print(format_args!("{}", pfmt!("", C))); } diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 5c91f3dbe05f..6b8f02c5eeea 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -393,6 +393,43 @@ it("inspect", () => { ); }); +it("String wrapper objects print as [String: ...] at every depth", () => { + class MyString extends String {} + + // Top level. Bun.inspect quotes strings even at the top level, which used to + // turn the wrapper into a plain quoted string. + expect(Bun.inspect(new String("s"))).toBe('[String: "s"]'); + expect(Bun.inspect(new String(""))).toBe('[String: ""]'); + expect(Bun.inspect(new MyString("s"))).toBe('[String: "s"]'); + + // Nested in every container, alongside the other wrappers which already printed correctly. + expect(Bun.inspect({ n: new Number(3), b: new Boolean(true), s: new String("s") })).toBe( + '{\n n: [Number: 3],\n b: [Boolean: true],\n s: [String: "s"],\n}', + ); + expect(Bun.inspect([new String("s"), new String(""), new MyString("sub")])).toBe( + '[ [String: "s"], [String: ""], [String: "sub"] ]', + ); + expect(Bun.inspect(new Map([[new String("k"), new String("v")]]))).toBe( + 'Map(1) {\n [String: "k"]: [String: "v"],\n}', + ); + expect(Bun.inspect(new Set([new String("s")]))).toBe('Set(1) {\n [String: "s"],\n}'); + + // The contents go through the same escaping as a quoted primitive string, for + // both the Latin-1 and the UTF-16 string representations. + expect(Bun.inspect({ s: new String('a"b\\c\n') })).toBe('{\n s: [String: "a\\"b\\\\c\\n"],\n}'); + expect(Bun.inspect({ s: new String("héllo") })).toBe('{\n s: [String: "héllo"],\n}'); + expect(Bun.inspect({ s: new String('日"本') })).toBe('{\n s: [String: "日\\"本"],\n}'); + expect(Bun.inspect({ s: new String("héllo 🌍") })).toBe('{\n s: [String: "héllo 🌍"],\n}'); + + // Primitive strings and RegExps share the printer and keep their rendering. + expect(Bun.inspect({ s: "s", r: /r/g })).toBe('{\n s: "s",\n r: /r/g,\n}'); + expect(Bun.inspect(["s", /r/g])).toBe('[ "s", /r/g ]'); + + // The colored printer is a separate instantiation of the same code. + expect(Bun.inspect([new String("s")], { colors: true })).toContain('\x1b[32m[String: "s"]\x1b[0m'); + expect(Bun.inspect([new String("日本語")], { colors: true })).toContain('\x1b[32m[String: "日本語"]\x1b[0m'); +}); + describe("latin1 supplemental", () => { const fixture = [ [["äbc"], '[ "äbc" ]'], diff --git a/test/js/web/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt index 9a6cdf569c3c..032c2ed87718 100644 --- a/test/js/web/console/console-log.expected.txt +++ b/test/js/web/console/console-log.expected.txt @@ -20,6 +20,12 @@ Symbol(Symbol Description) [String: "Hello 👋🏼"] [Number: 5] [Boolean: true] +{ + s: [String: "Hello"], + n: [Number: 5], + b: [Boolean: true], +} +[ [String: "Hello"], [String: "Hello 👋🏼"], "Hello" ] [ 123, 456, 789 ] { name: "foo", diff --git a/test/js/web/console/console-log.js b/test/js/web/console/console-log.js index 91c3bddd0278..6cdeb1b06e77 100644 --- a/test/js/web/console/console-log.js +++ b/test/js/web/console/console-log.js @@ -18,6 +18,8 @@ console.log(new String("Hello")); console.log(new String("Hello 👋🏼")); console.log(new Number(5)); console.log(new Boolean(true)); +console.log({ s: new String("Hello"), n: new Number(5), b: new Boolean(true) }); +console.log([new String("Hello"), new String("Hello 👋🏼"), "Hello"]); console.log([123, 456, 789]); console.log({ name: "foo" }); console.log({ a: 123, b: 456, c: 789 });