Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
b79bef892f fix(parser): allow accessor keyword with experimentalDecorators: true
The `accessor` keyword (TC39 auto-accessors proposal) was incorrectly
gated behind the `standard_decorators` feature flag, causing it to be
rejected as a syntax error when `experimentalDecorators: true` was set
in tsconfig.json. TypeScript supports `accessor` in both decorator modes.

Two changes:
- Remove `standard_decorators` guard from accessor keyword recognition
  in the parser so it's always recognized in class bodies.
- Set `should_lower_standard_decorators` when auto-accessors are present
  regardless of the decorator mode, since auto-accessors always need the
  standard lowering path (WeakMap + getter/setter transformation).

Closes #27335

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-21 23:33:48 +00:00
3 changed files with 144 additions and 3 deletions

View File

@@ -209,7 +209,7 @@ pub fn Parse(
.body_loc = body_loc,
.properties = properties.items,
.has_decorators = has_any_decorators,
.should_lower_standard_decorators = p.options.features.standard_decorators and (has_any_decorators or has_auto_accessor),
.should_lower_standard_decorators = has_auto_accessor or (p.options.features.standard_decorators and has_any_decorators),
};
}

View File

@@ -300,8 +300,9 @@ pub fn ParseProperty(
}
},
.p_accessor => {
// "accessor" keyword for auto-accessor fields (TC39 standard decorators)
if (opts.is_class and p.options.features.standard_decorators and
// "accessor" keyword for auto-accessor fields (TC39 proposal)
// Always recognized in classes regardless of decorator mode
if (opts.is_class and
(js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_accessor)
{
kind = .auto_accessor;

View File

@@ -0,0 +1,140 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// https://github.com/oven-sh/bun/issues/27335
// The `accessor` keyword should work in TypeScript classes even when
// `experimentalDecorators: true` is set in tsconfig.json.
test("accessor keyword works with experimentalDecorators: true", async () => {
using dir = tempDir("issue-27335", {
"tsconfig.json": JSON.stringify({
compilerOptions: {
experimentalDecorators: true,
},
}),
"main.ts": `
class Person {
public accessor name: string = "John";
}
const p = new Person();
console.log(p.name);
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "main.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("John\n");
expect(exitCode).toBe(0);
});
test("accessor keyword works with various modifiers and experimentalDecorators", async () => {
using dir = tempDir("issue-27335-modifiers", {
"tsconfig.json": JSON.stringify({
compilerOptions: {
experimentalDecorators: true,
},
}),
"main.ts": `
class Foo {
accessor x = 1;
public accessor y = 2;
private accessor z = 3;
static accessor w = 4;
getZ() { return this.z; }
}
const f = new Foo();
console.log(f.x);
console.log(f.y);
console.log(f.getZ());
console.log(Foo.w);
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "main.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("1\n2\n3\n4\n");
expect(exitCode).toBe(0);
});
test("accessor keyword works without experimentalDecorators (standard mode)", async () => {
using dir = tempDir("issue-27335-standard", {
"tsconfig.json": JSON.stringify({
compilerOptions: {},
}),
"main.ts": `
class Person {
public accessor name: string = "John";
}
const p = new Person();
console.log(p.name);
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "main.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("John\n");
expect(exitCode).toBe(0);
});
test("accessor with experimental decorators on other members", async () => {
using dir = tempDir("issue-27335-mixed", {
"tsconfig.json": JSON.stringify({
compilerOptions: {
experimentalDecorators: true,
},
}),
"main.ts": `
function log(target: any, key: string) {
// simple experimental decorator
}
class MyClass {
@log
greet() { return "hello"; }
accessor count: number = 42;
}
const obj = new MyClass();
console.log(obj.greet());
console.log(obj.count);
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "main.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("hello\n42\n");
expect(exitCode).toBe(0);
});