aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-08-04 19:34:09 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-04 19:34:09 -0700
commit6bdee80cfce0205ea1b59679fda93f816a66eed0 (patch)
tree72bfaa306054017d58186343e7bfa56086307d91
parent637a38f394704eaea29b503a69d554b5726d6214 (diff)
downloadbun-6bdee80cfce0205ea1b59679fda93f816a66eed0.tar.gz
bun-6bdee80cfce0205ea1b59679fda93f816a66eed0.tar.zst
bun-6bdee80cfce0205ea1b59679fda93f816a66eed0.zip
fix macro string escaping (#3967)
* handle macro escaping * remove printer * use `js_lexer.decodeEscapeSequences`
-rw-r--r--src/js_ast.zig17
-rw-r--r--src/js_lexer.zig13
-rw-r--r--test/transpiler/macro-test.test.ts63
-rw-r--r--test/transpiler/macro.ts12
4 files changed, 97 insertions, 8 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index d25c494fa..62089b3b2 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -25,6 +25,7 @@ const JSONParser = bun.JSON;
const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false;
const ComptimeStringMap = bun.ComptimeStringMap;
const JSPrinter = @import("./js_printer.zig");
+const js_lexer = @import("./js_lexer.zig");
const ThreadlocalArena = @import("./mimalloc_arena.zig").Arena;
/// This is the index to the automatically-generated part containing code that
@@ -2359,7 +2360,6 @@ pub const E = struct {
}
pub fn toJS(s: *String, allocator: std.mem.Allocator, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
- s.resolveRopeIfNeeded(allocator);
if (!s.isPresent()) {
var emp = bun.String.empty;
return emp.toJS(globalObject);
@@ -2372,8 +2372,15 @@ pub const E = struct {
}
{
- var out = bun.String.create(s.slice(allocator));
+ s.resolveRopeIfNeeded(allocator);
+
+ const decoded = js_lexer.decodeUTF8(s.slice(allocator), allocator) catch unreachable;
+ defer allocator.free(decoded);
+
+ var out = bun.String.createUninitializedUTF16(decoded.len);
defer out.deref();
+ @memcpy(@constCast(out.utf16()), decoded);
+
return out.toJS(globalObject);
}
}
@@ -9951,12 +9958,8 @@ pub const Macro = struct {
},
.String => {
var bun_str = value.toBunString(this.global);
- if (bun_str.is8Bit()) {
- if (strings.isAllASCII(bun_str.latin1())) {
- return Expr.init(E.String, E.String.init(this.allocator.dupe(u8, bun_str.latin1()) catch unreachable), this.caller.loc);
- }
- }
+ // encode into utf16 so the printer escapes the string correctly
var utf16_bytes = this.allocator.alloc(u16, bun_str.length()) catch unreachable;
var out_slice = utf16_bytes[0 .. (bun_str.encodeInto(std.mem.sliceAsBytes(utf16_bytes), .utf16le) catch 0) / 2];
return Expr.init(E.String, E.String.init(out_slice), this.caller.loc);
diff --git a/src/js_lexer.zig b/src/js_lexer.zig
index a0ad75c7b..e54e738e0 100644
--- a/src/js_lexer.zig
+++ b/src/js_lexer.zig
@@ -75,6 +75,19 @@ pub const JSONOptions = struct {
was_originally_macro: bool = false,
};
+pub fn decodeUTF8(bytes: string, allocator: std.mem.Allocator) ![]const u16 {
+ var log = logger.Log.init(allocator);
+ defer log.deinit();
+ var source = logger.Source.initEmptyFile("");
+ var lexer = try NewLexer(.{}).init(&log, source, allocator);
+ defer lexer.deinit();
+
+ var buf = std.ArrayList(u16).init(allocator);
+ try lexer.decodeEscapeSequences(0, bytes, @TypeOf(buf), &buf);
+
+ return buf.items;
+}
+
pub fn NewLexer(
comptime json_options: JSONOptions,
) type {
diff --git a/test/transpiler/macro-test.test.ts b/test/transpiler/macro-test.test.ts
index 73ef430e8..f3df00c5b 100644
--- a/test/transpiler/macro-test.test.ts
+++ b/test/transpiler/macro-test.test.ts
@@ -1,4 +1,4 @@
-import { identity } from "./macro.ts" assert { type: "macro" };
+import { identity, escape, addStringsUTF16, addStrings } from "./macro.ts" assert { type: "macro" };
test("latin1 string", () => {
expect(identity("©")).toBe("©");
@@ -8,6 +8,67 @@ test("ascii string", () => {
expect(identity("abc")).toBe("abc");
});
+test("escaping", () => {
+ expect(identity("\\")).toBe("\\");
+ expect(identity("\f")).toBe("\f");
+ expect(identity("\n")).toBe("\n");
+ expect(identity("\r")).toBe("\r");
+ expect(identity("\t")).toBe("\t");
+ expect(identity("\v")).toBe("\v");
+ expect(identity("\0")).toBe("\0");
+ expect(identity("'")).toBe("'");
+ expect(identity('"')).toBe('"');
+ expect(identity("`")).toBe("`");
+ // prettier-ignore
+ expect(identity("\'")).toBe("\'");
+ // prettier-ignore
+ expect(identity('\"')).toBe('\"');
+ // prettier-ignore
+ expect(identity("\`")).toBe("\`");
+ expect(identity("$")).toBe("$");
+ expect(identity("\x00")).toBe("\x00");
+ expect(identity("\x0B")).toBe("\x0B");
+ expect(identity("\x0C")).toBe("\x0C");
+
+ expect(identity("\\")).toBe("\\");
+
+ expect(escape()).toBe("\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C");
+
+ expect(addStrings("abc")).toBe("abc\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\n")).toBe("\n\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\r")).toBe("\r\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\t")).toBe("\t\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("©")).toBe("©\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\x00")).toBe("\x00\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\x0B")).toBe("\x0B\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\x0C")).toBe("\x0C\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\\")).toBe("\\\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\f")).toBe("\f\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\v")).toBe("\v\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("\0")).toBe("\0\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("'")).toBe("'\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings('"')).toBe('"\\\f\n\r\t\v\0\'"`$\x00\x0B\x0C©');
+ expect(addStrings("`")).toBe("`\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+ expect(addStrings("😊")).toBe("😊\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C©");
+
+ expect(addStringsUTF16("abc")).toBe("abc\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\n")).toBe("\n\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\r")).toBe("\r\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\t")).toBe("\t\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("©")).toBe("©\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\x00")).toBe("\x00\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\x0B")).toBe("\x0B\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\x0C")).toBe("\x0C\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\\")).toBe("\\\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\f")).toBe("\f\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\v")).toBe("\v\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("\0")).toBe("\0\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("'")).toBe("'\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16('"')).toBe('"\\\f\n\r\t\v\0\'"`$\x00\x0B\x0C😊');
+ expect(addStringsUTF16("`")).toBe("`\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+ expect(addStringsUTF16("😊")).toBe("😊\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C😊");
+});
+
test("utf16 string", () => {
expect(identity("😊 Smiling Face with Smiling Eyes Emoji")).toBe("😊 Smiling Face with Smiling Eyes Emoji");
});
diff --git a/test/transpiler/macro.ts b/test/transpiler/macro.ts
index 8516d7d0d..fc747333c 100644
--- a/test/transpiler/macro.ts
+++ b/test/transpiler/macro.ts
@@ -1,3 +1,15 @@
export function identity(arg: any) {
return arg;
}
+
+export function escape() {
+ return "\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C";
+}
+
+export function addStrings(arg: string) {
+ return arg + "\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C" + "©";
+}
+
+export function addStringsUTF16(arg: string) {
+ return arg + "\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C" + "😊";
+}