fix(transformer): avoid synthetic private fields for static blocks (#27300)

## Summary

Fixes #27244.

Lowering each static block to a synthetic private static field can
introduce a field after earlier static initialization has made the class
non-extensible. Under the proposal to make non-extensibility apply to
private fields, this adds an observable TypeError.

This change folds blocks into subsequent static field initializers and
defers trailing blocks through a closure initialized safely inside the
class. Any synthetic carrier field is initialized before the deferred
blocks execute.

## Changes

- Preserve static initialization order and class-expression results,
including recursive initialization.
- Keep class declarations in their temporal dead zone until
initialization completes, while preserving the separate inner class
binding and export behavior.
- Preserve inferred function and class names, including computed,
private, and symbol keys. Capture computed names for each class
evaluation so later evaluations cannot overwrite them.
- Fix the CommonJS and ESM `setFunctionName` helpers to distinguish
`Symbol("")` from `Symbol()`, producing `"[]"` and `""` respectively.
- Introduce wrapper scopes only when the transformation needs temporary
bindings.
- Add regression fixtures and update expected outputs and conformance
reports.

---------

Co-authored-by: Dunqing <dengqing0821@gmail.com>
This commit is contained in:
Kuroda KaynandDunqing authored and GitHub committed 2026-10-08 15:29:23 +08:00
1 parent 676cc60a96
commit aec14bc7d2
69 files changed
+2151 -109

No files matched your search

@@ -197,6 +197,8 @@ pub enum Helper {
UsingCtx,
/// Runtime helper `taggedTemplateLiteral`.
TaggedTemplateLiteral,
/// Runtime helper `setFunctionName`.
SetFunctionName,
}
impl Helper {
@@ -232,6 +234,7 @@ impl Helper {
Self::DecorateMetadata => "decorateMetadata",
Self::UsingCtx => "usingCtx",
Self::TaggedTemplateLiteral => "taggedTemplateLiteral",
Self::SetFunctionName => "setFunctionName",
}
}
File diff suppressed because it is too large. Load diff
+15 -1
View File
@@ -17,7 +17,7 @@ pub struct ES2022<'a> {
options: ES2022Options,
// Plugins
class_static_block: Option<ClassStaticBlock>,
class_static_block: Option<ClassStaticBlock<'a>>,
class_properties: Option<ClassProperties<'a>>,
}
@@ -43,6 +43,12 @@ impl ES2022<'_> {
}
impl<'a> Traverse<'a, TransformState<'a>> for ES2022<'a> {
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(class_static_block) = &mut self.class_static_block {
class_static_block.enter_statement(stmt, ctx);
}
}
#[inline] // Because this is a no-op in release mode
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(class_properties) = &mut self.class_properties {
@@ -54,12 +60,16 @@ impl<'a> Traverse<'a, TransformState<'a>> for ES2022<'a> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(class_properties) = &mut self.class_properties {
class_properties.enter_expression(expr, ctx);
} else if let Some(class_static_block) = &mut self.class_static_block {
class_static_block.enter_expression(expr, ctx);
}
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(class_properties) = &mut self.class_properties {
class_properties.exit_expression(expr, ctx);
} else if let Some(class_static_block) = &mut self.class_static_block {
class_static_block.exit_expression(expr, ctx);
}
}
@@ -79,6 +89,8 @@ impl<'a> Traverse<'a, TransformState<'a>> for ES2022<'a> {
fn exit_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(class_properties) = &mut self.class_properties {
class_properties.exit_class(class, ctx);
} else if let Some(class_static_block) = &mut self.class_static_block {
class_static_block.exit_class(class, ctx);
}
}
@@ -99,6 +111,8 @@ impl<'a> Traverse<'a, TransformState<'a>> for ES2022<'a> {
) {
if let Some(class_properties) = &mut self.class_properties {
class_properties.enter_property_definition(prop, ctx);
} else if let Some(class_static_block) = &mut self.class_static_block {
class_static_block.enter_property_definition(prop, ctx);
}
}
+1
View File
@@ -633,6 +633,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a> {
}
self.x2_es2018.enter_statement(stmt, ctx);
self.x2_es2026.enter_statement(stmt, ctx);
self.x2_es2022.enter_statement(stmt, ctx);
}
fn enter_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) {
@@ -1,6 +1,6 @@
import _typeof from "./typeof.js";
function setFunctionName(e, t, n) {
"symbol" == _typeof(t) && (t = (t = t.description) ? "[" + t + "]" : "");
"symbol" == _typeof(t) && (t = void 0 === (t = t.description) ? "" : "[" + t + "]");
try {
Object.defineProperty(e, "name", {
configurable: !0,
+1 -1
View File
@@ -1,6 +1,6 @@
var _typeof = require("./typeof.js")["default"];
function setFunctionName(e, t, n) {
"symbol" == _typeof(t) && (t = (t = t.description) ? "[" + t + "]" : "");
"symbol" == _typeof(t) && (t = void 0 === (t = t.description) ? "" : "[" + t + "]");
try {
Object.defineProperty(e, "name", {
configurable: !0,
@@ -0,0 +1,5 @@
var _staticBlock;
let Foo = (class Foo {
static bar = ((_value) => (_staticBlock = () => (this.foo = Foo.bar, this), _value))(42);
}, _staticBlock());
expect(Foo.foo).toBe(42);
@@ -0,0 +1,5 @@
var _staticBlock;
let Foo = (class Foo {
static bar = ((_value) => (_staticBlock = () => (this.foo = this.bar, this), _value))(42);
}, _staticBlock());
expect(Foo.foo).toBe(42);
@@ -0,0 +1,15 @@
var _staticBlock, _staticBlock2;
let Foo = ({ "Foo": class {
static #_ = _staticBlock = () => ((() => {
expect(this.name).toBe("Foo");
if (false) use(this);
})(), this);
} }["Foo"], _staticBlock());
let obj = { ["someName"]: ({ "someName": class {
static x = ((_value) => (_staticBlock2 = () => ((() => {
if (false) use(this);
expect(this.name).toBe("someName");
})(), this), _value))(expect(this.name).toBe("someName"));
} }["someName"], _staticBlock2()) };
expect(Foo.name).toBe("Foo");
expect(obj.someName.name).toBe("someName");
@@ -0,0 +1,9 @@
var _staticBlock, _staticBlock2, _staticBlock3;
let Foo = (class Foo extends (class extends (class Base {
static #_ = _staticBlock = () => (this.qux = 21, this);
}, _staticBlock()) {
static #_ = _staticBlock2 = () => (this.bar = 21, this);
}, _staticBlock2()) {
static #_ = _staticBlock3 = () => (this.foo = this.bar + this.qux, this);
}, _staticBlock3());
expect(Foo.foo).toBe(42);
@@ -0,0 +1,10 @@
var _staticBlock;
let Foo = (class Foo {
static #bar = 21;
static qux = ((_value) => (_staticBlock = () => (this.qux2 = this.qux, this), _value))(((() => {
this.foo = this.#bar;
this.qux1 = this.qux;
})(), (() => {
if (foo) bar;
})(), 21));
}, _staticBlock());
@@ -0,0 +1,6 @@
var _staticBlock;
let Foo = (class Foo {
static #_() {}
static #_2 = _staticBlock = () => (this.foo = this.#_, this);
}, _staticBlock());
expect(Foo.foo).toBe(42);
@@ -0,0 +1,7 @@
var _staticBlock;
let C = (class C {
static #_ = _staticBlock = () => ((() => {})(), this.foo = 42, (() => {
this.bar = 42;
this.bar = 42;
})(), this);
}, _staticBlock());
@@ -0,0 +1,11 @@
var _staticBlock;
let res = [];
let foo = 1;
let A = (class A {
static x = ((_value) => (_staticBlock = () => ((() => {
var foo = 4;
})(), res.push(foo), this), _value))(((() => {
var foo = 3;
})(), res.push(foo), void 0));
}, _staticBlock());
expect(res).toEqual([1, 1]);
@@ -1,6 +1,6 @@
commit: 4f4ef5d4
Passed: 731/1166
Passed: 741/1166
# All Passed:
* babel-plugin-transform-logical-assignment-operators
@@ -369,37 +369,7 @@ x Output mismatch
x Output mismatch
# babel-plugin-transform-class-static-block (11/24)
* class-static-block/before-static-fields/input.js
x Output mismatch
* class-static-block/class-binding/input.js
x Output mismatch
* class-static-block/class-declaration/input.js
x Output mismatch
* class-static-block/class-inferred-name/input.js
x Output mismatch
* class-static-block/in-class-heritage/input.js
x Output mismatch
* class-static-block/multiple-static-initializers/input.js
x Output mismatch
* class-static-block/name-conflict/input.js
x Output mismatch
* class-static-block/new-target/input.js
x Output mismatch
* class-static-block/preserve-comments/input.js
x Output mismatch
* class-static-block/var-scope/input.js
x Output mismatch
# babel-plugin-transform-class-static-block (21/24)
* integration/in-class-heritage/input.js
x Output mismatch
@@ -1,6 +1,6 @@
commit: 4f4ef5d4
Passed: 282/410
Passed: 298/426
# All Passed:
* babel-plugin-transform-class-static-block
@@ -2,7 +2,7 @@ commit: 4f4ef5d4
node: v26.5.0
Passed: 23 of 25 (92.00%)
Passed: 34 of 36 (94.44%)
Failures:
@@ -1,9 +1,5 @@
var _staticBlock;
let a, b, d, e;
class C {
static #_ = a = this;
static #_2 = [b, c] = this;
static #_3 = d ??= this;
static #_4 = e.f = this;
static #_5 = [g.h, i] = this;
}
let C = (class C {
static #_ = _staticBlock = () => (a = this, [b, c] = this, d ??= this, e.f = this, [g.h, i] = this, this);
}, _staticBlock());
@@ -1,4 +1,4 @@
class C {
static #_ = C;
static #_2 = x;
}
var _staticBlock;
let C = (class C {
static #_ = _staticBlock = () => (C, x, this);
}, _staticBlock());
@@ -1,4 +1,4 @@
class C {
static #_ = (() => {})();
static #_2 = (() => {})();
}
var _staticBlock;
let C = (class C {
static #_ = _staticBlock = () => ((() => {})(), (() => {})(), this);
}, _staticBlock());
@@ -1,11 +1,10 @@
var _staticBlock;
let x, y;
class C {
static #_ = x = (() => this)();
static #_2 = (() => {
let C = (class C {
static #_ = _staticBlock = () => (x = (() => this)(), (() => {
if (true) {
y = this;
z = this;
}
})();
}
})(), this);
}, _staticBlock());
@@ -0,0 +1,105 @@
const classes = [];
for (const key of ['a', 'b']) {
classes.push(class { [key] = class { static { this.seen = this.name; } }; });
}
expect(new classes[0]().a.seen).toBe("a");
expect(new classes[1]().b.seen).toBe("b");
let nextKey = "first";
class Factory {
Outer = class { [nextKey] = class { static { this.seen = this.name; } }; };
}
const First = new Factory().Outer;
nextKey = "second";
const Second = new Factory().Outer;
expect(new First().first.seen).toBe("first");
expect(new Second().second.seen).toBe("second");
function parameter(Outer = class { [nextKey] = class { static { this.seen = this.name; } }; }) {
return Outer;
}
nextKey = "third";
const Third = parameter();
nextKey = "fourth";
const Fourth = parameter();
expect(new Third().third.seen).toBe("third");
expect(new Fourth().fourth.seen).toBe("fourth");
const repeated = [];
while (repeated.length < 2 && repeated.push(class {
[String(repeated.length)] = class { static { this.seen = this.name; } };
})) {}
expect(new repeated[0]()["0"].seen).toBe("0");
expect(new repeated[1]()["1"].seen).toBe("1");
function* suspended() {
const result = [];
for (const key of ["a", "b"]) {
result.push(class { [yield key] = class { static { this.seen = this.name; } }; });
}
return result;
}
const generator = suspended();
expect(generator.next().value).toBe("a");
expect(generator.next("a").value).toBe("b");
const suspendedClasses = generator.next("b").value;
expect(new suspendedClasses[0]().a.seen).toBe("a");
expect(new suspendedClasses[1]().b.seen).toBe("b");
function* suspendedHeader() {
const result = [];
while (result.length < 2 && result.push(class {
[yield result.length] = class { static { this.seen = this.name; } };
static { Object.freeze(this); }
})) {}
return result;
}
const headerGenerator = suspendedHeader();
expect(headerGenerator.next().value).toBe(0);
expect(headerGenerator.next("a").value).toBe(1);
const headerClasses = headerGenerator.next("b").value;
expect(new headerClasses[0]().a.seen).toBe("a");
expect(new headerClasses[1]().b.seen).toBe("b");
expect(headerClasses[0].name).toBe("");
expect(Object.isFrozen(headerClasses[0])).toBe(true);
function* suspendedName() {
const Named = class {
[yield "key"] = class { static {} };
static seen = this.name;
};
const Method = class {
[yield "key"] = class { static {} };
static ["na" + "me"]() {}
static seen = this.name;
};
const Getter = class {
[yield "key"] = class { static {} };
static get ["na" + "me"]() { return "getter"; }
static seen = this.name;
};
class Declaration {
[yield "key"] = class { static {} };
}
const Original = Declaration;
Declaration = null;
return [Named, Method, Getter, Original];
}
const namesGenerator = suspendedName();
namesGenerator.next();
namesGenerator.next("a");
namesGenerator.next("a");
namesGenerator.next("a");
const [Named, Method, Getter, Original] = namesGenerator.next("a").value;
expect(Named.seen).toBe("Named");
expect(Method.seen).toBe(Method.name);
expect(typeof Method.seen).toBe("function");
expect(Getter.seen).toBe("getter");
expect(new Original().a.name).toBe("a");
function* symbolNamedClass() {
const key = Symbol("");
return { [key]: class {
[yield "field"] = class { static {} };
} }[key];
}
const symbolGenerator = symbolNamedClass();
expect(symbolGenerator.next().value).toBe("field");
const SymbolNamed = symbolGenerator.next("field").value;
expect(SymbolNamed.name).toBe("[]");
expect(new SymbolNamed().field.name).toBe("field");
@@ -0,0 +1,14 @@
const classes = [];
for (const key of ['a', 'b']) {
classes.push(class { [key] = class { static { this.seen = this.name; } }; });
}
console.log(new classes[0]().a.seen, new classes[1]().b.seen);
function* suspended() {
const classes = [];
while (classes.length < 2 && classes.push(class {
[yield classes.length] = class { static { this.seen = this.name; } };
static { Object.freeze(this); }
})) {}
return classes;
}
@@ -0,0 +1,30 @@
const classes = [];
for (const key of ["a", "b"]) {
classes.push((() => {
let _key;
return class {
[_key = babelHelpers.toPropertyKey(key)] = (() => {
var _staticBlock;
return ((_name, _object) => _object[_name])(_key, { [_key]: class {
static #_ = _staticBlock = () => (this.seen = this.name, this);
} }), _staticBlock();
})();
};
})());
}
console.log(new classes[0]().a.seen, new classes[1]().b.seen);
function* suspended() {
var _staticBlock2;
let _key2;
const classes = [];
while (classes.length < 2 && classes.push((class _Class {
static #_ = ((_value) => (_staticBlock2 = () => (Object.freeze(this), this), _value))((babelHelpers.setFunctionName(this, ""), _key2));
[_key2 = babelHelpers.toPropertyKey(yield classes.length)] = (() => {
var _staticBlock3;
return { [_Class.#_]: class {
static #_ = _staticBlock3 = () => (this.seen = this.name, this);
} }[_Class.#_], _staticBlock3();
})();
}, _staticBlock2()))) {}
return classes;
}
@@ -0,0 +1,3 @@
export default class {
static { expect(this.name).toBe("default"); }
}
@@ -0,0 +1,4 @@
var _staticBlock;
export default ({ "default": class {
static #_ = _staticBlock = () => (expect(this.name).toBe("default"), this);
} }["default"], _staticBlock());
@@ -0,0 +1,5 @@
const read = () => A;
export default class A {
static { expect(() => read()).toThrow(ReferenceError); expect(A).toBe(this); }
}
expect(read()).toBe(A);
@@ -0,0 +1,10 @@
var _staticBlock;
const read = () => A;
let A = (class A {
static #_ = _staticBlock = () => ((() => {
expect(() => read()).toThrow(ReferenceError);
expect(A).toBe(this);
})(), this);
}, _staticBlock());
export { A as default };
expect(read()).toBe(A);
@@ -0,0 +1,5 @@
const read = () => A;
export class A {
static { expect(() => read()).toThrow(ReferenceError); expect(A).toBe(this); }
}
expect(read()).toBe(A);
@@ -0,0 +1,9 @@
var _staticBlock;
const read = () => A;
export let A = (class A {
static #_ = _staticBlock = () => ((() => {
expect(() => read()).toThrow(ReferenceError);
expect(A).toBe(this);
})(), this);
}, _staticBlock());
expect(read()).toBe(A);
@@ -0,0 +1,31 @@
const read = () => A;
let observed;
class A {
static {
expect(A).toBe(this);
try { read(); observed = "initialized"; } catch (error) { observed = error.name; }
}
}
expect(observed).toBe("ReferenceError");
expect(read()).toBe(A);
const Original = A;
A = 42;
expect(A).toBe(42);
expect(typeof Original).toBe("function");
let Saved;
let readThrowing;
try {
readThrowing = () => Throwing;
class Throwing {
static { Saved = this; throw new Error("stop"); }
}
} catch {}
expect(typeof Saved).toBe("function");
expect(readThrowing).toThrow(ReferenceError);
class Self {
static getSelf() { return Self; }
static { expect(() => { Self = null; }).toThrow(TypeError); }
}
const OriginalSelf = Self;
Self = null;
expect(OriginalSelf.getSelf()).toBe(OriginalSelf);
@@ -0,0 +1,4 @@
const read = () => A;
class A {
static { try { read(); console.log('initialized'); } catch (e) { console.log(e.name); } }
}
@@ -0,0 +1,12 @@
var _staticBlock;
const read = () => A;
let A = (class A {
static #_ = _staticBlock = () => ((() => {
try {
read();
console.log("initialized");
} catch (e) {
console.log(e.name);
}
})(), this);
}, _staticBlock());
@@ -0,0 +1,104 @@
let index = 0;
let recursive;
function objectPattern({ value = {
[index++]: class {
static { if (index < 2) recursive = objectPattern({}); }
static [0] = () => {};
}
} } = {}) { return value; }
const result = objectPattern({});
expect(index).toBe(2);
expect(result[0].name).toBe("0");
expect(recursive[1].name).toBe("1");
expect(result[0][0].name).toBe("0");
let arrayIndex = 0;
let arrayRecursive;
function arrayPattern([value = {
[arrayIndex++]: class {
static { if (arrayIndex < 2) arrayRecursive = arrayPattern([]); }
static [0] = () => {};
}
}] = []) { return value; }
const arrayResult = arrayPattern([]);
expect(arrayIndex).toBe(2);
expect(arrayResult[0].name).toBe("0");
expect(arrayRecursive[1].name).toBe("1");
expect(arrayResult[0][0].name).toBe("0");
let nestedIndex = 0;
let nestedRecursive;
function nestedPattern({ outer: { value = {
[nestedIndex++]: class {
static { if (nestedIndex < 2) nestedRecursive = nestedPattern({ outer: {} }); }
static [0] = () => {};
}
} } } = { outer: {} }) { return value; }
const nestedResult = nestedPattern({ outer: {} });
expect(nestedIndex).toBe(2);
expect(nestedResult[0].name).toBe("0");
expect(nestedRecursive[1].name).toBe("1");
expect(nestedResult[0][0].name).toBe("0");
let restIndex = 0;
let restRecursive;
function restPattern(...[{ value = {
[restIndex++]: class {
static { if (restIndex < 2) restRecursive = restPattern({}); }
static [0] = () => {};
}
} }]) { return value; }
const restResult = restPattern({});
expect(restIndex).toBe(2);
expect(restResult[0].name).toBe("0");
expect(restRecursive[1].name).toBe("1");
expect(restResult[0][0].name).toBe("0");
let keyIndex = 0;
let keyRecursive;
function computedPattern({ [Object.values({
[keyIndex++]: class {
static { if (keyIndex < 2) keyRecursive = computedPattern({ 1: "inner" }); }
static [0] = () => {};
}
})[0].name]: value }) { return value; }
const keyResult = computedPattern({ 0: "outer" });
expect(keyIndex).toBe(2);
expect(keyResult).toBe("outer");
expect(keyRecursive).toBe("inner");
function parenthesized(C = (((class {
static value = 0;
static { this.seen = this.name; }
})))) { return C; }
const Parenthesized = parenthesized();
expect([Parenthesized.name, Parenthesized.seen]).toEqual(["C", "C"]);
function nestedClassDefault(Outer = ((class {
static Inner = class {
static value = 0;
static { this.seen = this.name; }
};
}))) { return Outer; }
const NestedOuter = nestedClassDefault();
expect(NestedOuter.name).toBe("Outer");
expect([NestedOuter.Inner.name, NestedOuter.Inner.seen]).toEqual(["Inner", "Inner"]);
function noTemporaryDefault(C = (class {
static {}
static value = 1;
})) { return C; }
function bodyDefault(callback = function inner(C = (class {
static {}
static value = 2;
})) { return C; }) { return callback(); }
expect([noTemporaryDefault().name, noTemporaryDefault().value]).toEqual(["C", 1]);
expect([bodyDefault().name, bodyDefault().value]).toEqual(["C", 2]);
let SwitchClass;
switch (0) {
case ({ [0]: class { static {} } }[0], 0):
SwitchClass = { [0]: class { static {} } }[0];
break;
}
expect(SwitchClass.name).toBe("0");
@@ -0,0 +1,79 @@
let index = 0;
let recursive;
function objectPattern({ value = {
[index++]: class {
static { if (index < 2) recursive = objectPattern({}); }
static [0] = () => {};
}
} } = {}) { return value; }
const result = objectPattern({});
let arrayIndex = 0;
let arrayRecursive;
function arrayPattern([value = {
[arrayIndex++]: class {
static { if (arrayIndex < 2) arrayRecursive = arrayPattern([]); }
static [0] = () => {};
}
}] = []) { return value; }
const arrayResult = arrayPattern([]);
let nestedIndex = 0;
let nestedRecursive;
function nestedPattern({ outer: { value = {
[nestedIndex++]: class {
static { if (nestedIndex < 2) nestedRecursive = nestedPattern({ outer: {} }); }
static [0] = () => {};
}
} } } = { outer: {} }) { return value; }
const nestedResult = nestedPattern({ outer: {} });
let restIndex = 0;
let restRecursive;
function restPattern(...[{ value = {
[restIndex++]: class {
static { if (restIndex < 2) restRecursive = restPattern({}); }
static [0] = () => {};
}
} }]) { return value; }
const restResult = restPattern({});
let keyIndex = 0;
let keyRecursive;
function computedPattern({ [Object.values({
[keyIndex++]: class {
static { if (keyIndex < 2) keyRecursive = computedPattern({ 1: "inner" }); }
static [0] = () => {};
}
})[0].name]: value }) { return value; }
const keyResult = computedPattern({ 0: "outer" });
function parenthesized(C = (((class {
static value = 0;
static { this.seen = this.name; }
})))) { return C; }
const Parenthesized = parenthesized();
function nestedClassDefault(Outer = ((class {
static Inner = class {
static value = 0;
static { this.seen = this.name; }
};
}))) { return Outer; }
const NestedOuter = nestedClassDefault();
function noTemporaryDefault(C = (class {
static {}
static value = 1;
})) { return C; }
function bodyDefault(callback = function inner(C = (class {
static {}
static value = 2;
})) { return C; }) { return callback(); }
let SwitchClass;
switch (0) {
case ({ [0]: class { static {} } }[0], 0):
SwitchClass = { [0]: class { static {} } }[0];
break;
}
@@ -0,0 +1,4 @@
{
"plugins": [],
"presets": [["env", {"targets": {"node": "16"}}]]
}
@@ -0,0 +1,111 @@
var _staticBlock3;
let _key, _key3, _key5, _key7, _key9, _key11;
let index = 0;
let recursive;
function objectPattern({ value = { [_key = babelHelpers.toPropertyKey(index++)]: (() => {
let _key2;
return ((_name, _object) => _object[_name])(_key, { [_key]: class {
static [_key2 = babelHelpers.toPropertyKey(0)] = ((() => {
if (index < 2) recursive = objectPattern({});
})(), babelHelpers.setFunctionName(() => {}, _key2));
} });
})() } } = {}) {
return value;
}
const result = objectPattern({});
let arrayIndex = 0;
let arrayRecursive;
function arrayPattern([value = { [_key3 = babelHelpers.toPropertyKey(arrayIndex++)]: (() => {
let _key4;
return ((_name2, _object2) => _object2[_name2])(_key3, { [_key3]: class {
static [_key4 = babelHelpers.toPropertyKey(0)] = ((() => {
if (arrayIndex < 2) arrayRecursive = arrayPattern([]);
})(), babelHelpers.setFunctionName(() => {}, _key4));
} });
})() }] = []) {
return value;
}
const arrayResult = arrayPattern([]);
let nestedIndex = 0;
let nestedRecursive;
function nestedPattern({ outer: { value = { [_key5 = babelHelpers.toPropertyKey(nestedIndex++)]: (() => {
let _key6;
return ((_name3, _object3) => _object3[_name3])(_key5, { [_key5]: class {
static [_key6 = babelHelpers.toPropertyKey(0)] = ((() => {
if (nestedIndex < 2) nestedRecursive = nestedPattern({ outer: {} });
})(), babelHelpers.setFunctionName(() => {}, _key6));
} });
})() } } } = { outer: {} }) {
return value;
}
const nestedResult = nestedPattern({ outer: {} });
let restIndex = 0;
let restRecursive;
function restPattern(...[{ value = { [_key7 = babelHelpers.toPropertyKey(restIndex++)]: (() => {
let _key8;
return ((_name4, _object4) => _object4[_name4])(_key7, { [_key7]: class {
static [_key8 = babelHelpers.toPropertyKey(0)] = ((() => {
if (restIndex < 2) restRecursive = restPattern({});
})(), babelHelpers.setFunctionName(() => {}, _key8));
} });
})() } }]) {
return value;
}
const restResult = restPattern({});
let keyIndex = 0;
let keyRecursive;
function computedPattern({ [Object.values({ [_key9 = babelHelpers.toPropertyKey(keyIndex++)]: (() => {
let _key10;
return ((_name5, _object5) => _object5[_name5])(_key9, { [_key9]: class {
static [_key10 = babelHelpers.toPropertyKey(0)] = ((() => {
if (keyIndex < 2) keyRecursive = computedPattern({ 1: "inner" });
})(), babelHelpers.setFunctionName(() => {}, _key10));
} });
})() })[0].name]: value }) {
return value;
}
const keyResult = computedPattern({ 0: "outer" });
function parenthesized(C = (() => {
var _staticBlock;
return { "C": class {
static value = ((_value) => (_staticBlock = () => (this.seen = this.name, this), _value))(0);
} }["C"], _staticBlock();
})()) {
return C;
}
const Parenthesized = parenthesized();
function nestedClassDefault(Outer = (() => {
var _staticBlock2;
return { "Outer": class {
static Inner = ({ "Inner": class {
static value = ((_value2) => (_staticBlock2 = () => (this.seen = this.name, this), _value2))(0);
} }["Inner"], _staticBlock2());
} }["Outer"];
})()) {
return Outer;
}
const NestedOuter = nestedClassDefault();
function noTemporaryDefault(C = class {
static value = ((() => {})(), 1);
}) {
return C;
}
function bodyDefault(callback = function inner(C = class {
static value = ((() => {})(), 2);
}) {
return C;
}) {
return callback();
}
let SwitchClass;
switch (0) {
case { [_key11 = babelHelpers.toPropertyKey(0)]: (((_name6, _object6) => _object6[_name6])(_key11, { [_key11]: class {
static #_ = _staticBlock3 = () => ((() => {})(), this);
} }), _staticBlock3()) }[0], 0:
var _staticBlock4;
let _key12;
SwitchClass = { [_key12 = babelHelpers.toPropertyKey(0)]: (((_name7, _object7) => _object7[_name7])(_key12, { [_key12]: class {
static #_ = _staticBlock4 = () => ((() => {})(), this);
} }), _staticBlock4()) }[0];
break;
}
@@ -0,0 +1,22 @@
const calls = [];
class Before {
static { calls.push("before"); }
declare static omitted: unknown;
}
class After {
declare static omitted: unknown;
static { calls.push("after"); }
}
class Mixed {
static { calls.push("first"); }
declare static omitted: unknown;
static present = (calls.push("field"), 1);
declare static trailing: unknown;
static { calls.push("last"); }
}
expect(calls).toEqual(["before", "after", "first", "field", "last"]);
expect(Object.hasOwn(Before, "omitted")).toBe(false);
expect(Object.hasOwn(After, "omitted")).toBe(false);
expect(Object.hasOwn(Mixed, "omitted")).toBe(false);
expect(Object.hasOwn(Mixed, "trailing")).toBe(false);
expect(Mixed.present).toBe(1);
@@ -0,0 +1,16 @@
const calls = [];
class Before {
static { calls.push("before"); }
declare static omitted: unknown;
}
class After {
declare static omitted: unknown;
static { calls.push("after"); }
}
class Mixed {
static { calls.push("first"); }
declare static omitted: unknown;
static present = (calls.push("field"), 1);
declare static trailing: unknown;
static { calls.push("last"); }
}
@@ -0,0 +1,4 @@
{
"plugins": ["transform-typescript"],
"presets": [["env", {"targets": {"node": "16"}}]]
}
@@ -0,0 +1,11 @@
var _staticBlock, _staticBlock2, _staticBlock3;
const calls = [];
let Before = (class Before {
static #_ = _staticBlock = () => (calls.push("before"), this);
}, _staticBlock());
let After = (class After {
static #_ = _staticBlock2 = () => (calls.push("after"), this);
}, _staticBlock2());
let Mixed = (class Mixed {
static present = ((_value) => (_staticBlock3 = () => (calls.push("last"), this), _value))((calls.push("first"), calls.push("field"), 1));
}, _staticBlock3());
@@ -0,0 +1,15 @@
const key = Symbol("");
class A {
Computed = class { static [key] = function () {}; static {} static x = 4; };
Child = class { static {} static x = 1; };
["computed"] = class { static {} static x = 2; };
}
function create(Child = class { static {} static x = 3; }) {
return Child;
}
const a = new A();
expect([a.Child.name, a.Child.x]).toEqual(["Child", 1]);
expect([a.computed.name, a.computed.x]).toEqual(["computed", 2]);
const child = create();
expect([child.name, child.x]).toEqual(["Child", 3]);
expect([a.Computed.name, a.Computed[key].name, a.Computed.x]).toEqual(["Computed", "[]", 4]);
@@ -0,0 +1,9 @@
const key = Symbol("");
class A {
Computed = class { static [key] = function () {}; static {} static x = 4; };
Child = class { static {} static x = 1; };
["computed"] = class { static {} static x = 2; };
}
function create(Child = class { static {} static x = 3; }) {
return Child;
}
@@ -0,0 +1,18 @@
const key = Symbol("");
class A {
Computed = class {
static [key] = function () {};
static x = ((() => {})(), 4);
};
Child = class {
static x = ((() => {})(), 1);
};
["computed"] = class {
static x = ((() => {})(), 2);
};
}
function create(Child = class {
static x = ((() => {})(), 3);
}) {
return Child;
}
@@ -0,0 +1,16 @@
const events = [];
class A {
static x = 42;
static #value = 7;
static { events.push(this.x, this.#value); Object.preventExtensions(this); }
static { events.push(this.x, this.#value); }
}
expect(A.x).toBe(42);
expect(events).toEqual([42, 7, 42, 7]);
expect(Object.isExtensible(A)).toBe(false);
const BlockOnly = class {
static { Object.preventExtensions(this); }
static { events.push("done"); }
};
expect(Object.isExtensible(BlockOnly)).toBe(false);
expect(events[4]).toBe("done");
@@ -0,0 +1,6 @@
class A {
static x = Object.preventExtensions(this);
static {
this.y = 1;
}
}
@@ -0,0 +1,4 @@
var _staticBlock;
let A = (class A {
static x = ((_value) => (_staticBlock = () => (this.y = 1, this), _value))(Object.preventExtensions(this));
}, _staticBlock());
@@ -0,0 +1,7 @@
function preserveEval(
{ value = (eval("var exposed = 7"), class { static {} }) } = {},
other = exposed,
) {
return other;
}
const result = preserveEval({});
@@ -0,0 +1,4 @@
{
"sourceType": "script",
"presets": [["env", {"targets": {"node": "16"}}]]
}
@@ -0,0 +1,7 @@
var _staticBlock;
function preserveEval({ value = (eval("var exposed = 7"), class {
static #_ = _staticBlock = () => ((() => {})(), this);
}, _staticBlock()) } = {}, other = exposed) {
return other;
}
const result = preserveEval({});
@@ -0,0 +1,31 @@
class A {
static {}
static C = class { static seen = this.name; };
static {}
static Custom = class { static name = "custom"; };
static {}
static Frozen = class { static { Object.freeze(this); } };
static {}
static #Private = class { static seen = this.name; };
static {}
static getPrivate() { return this.#Private; }
static Method = class { static name() {} static seen = this.name; };
static {}
static Getter = class { static get name() { return "getter"; } static seen = this.name; };
static {}
}
expect([A.C.name, A.C.seen]).toEqual(["C", "C"]);
expect(A.Custom.name).toBe("custom");
expect(A.Frozen.name).toBe("Frozen");
expect(Object.isFrozen(A.Frozen)).toBe(true);
expect([A.getPrivate().name, A.getPrivate().seen]).toEqual(["#Private", "#Private"]);
expect(A.Method.seen).toBe(A.Method.name);
expect(typeof A.Method.name).toBe("function");
expect([A.Getter.name, A.Getter.seen]).toEqual(["getter", "getter"]);
const MethodName = class { static name() {} static { this.seen = this.name; } };
expect(MethodName.seen).toBe(MethodName.name);
expect(typeof MethodName.name).toBe("function");
const methodKey = "name";
const ComputedMethodName = class { static [methodKey]() {} static { this.seen = this.name; } };
expect(ComputedMethodName.seen).toBe(ComputedMethodName.name);
expect(typeof ComputedMethodName.name).toBe("function");
@@ -0,0 +1,6 @@
class A {
static {}
static C = class { static seen = this.name; };
static {}
static Custom = class { static name = "custom"; };
}
@@ -0,0 +1,8 @@
class A {
static C = ((() => {})(), { "C": class {
static seen = this.name;
} }["C"]);
static Custom = ((() => {})(), { "Custom": class {
static name = "custom";
} }["Custom"]);
}
@@ -0,0 +1,45 @@
const A = class { static { this.seen = this.name; } };
expect([A.name, A.seen]).toEqual(["A", "A"]);
let assigned;
assigned = class { static { this.seen = this.name; } };
expect([assigned.name, assigned.seen]).toEqual(["assigned", "assigned"]);
let logical;
logical ||= class { static { this.seen = this.name; } };
expect([logical.name, logical.seen]).toEqual(["logical", "logical"]);
function defaults(Param = class { static { this.seen = this.name; } }) {
return [Param.name, Param.seen];
}
expect(defaults()).toEqual(["Param", "Param"]);
const { value: Destructured = class { static { this.seen = this.name; } } } = {};
expect([Destructured.name, Destructured.seen]).toEqual(["Destructured", "Destructured"]);
let target;
({ target = class { static { this.seen = this.name; } } } = {});
expect([target.name, target.seen]).toEqual(["target", "target"]);
let other;
[other = class { static { this.seen = this.name; } }] = [];
expect([other.name, other.seen]).toEqual(["other", "other"]);
const named = class Explicit { static { this.seen = this.name; } };
expect([named.name, named.seen]).toEqual(["Explicit", "Explicit"]);
const anonymous = (0, class { static { this.seen = this.name; } });
expect([anonymous.name, anonymous.seen]).toEqual(["", ""]);
const object = {
entry: class { static { this.seen = this.name; } },
[Symbol.for("key")]: class { static { this.seen = this.name; } },
["__proto__"]: class { static { this.seen = this.name; } },
};
expect([object.entry.name, object.entry.seen]).toEqual(["entry", "entry"]);
expect([object[Symbol.for("key")].name, object[Symbol.for("key")].seen]).toEqual(["[key]", "[key]"]);
expect(object.__proto__.seen).toBe("__proto__");
const proto = { __proto__: class { static { this.seen = this.name; } } };
expect(Object.getPrototypeOf(proto).seen).toBe("");
class Fields {
Child = class { static { this.seen = this.name; } };
static Child = class { static { this.seen = this.name; } };
}
expect(new Fields().Child.seen).toBe("Child");
expect(Fields.Child.seen).toBe("Child");
const receiver = {};
receiver.member = class { static { this.seen = this.name; } };
expect([receiver.member.name, receiver.member.seen]).toEqual(["", ""]);
[receiver.defaulted = class { static { this.seen = this.name; } }] = [];
expect([receiver.defaulted.name, receiver.defaulted.seen]).toEqual(["", ""]);
@@ -0,0 +1,2 @@
const A = class { static { this.seen = this.name; } };
const object = { [Symbol.for("key")]: class { static { this.seen = this.name; } } };
@@ -0,0 +1,8 @@
var _staticBlock, _staticBlock2;
let _key;
const A = ({ "A": class {
static #_ = _staticBlock = () => (this.seen = this.name, this);
} }["A"], _staticBlock());
const object = { [_key = babelHelpers.toPropertyKey(Symbol.for("key"))]: (((_name, _object) => _object[_name])(_key, { [_key]: class {
static #_ = _staticBlock2 = () => (this.seen = this.name, this);
} }), _staticBlock2()) };
@@ -0,0 +1,50 @@
let conversions = 0;
let evaluations = 0;
const key = { [Symbol.toPrimitive]() { conversions++; return "method"; } };
function getKey() { evaluations++; return key; }
let fieldName = "before";
const symbol = Symbol("symbol");
const unnamed = Symbol();
class A {
static { fieldName = "after"; }
static [fieldName] = function () {};
static {}
static [getKey()] = () => {};
static {}
static [symbol] = function () {};
static {}
static [unnamed] = () => {};
static {}
static #privateFunction = function () {};
static {}
static #privateArrow = () => {};
static {}
static 123 = function () {};
static {}
static getNames() { return [this.#privateFunction.name, this.#privateArrow.name]; }
}
expect(A.before.name).toBe("before");
expect(A.method.name).toBe("method");
expect([evaluations, conversions]).toEqual([1, 1]);
expect(A[symbol].name).toBe("[symbol]");
expect(A[unnamed].name).toBe("");
expect(A.getNames()).toEqual(["#privateFunction", "#privateArrow"]);
expect(A[123].name).toBe("123");
let objectConversions = 0;
const objectKey = { toString() { objectConversions++; return "ObjectClass"; } };
const object = { [objectKey]: class { static { this.seen = this.name; } } };
expect(object.ObjectClass.seen).toBe("ObjectClass");
expect(objectConversions).toBe(1);
for (const [key, name] of [[Symbol(), ""], [Symbol(""), "[]"], [Symbol("symbol"), "[symbol]"]]) {
class FunctionField {
static {}
static [key] = function () {};
}
class ArrowField {
static [key] = () => {};
static {}
}
expect(FunctionField[key].name).toBe(name);
expect(ArrowField[key].name).toBe(name);
}
@@ -0,0 +1,8 @@
const key = Symbol("method");
class A {
static {}
static [key] = function () {};
static {}
static #f = () => {};
static getName() { return this.#f.name; }
}
@@ -0,0 +1,9 @@
let _key;
const key = Symbol("method");
class A {
static [_key = babelHelpers.toPropertyKey(key)] = ((() => {})(), babelHelpers.setFunctionName(function() {}, _key));
static #f = ((() => {})(), babelHelpers.setFunctionName(() => {}, "#f"));
static getName() {
return this.#f.name;
}
}
@@ -0,0 +1,5 @@
class A {
static {
}
static f = function () {};
}
@@ -0,0 +1,3 @@
class A {
static f = ((() => {})(), babelHelpers.setFunctionName(function () {}, "f"));
}
@@ -0,0 +1,24 @@
let depth = 0;
const seen = [];
class Outer {
Child = class {
static id = depth++;
static child = depth < 2 ? new Outer().Child : null;
static { seen.push(this.id); }
};
}
const result = new Outer();
expect(seen).toEqual([1, 0]);
expect(result.Child.id).toBe(0);
expect(result.Child.child.id).toBe(1);
let calls = 0;
const initialized = [];
function make(C = class {
static id = calls++;
static child = calls < 2 ? make() : null;
static { initialized.push(this.id); }
}) { return C; }
const C = make();
expect(initialized).toEqual([1, 0]);
expect(C.id).toBe(0);
expect(C.child.id).toBe(1);
@@ -0,0 +1,11 @@
let depth = 0;
const seen = [];
class Outer {
Child = class {
static id = depth++;
static child = depth < 2 ? new Outer().Child : null;
static { seen.push(this.id); }
};
}
const result = new Outer();
console.log(JSON.stringify(seen), result.Child.id);
@@ -0,0 +1,13 @@
let depth = 0;
const seen = [];
class Outer {
Child = (() => {
var _staticBlock;
return { "Child": class {
static id = depth++;
static child = ((_value) => (_staticBlock = () => (seen.push(this.id), this), _value))(depth < 2 ? new Outer().Child : null);
} }["Child"], _staticBlock();
})();
}
const result = new Outer();
console.log(JSON.stringify(seen), result.Child.id);
@@ -0,0 +1,65 @@
class Names {
static {}
static asFunction = (function () {} as any);
static {}
static assertedArrow = (<any>(() => {}));
static {}
static satisfiesFunction = (function () {} satisfies Function);
static {}
static nonNullClass = class { static seen = this.name; }!;
static {}
static asClass = (class { static seen = this.name; } as any);
static {}
static satisfiesClass = (class { static seen = this.name; } satisfies { new(): unknown; seen: string });
static {}
static namedFunction = (function Explicit() {} as any);
static {}
static nestedClass = (((class { static seen = this.name; }) satisfies { new(): unknown; seen: string }) as any);
static {}
}
expect(Names.asFunction.name).toBe("asFunction");
expect(Names.assertedArrow.name).toBe("assertedArrow");
expect(Names.satisfiesFunction.name).toBe("satisfiesFunction");
expect([Names.nonNullClass.name, Names.nonNullClass.seen]).toEqual(["nonNullClass", "nonNullClass"]);
expect([Names.asClass.name, Names.asClass.seen]).toEqual(["asClass", "asClass"]);
expect([Names.satisfiesClass.name, Names.satisfiesClass.seen]).toEqual(["satisfiesClass", "satisfiesClass"]);
expect(Names.namedFunction.name).toBe("Explicit");
expect([Names.nestedClass.name, Names.nestedClass.seen]).toEqual(["nestedClass", "nestedClass"]);
function defaultClass(C = (class {
static value = 0;
static { this.seen = this.name; }
} as any)) { return C; }
const Default = defaultClass();
expect([Default.name, Default.seen]).toEqual(["C", "C"]);
const objectClasses = {
ObjectClass: (class {
static value = 0;
static { this.seen = this.name; }
} as any),
};
expect([objectClasses.ObjectClass.name, objectClasses.ObjectClass.seen]).toEqual(["ObjectClass", "ObjectClass"]);
const instanceKey = "instance";
class Holder {
[instanceKey] = (class {
static value = 0;
static { this.seen = this.name; }
} as any);
}
const Instance = new Holder().instance;
expect([Instance.name, Instance.seen]).toEqual(["instance", "instance"]);
function* suspendedFields() {
const Holder = (class {
[yield "key"] = (class {
static value = 0;
static { this.seen = this.name; }
} as any);
} as any);
return Holder;
}
const generator = suspendedFields();
expect(generator.next().value).toBe("key");
const Suspended = generator.next("field").value;
expect(Suspended.name).toBe("Holder");
const Field = new Suspended().field;
expect([Field.name, Field.seen]).toEqual(["field", "field"]);
@@ -0,0 +1,47 @@
class Names {
static {}
static asFunction = (function () {} as any);
static {}
static assertedArrow = (<any>(() => {}));
static {}
static satisfiesFunction = (function () {} satisfies Function);
static {}
static nonNullClass = class { static seen = this.name; }!;
static {}
static asClass = (class { static seen = this.name; } as any);
static {}
static satisfiesClass = (class { static seen = this.name; } satisfies { new(): unknown; seen: string });
static {}
static namedFunction = (function Explicit() {} as any);
static {}
static nestedClass = (((class { static seen = this.name; }) satisfies { new(): unknown; seen: string }) as any);
static {}
}
function defaultClass(C = (class {
static value = 0;
static { this.seen = this.name; }
} as any)) { return C; }
const Default = defaultClass();
const objectClasses = {
ObjectClass: (class {
static value = 0;
static { this.seen = this.name; }
} as any),
};
const instanceKey = "instance";
class Holder {
[instanceKey] = (class {
static value = 0;
static { this.seen = this.name; }
} as any);
}
function* suspendedFields() {
const Holder = (class {
[yield "key"] = (class {
static value = 0;
static { this.seen = this.name; }
} as any);
} as any);
return Holder;
}
@@ -0,0 +1,4 @@
{
"plugins": ["transform-typescript"],
"presets": [["env", {"targets": {"node": "16"}}]]
}
@@ -0,0 +1,54 @@
var _staticBlock, _staticBlock3;
let _key;
let Names = (class Names {
static asFunction = ((() => {})(), babelHelpers.setFunctionName(function() {}, "asFunction"));
static assertedArrow = ((() => {})(), babelHelpers.setFunctionName((() => {}), "assertedArrow"));
static satisfiesFunction = ((() => {})(), babelHelpers.setFunctionName(function() {}, "satisfiesFunction"));
static nonNullClass = ((() => {})(), { "nonNullClass": class {
static seen = this.name;
} }["nonNullClass"]);
static asClass = ((() => {})(), { "asClass": class {
static seen = this.name;
} }["asClass"]);
static satisfiesClass = ((() => {})(), { "satisfiesClass": class {
static seen = this.name;
} }["satisfiesClass"]);
static namedFunction = ((() => {})(), function Explicit() {});
static nestedClass = ((_value) => (_staticBlock = () => ((() => {})(), this), _value))(((() => {})(), { "nestedClass": class {
static seen = this.name;
} }["nestedClass"]));
}, _staticBlock());
function defaultClass(C = (() => {
var _staticBlock2;
return { "C": class {
static value = ((_value2) => (_staticBlock2 = () => (this.seen = this.name, this), _value2))(0);
} }["C"], _staticBlock2();
})()) {
return C;
}
const Default = defaultClass();
const objectClasses = { ObjectClass: ({ "ObjectClass": class {
static value = ((_value3) => (_staticBlock3 = () => (this.seen = this.name, this), _value3))(0);
} }["ObjectClass"], _staticBlock3()) };
const instanceKey = "instance";
class Holder {
[_key = babelHelpers.toPropertyKey(instanceKey)] = (() => {
var _staticBlock4;
return ((_name, _object) => _object[_name])(_key, { [_key]: class {
static value = ((_value4) => (_staticBlock4 = () => (this.seen = this.name, this), _value4))(0);
} }), _staticBlock4();
})();
}
function* suspendedFields() {
let _key2;
const Holder = class _Class {
static #_ = (babelHelpers.setFunctionName(this, "Holder"), _key2);
[_key2 = babelHelpers.toPropertyKey(yield "key")] = (() => {
var _staticBlock5;
return { [_Class.#_]: class {
static value = ((_value5) => (_staticBlock5 = () => (this.seen = this.name, this), _value5))(0);
} }[_Class.#_], _staticBlock5();
})();
};
return Holder;
}
@@ -1,6 +1,6 @@
function foo() { }
export class C {
var _staticBlock;
function foo() {}
export let C = (class C {
#_ = 1;
static #_2 = 2;
#_3() {}
@@ -8,9 +8,7 @@ export class C {
accessor #_5 = 5;
static accessor #_6 = 6;
_7 = 7;
static _8 = 8;
static _8 = ((_value) => (_staticBlock = () => (foo(), this), _value))(8);
_9() {}
static _10() {}
static #_7 = foo();
}
}, _staticBlock());