Skip to content
Draft
6 changes: 5 additions & 1 deletion src/js_parser/visit/visit_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
match s2_data {
StmtData::SFunction(mut func_ref) => {
let func: &mut S::Function = &mut *func_ref;
// Leave anonymous so SetFunctionName assigns `.name = "default"`;
// React Fast Refresh injects a name for its `$RefreshReg$` binding.
Comment thread
robobun marked this conversation as resolved.
Resolved
let name: &'a [u8] = if let Some(func_loc) = func.func.name {
p.load_name_from_ref(func_loc.ref_)
} else {
func.func.name = Some(data.default_name);
if p.options.features.react_fast_refresh {
func.func.name = Some(data.default_name);
}
js_ast::ClauseItem::DEFAULT_ALIAS
Comment thread
robobun marked this conversation as resolved.
Resolved
};

Expand Down
3 changes: 2 additions & 1 deletion src/jsc/RuntimeTranspilerCache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ bun_core::declare_scope!(cache, visible);
/// Version 42: The body of a class with lowered class decorators names the class by the
/// binding that holds what the decorators return.
/// Version 43: A `bun:` specifier that names no builtin is printed as it is written.
const EXPECTED_VERSION: u32 = 43;
/// Version 44: An anonymous `export default function` is printed without a generated name.
const EXPECTED_VERSION: u32 = 44;

/// Source files smaller than this are not written to / read from the on-disk
/// transpiler cache. Originally 50 KiB, which excluded almost every file in a
Expand Down
39 changes: 39 additions & 0 deletions test/bundler/transpiler/export-default-name.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";

describe("anonymous export default .name", () => {
Comment thread
robobun marked this conversation as resolved.
Resolved
test.concurrent.each([
["function", "export default function () {}"],
["async function", "export default async function () {}"],
["function*", "export default function* () {}"],
["async function*", "export default async function* () {}"],
["class", "export default class {}"],
["arrow", "export default () => 1;"],
])("%s: .name is 'default'", async (_label, decl) => {
using dir = tempDir("export-default-name", {
"mod.mjs": `${decl}\nimport self from "./mod.mjs";\nconsole.log(JSON.stringify(self.name));\n`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "mod.mjs"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout, stderr, exitCode }).toEqual({ stdout: '"default"\n', stderr: "", exitCode: 0 });
});

test.concurrent("named function keeps its own name", async () => {
using dir = tempDir("export-default-named", {
"mod.mjs": `export default function Foo() {}\nimport self from "./mod.mjs";\nconsole.log(JSON.stringify(self.name));\n`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "mod.mjs"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout, stderr, exitCode }).toEqual({ stdout: '"Foo"\n', stderr: "", exitCode: 0 });
});
});
9 changes: 5 additions & 4 deletions test/regression/issue/31401.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ describe.concurrent("issue 31401: anonymous default export from digit-named modu
expect(exitCode).toBe(0);
});

test("transpile-only output uses a valid identifier for the generated default name", async () => {
using dir = tempDir("issue-31401-transpile", {
test("bundled output uses a valid identifier for the generated default name", async () => {
using dir = tempDir("issue-31401-bundle", {
"1.ts": `export default function () {}\n`,
"entry.ts": `import f from "./1.ts"; console.log(typeof f);\n`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), "build", "--no-bundle", join(String(dir), "1.ts")],
cmd: [bunExe(), "build", join(String(dir), "entry.ts")],
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
Expand All @@ -60,7 +61,7 @@ describe.concurrent("issue 31401: anonymous default export from digit-named modu

expect(stderr).not.toContain("No identifiers allowed directly after numeric literal");
// Must be a valid identifier: the leading digit gets an underscore prefix.
expect(normalizeBunSnapshot(stdout)).toContain("export default function _1_default() {}");
expect(normalizeBunSnapshot(stdout)).toContain("function _1_default() {}");
expect(exitCode).toBe(0);
});
});
Loading