aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/bindings/exports.zig2
-rw-r--r--src/js_printer.zig92
-rw-r--r--test/transpiler/property-non-ascii-fixture.js34
-rw-r--r--test/transpiler/property.test.ts28
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",
+ );
+});