* Fixes #3031

* Leave original input in there

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2023-05-24 12:02:33 -07:00
committed by GitHub
parent b3d5f37598
commit cc84c689ea
3 changed files with 105 additions and 2 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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",
);
});