From 9cc1bb4397ff8e7f8dafffe16f0d9ef8323f5a99 Mon Sep 17 00:00:00 2001 From: RiskyMH Date: Wed, 6 Aug 2025 01:27:54 +1000 Subject: [PATCH] allow template literals in bun macros --- src/ast/Macro.zig | 51 +++++++++++++++++++++- test/bundler/transpiler/macro-test.test.ts | 24 ++++++++++ test/bundler/transpiler/macro.ts | 13 ++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/ast/Macro.zig b/src/ast/Macro.zig index b4b3f6dbd4..261ddb2152 100644 --- a/src/ast/Macro.zig +++ b/src/ast/Macro.zig @@ -579,8 +579,55 @@ pub const Runner = struct { out.* = value; } }, - .e_template => { - @panic("TODO: support template literals in macros"); + .e_template => |template| { + // For tagged template literals, the first argument is an array of template strings + // followed by the interpolated values as separate arguments + const parts_count = template.parts.len; + const total_args = 1 + parts_count + @as(usize, @intFromBool(javascript_object != .zero)); + js_args = try allocator.alloc(jsc.JSValue, total_args); + js_processed_args_len = total_args; + + var template_strings = try allocator.alloc(jsc.JSValue, parts_count + 1); + defer allocator.free(template_strings); + + var head_str = switch (template.head) { + .cooked => |str| str, + .raw => |raw| E.String.init(raw), + }; + template_strings[0] = try head_str.toJS(allocator, globalObject); + template_strings[0].protect(); + + for (template.parts, 1..) |part, i| { + var tail_str = switch (part.tail) { + .cooked => |str| str, + .raw => |raw| E.String.init(raw), + }; + template_strings[i] = try tail_str.toJS(allocator, globalObject); + template_strings[i].protect(); + } + + js_args[0] = try jsc.JSArray.create(globalObject, template_strings); + js_args[0].protect(); + + for (template_strings) |str| { + str.unprotect(); + } + + for (template.parts, 0..) |part, i| { + const expr = part.value; + const value = expr.toJS( + allocator, + globalObject, + ) catch |e| { + for (js_args[0 .. i + 1]) |arg| { + arg.unprotect(); + } + js_processed_args_len = i + 1; + return e; + }; + value.protect(); + js_args[i + 1] = value; + } }, else => { @panic("Unexpected caller type"); diff --git a/test/bundler/transpiler/macro-test.test.ts b/test/bundler/transpiler/macro-test.test.ts index bc5ea871f3..e08981dceb 100644 --- a/test/bundler/transpiler/macro-test.test.ts +++ b/test/bundler/transpiler/macro-test.test.ts @@ -9,6 +9,9 @@ import defaultMacro, { identity as identity1, identity as identity2, ireturnapromise, + simpleTag, + interpolateTag, + objectTag, } from "./macro.ts" assert { type: "macro" }; import * as macros from "./macro.ts" assert { type: "macro" }; @@ -118,6 +121,15 @@ test("namespace import", () => { expect(macros.escape()).toBe("\\\f\n\r\t\v\0'\"`$\x00\x0B\x0C"); }); +test("varables", () => { + const a = "A"; + expect(identity(a)).toBe("A"); +}); + +test("template string basic", () => { + expect(identity(`A`)).toBe("A"); +}); + // test("template string ascii", () => { // expect(identity(`A${""}`)).toBe("A"); // }); @@ -129,3 +141,15 @@ test("namespace import", () => { test("ireturnapromise", async () => { expect(await ireturnapromise()).toEqual("aaa"); }); + +test("template literals with macros", () => { + expect(simpleTag`World`).toBe("Hello World!"); + expect(interpolateTag`Alice${100}`).toBe("User Alice has 100 points"); + expect(objectTag`${{ name: "Bob", age: 25 }}`).toBe("Name: Bob, Age: 25"); +}); + +test("template literals with namespace import", () => { + expect(macros.simpleTag`Test`).toBe("Hello Test!"); + expect(macros.interpolateTag`Jane${50}`).toBe("User Jane has 50 points"); + expect(macros.objectTag`${{ name: "Alice", age: 30 }}`).toBe("Name: Alice, Age: 30"); +}); diff --git a/test/bundler/transpiler/macro.ts b/test/bundler/transpiler/macro.ts index 9da8d72c5a..17db327147 100644 --- a/test/bundler/transpiler/macro.ts +++ b/test/bundler/transpiler/macro.ts @@ -23,3 +23,16 @@ export async function ireturnapromise() { setTimeout(() => resolve("aaa"), 100); return promise; } + +export function simpleTag(strings: TemplateStringsArray, ..._values: any[]) { + return `Hello ${strings[0]}!`; +} + +export function interpolateTag(strings: TemplateStringsArray, ...values: any[]) { + return `User ${strings[0]} has ${values[0]} points`; +} + +export function objectTag(_strings: TemplateStringsArray, ...values: any[]) { + const obj = values[0]; + return `Name: ${obj.name}, Age: ${obj.age}`; +}