diff options
-rw-r--r-- | src/bun.js/bindings/exports.zig | 2 | ||||
-rw-r--r-- | src/js_printer.zig | 92 | ||||
-rw-r--r-- | test/transpiler/property-non-ascii-fixture.js | 34 | ||||
-rw-r--r-- | test/transpiler/property.test.ts | 28 |
4 files changed, 113 insertions, 43 deletions
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index acbf7944a..5c171adf8 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -1774,7 +1774,7 @@ pub const ZigConsoleClient = struct { writer.writeAll(comptime Output.prettyFmt("<r><green>", true)); } - writer.writeAll("'"); + writer.writeAll("\""); while (strings.indexOfAny16(utf16Slice, "\"")) |j| { writer.write16Bit(utf16Slice[0..j]); diff --git a/src/js_printer.zig b/src/js_printer.zig index e6d04a7c0..52dfdc5bd 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -2211,13 +2211,14 @@ fn NewPrinter( ); if (p.canPrintIdentifier(e.name)) { - if (!isOptionalChain and p.prev_num_end == p.writer.written) { - // "1.toString" is a syntax error, so print "1 .toString" instead - p.print(" "); - } if (isOptionalChain) { p.print("?."); } else { + if (p.prev_num_end == p.writer.written) { + // "1.toString" is a syntax error, so print "1 .toString" instead + p.print(" "); + } + p.print("."); } @@ -2225,11 +2226,16 @@ fn NewPrinter( p.printIdentifier(e.name); } else { if (isOptionalChain) { - p.print("?."); + p.print("?.["); + } else { + p.print("["); } - p.print("["); - p.addSourceMapping(e.name_loc); - p.printQuotedUTF8(e.name, true); + + p.printPossiblyEscapedIdentifierString( + e.name, + true, + ); + p.print("]"); } @@ -2700,7 +2706,7 @@ fn NewPrinter( p.print("["); // TODO: addSourceMappingForName // p.addSourceMappingForName(alias); - p.printQuotedUTF8(alias, true); + p.printPossiblyEscapedIdentifierString(alias, true); p.print("]"); } @@ -3005,6 +3011,34 @@ fn NewPrinter( } } + fn printBindingIdentifierName(p: *Printer, name: string, name_loc: logger.Loc) void { + p.addSourceMapping(name_loc); + + if (comptime !is_json and ascii_only) { + const quote = bestQuoteCharForString(u8, name, false); + p.print(quote); + p.printQuotedIdentifier(name); + p.print(quote); + } else { + p.printQuotedUTF8(name, false); + } + } + + fn printPossiblyEscapedIdentifierString(p: *Printer, name: string, allow_backtick: bool) void { + if (comptime !ascii_only or is_json) { + p.printQuotedUTF8(name, allow_backtick); + } else { + const quote = if (comptime !is_json) + bestQuoteCharForString(u8, name, allow_backtick) + else + '"'; + + p.print(quote); + p.printQuotedIdentifier(name); + p.print(quote); + } + } + pub fn printNamespaceAlias(p: *Printer, import_record: ImportRecord, namespace: G.NamespaceAlias) void { if (import_record.module_id > 0 and !import_record.contains_import_star) { p.print("$"); @@ -3025,7 +3059,7 @@ fn NewPrinter( p.printIdentifier(namespace.alias); } else { p.print("["); - p.printQuotedUTF8(namespace.alias, true); + p.printPossiblyEscapedIdentifierString(namespace.alias, true); p.print("]"); } } @@ -3216,23 +3250,11 @@ fn NewPrinter( // While each of those property keys are ASCII, a subset of ASCII is valid as the start of an identifier // "=" and ":" are not valid // So we need to check - if ((comptime !is_json) and p.canPrintIdentifier(key.data)) { + if (p.canPrintIdentifier(key.data)) { p.print(key.data); } else { allow_shorthand = false; - const quote = if (comptime !is_json) - bestQuoteCharForString(u8, key.data, true) - else - '"'; - if (quote == '`') { - p.print('['); - } - p.print(quote); - p.printUTF8StringEscapedQuotes(key.data, quote); - p.print(quote); - if (quote == '`') { - p.print(']'); - } + p.printBindingIdentifierName(key.data, logger.Loc.Empty); } // Use a shorthand property if the names are the same @@ -3478,7 +3500,7 @@ fn NewPrinter( else => {}, } } else { - p.printQuotedUTF8(str.data, false); + p.printPossiblyEscapedIdentifierString(str.data, false); } } else if (p.canPrintIdentifierUTF16(str.slice16())) { p.printSpaceBeforeIdentifier(); @@ -5081,23 +5103,9 @@ fn NewPrinter( is_ascii = false; } - switch (cursor.c) { - 0...0xFFFF => { - p.print([_]u8{ - '\\', - 'u', - hex_chars[cursor.c >> 12], - hex_chars[(cursor.c >> 8) & 15], - hex_chars[(cursor.c >> 4) & 15], - hex_chars[cursor.c & 15], - }); - }, - else => { - p.print("\\u{"); - std.fmt.formatInt(cursor.c, 16, .lower, .{}, p) catch unreachable; - p.print("}"); - }, - } + p.print("\\u{"); + std.fmt.formatInt(cursor.c, 16, .lower, .{}, p) catch unreachable; + p.print("}"); }, } } diff --git a/test/transpiler/property-non-ascii-fixture.js b/test/transpiler/property-non-ascii-fixture.js new file mode 100644 index 000000000..72299ccc6 --- /dev/null +++ b/test/transpiler/property-non-ascii-fixture.js @@ -0,0 +1,34 @@ +// prettier-ignore +let object2 = { + código: 1, + ["código2"]: 2, + "código3": 3, + 'código4': 4, + [`código5`]: 5, + "😋 Get ": 6, + }; +// prettier-ignore +let { + código, + ["código3"]: bound3, + ['código2']: bound2, + [`código2`]: bound22, + "código4": bound4, + 'código5': bound5, + "😋 Get ": bound6, + '😋 Get ': bound7, + [`😋 Get `]: bound8, + ["😋 Get "]: bound9, + ['😋 Get ']: bound10, + } = object2; +// prettier-ignore +console.log(object2, código, object2.código, object2['código2'], + object2["código3"], + object2[`código4`], bound3, bound2, bound4, bound5, bound22,bound6, + bound7, + bound8, + bound9, + bound10, + object2[`😋 Get `], + object2["😋 Get "], + object2['😋 Get '],); diff --git a/test/transpiler/property.test.ts b/test/transpiler/property.test.ts new file mode 100644 index 000000000..0e9f436c7 --- /dev/null +++ b/test/transpiler/property.test.ts @@ -0,0 +1,28 @@ +import { test, expect } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +// See https://github.com/oven-sh/bun/pull/2939 +test("non-ascii property name", () => { + const { stdout } = Bun.spawnSync({ + cmd: [bunExe(), "run", require("path").join(import.meta.dir, "./property-non-ascii-fixture.js")], + env: bunEnv, + }); + const filtered = stdout.toString().replaceAll("\n", "").replaceAll(" ", ""); + expect(filtered).toBe( + `{ + "código": 1, + "código2": 2, + "código3": 3, + "código4": 4, + "código5": 5, + "😋 Get ": 6 + } 1 1 2 3 4 3 2 4 5 2 6 6 6 6 6 6 6 6 +` + .replaceAll("\n", "") + .replaceAll(" ", ""), + ); + // just to be sure + expect(Buffer.from(Bun.CryptoHasher.hash("sha1", filtered) as Uint8Array).toString("hex")).toBe( + "4dd3c3a66c282e3463048a952f21227485f91822", + ); +}); |