Skip to content

Commit

Permalink
feat(transformer/class-properties): transform super expressions and i…
Browse files Browse the repository at this point in the history
…dentifiers that refers to class binding in private method
  • Loading branch information
Dunqing committed Dec 25, 2024
1 parent cf82e06 commit 8f5e0ee
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
//! ES2022: Class Properties
//! Transform of private method uses e.g. `this.#method()`.
use oxc_ast::ast::{Argument, Expression, FunctionType, MethodDefinition, PropertyKey, Statement};
use oxc_ast::{ast::*, visit::walk_mut, VisitMut};
use oxc_semantic::ScopeFlags;
use oxc_span::SPAN;
use oxc_traverse::TraverseCtx;

use crate::Helper;

use super::ClassProperties;
use super::{
super_converter::{ClassPropertiesSuperConverter, ClassPropertiesSuperConverterMode},
ClassProperties,
};

impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
/// Convert method definition where the key is a private identifier and
Expand Down Expand Up @@ -47,10 +50,17 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
function.id = Some(temp_binding.create_binding_identifier(ctx));
function.r#type = FunctionType::FunctionDeclaration;

// Change parent scope of function to current scope id and remove
// strict mode flag if parent scope is not strict mode.
let scope_id = function.scope_id();
let new_parent_id = ctx.current_block_scope_id();
let new_parent_id = ctx.current_scope_id();
ctx.scopes_mut().change_parent_id(scope_id, Some(new_parent_id));
*ctx.scopes_mut().get_flags_mut(scope_id) -= ScopeFlags::StrictMode;
if !ctx.current_scope_flags().is_strict_mode() {
*ctx.scopes_mut().get_flags_mut(scope_id) -= ScopeFlags::StrictMode;
}

PrivateMethodVisitor::new(method.r#static, self, ctx)
.visit_function(&mut function, ScopeFlags::Function);

let function = ctx.ast.alloc(function);
self.insert_after_stmts.push(Statement::FunctionDeclaration(function));
Expand All @@ -71,3 +81,76 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
self.ctx.helper_call_expr(Helper::ClassPrivateMethodInitSpec, SPAN, arguments, ctx)
}
}

/// Visitor to transform:
///
/// Almost the same as `super::static_block_and_prop_init::StaticVisitor`,
/// but only does following:
///
/// 1. Reference to class name to class temp var.
/// 2. Transform `super` expressions.
struct PrivateMethodVisitor<'a, 'ctx, 'v> {
super_converter: ClassPropertiesSuperConverter<'a, 'ctx, 'v>,
/// `TraverseCtx` object.
ctx: &'v mut TraverseCtx<'a>,
}

impl<'a, 'ctx, 'v> PrivateMethodVisitor<'a, 'ctx, 'v> {
fn new(
r#static: bool,
class_properties: &'v mut ClassProperties<'a, 'ctx>,
ctx: &'v mut TraverseCtx<'a>,
) -> Self {
let mode = if r#static {
ClassPropertiesSuperConverterMode::StaticPrivateMethod
} else {
ClassPropertiesSuperConverterMode::PrivateMethod
};
Self { super_converter: ClassPropertiesSuperConverter::new(mode, class_properties), ctx }
}
}

impl<'a, 'ctx, 'v> VisitMut<'a> for PrivateMethodVisitor<'a, 'ctx, 'v> {
#[inline]
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
match expr {
// `super.prop`
Expression::StaticMemberExpression(_) => {
self.super_converter.transform_static_member_expression(expr, self.ctx);
}
// `super[prop]`
Expression::ComputedMemberExpression(_) => {
self.super_converter.transform_computed_member_expression(expr, self.ctx);
}
// `super.prop()`
Expression::CallExpression(call_expr) => {
self.super_converter
.transform_call_expression_for_super_member_expr(call_expr, self.ctx);
}
// `super.prop = value`, `super.prop += value`, `super.prop ??= value`
Expression::AssignmentExpression(_) => {
self.super_converter
.transform_assignment_expression_for_super_assignment_target(expr, self.ctx);
}
// `super.prop++`, `--super.prop`
Expression::UpdateExpression(_) => {
self.super_converter
.transform_update_expression_for_super_assignment_target(expr, self.ctx);
}
_ => {}
}
walk_mut::walk_expression(self, expr);
}

/// Transform reference to class name to temp var
fn visit_identifier_reference(&mut self, ident: &mut IdentifierReference<'a>) {
self.super_converter.class_properties.replace_class_name_with_temp_var(ident, self.ctx);
}

#[inline]
fn visit_class(&mut self, _class: &mut Class<'a>) {
// Ignore because we don't need to transform `super` for other classes.
}
}

impl<'a, 'ctx, 'v> PrivateMethodVisitor<'a, 'ctx, 'v> {}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}

/// Replace reference to class name with reference to temp var for class.
fn replace_class_name_with_temp_var(
pub(super) fn replace_class_name_with_temp_var(
&mut self,
ident: &mut IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@ use oxc_traverse::{ast_operations::get_var_name_from_node, TraverseCtx};

use crate::Helper;

use super::{utils::create_assignment, ClassProperties};
use super::{
utils::{create_assignment, create_prototype_member},
ClassProperties,
};

#[derive(Debug)]
pub(super) enum ClassPropertiesSuperConverterMode {
// `static prop` or `static {}`
Static,
// `#method() {}`
PrivateMethod,
// `static #method() {}`
StaticPrivateMethod,
}

/// Convert `super` expressions.
pub(super) struct ClassPropertiesSuperConverter<'a, 'ctx, 'v> {
#[expect(unused)]
mode: ClassPropertiesSuperConverterMode,
pub(super) class_properties: &'v mut ClassProperties<'a, 'ctx>,
}
Expand Down Expand Up @@ -557,20 +563,16 @@ impl<'a, 'ctx, 'v> ClassPropertiesSuperConverter<'a, 'ctx, 'v> {
is_callee: bool,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let temp_binding =
self.class_properties.current_class_mut().bindings.get_or_init_static_binding(ctx);

let ident1 = Argument::from(temp_binding.create_read_expression(ctx));
let ident2 = Argument::from(temp_binding.create_read_expression(ctx));
let (class, receiver) = self.get_class_binding_arguments(ctx);
let property = Argument::from(property);

let arguments = if is_callee {
// `(_Class, prop, _Class, 2)`
let two = ctx.ast.expression_numeric_literal(SPAN, 2.0, None, NumberBase::Decimal);
ctx.ast.vec_from_array([ident1, property, ident2, Argument::from(two)])
ctx.ast.vec_from_array([class, property, receiver, Argument::from(two)])
} else {
// `(_Class, prop, _Class)`
ctx.ast.vec_from_array([ident1, property, ident2])
ctx.ast.vec_from_array([class, property, receiver])
};

// `_superPropGet(_Class, prop, _Class)` or `_superPropGet(_Class, prop, _Class, 2)`
Expand All @@ -585,13 +587,12 @@ impl<'a, 'ctx, 'v> ClassPropertiesSuperConverter<'a, 'ctx, 'v> {
value: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let temp_binding =
self.class_properties.current_class_mut().bindings.get_or_init_static_binding(ctx);
let (class, receiver) = self.get_class_binding_arguments(ctx);
let arguments = ctx.ast.vec_from_array([
Argument::from(temp_binding.create_read_expression(ctx)),
class,
Argument::from(property),
Argument::from(value),
Argument::from(temp_binding.create_read_expression(ctx)),
receiver,
Argument::from(ctx.ast.expression_numeric_literal(
SPAN,
1.0,
Expand All @@ -601,4 +602,34 @@ impl<'a, 'ctx, 'v> ClassPropertiesSuperConverter<'a, 'ctx, 'v> {
]);
self.class_properties.ctx.helper_call_expr(Helper::SuperPropSet, span, arguments, ctx)
}

/// * [`ClassPropertiesSuperConverterMode::Static`]
/// (_Class, _Class)
///
/// * [`ClassPropertiesSuperConverterMode::PrivateMethod`]
/// (_Class.prototype, this)
///
/// * [`ClassPropertiesSuperConverterMode::StaticPrivateMethod`]
/// (_Class, this)
fn get_class_binding_arguments(
&mut self,
ctx: &mut TraverseCtx<'a>,
) -> (Argument<'a>, Argument<'a>) {
let temp_binding =
self.class_properties.current_class_mut().bindings.get_or_init_static_binding(ctx);
let mut class = temp_binding.create_read_expression(ctx);
let receiver = match self.mode {
ClassPropertiesSuperConverterMode::Static => temp_binding.create_read_expression(ctx),
ClassPropertiesSuperConverterMode::PrivateMethod => {
// TODO(improve-on-babel): `superPropGet` and `superPropSet` helper function has a flag
// to use `class.prototype` rather than `class`. We should consider using that flag here.
// <https://github.com/babel/babel/blob/1fbdb64a7fcc3488797e312506dbacff746d4e41/packages/babel-helpers/src/helpers/superPropGet.ts>
class = create_prototype_member(class, ctx);
ctx.ast.expression_this(SPAN)
}
ClassPropertiesSuperConverterMode::StaticPrivateMethod => ctx.ast.expression_this(SPAN),
};

(Argument::from(class), Argument::from(receiver))
}
}
10 changes: 10 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,13 @@ pub(super) fn debug_assert_expr_is_not_parenthesis_or_typescript_syntax(
"Should not be: {expr:?} in {path:?}",
);
}

/// `object` -> `object.prototype`.
pub(super) fn create_prototype_member<'a>(
object: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let property = ctx.ast.identifier_name(SPAN, Atom::from("prototype"));
let static_member = ctx.ast.member_expression_static(SPAN, object, property, false);
Expression::from(static_member)
}
7 changes: 2 additions & 5 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 54a8389f

Passed: 630/1095
Passed: 631/1095

# All Passed:
* babel-plugin-transform-logical-assignment-operators
Expand Down Expand Up @@ -462,7 +462,7 @@ x Output mismatch
x Output mismatch


# babel-plugin-transform-private-methods (15/148)
# babel-plugin-transform-private-methods (16/148)
* accessors/arguments/input.js
x Output mismatch

Expand Down Expand Up @@ -565,9 +565,6 @@ x Output mismatch
* misc/multiple/input.js
x Output mismatch

* private-method/class-binding/input.js
x Output mismatch

* private-method/class-expression/input.js
x Output mismatch

Expand Down
Loading

0 comments on commit 8f5e0ee

Please sign in to comment.