mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(transformer): exponentiation transform: support private fields (#6345)
Babel doesn't support private fields in this transform, but there's no reason not to. So we can.
This commit is contained in:
parent
bd5fb5ab5c
commit
cf20f3a89d
5 changed files with 105 additions and 23 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
class C {
|
||||
#p = 1;
|
||||
|
||||
method(obj) {
|
||||
obj.x **= 2; // Transform
|
||||
obj['y'] **= 3; // Transform
|
||||
obj.#p **= 4; // Bail
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue