diff options
author | 2023-05-24 12:02:33 -0700 | |
---|---|---|
committer | 2023-05-24 12:02:33 -0700 | |
commit | cc84c689ea6a8d76313caecebb665666893ac362 (patch) | |
tree | f7466a0b834e24f86fe9d35d3968544811d74dab | |
parent | b3d5f37598ea4ca37a863f05ade94637477f3700 (diff) | |
download | bun-cc84c689ea6a8d76313caecebb665666893ac362.tar.gz bun-cc84c689ea6a8d76313caecebb665666893ac362.tar.zst bun-cc84c689ea6a8d76313caecebb665666893ac362.zip |
Fixes #3031 (#3041)
* Fixes #3031
* Leave original input in there
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/js_printer.zig | 63 | ||||
-rw-r--r-- | test/transpiler/template-literal-fixture-test.js | 22 | ||||
-rw-r--r-- | test/transpiler/template-literal.test.ts | 22 |
3 files changed, 105 insertions, 2 deletions
diff --git a/src/js_printer.zig b/src/js_printer.zig index 54aa0150c..af820520e 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -1942,6 +1942,65 @@ fn NewPrinter( } } + fn printRawTemplateLiteral(p: *Printer, bytes: []const u8) void { + if (comptime is_json or !ascii_only) { + p.print(bytes); + return; + } + + // Translate any non-ASCII to unicode escape sequences + // Note that this does not correctly handle malformed template literal strings + // template literal strings can contain invalid unicode code points + // and pretty much anything else + // + // we use WTF-8 here, but that's still not good enough. + // + var ascii_start: usize = 0; + var is_ascii = false; + var iter = CodepointIterator.init(bytes); + var cursor = CodepointIterator.Cursor{}; + + while (iter.next(&cursor)) { + switch (cursor.c) { + // unlike other versions, we only want to mutate > 0x7F + 0...last_ascii => { + if (!is_ascii) { + ascii_start = cursor.i; + is_ascii = true; + } + }, + else => { + if (is_ascii) { + p.print(bytes[ascii_start..cursor.i]); + 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("}"); + }, + } + }, + } + } + + if (is_ascii) { + p.print(bytes[ascii_start..]); + } + } + pub fn printExpr(p: *Printer, expr: Expr, level: Level, _flags: ExprFlag.Set) void { var flags = _flags; @@ -2553,7 +2612,7 @@ fn NewPrinter( p.print("`"); switch (e.head) { - .raw => |raw| p.print(raw), + .raw => |raw| p.printRawTemplateLiteral(raw), .cooked => |*cooked| { if (cooked.isPresent()) { cooked.resolveRopeIfNeeded(p.options.allocator); @@ -2567,7 +2626,7 @@ fn NewPrinter( p.printExpr(part.value, .lowest, ExprFlag.None()); p.print("}"); switch (part.tail) { - .raw => |raw| p.print(raw), + .raw => |raw| p.printRawTemplateLiteral(raw), .cooked => |*cooked| { if (cooked.isPresent()) { cooked.resolveRopeIfNeeded(p.options.allocator); diff --git a/test/transpiler/template-literal-fixture-test.js b/test/transpiler/template-literal-fixture-test.js new file mode 100644 index 000000000..20fbfbdb1 --- /dev/null +++ b/test/transpiler/template-literal-fixture-test.js @@ -0,0 +1,22 @@ +console.write ??= process.stdout.write.bind(process.stdout); +var bufs = []; +function template(...args) { + bufs.push(Buffer.from(args.join(""))); +} + +template`🐰123`; +template`123🐰`; +template`🐰`; +template`🐰🐰`; +template`🐰🐰123`; +template`🐰123🐰123`; +template`123🐰`; +template`123🐰123`; +template`🐰${(globalThis.boop ||= true)}🐰`; +const outBuf = Buffer.concat(bufs); +const out = outBuf.toString("base64"); +console.write(out); +if (!outBuf.equals(Buffer.from(out, "base64"))) { + throw new Error("Buffer mismatch"); +} +process.exit(0); diff --git a/test/transpiler/template-literal.test.ts b/test/transpiler/template-literal.test.ts new file mode 100644 index 000000000..a0fd2170f --- /dev/null +++ b/test/transpiler/template-literal.test.ts @@ -0,0 +1,22 @@ +import { test, expect } from "bun:test"; +import { bunEnv, bunExe } from "harness"; +import { join } from "path"; + +// When targeting Bun's runtime, +// We must escape latin1 characters in raw template literals +// This is somewhat brittle +test("template literal", () => { + const { stdout, exitCode } = Bun.spawnSync({ + cmd: [bunExe(), "run", join(import.meta.dir, "template-literal-fixture-test.js")], + env: bunEnv, + stdout: "pipe", + stderr: "inherit", + }); + + expect(exitCode).toBe(0); + expect(stdout.toString()).toBe( + // This is base64 encoded contents of the template literal + // this narrows down the test to the transpiler instead of the runtime + "8J+QsDEyMzEyM/CfkLDwn5Cw8J+QsPCfkLDwn5Cw8J+QsDEyM/CfkLAxMjPwn5CwMTIzMTIz8J+QsDEyM/CfkLAxMjPwn5CwLPCfkLB0cnVl", + ); +}); |