Skip to content

Commit

Permalink
fix(transformer/class-properties): fix ScopeIds in instance prop in…
Browse files Browse the repository at this point in the history
…itializers (#7823)

Code in instance property initializers moves from class body into constructor, or a `_super` function. Update parent `ScopeId`s for first level scopes in initializers.
  • Loading branch information
overlookmotel committed Dec 13, 2024
1 parent e70deb9 commit 25bb6da
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 260 deletions.
5 changes: 4 additions & 1 deletion crates/oxc_transformer/src/es2022/class_properties/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,10 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
) {
// Get value
let value = match &mut prop.value {
Some(value) => ctx.ast.move_expression(value),
Some(value) => {
self.transform_instance_initializer(value, ctx);
ctx.ast.move_expression(value)
}
None => ctx.ast.void_0(SPAN),
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! ES2022: Class Properties
//! Transform of instance property initializers.
use std::cell::Cell;

use oxc_ast::{ast::*, visit::VisitMut};
use oxc_syntax::scope::{ScopeFlags, ScopeId};
use oxc_traverse::TraverseCtx;

use super::ClassProperties;

impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
/// Transform instance property initializer.
///
/// Instance property initializers move from the class body into either class constructor,
/// or a `_super` function. Change parent scope of first-level scopes in initializer to reflect this.
pub(super) fn transform_instance_initializer(
&mut self,
value: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let mut updater = InstanceInitializerVisitor::new(self, ctx);
updater.visit_expression(value);
}
}

/// Visitor to change parent scope of first-level scopes in instance property initializer.
struct InstanceInitializerVisitor<'a, 'v> {
/// Incremented when entering a scope, decremented when exiting it.
/// Parent `ScopeId` should be updated when `scope_depth == 0`.
scope_depth: u32,
/// Parent scope
parent_scope_id: ScopeId,
/// `TraverseCtx` object.
ctx: &'v mut TraverseCtx<'a>,
}

impl<'a, 'v> InstanceInitializerVisitor<'a, 'v> {
fn new(
class_properties: &'v mut ClassProperties<'a, '_>,
ctx: &'v mut TraverseCtx<'a>,
) -> Self {
let parent_scope_id = class_properties.instance_inits_scope_id;
Self { scope_depth: 0, parent_scope_id, ctx }
}
}

impl<'a, 'v> VisitMut<'a> for InstanceInitializerVisitor<'a, 'v> {
/// Update parent scope for first level of scopes.
/// Convert scope to sloppy mode if `self.make_sloppy_mode == true`.
fn enter_scope(&mut self, _flags: ScopeFlags, scope_id: &Cell<Option<ScopeId>>) {
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;
}

fn leave_scope(&mut self) {
self.scope_depth -= 1;
}
}

impl<'a, 'v> InstanceInitializerVisitor<'a, 'v> {
/// Update parent of scope to scope above class.
fn reparent_scope(&mut self, scope_id: ScopeId) {
self.ctx.scopes_mut().change_parent_id(scope_id, Some(self.parent_scope_id));
}
}
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
//! * `constructor.rs`: Insertion of property initializers into class constructor.
//! * `private.rs`: Transform of private property usages (`this.#prop`).
//! * `private_props.rs`: Structures storing details of private properties.
//! * `instance_prop_init.rs`: Transform of instance property initializers.
//! * `static_prop_init.rs`: Transform of static property initializers.
//! * `class_bindings.rs`: Structure containing bindings for class name and temp var.
//! * `super.rs`: Transform `super` expressions.
Expand Down Expand Up @@ -158,6 +159,7 @@ use crate::TransformCtx;
mod class;
mod class_bindings;
mod constructor;
mod instance_prop_init;
mod private;
mod private_props;
mod static_prop_init;
Expand Down
Loading

0 comments on commit 25bb6da

Please sign in to comment.