feat(transformer): class static block transform (#6733)

Add ES2022 class static block transform.
This commit is contained in:
overlookmotel 2024-10-22 03:40:02 +00:00
parent 78fee6ee24
commit 10484cdeeb
25 changed files with 700 additions and 5 deletions

1
Cargo.lock generated
View file

@ -2018,6 +2018,7 @@ dependencies = [
"cow-utils",
"dashmap 6.1.0",
"indexmap",
"itoa",
"oxc-browserslist",
"oxc_allocator",
"oxc_ast",

View file

@ -173,6 +173,17 @@ impl ScopeTree {
}
}
/// Delete a scope.
pub fn delete_scope(&mut self, scope_id: ScopeId) {
if self.build_child_ids {
self.child_ids[scope_id].clear();
let parent_id = self.parent_ids[scope_id];
if let Some(parent_id) = parent_id {
self.child_ids[parent_id].retain(|&child_id| child_id != scope_id);
}
}
}
/// Get a variable binding by name that was declared in the top-level scope
#[inline]
pub fn get_root_binding(&self, name: &str) -> Option<SymbolId> {

View file

@ -38,6 +38,7 @@ base64 = { workspace = true }
cow-utils = { workspace = true }
dashmap = { workspace = true }
indexmap = { workspace = true }
itoa = { workspace = true }
ropey = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true, features = ["derive"] }

View file

@ -0,0 +1,446 @@
//! ES2022: Class Static Block
//!
//! This plugin transforms class static blocks (`class C { static { foo } }`) to an equivalent
//! using private fields (`class C { static #_ = foo }`).
//!
//! > This plugin is included in `preset-env`, in ES2022
//!
//! ## Example
//!
//! Input:
//! ```js
//! class C {
//! static {
//! foo();
//! }
//! static {
//! foo();
//! bar();
//! }
//! }
//! ```
//!
//! Output:
//! ```js
//! class C {
//! static #_ = foo();
//! static #_2 = (() => {
//! foo();
//! bar();
//! })();
//! }
//! ```
//!
//! ## Implementation
//!
//! Implementation based on [@babel/plugin-transform-class-static-block](https://babel.dev/docs/babel-plugin-transform-class-static-block).
//!
//! ## References:
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-class-static-block>
//! * Class static initialization blocks TC39 proposal: <https://github.com/tc39/proposal-class-static-block>
use itoa::Buffer as ItoaBuffer;
use oxc_allocator::String as AString;
use oxc_ast::{ast::*, Visit, NONE};
use oxc_semantic::SymbolTable;
use oxc_span::SPAN;
use oxc_syntax::{
reference::ReferenceFlags,
scope::{ScopeFlags, ScopeId},
};
use oxc_traverse::{Traverse, TraverseCtx};
pub struct ClassStaticBlock;
impl ClassStaticBlock {
pub fn new() -> Self {
Self
}
}
impl<'a> Traverse<'a> for ClassStaticBlock {
fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
// Loop through class body elements and:
// 1. Find if there are any `StaticBlock`s.
// 2. Collate list of private keys matching `#_` or `#_[1-9]...`.
//
// Don't collate private keys list conditionally only if a static block is found, as usually
// there will be no matching private keys, so those checks are cheap and will not allocate.
let mut has_static_block = false;
let mut keys = Keys::default();
for element in &body.body {
let key = match element {
ClassElement::StaticBlock(_) => {
has_static_block = true;
continue;
}
ClassElement::MethodDefinition(def) => &def.key,
ClassElement::PropertyDefinition(def) => &def.key,
ClassElement::AccessorProperty(def) => &def.key,
ClassElement::TSIndexSignature(_) => continue,
};
if let PropertyKey::PrivateIdentifier(id) = key {
keys.reserve(id.name.as_str());
}
}
// Transform static blocks
if !has_static_block {
return;
}
for element in body.body.iter_mut() {
if let ClassElement::StaticBlock(block) = element {
*element = Self::convert_block_to_private_field(block, &mut keys, ctx);
}
}
}
}
impl ClassStaticBlock {
/// Convert static block to private field.
/// `static { foo }` -> `static #_ = foo;`
/// `static { foo; bar; }` -> `static #_ = (() => { foo; bar; })();`
fn convert_block_to_private_field<'a>(
block: &mut StaticBlock<'a>,
keys: &mut Keys<'a>,
ctx: &mut TraverseCtx<'a>,
) -> ClassElement<'a> {
let expr = Self::convert_block_to_expression(block, ctx);
let key = keys.get_unique(ctx);
let key = ctx.ast.property_key_private_identifier(SPAN, key);
ctx.ast.class_element_property_definition(
PropertyDefinitionType::PropertyDefinition,
block.span,
ctx.ast.vec(),
key,
Some(expr),
false,
true,
false,
false,
false,
false,
false,
NONE,
None,
)
}
/// Convert static block to expression which will be value of private field.
/// `static { foo }` -> `foo`
/// `static { foo; bar; }` -> `(() => { foo; bar; })()`
fn convert_block_to_expression<'a>(
block: &mut StaticBlock<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let scope_id = block.scope_id.get().unwrap();
// If block contains only a single `ExpressionStatement`, no need to wrap in an IIFE.
// `static { foo }` -> `foo`
// TODO(improve-on-babel): If block has no statements, could remove it entirely.
let stmts = &mut block.body;
if stmts.len() == 1 {
if let Statement::ExpressionStatement(stmt) = stmts.first_mut().unwrap() {
return Self::convert_block_with_single_expression_to_expression(
&mut stmt.expression,
scope_id,
ctx,
);
}
}
// Convert block to arrow function IIFE.
// `static { foo; bar; }` -> `(() => { foo; bar; })()`
// Re-use the static block's scope for the arrow function.
// Always strict mode since we're in a class.
*ctx.scopes_mut().get_flags_mut(scope_id) =
ScopeFlags::Function | ScopeFlags::Arrow | ScopeFlags::StrictMode;
let stmts = ctx.ast.move_vec(stmts);
let params = ctx.ast.alloc_formal_parameters(
SPAN,
FormalParameterKind::ArrowFormalParameters,
ctx.ast.vec(),
NONE,
);
let body = ctx.ast.alloc_function_body(SPAN, ctx.ast.vec(), stmts);
let arrow = Expression::ArrowFunctionExpression(
ctx.ast.alloc_arrow_function_expression_with_scope_id(
SPAN, false, false, NONE, params, NONE, body, scope_id,
),
);
ctx.ast.expression_call(SPAN, arrow, NONE, ctx.ast.vec(), false)
}
/// Convert static block to expression which will be value of private field,
/// where the static block contains only a single expression.
/// `static { foo }` -> `foo`
fn convert_block_with_single_expression_to_expression<'a>(
expr: &mut Expression<'a>,
scope_id: ScopeId,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let expr = ctx.ast.move_expression(expr);
// Remove the scope for the static block from the scope chain
ctx.remove_scope_for_expression(scope_id, &expr);
// If expression is an assignment, left side has moved from a write-only position to a read + write one.
// `static { x = 1; }` -> `static #_ = x = 1;`
// So set `ReferenceFlags::Read` on the left side.
if let Expression::AssignmentExpression(assign_expr) = &expr {
if assign_expr.operator == AssignmentOperator::Assign {
let mut setter = ReferenceFlagsSetter { symbols: ctx.symbols_mut() };
setter.visit_assignment_target(&assign_expr.left);
}
}
expr
}
}
/// Visitor which sets `ReferenceFlags::Read` flag on all `IdentifierReference`s.
/// It skips `MemberExpression`s, because their flags are not affected by the change in position.
struct ReferenceFlagsSetter<'s> {
symbols: &'s mut SymbolTable,
}
impl<'a, 's> Visit<'a> for ReferenceFlagsSetter<'s> {
fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) {
let reference_id = ident.reference_id().unwrap();
let reference = self.symbols.get_reference_mut(reference_id);
*reference.flags_mut() |= ReferenceFlags::Read;
}
fn visit_member_expression(&mut self, _member_expr: &MemberExpression<'a>) {
// Don't traverse further
}
}
/// Store of private identifier keys matching `#_` or `#_[1-9]...`.
///
/// Most commonly there will be no existing keys matching this pattern
/// (why would you prefix a private key with `_`?).
/// It's also uncommon to have more than 1 static block in a class.
///
/// Therefore common case is only 1 static block, which will use key `#_`.
/// So store whether `#_` is in set as a separate `bool`, to make a fast path this common case,
/// which does not involve any allocations (`numbered` will remain empty).
///
/// Use a `Vec` rather than a `HashMap`, because number of matching private keys is usually small,
/// and `Vec` is lower overhead in that case.
#[derive(Default)]
struct Keys<'a> {
/// `true` if keys includes `#_`.
underscore: bool,
/// Keys matching `#_[1-9]...`. Stored without the `_` prefix.
numbered: Vec<&'a str>,
}
impl<'a> Keys<'a> {
/// Add a key to set.
///
/// Key will only be added to set if it's `_`, or starts with `_[1-9]`.
fn reserve(&mut self, key: &'a str) {
let mut bytes = key.as_bytes().iter().copied();
if bytes.next() != Some(b'_') {
return;
}
match bytes.next() {
None => {
self.underscore = true;
}
Some(b'1'..=b'9') => {
self.numbered.push(&key[1..]);
}
_ => {}
}
}
/// Get a key which is not in the set.
///
/// Returned key will be either `_`, or `_<integer>` starting with `_2`.
#[inline]
fn get_unique(&mut self, ctx: &mut TraverseCtx<'a>) -> Atom<'a> {
#[expect(clippy::if_not_else)]
if !self.underscore {
self.underscore = true;
Atom::from("_")
} else {
self.get_unique_slow(ctx)
}
}
// `#[cold]` and `#[inline(never)]` as it should be very rare to need a key other than `#_`.
#[cold]
#[inline(never)]
fn get_unique_slow(&mut self, ctx: &mut TraverseCtx<'a>) -> Atom<'a> {
// Source text length is limited to `u32::MAX` so impossible to have more than `u32::MAX`
// private keys. So `u32` is sufficient here.
let mut i = 2u32;
let mut buffer = ItoaBuffer::new();
let mut num_str;
loop {
num_str = buffer.format(i);
if !self.numbered.contains(&num_str) {
break;
}
i += 1;
}
let mut key = AString::with_capacity_in(num_str.len() + 1, ctx.ast.allocator);
key.push('_');
key.push_str(num_str);
let key = Atom::from(key.into_bump_str());
self.numbered.push(&key.as_str()[1..]);
key
}
}
#[cfg(test)]
mod test {
use oxc_allocator::Allocator;
use oxc_semantic::{ScopeTree, SymbolTable};
use oxc_traverse::TraverseCtx;
use super::Keys;
macro_rules! setup {
($ctx:ident) => {
let allocator = Allocator::default();
let scopes = ScopeTree::default();
let symbols = SymbolTable::default();
let mut $ctx = TraverseCtx::new(scopes, symbols, &allocator);
};
}
#[test]
fn keys_no_reserved() {
setup!(ctx);
let mut keys = Keys::default();
assert_eq!(keys.get_unique(&mut ctx), "_");
assert_eq!(keys.get_unique(&mut ctx), "_2");
assert_eq!(keys.get_unique(&mut ctx), "_3");
assert_eq!(keys.get_unique(&mut ctx), "_4");
assert_eq!(keys.get_unique(&mut ctx), "_5");
assert_eq!(keys.get_unique(&mut ctx), "_6");
assert_eq!(keys.get_unique(&mut ctx), "_7");
assert_eq!(keys.get_unique(&mut ctx), "_8");
assert_eq!(keys.get_unique(&mut ctx), "_9");
assert_eq!(keys.get_unique(&mut ctx), "_10");
assert_eq!(keys.get_unique(&mut ctx), "_11");
assert_eq!(keys.get_unique(&mut ctx), "_12");
}
#[test]
fn keys_no_relevant_reserved() {
setup!(ctx);
let mut keys = Keys::default();
keys.reserve("a");
keys.reserve("foo");
keys.reserve("__");
keys.reserve("_0");
keys.reserve("_1");
keys.reserve("_a");
keys.reserve("_foo");
keys.reserve("_2foo");
assert_eq!(keys.get_unique(&mut ctx), "_");
assert_eq!(keys.get_unique(&mut ctx), "_2");
assert_eq!(keys.get_unique(&mut ctx), "_3");
}
#[test]
fn keys_reserved_underscore() {
setup!(ctx);
let mut keys = Keys::default();
keys.reserve("_");
assert_eq!(keys.get_unique(&mut ctx), "_2");
assert_eq!(keys.get_unique(&mut ctx), "_3");
assert_eq!(keys.get_unique(&mut ctx), "_4");
}
#[test]
fn keys_reserved_numbers() {
setup!(ctx);
let mut keys = Keys::default();
keys.reserve("_2");
keys.reserve("_4");
keys.reserve("_11");
assert_eq!(keys.get_unique(&mut ctx), "_");
assert_eq!(keys.get_unique(&mut ctx), "_3");
assert_eq!(keys.get_unique(&mut ctx), "_5");
assert_eq!(keys.get_unique(&mut ctx), "_6");
assert_eq!(keys.get_unique(&mut ctx), "_7");
assert_eq!(keys.get_unique(&mut ctx), "_8");
assert_eq!(keys.get_unique(&mut ctx), "_9");
assert_eq!(keys.get_unique(&mut ctx), "_10");
assert_eq!(keys.get_unique(&mut ctx), "_12");
}
#[test]
fn keys_reserved_later_numbers() {
setup!(ctx);
let mut keys = Keys::default();
keys.reserve("_5");
keys.reserve("_4");
keys.reserve("_12");
keys.reserve("_13");
assert_eq!(keys.get_unique(&mut ctx), "_");
assert_eq!(keys.get_unique(&mut ctx), "_2");
assert_eq!(keys.get_unique(&mut ctx), "_3");
assert_eq!(keys.get_unique(&mut ctx), "_6");
assert_eq!(keys.get_unique(&mut ctx), "_7");
assert_eq!(keys.get_unique(&mut ctx), "_8");
assert_eq!(keys.get_unique(&mut ctx), "_9");
assert_eq!(keys.get_unique(&mut ctx), "_10");
assert_eq!(keys.get_unique(&mut ctx), "_11");
assert_eq!(keys.get_unique(&mut ctx), "_14");
}
#[test]
fn keys_reserved_underscore_and_numbers() {
setup!(ctx);
let mut keys = Keys::default();
keys.reserve("_2");
keys.reserve("_4");
keys.reserve("_");
assert_eq!(keys.get_unique(&mut ctx), "_3");
assert_eq!(keys.get_unique(&mut ctx), "_5");
assert_eq!(keys.get_unique(&mut ctx), "_6");
}
#[test]
fn keys_reserved_underscore_and_later_numbers() {
setup!(ctx);
let mut keys = Keys::default();
keys.reserve("_5");
keys.reserve("_4");
keys.reserve("_");
assert_eq!(keys.get_unique(&mut ctx), "_2");
assert_eq!(keys.get_unique(&mut ctx), "_3");
assert_eq!(keys.get_unique(&mut ctx), "_6");
}
}

View file

@ -0,0 +1,29 @@
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
mod class_static_block;
mod options;
use class_static_block::ClassStaticBlock;
pub use options::ES2022Options;
pub struct ES2022 {
options: ES2022Options,
// Plugins
class_static_block: ClassStaticBlock,
}
impl ES2022 {
pub fn new(options: ES2022Options) -> Self {
Self { options, class_static_block: ClassStaticBlock::new() }
}
}
impl<'a> Traverse<'a> for ES2022 {
fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.class_static_block {
self.class_static_block.enter_class_body(body, ctx);
}
}
}

View file

@ -0,0 +1,28 @@
use serde::Deserialize;
use crate::env::{can_enable_plugin, Versions};
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2022Options {
#[serde(skip)]
pub class_static_block: bool,
}
impl ES2022Options {
pub fn with_class_static_block(&mut self, enable: bool) -> &mut Self {
self.class_static_block = enable;
self
}
#[must_use]
pub fn from_targets_and_bugfixes(targets: Option<&Versions>, bugfixes: bool) -> Self {
Self {
class_static_block: can_enable_plugin(
"transform-class-static-block",
targets,
bugfixes,
),
}
}
}

View file

@ -30,6 +30,7 @@ mod es2018;
mod es2019;
mod es2020;
mod es2021;
mod es2022;
mod react;
mod regexp;
mod typescript;
@ -45,6 +46,7 @@ use es2018::ES2018;
use es2019::ES2019;
use es2020::ES2020;
use es2021::ES2021;
use es2022::ES2022;
use react::React;
use regexp::RegExp;
use typescript::TypeScript;
@ -93,6 +95,7 @@ impl<'a> Transformer<'a> {
let mut transformer = TransformerImpl {
x0_typescript: TypeScript::new(&self.options.typescript, &self.ctx),
x1_react: React::new(self.options.react, ast_builder, &self.ctx),
x2_es2022: ES2022::new(self.options.es2022),
x2_es2021: ES2021::new(self.options.es2021, &self.ctx),
x2_es2020: ES2020::new(self.options.es2020, &self.ctx),
x2_es2019: ES2019::new(self.options.es2019),
@ -113,6 +116,7 @@ struct TransformerImpl<'a, 'ctx> {
// NOTE: all callbacks must run in order.
x0_typescript: TypeScript<'a, 'ctx>,
x1_react: React<'a, 'ctx>,
x2_es2022: ES2022,
x2_es2021: ES2021<'a, 'ctx>,
x2_es2020: ES2020<'a, 'ctx>,
x2_es2019: ES2019,
@ -170,6 +174,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.enter_class_body(body, ctx);
self.x2_es2022.enter_class_body(body, ctx);
}
fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {

View file

@ -15,6 +15,7 @@ use crate::{
es2019::ES2019Options,
es2020::ES2020Options,
es2021::ES2021Options,
es2022::ES2022Options,
options::babel::BabelOptions,
react::JsxOptions,
regexp::RegExpOptions,
@ -59,6 +60,8 @@ pub struct TransformOptions {
pub es2021: ES2021Options,
pub es2022: ES2022Options,
pub helper_loader: HelperLoaderOptions,
}
@ -97,6 +100,7 @@ impl TransformOptions {
es2019: ES2019Options { optional_catch_binding: true },
es2020: ES2020Options { nullish_coalescing_operator: true },
es2021: ES2021Options { logical_assignment_operators: true },
es2022: ES2022Options { class_static_block: true },
helper_loader: HelperLoaderOptions {
mode: HelperLoaderMode::Runtime,
..Default::default()
@ -113,6 +117,7 @@ impl TransformOptions {
es2019: ES2019Options::from_targets_and_bugfixes(targets, bugfixes),
es2020: ES2020Options::from_targets_and_bugfixes(targets, bugfixes),
es2021: ES2021Options::from_targets_and_bugfixes(targets, bugfixes),
es2022: ES2022Options::from_targets_and_bugfixes(targets, bugfixes),
regexp: RegExpOptions::from_targets_and_bugfixes(targets, bugfixes),
..Default::default()
}
@ -254,6 +259,11 @@ impl TransformOptions {
get_enabled_plugin_options(plugin_name, options, targets.as_ref(), bugfixes).is_some()
});
transformer_options.es2022.with_class_static_block({
let plugin_name = "transform-class-static-block";
get_enabled_plugin_options(plugin_name, options, targets.as_ref(), bugfixes).is_some()
});
transformer_options.typescript = {
let preset_name = "typescript";
if options.has_preset("typescript") {

View file

@ -284,6 +284,23 @@ impl<'a> TraverseCtx<'a> {
self.scoping.insert_scope_below_expression(expr, flags)
}
/// Remove scope for an expression from the scope chain.
///
/// Delete the scope and set parent of its child scopes to its parent scope.
/// e.g.:
/// * Starting scopes parentage `A -> B`, `B -> C`, `B -> D`.
/// * Remove scope `B` from chain.
/// * End result: scopes `A -> C`, `A -> D`.
///
/// Use this when removing an expression which owns a scope, without removing its children.
/// For example when unwrapping `(() => foo)()` to just `foo`.
/// `foo` here could be an expression which itself contains scopes.
///
/// This is a shortcut for `ctx.scoping.remove_scope_for_expression`.
pub fn remove_scope_for_expression(&mut self, scope_id: ScopeId, expr: &Expression) {
self.scoping.remove_scope_for_expression(scope_id, expr);
}
/// Generate UID var name.
///
/// Finds a unique variable name which does clash with any other variables used in the program.

View file

@ -139,6 +139,32 @@ impl TraverseScoping {
new_scope_id
}
/// Remove scope for an expression from the scope chain.
///
/// Delete the scope and set parent of its child scopes to its parent scope.
/// e.g.:
/// * Starting scopes parentage `A -> B`, `B -> C`, `B -> D`.
/// * Remove scope `B` from chain.
/// * End result: scopes `A -> C`, `A -> D`.
///
/// Use this when removing an expression which owns a scope, without removing its children.
/// For example when unwrapping `(() => foo)()` to just `foo`.
/// `foo` here could be an expression which itself contains scopes.
pub fn remove_scope_for_expression(&mut self, scope_id: ScopeId, expr: &Expression) {
let mut collector = ChildScopeCollector::new();
collector.visit_expression(expr);
let child_ids = collector.scope_ids;
if !child_ids.is_empty() {
let parent_id = self.scopes.get_parent_id(scope_id);
for child_id in child_ids {
self.scopes.set_parent_id(child_id, parent_id);
}
}
self.scopes.delete_scope(scope_id);
}
/// Generate UID var name.
///
/// Finds a unique variable name which does clash with any other variables used in the program.

View file

@ -1,8 +1,9 @@
commit: d20b314c
Passed: 342/1051
Passed: 348/1058
# All Passed:
* babel-plugin-transform-class-static-block
* babel-plugin-transform-logical-assignment-operators
* babel-plugin-transform-optional-catch-binding
* babel-preset-react
@ -11,7 +12,7 @@ Passed: 342/1051
* babel-plugin-transform-react-jsx-source
# babel-preset-env (109/585)
# babel-preset-env (108/585)
* .plugins-overlapping/chrome-49/input.js
x Output mismatch
@ -1437,6 +1438,9 @@ x Output mismatch
* shipped-proposals/new-class-features-chrome-90/input.js
x Output mismatch
* shipped-proposals/new-class-features-chrome-94/input.js
x Output mismatch
* shipped-proposals/new-class-features-firefox-70/input.js
x Output mismatch

View file

@ -1,8 +1,9 @@
commit: d20b314c
Passed: 34/62
Passed: 45/73
# All Passed:
* babel-plugin-transform-class-static-block
* babel-plugin-transform-logical-assignment-operators
* babel-plugin-transform-nullish-coalescing-operator
* babel-plugin-transform-optional-catch-binding

View file

@ -1,8 +1,9 @@
commit: d20b314c
Passed: 61/70
Passed: 66/75
# All Passed:
* babel-plugin-transform-class-static-block
* babel-plugin-transform-nullish-coalescing-operator
* babel-plugin-transform-optional-catch-binding
* babel-plugin-transform-exponentiation-operator

View file

@ -4,7 +4,7 @@ pub(crate) const PLUGINS: &[&str] = &[
// "babel-plugin-transform-unicode-sets-regex",
// // ES2022
// "babel-plugin-transform-class-properties",
// "babel-plugin-transform-class-static-block",
"babel-plugin-transform-class-static-block",
// "babel-plugin-transform-private-methods",
// "babel-plugin-transform-private-property-in-object",
// // [Syntax] "babel-plugin-transform-syntax-top-level-await",

View file

@ -0,0 +1,19 @@
let a, b, d, e;
class C {
static {
a = this;
}
static {
[b, c] = this;
}
static {
d ??= this;
}
static {
e.f = this;
}
static {
[g.h, i] = this;
}
}

View file

@ -0,0 +1,9 @@
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;
}

View file

@ -0,0 +1,8 @@
class C {
static {
C;
}
static {
x;
}
}

View file

@ -0,0 +1,4 @@
class C {
static #_ = C;
static #_2 = x;
}

View file

@ -0,0 +1,4 @@
class C {
static {}
static {}
}

View file

@ -0,0 +1,4 @@
class C {
static #_ = (() => {})();
static #_2 = (() => {})();
}

View file

@ -0,0 +1,14 @@
let x, y;
class C {
static {
x = (() => this)();
}
static {
if (true) {
y = this;
z = this;
}
}
}

View file

@ -0,0 +1,11 @@
let x, y;
class C {
static #_ = x = (() => this)();
static #_2 = (() => {
if (true) {
y = this;
z = this;
}
})();
}

View file

@ -0,0 +1,5 @@
{
"plugins": [
"transform-class-static-block"
]
}

View file

@ -0,0 +1,21 @@
function foo() {}
export class C {
// Private properties and methods use up prop names for static block
#_ = 1;
static #_2 = 2;
#_3() {}
static #_4() {}
accessor #_5 = 5;
static accessor #_6 = 6;
// Non-private don't use up prop names
_7 = 7;
static _8 = 8;
_9() {}
static _10() {}
static {
foo();
}
}

View file

@ -0,0 +1,16 @@
function foo() { }
export class C {
#_ = 1;
static #_2 = 2;
#_3() {}
static #_4() {}
accessor #_5 = 5;
static accessor #_6 = 6;
_7 = 7;
static _8 = 8;
_9() {}
static _10() {}
static #_7 = foo();
}