ladybird/Tests/LibJS/Runtime/classes/class-public-fields.js
Andreas Kling 99596d68ab LibJS: Fix await boundaries in class elements
Treat ordinary and generator function expressions inside class static
blocks as boundaries for await binding names, while still rejecting
await as an arrow parameter in the static block itself.

Parse field initializers without inheriting an enclosing async
function's await-expression context, so script field initializers can
resolve await as an identifier while computed field names still use the
enclosing expression context.

Cover static block function expressions, class field initializers, and
the AST shape for an await arrow inside a nested function.
2026-05-22 01:56:57 +02:00

230 lines
5.2 KiB
JavaScript

test("basic functionality", () => {
class A {
number = 3;
string = "foo";
uninitialized;
}
const a = new A();
expect(a.number).toBe(3);
expect(a.string).toBe("foo");
expect(a.uninitialized).toBeUndefined();
});
test("literal field initializers", () => {
class A {
pos_int = 42;
pos_float = 3.14;
neg_int = -1;
neg_float = -2.5;
neg_zero = -0;
bool_true = true;
bool_false = false;
null_val = null;
str_val = "hello";
static s_int = 99;
static s_neg = -7;
static s_str = "world";
}
const a = new A();
expect(a.pos_int).toBe(42);
expect(a.pos_float).toBe(3.14);
expect(a.neg_int).toBe(-1);
expect(a.neg_float).toBe(-2.5);
expect(a.neg_zero).toBe(-0);
expect(1 / a.neg_zero).toBe(-Infinity);
expect(a.bool_true).toBeTrue();
expect(a.bool_false).toBeFalse();
expect(a.null_val).toBeNull();
expect(a.str_val).toBe("hello");
expect(A.s_int).toBe(99);
expect(A.s_neg).toBe(-7);
expect(A.s_str).toBe("world");
});
test("extended name syntax", () => {
class A {
"field with space" = 1;
12 = "twelve";
[`he${"llo"}`] = 3;
}
const a = new A();
expect(a["field with space"]).toBe(1);
expect(a[12]).toBe("twelve");
expect(a.hello).toBe(3);
});
test("initializer has correct this value", () => {
class A {
this_val = this;
this_name = this.this_val;
}
const a = new A();
expect(a.this_val).toBe(a);
expect(a.this_name).toBe(a);
});
test("field initializers do not inherit await expressions", () => {
var await = 42;
async function getClass() {
return class {
field = await;
};
}
expect("async () => class { [await] = 1 };").not.toEval();
expect("async () => class { field = await 1 };").not.toEval();
return getClass().then(A => {
expect(new A().field).toBe(42);
});
});
test("static fields", () => {
class A {
static simple = 1;
simple = 2;
static "with space" = 3;
static 24 = "two dozen";
static [`he${"llo"}`] = "friends";
static this_val = this;
static this_name = this.name;
static this_val2 = this.this_val;
}
expect(A.simple).toBe(1);
expect(A["with space"]).toBe(3);
expect(A[24]).toBe("two dozen");
expect(A.hello).toBe("friends");
expect(A.this_val).toBe(A);
expect(A.this_name).toBe("A");
expect(A.this_val2).toBe(A);
const a = new A();
expect(a.simple).toBe(2);
});
test("with super class", () => {
class A {
super_field = 3;
}
class B extends A {
references_super_field = super.super_field;
arrow_ref_super = () => (super.super_field = 4);
}
const b = new B();
expect(b.super_field).toBe(3);
expect(b.references_super_field).toBeUndefined();
b.arrow_ref_super();
expect(b.super_field).toBe(4);
});
test("'arguments' is not allowed in class field initializer", () => {
expect("class A { a = arguments; }").not.toEval();
expect("class B { static b = arguments; }").not.toEval();
class C {
c = eval("arguments");
}
expect(() => {
new C();
}).toThrowWithMessage(SyntaxError, "'arguments' is not allowed in class field initializer");
expect(() => {
class D {
static d = eval("arguments");
}
}).toThrowWithMessage(SyntaxError, "'arguments' is not allowed in class field initializer");
});
test("using 'arguments' via indirect eval throws at runtime instead of parse time", () => {
const indirect = eval;
class A {
a = indirect("arguments");
}
expect(() => {
new A();
}).toThrowWithMessage(ReferenceError, "'arguments' is not defined");
expect(() => {
class B {
static b = indirect("arguments");
}
}).toThrowWithMessage(ReferenceError, "'arguments' is not defined");
});
test("regex field initializer", () => {
class A {
pattern = /hello/;
flags = /world/gi;
static s_pattern = /static/m;
}
const a = new A();
expect(a.pattern).toBeInstanceOf(RegExp);
expect(a.pattern.source).toBe("hello");
expect(a.flags.flags).toBe("gi");
expect(A.s_pattern.source).toBe("static");
expect(A.s_pattern.flags).toBe("m");
});
describe("class fields with a 'special' name", () => {
test("static", () => {
class A {
static;
}
expect("static" in new A()).toBeTrue();
class B {
static;
}
expect("static" in new B()).toBeTrue();
class C {
static a;
}
expect("static" in new C()).toBeFalse();
expect("a" in new C()).toBeFalse();
expect("a" in C).toBeTrue();
expect("static" in C).toBeFalse();
});
test("async", () => {
class A {
async;
}
expect("async" in new A()).toBeTrue();
class B {
async;
}
expect("async" in new B()).toBeTrue();
class C {
async;
a;
}
expect("async" in new C()).toBeTrue();
expect("a" in new C()).toBeTrue();
});
});