From cc57db38e1fadbb14dfdf44bd257fd784b447465 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:28:39 +0000 Subject: [PATCH] feat(transformer/class-properties): transform super assignment expressions within static prop initializer (#7991) Alternative of #7956 and #7959. Unlike the previous method, adapting duplicating the same logic rather than making the same logic transform function to be generic --- .../src/common/helper_loader.rs | 2 + .../es2022/class_properties/private_field.rs | 2 +- .../class_properties/static_prop_init.rs | 18 +- .../src/es2022/class_properties/supers.rs | 170 ++++++++++++++++++ .../snapshots/oxc.snap.md | 4 +- .../input.js | 50 ++++++ .../output.js | 53 ++++++ 7 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/output.js diff --git a/crates/oxc_transformer/src/common/helper_loader.rs b/crates/oxc_transformer/src/common/helper_loader.rs index cefbe44f6a35e..eb8218e21e67f 100644 --- a/crates/oxc_transformer/src/common/helper_loader.rs +++ b/crates/oxc_transformer/src/common/helper_loader.rs @@ -159,6 +159,7 @@ pub enum Helper { ClassPrivateFieldLooseKey, ClassPrivateFieldLooseBase, SuperPropGet, + SuperPropSet, } impl Helper { @@ -183,6 +184,7 @@ impl Helper { Self::ClassPrivateFieldLooseKey => "classPrivateFieldLooseKey", Self::ClassPrivateFieldLooseBase => "classPrivateFieldLooseBase", Self::SuperPropGet => "superPropGet", + Self::SuperPropSet => "superPropSet", } } } diff --git a/crates/oxc_transformer/src/es2022/class_properties/private_field.rs b/crates/oxc_transformer/src/es2022/class_properties/private_field.rs index cd2f257c8d780..0e7c5ae86b8d5 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/private_field.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/private_field.rs @@ -1608,7 +1608,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { /// * Anything else `foo()` -> `_foo = foo()`, `_foo` /// /// Returns 2 `Expression`s. The first must be inserted into output first. - fn duplicate_object( + pub(super) fn duplicate_object( &self, object: Expression<'a>, ctx: &mut TraverseCtx<'a>, 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 c8024dc6a39a1..2d85e95983820 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 @@ -195,9 +195,15 @@ impl<'a, 'ctx, 'v> VisitMut<'a> for StaticInitializerVisitor<'a, 'ctx, 'v> { self.transform_call_expression_if_super_member_expression(call_expr); self.class_properties.transform_call_expression(expr, self.ctx); } + // `super.prop = value`, `super.prop += value`, `super.prop ??= value` or // `object.#prop = value`, `object.#prop += value`, `object.#prop ??= value` etc Expression::AssignmentExpression(_) => { - self.class_properties.transform_assignment_expression(expr, self.ctx); + self.transform_assignment_expression_if_super_member_assignment_target(expr); + // Check again if it's an assignment expression, because it could have been transformed + // to other expression. + if matches!(expr, Expression::AssignmentExpression(_)) { + self.class_properties.transform_assignment_expression(expr, self.ctx); + } } // `object.#prop++`, `--object.#prop` Expression::UpdateExpression(_) => { @@ -521,4 +527,14 @@ impl<'a, 'ctx, 'v> StaticInitializerVisitor<'a, 'ctx, 'v> { .transform_call_expression_for_super_member_expr(call_expr, self.ctx); } } + + fn transform_assignment_expression_if_super_member_assignment_target( + &mut self, + expr: &mut Expression<'a>, + ) { + if self.this_depth == 0 { + self.class_properties + .transform_assignment_expression_for_super_assignment_target(expr, self.ctx); + } + } } diff --git a/crates/oxc_transformer/src/es2022/class_properties/supers.rs b/crates/oxc_transformer/src/es2022/class_properties/supers.rs index cbeabac882dc4..014bbde06ebd7 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/supers.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/supers.rs @@ -125,6 +125,152 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { arguments.push(Argument::from(array)); } + /// Transform assignment expression where the left-hand side is a member expression with `super`. + /// + /// * `super.prop = value` + /// -> `_superPropSet(_Class, "prop", value, _Class, 1)` + /// * `super.prop += value` + /// -> `_superPropSet(_Class, "prop", _superPropGet(_Class, "prop", _Class) + value, _Class, 1)` + /// * `super.prop &&= value` + /// -> `_superPropGet(_Class, "prop", _Class) && _superPropSet(_Class, "prop", value, _Class, 1)` + /// + /// * `super[prop] = value` + /// -> `_superPropSet(_Class, prop, value, _Class, 1)` + /// * `super[prop] += value` + /// -> `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, _Class, 1)` + /// * `super[prop] &&= value` + /// -> `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)` + // + // `#[inline]` so can bail out fast without a function call if `left` is not a member expression + // with `super` as member expression object (fairly rare). + // Actual transform is broken out into separate functions. + pub(super) fn transform_assignment_expression_for_super_assignment_target( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() }; + match &assign_expr.left { + AssignmentTarget::StaticMemberExpression(member) if member.object.is_super() => { + self.transform_assignment_expression_for_super_static_member_expr(expr, ctx); + } + AssignmentTarget::ComputedMemberExpression(member) if member.object.is_super() => { + self.transform_assignment_expression_for_super_computed_member_expr(expr, ctx); + } + _ => {} + }; + } + + /// Transform assignment expression where the left-hand side is a static member expression + /// with `super`. + /// + /// * `super.prop = value` + /// -> `_superPropSet(_Class, "prop", value, _Class, 1)` + /// * `super.prop += value` + /// -> `_superPropSet(_Class, "prop", _superPropGet(_Class, "prop", _Class) + value, _Class, 1)` + /// * `super.prop &&= value` + /// -> `_superPropGet(_Class, "prop", _Class) && _superPropSet(_Class, "prop", value, _Class, 1)` + // + // `#[inline]` so that compiler sees that `expr` is an `Expression::AssignmentExpression`. + #[inline] + fn transform_assignment_expression_for_super_static_member_expr( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::AssignmentExpression(assign_expr) = ctx.ast.move_expression(expr) else { + unreachable!() + }; + let AssignmentExpression { span, operator, right: value, left } = assign_expr.unbox(); + let AssignmentTarget::StaticMemberExpression(member) = left else { unreachable!() }; + let property = ctx.ast.expression_string_literal( + member.property.span, + member.property.name.clone(), + None, + ); + *expr = + self.transform_super_assignment_expression_impl(span, operator, property, value, ctx); + } + + /// Transform assignment expression where the left-hand side is a computed member expression + /// with `super` as member expr object. + /// + /// * `super[prop] = value` + /// -> `_superPropSet(_Class, prop, value, _Class, 1)` + /// * `super[prop] += value` + /// -> `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, _Class, 1)` + /// * `super[prop] &&= value` + /// -> `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)` + /// + // `#[inline]` so that compiler sees that `expr` is an `Expression::AssignmentExpression`. + #[inline] + fn transform_assignment_expression_for_super_computed_member_expr( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::AssignmentExpression(assign_expr) = ctx.ast.move_expression(expr) else { + unreachable!() + }; + let AssignmentExpression { span, operator, right: value, left } = assign_expr.unbox(); + let AssignmentTarget::ComputedMemberExpression(member) = left else { unreachable!() }; + let property = member.unbox().expression; + *expr = + self.transform_super_assignment_expression_impl(span, operator, property, value, ctx); + } + + /// Transform assignment expression where the left-hand side is a member expression with `super` + /// as member expr object. + /// + /// * `=` -> `_superPropSet(_Class, , , _Class, 1)` + /// * `+=` -> `_superPropSet(_Class, , _superPropGet(_Class, , _Class) + , 1)` + /// * `&&=` -> `_superPropGet(_Class, , _Class) && _superPropSet(_Class, , , _Class, 1)` + fn transform_super_assignment_expression_impl( + &mut self, + span: Span, + operator: AssignmentOperator, + property: Expression<'a>, + value: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + if operator == AssignmentOperator::Assign { + // `super[prop] = value` -> `_superPropSet(_Class, prop, value, _Class, 1)` + self.create_super_prop_set(span, property, value, ctx) + } else { + // Make 2 copies of `object` + let (property1, property2) = self.duplicate_object(property, ctx); + + if let Some(operator) = operator.to_binary_operator() { + // `super[prop] += value` + // -> `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, _Class, 1)` + + // `_superPropGet(_Class, prop, _Class)` + let get_call = self.create_super_prop_get(SPAN, property2, false, ctx); + + // `_superPropGet(_Class, prop, _Class) + value` + let value = ctx.ast.expression_binary(SPAN, get_call, operator, value); + + // `_superPropSet(_Class, prop, _superPropGet(_Class, prop, _Class) + value, 1)` + self.create_super_prop_set(span, property1, value, ctx) + } else if let Some(operator) = operator.to_logical_operator() { + // `super[prop] &&= value` + // -> `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)` + + // `_superPropGet(_Class, prop, _Class)` + let get_call = self.create_super_prop_get(SPAN, property1, false, ctx); + + // `_superPropSet(_Class, prop, value, _Class, 1)` + let set_call = self.create_super_prop_set(span, property2, value, ctx); + + // `_superPropGet(_Class, prop, _Class) && _superPropSet(_Class, prop, value, _Class, 1)` + ctx.ast.expression_logical(span, get_call, operator, set_call) + } else { + // The above covers all types of `AssignmentOperator` + unreachable!(); + } + } + } + /// Member: /// `_superPropGet(_Class, prop, _Class)` /// @@ -155,4 +301,28 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // `_superPropGet(_Class, prop, _Class)` or `_superPropGet(_Class, prop, _Class, 2)` self.ctx.helper_call_expr(Helper::SuperPropGet, span, arguments, ctx) } + + /// `_superPropSet(_Class, prop, value, _Class, 1)` + fn create_super_prop_set( + &mut self, + span: Span, + property: Expression<'a>, + value: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + let temp_binding = self.current_class_mut().bindings.get_or_init_static_binding(ctx); + let arguments = ctx.ast.vec_from_array([ + Argument::from(temp_binding.create_read_expression(ctx)), + Argument::from(property), + Argument::from(value), + Argument::from(temp_binding.create_read_expression(ctx)), + Argument::from(ctx.ast.expression_numeric_literal( + SPAN, + 1.0, + None, + NumberBase::Decimal, + )), + ]); + self.ctx.helper_call_expr(Helper::SuperPropSet, span, arguments, ctx) + } } diff --git a/tasks/transform_conformance/snapshots/oxc.snap.md b/tasks/transform_conformance/snapshots/oxc.snap.md index f99f561be09c5..1a266bed558b9 100644 --- a/tasks/transform_conformance/snapshots/oxc.snap.md +++ b/tasks/transform_conformance/snapshots/oxc.snap.md @@ -1,6 +1,6 @@ commit: 54a8389f -Passed: 112/127 +Passed: 113/128 # All Passed: * babel-plugin-transform-class-static-block @@ -16,7 +16,7 @@ Passed: 112/127 * regexp -# babel-plugin-transform-class-properties (13/16) +# babel-plugin-transform-class-properties (14/17) * static-super-tagged-template/input.js x Output mismatch diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/input.js new file mode 100644 index 0000000000000..e0cfeb2ab38a3 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/input.js @@ -0,0 +1,50 @@ +const ident = "A"; + +class Outer { + static B = () => { + // Transform + super.A = 1; + super.A += 1; + super.A -= 1; + super.A &&= 1; + super.A ||= 1; + + super[ident] = 1; + super[ident] += 1; + super[ident] -= 1; + super[ident] &&= 1; + super[ident] ||= 1; + + class Inner { + method() { + // Don't transform + super.A = 1; + super.A += 1; + super.A -= 1; + super.A &&= 1; + super.A ||= 1; + + super[ident] = 1; + super[ident] += 1; + super[ident] -= 1; + super[ident] &&= 1; + super[ident] ||= 1; + } + + static staticMethod() { + // Don't transform + super.A = 1; + super.A += 1; + super.A -= 1; + super.A &&= 1; + super.A ||= 1; + + super[ident] = 1; + super[ident] += 1; + super[ident] -= 1; + super[ident] &&= 1; + super[ident] ||= 1; + } + } + }; +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/output.js new file mode 100644 index 0000000000000..45084bc8fa9d5 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-class-properties/test/fixtures/static-super-assignment-expression/output.js @@ -0,0 +1,53 @@ +var _Outer; + +const ident = "A"; + +class Outer {} + +_Outer = Outer; +babelHelpers.defineProperty(Outer, "B", () => { + // Transform + babelHelpers.superPropSet(_Outer, "A", 1, _Outer, 1); + babelHelpers.superPropSet(_Outer, "A", babelHelpers.superPropGet(_Outer, "A", _Outer) + 1, _Outer, 1); + babelHelpers.superPropSet(_Outer, "A", babelHelpers.superPropGet(_Outer, "A", _Outer) - 1, _Outer, 1); + babelHelpers.superPropGet(_Outer, "A", _Outer) && babelHelpers.superPropSet(_Outer, "A", 1, _Outer, 1); + babelHelpers.superPropGet(_Outer, "A", _Outer) || babelHelpers.superPropSet(_Outer, "A", 1, _Outer, 1); + + babelHelpers.superPropSet(_Outer, ident, 1, _Outer, 1); + babelHelpers.superPropSet(_Outer, ident, babelHelpers.superPropGet(_Outer, ident, _Outer) + 1, _Outer, 1); + babelHelpers.superPropSet(_Outer, ident, babelHelpers.superPropGet(_Outer, ident, _Outer) - 1, _Outer, 1); + babelHelpers.superPropGet(_Outer, ident, _Outer) && babelHelpers.superPropSet(_Outer, ident, 1, _Outer, 1); + babelHelpers.superPropGet(_Outer, ident, _Outer) || babelHelpers.superPropSet(_Outer, ident, 1, _Outer, 1); + + class Inner { + method() { + // Don't transform + super.A = 1; + super.A += 1; + super.A -= 1; + super.A &&= 1; + super.A ||= 1; + + super[ident] = 1; + super[ident] += 1; + super[ident] -= 1; + super[ident] &&= 1; + super[ident] ||= 1; + } + + static staticMethod() { + // Don't transform + super.A = 1; + super.A += 1; + super.A -= 1; + super.A &&= 1; + super.A ||= 1; + + super[ident] = 1; + super[ident] += 1; + super[ident] -= 1; + super[ident] &&= 1; + super[ident] ||= 1; + } + } +});