diff --git a/crates/oxc_transformer/src/es2022/class_properties/class.rs b/crates/oxc_transformer/src/es2022/class_properties/class.rs index 51debcd64b2e7d..6d65c0ab392ab9 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/class.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/class.rs @@ -135,6 +135,10 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Insert class + static property assignments + static blocks let class_expr = ctx.ast.move_expression(expr); if let Some(binding) = &self.class_bindings.temp { + if !self.temp_var_is_created { + self.ctx.var_declarations.insert_var(binding, None, ctx); + } + // `_Class = class {}` let assignment = create_assignment(binding, class_expr, ctx); exprs.push(assignment); @@ -207,6 +211,10 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Insert `_Class = Class` after class. // TODO(improve-on-babel): Could just insert `var _Class = Class;` after class, // rather than separate `var _Class` declaration. + if !self.temp_var_is_created { + self.ctx.var_declarations.insert_var(temp_binding, None, ctx); + } + let class_name = ctx.create_bound_ident_expr( SPAN, ident.name.clone(), @@ -219,6 +227,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { } else { // Class must be default export `export default class {}`, as all other class declarations // always have a name. Set class name. + *ctx.symbols_mut().get_flags_mut(temp_binding.symbol_id) = SymbolFlags::Class; class.id = Some(temp_binding.create_binding_identifier(ctx)); } } @@ -346,9 +355,23 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { }; // Static prop in class expression or anonymous `export default class {}` - // always requires a temp var. Static prop in class declaration doesn't. - if has_static_prop && (!self.is_declaration || self.class_bindings.name.is_none()) { - self.class_bindings.get_or_init_temp_binding(self.is_declaration, self.ctx, ctx); + // always requires a temp var for class. Static prop in class declaration doesn't. + // TODO(improve-on-babel): Inserting the temp var `var _Class` statement here is only necessary + // to match Babel's output. It'd be simpler just to insert it at the end and get rid of + // `temp_var_is_created` that tracks whether it's done already or not. + let need_temp_var = has_static_prop && (!self.is_declaration || class.id.is_none()); + self.temp_var_is_created = need_temp_var; + + if need_temp_var { + let temp_binding = self.class_bindings.get_or_init_temp_binding(ctx); + if self.is_declaration && class.id.is_none() { + // Anonymous `export default class {}`, set class name binding to temp var. + // Actual class name will be set to this later. + self.class_bindings.name = Some(temp_binding.clone()); + } else { + // Create temp var `var _Class;` statement + self.ctx.var_declarations.insert_var(temp_binding, None, ctx); + } } // Add entry to `private_props_stack` diff --git a/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs b/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs index 60b5a50f8e3cab..0d1bc5b49e4232 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/class_bindings.rs @@ -1,8 +1,6 @@ use oxc_syntax::symbol::{SymbolFlags, SymbolId}; use oxc_traverse::{BoundIdentifier, TraverseCtx}; -use crate::TransformCtx; - #[derive(Default, Clone)] pub(super) struct ClassBindings<'a> { /// Binding for class name, if class has name @@ -19,39 +17,13 @@ impl<'a> ClassBindings<'a> { } /// Create a binding for temp var, if there isn't one already. - pub fn get_or_init_temp_binding( - &mut self, - is_declaration: bool, - transform_ctx: &TransformCtx<'a>, - ctx: &mut TraverseCtx<'a>, - ) -> &BoundIdentifier<'a> { - if self.temp.is_none() { + pub fn get_or_init_temp_binding(&mut self, ctx: &mut TraverseCtx<'a>) -> &BoundIdentifier<'a> { + self.temp.get_or_insert_with(|| { // Base temp binding name on class name, or "Class" if no name. - // Create a `var _Class;` statement unless this is `export default class {}`, - // in which case the class itself will receive this name (`export default class _Class {}`). - // TODO(improve-on-babel): If class name var isn't mutated, no need for temp var for class - // declaration. Can just use class binding. - let (name, flags) = if let Some(binding) = self.name.as_ref() { - (binding.name.as_str(), SymbolFlags::FunctionScopedVariable) - } else { - // Class declaration can only be nameless if it's `export default class {}` - let flags = if is_declaration { - SymbolFlags::Class - } else { - SymbolFlags::FunctionScopedVariable - }; - ("Class", flags) - }; - - let binding = ctx.generate_uid_in_current_scope(name, flags); - if flags == SymbolFlags::FunctionScopedVariable { - transform_ctx.var_declarations.insert_var(&binding, None, ctx); - } else { - self.name = Some(binding.clone()); - } - self.temp = Some(binding); - } - - self.temp.as_ref().unwrap() + // TODO(improve-on-babel): If class name var isn't mutated, no need for temp var for + // class declaration. Can just use class binding. + let name = self.name.as_ref().map_or("Class", |binding| binding.name.as_str()); + ctx.generate_uid_in_current_scope(name, SymbolFlags::FunctionScopedVariable) + }) } } diff --git a/crates/oxc_transformer/src/es2022/class_properties/mod.rs b/crates/oxc_transformer/src/es2022/class_properties/mod.rs index 9be5ac038c3c5d..e24525ea1a09f3 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/mod.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/mod.rs @@ -207,6 +207,8 @@ pub struct ClassProperties<'a, 'ctx> { is_declaration: bool, /// Bindings for class name and temp var for class class_bindings: ClassBindings<'a>, + /// `true` if temp var for class has been inserted + temp_var_is_created: bool, /// Expressions to insert before class insert_before: Vec>, /// Expressions to insert after class expression @@ -234,6 +236,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Temporary values - overwritten when entering class is_declaration: false, class_bindings: ClassBindings::default(), + temp_var_is_created: false, // `Vec`s and `FxHashMap`s which are reused for every class being transformed insert_before: vec![], insert_after_exprs: vec![], diff --git a/crates/oxc_transformer/src/es2022/class_properties/private.rs b/crates/oxc_transformer/src/es2022/class_properties/private.rs index 862f1f93eb0f44..be65aee2929bcd 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/private.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/private.rs @@ -78,7 +78,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Only possible to reach here if we're currently transforming static prop initializers. // TODO: Test that we're transforming static prop initializers right now, // and not visiting class body. Add some debug assertions for this. - class_bindings.get_or_init_temp_binding(is_declaration, self.ctx, ctx) + class_bindings.get_or_init_temp_binding(ctx) }; let class_ident = class_binding.create_read_expression(ctx); @@ -240,7 +240,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Only possible to reach here if we're currently transforming static prop initializers. // TODO: Test that we're transforming static prop initializers right now, // and not visiting class body. Add some debug assertions for this. - class_bindings.get_or_init_temp_binding(is_declaration, self.ctx, ctx) + class_bindings.get_or_init_temp_binding(ctx) }; let class_ident = class_binding.create_read_expression(ctx); @@ -327,7 +327,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Only possible to reach here if we're currently transforming static prop initializers. // TODO: Test that we're transforming static prop initializers right now, // and not visiting class body. Add some debug assertions for this. - class_bindings.get_or_init_temp_binding(is_declaration, self.ctx, ctx) + class_bindings.get_or_init_temp_binding(ctx) }; let class_binding = class_binding.clone(); let class_symbol_id = class_bindings.name_symbol_id(); @@ -727,7 +727,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Only possible to reach here if we're currently transforming static prop initializers. // TODO: Test that we're transforming static prop initializers right now, // and not visiting class body. Add some debug assertions for this. - class_bindings.get_or_init_temp_binding(is_declaration, self.ctx, ctx) + class_bindings.get_or_init_temp_binding(ctx) }; let class_binding = class_binding.clone(); diff --git a/crates/oxc_transformer/src/es2022/class_properties/static_prop.rs b/crates/oxc_transformer/src/es2022/class_properties/static_prop.rs index 451f2dba5699e4..9450ed3174fc50 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/static_prop.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/static_prop.rs @@ -241,19 +241,15 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { /// Replace `this` with reference to class name binding. fn replace_this_with_class_name(&mut self, expr: &mut Expression<'a>, span: Span) { if self.this_depth == 0 { - let class_properties = &mut *self.class_properties; - let class_bindings = - if let Some(private_props) = class_properties.private_props_stack.last_mut() { - &mut private_props.class_bindings - } else { - &mut class_properties.class_bindings - }; + // `PrivateProps` is the source of truth for bindings if class has private props + // because other visitors which transform class fields may create a temp binding + // and store it on `PrivateProps` + let class_bindings = match self.class_properties.private_props_stack.last_mut() { + Some(private_props) => &mut private_props.class_bindings, + None => &mut self.class_properties.class_bindings, + }; - let class_binding = class_bindings.get_or_init_temp_binding( - class_properties.is_declaration, - class_properties.ctx, - self.ctx, - ); + let class_binding = class_bindings.get_or_init_temp_binding(self.ctx); *expr = class_binding.create_spanned_read_expression(span, self.ctx); } }