Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/js_parser/visit/visit_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,8 +826,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
p.create_default_name(stmt.loc).expect("unreachable");
}

// We only inject a name into classes when there is a decorator
if class.class.has_decorators {
// We only inject a name into classes when decorator lowering
// needs one: legacy TS decorators (`has_decorators`) or
// standard decorator lowering, which also covers classes with
// only auto-accessor fields and no decorators.
if class.class.has_decorators
|| class.class.should_lower_standard_decorators
{
if class.class.class_name.is_none()
|| class.class.class_name.unwrap().ref_.is_none()
{
Expand Down
92 changes: 92 additions & 0 deletions test/bundler/transpiler/es-decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,98 @@ describe("ES Decorators", () => {
expect(stdout).toBe("decorated foo\n42\n");
expect(exitCode).toBe(0);
});

test("export default anonymous class with auto-accessor and no decorators", async () => {
using dir = tempDir("es-dec-export-default-anon-accessor", {
"entry.js": `
import Cls from "./mod.js";
const c = new Cls();
console.log(c.op);
c.op = 42;
console.log(c.op);
const desc = Object.getOwnPropertyDescriptor(Cls.prototype, "op");
console.log(typeof desc.get, typeof desc.set);
`,
"mod.js": `
export default class {
accessor op;
}
`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), "entry.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});

const [stdout, rawStderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(filterStderr(rawStderr)).toBe("");
expect(stdout).toBe("undefined\n42\nfunction function\n");
expect(exitCode).toBe(0);
});

test("export default anonymous TypeScript class with auto-accessor and no decorators", async () => {
using dir = tempDir("es-dec-export-default-anon-accessor-ts", {
"tsconfig.json": JSON.stringify({ compilerOptions: {} }),
"entry.ts": `
import Cls from "./mod.ts";
const c = new Cls();
c.op = "hello";
console.log(c.op);
`,
"mod.ts": `
export default class {
accessor op: string | undefined;
}
`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), "entry.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});

const [stdout, rawStderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(filterStderr(rawStderr)).toBe("");
expect(stdout).toBe("hello\n");
expect(exitCode).toBe(0);
});

test("Bun.build bundles export default anonymous class with auto-accessor", async () => {
using dir = tempDir("es-dec-build-anon-accessor", {
"build.js": `
const result = await Bun.build({
entrypoints: ["./mod.ts"],
target: "bun",
minify: true,
sourcemap: "external",
throw: false,
});
console.log(result.success);
`,
"mod.ts": `
export default class {
accessor op;
}
`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), "build.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});

const [stdout, rawStderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(filterStderr(rawStderr)).toBe("");
expect(stdout).toBe("true\n");
expect(exitCode).toBe(0);
});
});

describe("anonymous class expressions with reserved-word inferred names", () => {
Expand Down
Loading