aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/js_parser.zig21
-rw-r--r--test/bun.js/decorators.test.ts191
2 files changed, 153 insertions, 59 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig
index a745da798..ab9c9bec6 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -14718,7 +14718,7 @@ fn NewParser_(
}
// This might be wrong.
- _ = p.visitClass(expr.loc, e_, true);
+ _ = p.visitClass(expr.loc, e_);
},
else => {},
}
@@ -15773,7 +15773,7 @@ fn NewParser_(
return;
},
.s_class => |class| {
- _ = p.visitClass(s2.loc, &class.class, false);
+ _ = p.visitClass(s2.loc, &class.class);
if (p.is_control_flow_dead)
return;
@@ -16277,7 +16277,7 @@ fn NewParser_(
}
}
- _ = p.visitClass(stmt.loc, &data.class, false);
+ _ = p.visitClass(stmt.loc, &data.class);
// Remove the export flag inside a namespace
const was_export_inside_namespace = data.is_export and p.enclosing_namespace_arg_ref != null;
@@ -17095,12 +17095,6 @@ fn NewParser_(
const i = if (super_index) |j| j + 1 else 0;
constructor_stmts.insertSlice(i, instance_members.items) catch unreachable;
- // move super behind statements generated from parameter properties
- if (super_index) |j| {
- const super = constructor_stmts.orderedRemove(j);
- constructor_stmts.insert(0, super) catch unreachable;
- }
-
constructor_function.?.func.body.stmts = constructor_stmts.items;
}
}
@@ -17437,7 +17431,7 @@ fn NewParser_(
return res;
}
- fn visitClass(p: *P, name_scope_loc: logger.Loc, class: *G.Class, comptime is_expr: bool) Ref {
+ fn visitClass(p: *P, name_scope_loc: logger.Loc, class: *G.Class) Ref {
if (only_scan_imports_and_do_not_visit) {
@compileError("only_scan_imports_and_do_not_visit must not run this.");
}
@@ -17592,7 +17586,7 @@ fn NewParser_(
// if this is an expression, we can move statements after super() because there will be 0 decorators
var super_index: ?usize = null;
- if (comptime is_expr and class.extends != null) {
+ if (class.extends != null) {
for (constructor.func.body.stmts) |stmt, index| {
if (stmt.data != .s_expr or stmt.data.s_expr.value.data != .e_call or stmt.data.s_expr.value.data.e_call.target.data != .e_super) continue;
super_index = index;
@@ -17615,10 +17609,7 @@ fn NewParser_(
const name = p.symbols.items[id.ref.innerIndex()].original_name;
const ident = p.newExpr(E.Identifier{ .ref = id.ref }, arg.binding.loc);
- if (comptime is_expr) {
- if (super_index) |k| j += k + 1;
- }
- stmts.insert(j, Expr.assignStmt(
+ stmts.insert(if (super_index) |k| j + k + 1 else j, Expr.assignStmt(
p.newExpr(E.Dot{
.target = p.newExpr(E.This{}, arg.binding.loc),
.name = name,
diff --git a/test/bun.js/decorators.test.ts b/test/bun.js/decorators.test.ts
index 8a2bc6d9a..831d1094d 100644
--- a/test/bun.js/decorators.test.ts
+++ b/test/bun.js/decorators.test.ts
@@ -1,5 +1,5 @@
// @ts-nocheck
-import { test, expect } from "bun:test";
+import { test, expect, describe } from "bun:test";
test("decorator order of evaluation", () => {
let counter = 0;
@@ -787,61 +787,164 @@ test("no decorators", () => {
expect(aa.b).toBe(300000);
});
-test("class constructor parameter properties", () => {
- class A {
- constructor(readonly d: string = "default") {
- expect(d).toBe(d);
- expect(this.d).toBe(d);
+describe("constructor statements", () => {
+ test("with parameter properties", () => {
+ class A {
+ constructor(readonly d: string = "default") {
+ expect(d).toBe(d);
+ expect(this.d).toBe(d);
+ }
}
- }
- const a = new A("c");
- expect(a.d).toBe("c");
+ const a = new A("c");
+ expect(a.d).toBe("c");
- class B extends A {}
+ class B extends A {}
- const b = new B();
- expect(b.d).toBe("default");
+ const b = new B();
+ expect(b.d).toBe("default");
- class C extends A {
- constructor(public f: number) {
- super();
- expect(this.d).toBe("default");
- expect(f).toBe(f);
- expect(this.f).toBe(f);
+ class C extends A {
+ constructor(public f: number) {
+ super();
+ expect(this.d).toBe("default");
+ expect(f).toBe(f);
+ expect(this.f).toBe(f);
+ }
}
- }
- const c = new C(5);
- expect(c.d).toBe("default");
- expect(c.f).toBe(5);
-});
+ const c = new C(5);
+ expect(c.d).toBe("default");
+ expect(c.f).toBe(5);
+ });
+
+ test("class expressions (no decorators)", () => {
+ const A = class a {
+ constructor(readonly b: string = "default") {
+ expect(b).toBe(b);
+ expect(this.b).toBe(b);
+ }
+ };
-test("class expressions are lowered correctly (no decorators)", () => {
- const A = class a {
- constructor(readonly b: string = "default") {
- expect(b).toBe(b);
- expect(this.b).toBe(b);
+ const a = new A("hello class expression");
+ expect(a.b).toBe("hello class expression");
+
+ const B = class b extends A {};
+ const b = new B();
+ expect(b.b).toBe("default");
+
+ const C = class c extends A {
+ constructor(public f: number) {
+ super();
+ expect(this.b).toBe("default");
+ expect(this.f).toBe(f);
+ expect(f).toBe(f);
+ }
+ };
+
+ const c = new C(5);
+ expect(c.b).toBe("default");
+ expect(c.f).toBe(5);
+ });
+
+ test("with parameter properties and statements", () => {
+ class B {
+ value: number;
+ v2: number;
+ constructor(value: number) {
+ this.value = value;
+ this.v2 = 0;
+ }
}
- };
- const a = new A("hello class expression");
- expect(a.b).toBe("hello class expression");
+ class A extends B {
+ constructor(value: number, public v: string = "test") {
+ const newValue = value * 10;
+ super(newValue);
+ }
+ }
- const B = class b extends A {};
- const b = new B();
- expect(b.b).toBe("default");
+ const a = new A(10);
+ expect(a.value).toBe(100);
+ expect(a.v).toBe("test");
+ expect(a.v2).toBe(0);
+ });
+
+ test("with parameter properties, statements, and decorators", () => {
+ class B {
+ value: number;
+ v2: number;
+ constructor(value: number) {
+ this.value = value;
+ this.v2 = 0;
+ }
+ }
- const C = class c extends A {
- constructor(public f: number) {
- super();
- expect(this.b).toBe("default");
- expect(this.f).toBe(f);
- expect(f).toBe(f);
+ function d1() {}
+
+ class A extends B {
+ b: number;
+ constructor(value: number, @d1 b: number, public v: string = "test") {
+ const newValue = value * 10;
+ super(newValue);
+ expect(this.v).toBe("test");
+ this.b = b;
+ expect(this.b).toBe(b);
+ }
+ }
+
+ const a = new A(10, 1);
+ expect(a.b).toBe(1);
+ expect(a.value).toBe(100);
+ expect(a.v).toBe("test");
+ expect(a.v2).toBe(0);
+ });
+
+ test("with more parameter properties, statements, and decorators", () => {
+ let decoratorCounter = 0;
+ function d1() {
+ expect(decoratorCounter).toBe(1);
+ decoratorCounter += 1;
+ }
+ function d2() {
+ expect(decoratorCounter).toBe(0);
+ decoratorCounter += 1;
+ }
+ function d3() {
+ expect(decoratorCounter).toBe(2);
+ decoratorCounter += 1;
+ }
+ function d4() {
+ expect(decoratorCounter).toBe(3);
+ decoratorCounter += 1;
+ }
+
+ class A {
+ l: number;
+ constructor(
+ protected u: string,
+ @d1 l: number = 3,
+ @d2 public k: number = 4,
+ ) {
+ this.l = l;
+ }
+ }
+
+ class B extends A {
+ @d3 e: string = "hello test";
+
+ constructor(private i: number) {
+ super("protected");
+ expect(this.i).toBe(i);
+ expect(this.u).toBe("protected");
+ }
+
+ @d4 f() {}
}
- };
- const c = new C(5);
- expect(c.b).toBe("default");
- expect(c.f).toBe(5);
+ let b = new B(9);
+ expect(b.k).toBe(4);
+ expect(b.l).toBe(3);
+ expect(b.e).toBe("hello test");
+ });
});