Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transformer/class-properties): transform super expressions and identifiers that refers to class binding in private method #8106

Draft
wants to merge 1 commit into
base: 12-25-refactor_transformer_class-properties_share_replace_class_name_with_temp_var_in_class_properties
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading