diff --git a/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs b/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs
index 101cfd711..4a57516bd 100644
--- a/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs
+++ b/crates/oxc_transformer/src/es2022/class_properties/static_prop_init.rs
@@ -95,8 +95,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
/// ```
///
/// If this class defines no private properties, class has no name, and no `ScopeFlags` need updating,
-/// then we only need to transform `this`. So can skip traversing into functions and other contexts
-/// which have their own `this`.
+/// then we only need to transform `this`, and re-parent first-level scopes. So can skip traversing
+/// into functions and other contexts which have their own `this`.
///
/// Note: Those functions could contain private fields referring to a *parent* class's private props,
/// but we don't need to transform them here as they remain in same class scope.
@@ -119,8 +119,13 @@ struct StaticInitializerVisitor<'a, 'ctx, 'v> {
/// Incremented when entering a different `this` context, decremented when exiting it.
/// `this` should be transformed when `this_depth == 0`.
this_depth: u32,
- /// Incremented when entering a scope, decremented when exiting it.
+ /// Incremented when entering scope, decremented when exiting it.
/// Parent `ScopeId` should be updated when `scope_depth == 0`.
+ /// Note: `scope_depth` does not aim to track scope depth completely accurately.
+ /// Only requirement is to ensure that `scope_depth == 0` only when we're in first-level scope.
+ /// So we don't bother incrementing + decrementing for scopes which are definitely not first level.
+ /// e.g. `BlockStatement` or `ForStatement` must be in a function, and therefore we're already in a
+ /// nested scope.
scope_depth: u32,
/// Main transform instance.
class_properties: &'v mut ClassProperties<'a, 'ctx>,
@@ -252,31 +257,16 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
self.replace_class_name_with_temp_var(ident);
}
- /// Update parent scope for first level of scopes.
/// Convert scope to sloppy mode if `self.make_sloppy_mode == true`.
+ // `#[inline]` because called from many `walk` functions and is small.
+ #[inline]
fn enter_scope(&mut self, _flags: ScopeFlags, scope_id: &Cell>) {
- let scope_id = scope_id.get().unwrap();
-
- // TODO: Not necessary to do this check for all scopes.
- // In JS, only `Function`, `ArrowFunctionExpression` or `Class` can be the first-level scope,
- // as all other types which have a scope are statements or `StaticBlock` which would need to be
- // inside a function or class. But some TS types with scopes could be first level via
- // e.g. `TaggedTemplateExpression::type_parameters`, which contains `TSType`.
- // Not sure if that matters though, as they'll be stripped out anyway by TS transform.
- if self.scope_depth == 0 {
- self.reparent_scope(scope_id);
- }
- self.scope_depth += 1;
-
if self.make_sloppy_mode {
+ let scope_id = scope_id.get().unwrap();
*self.ctx.scopes_mut().get_flags_mut(scope_id) -= ScopeFlags::StrictMode;
}
}
- fn leave_scope(&mut self) {
- self.scope_depth -= 1;
- }
-
// Increment `this_depth` when entering code where `this` refers to a different `this`
// from `this` within this class, and decrement it when exiting.
// Therefore `this_depth == 0` when `this` refers to the `this` which needs to be transformed.
@@ -295,14 +285,14 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
self.make_sloppy_mode = false;
}
+ self.reparent_scope_if_first_level(&func.scope_id);
+
if self.walk_deep {
self.this_depth += 1;
+ self.scope_depth += 1;
walk_mut::walk_function(self, func, flags);
self.this_depth -= 1;
- } else if self.scope_depth == 0 {
- // Need to call `reparent_scope` because not calling `walk_function`,
- // which is what calls `enter_scope`, and this can be first-level scope
- self.reparent_scope(func.scope_id());
+ self.scope_depth -= 1;
}
self.make_sloppy_mode = parent_sloppy_mode;
@@ -316,7 +306,11 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
self.make_sloppy_mode = false;
}
+ self.reparent_scope_if_first_level(&func.scope_id);
+
+ self.scope_depth += 1;
walk_mut::walk_arrow_function_expression(self, func);
+ self.scope_depth -= 1;
self.make_sloppy_mode = parent_sloppy_mode;
}
@@ -327,25 +321,34 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
// Classes are always strict mode
self.make_sloppy_mode = false;
+ self.reparent_scope_if_first_level(&class.scope_id);
+
+ self.scope_depth += 1;
walk_mut::walk_class(self, class);
+ self.scope_depth -= 1;
self.make_sloppy_mode = parent_sloppy_mode;
}
#[inline]
fn visit_static_block(&mut self, block: &mut StaticBlock<'a>) {
- if self.walk_deep {
- self.this_depth += 1;
- walk_mut::walk_static_block(self, block);
- self.this_depth -= 1;
- } else {
- // Not possible that `self.scope_depth == 0` here, because a `StaticBlock`
- // can only be in a class, and that class would be the first-level scope
- }
+ // Not possible that `self.scope_depth == 0` here, because a `StaticBlock`
+ // can only be in a class, and that class would be the first-level scope.
+ // So no need to call `reparent_scope_if_first_level`.
+
+ // `walk_deep` must be `true` or we couldn't get here, because a `StaticBlock`
+ // must be in a class, and traversal would have stopped in `visit_class` if it wasn't
+ self.this_depth += 1;
+ walk_mut::walk_static_block(self, block);
+ self.this_depth -= 1;
}
#[inline]
fn visit_ts_module_block(&mut self, block: &mut TSModuleBlock<'a>) {
+ // Not possible that `self.scope_depth == 0` here, because a `TSModuleBlock`
+ // can only be in a function, and that function would be the first-level scope.
+ // So no need to call `reparent_scope_if_first_level`.
+
let parent_sloppy_mode = self.make_sloppy_mode;
if self.make_sloppy_mode && block.has_use_strict_directive() {
// Block has a `"use strict"` directive in body
@@ -356,9 +359,6 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
self.this_depth += 1;
walk_mut::walk_ts_module_block(self, block);
self.this_depth -= 1;
- } else {
- // Not possible that `self.scope_depth == 0` here, because a `TSModuleBlock`
- // can only be in a function, and that function would be the first-level scope
}
self.make_sloppy_mode = parent_sloppy_mode;
@@ -375,26 +375,32 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
// }
// ```
// Don't visit `type_annotation` field because can't contain `this` or private props.
+
+ // Not possible that `self.scope_depth == 0` here, because a `PropertyDefinition`
+ // can only be in a class, and that class would be the first-level scope.
+ // So no need to call `reparent_scope_if_first_level`.
+
// TODO: Are decorators in scope?
self.visit_decorators(&mut prop.decorators);
if prop.computed {
self.visit_property_key(&mut prop.key);
}
- if self.walk_deep {
- if let Some(value) = &mut prop.value {
- self.this_depth += 1;
- self.visit_expression(value);
- self.this_depth -= 1;
- }
- } else {
- // Not possible that `self.scope_depth == 0` here, because a `PropertyDefinition`
- // can only be in a class, and that class would be the first-level scope
+ // `walk_deep` must be `true` or we couldn't get here, because a `PropertyDefinition`
+ // must be in a class, and traversal would have stopped in `visit_class` if it wasn't
+ if let Some(value) = &mut prop.value {
+ self.this_depth += 1;
+ self.visit_expression(value);
+ self.this_depth -= 1;
}
}
#[inline]
fn visit_accessor_property(&mut self, prop: &mut AccessorProperty<'a>) {
+ // Not possible that `self.scope_depth == 0` here, because an `AccessorProperty`
+ // can only be in a class, and that class would be the first-level scope.
+ // So no need to call `reparent_scope_if_first_level`.
+
// Treat `key` and `value` in same way as `visit_property_definition` above.
// TODO: Are decorators in scope?
self.visit_decorators(&mut prop.decorators);
@@ -402,17 +408,67 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> {
self.visit_property_key(&mut prop.key);
}
- if self.walk_deep {
- if let Some(value) = &mut prop.value {
- self.this_depth += 1;
- self.visit_expression(value);
- self.this_depth -= 1;
- }
- } else {
- // Not possible that `self.scope_depth == 0` here, because an `AccessorProperty`
- // can only be in a class, and that class would be the first-level scope
+ // `walk_deep` must be `true` or we couldn't get here, because an `AccessorProperty`
+ // must be in a class, and traversal would have stopped in `visit_class` if it wasn't
+ if let Some(value) = &mut prop.value {
+ self.this_depth += 1;
+ self.visit_expression(value);
+ self.this_depth -= 1;
}
}
+
+ // Remaining visitors are the only other types which have a scope which can be first-level
+ // when starting traversal from an `Expression`.
+ // `BlockStatement` and all other statements would need to be within a function,
+ // and that function would be the first-level scope.
+
+ #[inline]
+ fn visit_ts_conditional_type(&mut self, conditional: &mut TSConditionalType<'a>) {
+ self.reparent_scope_if_first_level(&conditional.scope_id);
+
+ // `check_type` field is outside `TSConditionalType`'s scope
+ self.visit_ts_type(&mut conditional.check_type);
+
+ self.enter_scope(ScopeFlags::empty(), &conditional.scope_id);
+
+ self.scope_depth += 1;
+ self.visit_ts_type(&mut conditional.extends_type);
+ self.visit_ts_type(&mut conditional.true_type);
+ self.scope_depth -= 1;
+
+ // `false_type` field is outside `TSConditionalType`'s scope
+ self.visit_ts_type(&mut conditional.false_type);
+ }
+
+ #[inline]
+ fn visit_ts_method_signature(&mut self, signature: &mut TSMethodSignature<'a>) {
+ self.reparent_scope_if_first_level(&signature.scope_id);
+
+ self.scope_depth += 1;
+ walk_mut::walk_ts_method_signature(self, signature);
+ self.scope_depth -= 1;
+ }
+
+ #[inline]
+ fn visit_ts_construct_signature_declaration(
+ &mut self,
+ signature: &mut TSConstructSignatureDeclaration<'a>,
+ ) {
+ self.reparent_scope_if_first_level(&signature.scope_id);
+
+ self.scope_depth += 1;
+ walk_mut::walk_ts_construct_signature_declaration(self, signature);
+ self.scope_depth -= 1;
+ }
+
+ #[inline]
+ fn visit_ts_mapped_type(&mut self, mapped: &mut TSMappedType<'a>) {
+ self.reparent_scope_if_first_level(&mapped.scope_id);
+
+ self.scope_depth += 1;
+ walk_mut::walk_ts_mapped_type(self, mapped);
+ self.scope_depth -= 1;
+ }
}
impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> {
@@ -455,10 +511,13 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> {
}
}
- /// Update parent of scope to scope above class.
- fn reparent_scope(&mut self, scope_id: ScopeId) {
- let current_scope_id = self.ctx.current_scope_id();
- self.ctx.scopes_mut().change_parent_id(scope_id, Some(current_scope_id));
+ /// Update parent of scope to scope above class if this is a first-level scope.
+ fn reparent_scope_if_first_level(&mut self, scope_id: &Cell >) {
+ if self.scope_depth == 0 {
+ let scope_id = scope_id.get().unwrap();
+ let current_scope_id = self.ctx.current_scope_id();
+ self.ctx.scopes_mut().change_parent_id(scope_id, Some(current_scope_id));
+ }
}
}