Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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