diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index a3412e3a7a79..06d634eb34ce 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -581,10 +581,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. 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 }; diff --git a/test/bundler/transpiler/export-default-name.test.ts b/test/bundler/transpiler/export-default-name.test.ts new file mode 100644 index 000000000000..c9fdbf205fbd --- /dev/null +++ b/test/bundler/transpiler/export-default-name.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from "bun:test"; +import { bunEnv, bunExe, tempDir } from "harness"; + +describe("anonymous export default .name", () => { + 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 }); + }); +}); diff --git a/test/regression/issue/31401.test.ts b/test/regression/issue/31401.test.ts index 7a507fe2c143..d16be0b8b374 100644 --- a/test/regression/issue/31401.test.ts +++ b/test/regression/issue/31401.test.ts @@ -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", @@ -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); }); });