diff --git a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs index 3576b775f..4e94db22e 100644 --- a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs +++ b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs @@ -78,17 +78,18 @@ impl<'a, 'ctx> Traverse<'a> for ExponentiationOperator<'a, 'ctx> { AssignmentTarget::AssignmentTargetIdentifier(_) => { self.convert_identifier_assignment(expr, ctx); } - // Note: We do not match `AssignmentTarget::PrivateFieldExpression` here. - // From Babel: "We can't generate property ref for private name, please install - // `@babel/plugin-transform-class-properties`". - // TODO: Ensure this plugin interacts correctly with class private properties - // transform, so the property is transformed before this transform. AssignmentTarget::StaticMemberExpression(_) => { self.convert_static_member_expression_assignment(expr, ctx); } AssignmentTarget::ComputedMemberExpression(_) => { self.convert_computed_member_expression_assignment(expr, ctx); } + // Babel refuses to transform this: "We can't generate property ref for private name, + // please install `@babel/plugin-transform-class-properties`". + // But there's no reason not to. + AssignmentTarget::PrivateFieldExpression(_) => { + self.convert_private_field_assignment(expr, ctx); + } _ => {} } } @@ -364,6 +365,82 @@ impl<'a, 'ctx> ExponentiationOperator<'a, 'ctx> { (pow_left, temp_var_inits) } + /// Convert `AssignmentExpression` where assignee is a private field member expression. + /// + /// `obj.#prop **= right` transformed to: + /// * If `obj` is a bound symbol: + /// -> `obj.#prop = Math.pow(obj.#prop, right)` + /// * If `obj` is unbound: + /// -> `var _obj; _obj = obj, _obj.#prop = Math.pow(_obj.#prop, right)` + /// + /// `obj.foo.bar.#qux **= right` transformed to: + /// ```js + /// var _obj$foo$bar; + /// _obj$foo$bar = obj.foo.bar, _obj$foo$bar.#qux = Math.pow(_obj$foo$bar.#qux, right) + /// ``` + /// + /// Temporary variable is to avoid side-effects of getting `obj` / `obj.foo.bar` being run twice. + // + // `#[inline]` so compiler knows `expr` is an `AssignmentExpression` with `PrivateFieldExpression` on left + #[inline] + fn convert_private_field_assignment( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() }; + let AssignmentTarget::PrivateFieldExpression(member_expr) = &mut assign_expr.left else { + unreachable!() + }; + + let (pow_left, temp_var_inits) = self.get_pow_left_private_field(member_expr, ctx); + Self::convert_assignment(assign_expr, pow_left, ctx); + Self::revise_expression(expr, temp_var_inits, ctx); + } + + /// Get left side of `Math.pow(pow_left, ...)` for static member expression + /// and replacement for left side of assignment. + fn get_pow_left_private_field( + &mut self, + field_expr: &mut PrivateFieldExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> ( + // Left side of `Math.pow(pow_left, ...)` + Expression<'a>, + // Temporary var initializations + Vec<'a, Expression<'a>>, + ) { + // Object part of 2nd member expression + // ``` + // obj.#prop = Math.pow(obj.#prop, right) + // ^^^ + // ``` + let mut temp_var_inits = ctx.ast.vec(); + let obj = self.get_second_member_expression_object( + &mut field_expr.object, + &mut temp_var_inits, + ctx, + ); + + // Property part of 2nd member expression + // ``` + // obj.#prop = Math.pow(obj.#prop, right) + // ^^^^^ + // ``` + let field = field_expr.field.clone_in(ctx.ast.allocator); + + // Complete 2nd member expression + // ``` + // obj.#prop = Math.pow(obj.#prop, right) + // ^^^^^^^^^ + // ``` + let pow_left = Expression::from( + ctx.ast.member_expression_private_field_expression(SPAN, obj, field, false), + ); + + (pow_left, temp_var_inits) + } + /// Get object part of 2nd member expression to be used as `left` in `Math.pow(left, right)`. /// /// Also update the original `obj` passed in to function, and add a temp var initializer, if necessary. diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/bail-private-properties/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/bail-private-properties/input.js deleted file mode 100644 index b61b2daf9..000000000 --- a/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/bail-private-properties/input.js +++ /dev/null @@ -1,9 +0,0 @@ -class C { - #p = 1; - - method(obj) { - obj.x **= 2; // Transform - obj['y'] **= 3; // Transform - obj.#p **= 4; // Bail - } -} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/bail-private-properties/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/bail-private-properties/output.js deleted file mode 100644 index def6e695e..000000000 --- a/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/bail-private-properties/output.js +++ /dev/null @@ -1,9 +0,0 @@ -class C { - #p = 1; - - method(obj) { - obj["x"] = Math.pow(obj["x"], 2); - obj["y"] = Math.pow(obj["y"], 3); - obj.#p **= 4; - } -} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/private-properties/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/private-properties/input.js new file mode 100644 index 000000000..edd5e6d50 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/private-properties/input.js @@ -0,0 +1,11 @@ +class C { + #p = 1; + + method(obj) { + this.#p **= 1; + obj.#p **= 2; + this.x.y.z.#p **= 3; + obj.x.y.z.#p **= 4; + fn().x.y.z.#p **= 5; + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/private-properties/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/private-properties/output.js new file mode 100644 index 000000000..6ea277ca6 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-exponentiation-operator/test/fixtures/private-properties/output.js @@ -0,0 +1,12 @@ +class C { + #p = 1; + + method(obj) { + var _this, _this$x$y$z, _obj$x$y$z, _fn$x$y$z; + _this = this, _this.#p = Math.pow(_this.#p, 1); + obj.#p = Math.pow(obj.#p, 2); + _this$x$y$z = this.x.y.z, _this$x$y$z.#p = Math.pow(_this$x$y$z.#p, 3); + _obj$x$y$z = obj.x.y.z, _obj$x$y$z.#p = Math.pow(_obj$x$y$z.#p, 4); + _fn$x$y$z = fn().x.y.z, _fn$x$y$z.#p = Math.pow(_fn$x$y$z.#p, 5); + } +}