diff options
-rw-r--r-- | src/js_parser.zig | 21 | ||||
-rw-r--r-- | test/bun.js/decorators.test.ts | 191 |
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"); + }); }); |