import { expect, test } from "bun:test"; import { bunEnv, bunExe, tempDir } from "harness"; // https://github.com/oven-sh/bun/issues/25648 // Named function expression names should be renamed when they shadow an outer symbol // that's referenced inside the function body. This prevents infinite recursion. test("named function expression should be renamed when shadowing outer symbol", async () => { using dir = tempDir("issue-25648", { "lib.ts": ` export function get(x: number) { return x * 2; } export function doSomething(fn: () => number) { return fn(); } `, "index.ts": ` import * as $ from './lib'; export function test() { return $.doSomething(function get() { return $.get(123); // This should reference the outer get, not the function expression }); } console.log(test()); `, }); // Bundle and run the code await using buildProc = Bun.spawn({ cmd: [bunExe(), "build", "index.ts", "--bundle", "--outfile=out.js"], env: bunEnv, cwd: String(dir), stdout: "pipe", stderr: "pipe", }); const [buildStdout, buildStderr, buildExitCode] = await Promise.all([ buildProc.stdout.text(), buildProc.stderr.text(), buildProc.exited, ]); expect(buildStderr).toBe(""); expect(buildExitCode).toBe(0); // Run the bundled output await using runProc = Bun.spawn({ cmd: [bunExe(), "out.js"], env: bunEnv, cwd: String(dir), stdout: "pipe", stderr: "pipe", }); const [runStdout, runStderr, runExitCode] = await Promise.all([ runProc.stdout.text(), runProc.stderr.text(), runProc.exited, ]); // Should print 246 (123 * 2), NOT cause infinite recursion expect(runStdout.trim()).toBe("246"); expect(runStderr).toBe(""); expect(runExitCode).toBe(0); }); test("named function expression with namespace import should not cause infinite recursion", async () => { using dir = tempDir("issue-25648-2", { "svelte-mock.ts": ` export function get(store: { value: T }): T { return store.value; } export function set(store: { value: T }, value: T) { store.value = value; } export function bind_value( element: HTMLElement, get_fn: () => string, set_fn: (value: string) => void ) { return get_fn(); } `, "index.ts": ` import * as $ from './svelte-mock'; const query = { value: "hello" }; // This pattern is generated by the Svelte compiler in dev mode const result = $.bind_value( {} as HTMLElement, function get() { return $.get(query); // Should call outer $.get, not this function }, function set($$value: string) { $.set(query, $$value); } ); console.log(result); `, }); // Bundle and run the code await using buildProc = Bun.spawn({ cmd: [bunExe(), "build", "index.ts", "--bundle", "--outfile=out.js"], env: bunEnv, cwd: String(dir), stdout: "pipe", stderr: "pipe", }); const [buildStdout, buildStderr, buildExitCode] = await Promise.all([ buildProc.stdout.text(), buildProc.stderr.text(), buildProc.exited, ]); expect(buildStderr).toBe(""); expect(buildExitCode).toBe(0); // Run the bundled output await using runProc = Bun.spawn({ cmd: [bunExe(), "out.js"], env: bunEnv, cwd: String(dir), stdout: "pipe", stderr: "pipe", }); const [runStdout, runStderr, runExitCode] = await Promise.all([ runProc.stdout.text(), runProc.stderr.text(), runProc.exited, ]); // Should print "hello", NOT cause "Maximum call stack size exceeded" expect(runStdout.trim()).toBe("hello"); expect(runStderr).toBe(""); expect(runExitCode).toBe(0); }); test("class expression name should be renamed when shadowing outer symbol", async () => { using dir = tempDir("issue-25648-3", { "lib.ts": ` export class Foo { value = 42; } export function makeThing(cls: new () => T): T { return new cls(); } `, "index.ts": ` import * as $ from './lib'; export function test() { return $.makeThing(class Foo extends $.Foo { getValue() { return this.value; } // Self-reference: uses the inner class name Foo static create() { return new Foo(); } clone() { return new Foo(); } }); } const instance = test(); console.log(instance.getValue()); // Test self-referencing static method console.log((instance.constructor as any).create().getValue()); // Test self-referencing instance method console.log(instance.clone().getValue()); `, }); // Bundle and run the code await using buildProc = Bun.spawn({ cmd: [bunExe(), "build", "index.ts", "--bundle", "--outfile=out.js"], env: bunEnv, cwd: String(dir), stdout: "pipe", stderr: "pipe", }); const [buildStdout, buildStderr, buildExitCode] = await Promise.all([ buildProc.stdout.text(), buildProc.stderr.text(), buildProc.exited, ]); expect(buildStderr).toBe(""); expect(buildExitCode).toBe(0); // Run the bundled output await using runProc = Bun.spawn({ cmd: [bunExe(), "out.js"], env: bunEnv, cwd: String(dir), stdout: "pipe", stderr: "pipe", }); const [runStdout, runStderr, runExitCode] = await Promise.all([ runProc.stdout.text(), runProc.stderr.text(), runProc.exited, ]); // Should print 42 three times (getValue, static create().getValue, clone().getValue) expect(runStdout.trim()).toBe("42\n42\n42"); expect(runStderr).toBe(""); expect(runExitCode).toBe(0); });