use oxc_allocator::{CloneIn, TakeIn, Vec as ArenaVec};
use oxc_ast::{NONE, ast::*};
use oxc_semantic::ReferenceFlags;
use oxc_span::{SPAN, Span};
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator};
use oxc_traverse::{BoundIdentifier, Traverse};
use crate::{
common::var_declarations::VarDeclarationsStore, context::TraverseCtx, state::TransformState,
};
pub struct ExponentiationOperator<'a> {
_marker: std::marker::PhantomData<&'a ()>,
}
impl ExponentiationOperator<'_> {
pub fn new() -> Self {
Self { _marker: std::marker::PhantomData }
}
}
impl<'a> Traverse<'a, TransformState<'a>> for ExponentiationOperator<'a> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
match expr {
Expression::BinaryExpression(binary_expr) => {
if binary_expr.operator != BinaryOperator::Exponential
|| binary_expr.left.is_big_int_literal()
|| binary_expr.right.is_big_int_literal()
{
return;
}
Self::convert_binary_expression(expr, ctx);
}
Expression::AssignmentExpression(assign_expr) => {
if assign_expr.operator != AssignmentOperator::Exponential
|| assign_expr.right.is_big_int_literal()
{
return;
}
match &assign_expr.left {
AssignmentTarget::AssignmentTargetIdentifier(_) => {
self.convert_identifier_assignment(expr, ctx);
}
AssignmentTarget::StaticMemberExpression(_) => {
self.convert_static_member_expression_assignment(expr, ctx);
}
AssignmentTarget::ComputedMemberExpression(_) => {
self.convert_computed_member_expression_assignment(expr, ctx);
}
AssignmentTarget::PrivateFieldExpression(_) => {
self.convert_private_field_assignment(expr, ctx);
}
_ => {}
}
}
_ => {}
}
}
}
impl<'a> ExponentiationOperator<'a> {
#[inline]
fn convert_binary_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let binary_expr = match expr.take_in(ctx.ast) {
Expression::BinaryExpression(binary_expr) => binary_expr.unbox(),
_ => unreachable!(),
};
*expr = Self::math_pow(binary_expr.span, binary_expr.left, binary_expr.right, ctx);
}
#[inline]
fn convert_identifier_assignment(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() };
let span = assign_expr.span;
let AssignmentTarget::AssignmentTargetIdentifier(ident) = &mut assign_expr.left else {
unreachable!()
};
let (pow_left, temp_var_inits) = self.get_pow_left_identifier(ident, ctx);
Self::convert_assignment(assign_expr, pow_left, ctx);
Self::revise_expression(expr, temp_var_inits, span, ctx);
}
fn get_pow_left_identifier(
&self,
ident: &IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (
// Left side of `Math.pow(pow_left, ...)`
Expression<'a>,
// Temporary var initializations
ArenaVec<'a, Expression<'a>>,
) {
let mut temp_var_inits = ctx.ast.vec();
let reference = ctx.scoping.scoping_mut().get_reference_mut(ident.reference_id());
*reference.flags_mut() = ReferenceFlags::Write;
let pow_left = if let Some(symbol_id) = reference.symbol_id() {
ctx.create_bound_ident_expr(SPAN, ident.name, symbol_id, ReferenceFlags::Read)
} else {
let reference = ctx.create_unbound_ident_expr(SPAN, ident.name, ReferenceFlags::Read);
let binding = self.create_temp_var(reference, &mut temp_var_inits, ctx);
binding.create_read_expression(ctx)
};
(pow_left, temp_var_inits)
}
#[inline]
fn convert_static_member_expression_assignment(
&self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() };
let span = assign_expr.span;
let AssignmentTarget::StaticMemberExpression(member_expr) = &mut assign_expr.left else {
unreachable!()
};
let (replacement_left, pow_left, temp_var_inits) =
self.get_pow_left_static_member(member_expr, ctx);
assign_expr.left = replacement_left;
Self::convert_assignment(assign_expr, pow_left, ctx);
Self::revise_expression(expr, temp_var_inits, span, ctx);
}
fn get_pow_left_static_member(
&self,
member_expr: &mut StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (
// Replacement left of assignment
AssignmentTarget<'a>,
// Left side of `Math.pow(pow_left, ...)`
Expression<'a>,
// Temporary var initializations
ArenaVec<'a, Expression<'a>>,
) {
let mut temp_var_inits = ctx.ast.vec();
let obj = self.get_second_member_expression_object(
&mut member_expr.object,
&mut temp_var_inits,
ctx,
);
let prop_span = member_expr.property.span;
let prop_name = member_expr.property.name;
let prop = ctx.ast.expression_string_literal(prop_span, prop_name, None);
let pow_left = Expression::from(ctx.ast.member_expression_computed(SPAN, obj, prop, false));
let replacement_left =
AssignmentTarget::ComputedMemberExpression(ctx.ast.alloc_computed_member_expression(
member_expr.span,
member_expr.object.take_in(ctx.ast),
ctx.ast.expression_string_literal(prop_span, prop_name, None),
false,
));
(replacement_left, pow_left, temp_var_inits)
}
#[inline]
fn convert_computed_member_expression_assignment(
&self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() };
let span = assign_expr.span;
let AssignmentTarget::ComputedMemberExpression(member_expr) = &mut assign_expr.left else {
unreachable!()
};
let (pow_left, temp_var_inits) = self.get_pow_left_computed_member(member_expr, ctx);
Self::convert_assignment(assign_expr, pow_left, ctx);
Self::revise_expression(expr, temp_var_inits, span, ctx);
}
fn get_pow_left_computed_member(
&self,
member_expr: &mut ComputedMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (
// Left side of `Math.pow(pow_left, ...)`
Expression<'a>,
// Temporary var initializations
ArenaVec<'a, Expression<'a>>,
) {
let mut temp_var_inits = ctx.ast.vec();
let obj = self.get_second_member_expression_object(
&mut member_expr.object,
&mut temp_var_inits,
ctx,
);
let prop = &mut member_expr.expression;
let prop = if prop.is_literal() {
prop.clone_in(ctx.ast.allocator)
} else {
let owned_prop = prop.take_in(ctx.ast);
let binding = self.create_temp_var(owned_prop, &mut temp_var_inits, ctx);
*prop = binding.create_read_expression(ctx);
binding.create_read_expression(ctx)
};
let pow_left = Expression::from(ctx.ast.member_expression_computed(SPAN, obj, prop, false));
(pow_left, temp_var_inits)
}
#[inline]
fn convert_private_field_assignment(
&self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assign_expr) = expr else { unreachable!() };
let span = assign_expr.span;
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, span, ctx);
}
fn get_pow_left_private_field(
&self,
field_expr: &mut PrivateFieldExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (
// Left side of `Math.pow(pow_left, ...)`
Expression<'a>,
// Temporary var initializations
ArenaVec<'a, Expression<'a>>,
) {
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,
);
let field = field_expr.field.clone();
let pow_left = Expression::from(
ctx.ast.member_expression_private_field_expression(SPAN, obj, field, false),
);
(pow_left, temp_var_inits)
}
fn get_second_member_expression_object(
&self,
obj: &mut Expression<'a>,
temp_var_inits: &mut ArenaVec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
match obj {
Expression::Super(super_) => return ctx.ast.expression_super(super_.span),
Expression::Identifier(ident) => {
let symbol_id = ctx.scoping().get_reference(ident.reference_id()).symbol_id();
if let Some(symbol_id) = symbol_id {
return ctx.create_bound_ident_expr(
SPAN,
ident.name,
symbol_id,
ReferenceFlags::Read,
);
}
}
_ => {
}
}
let binding = self.create_temp_var(obj.take_in(ctx.ast), temp_var_inits, ctx);
*obj = binding.create_read_expression(ctx);
binding.create_read_expression(ctx)
}
fn convert_assignment(
assign_expr: &mut AssignmentExpression<'a>,
pow_left: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let span = assign_expr.span;
let pow_right = assign_expr.right.take_in(ctx.ast);
assign_expr.right = Self::math_pow(span, pow_left, pow_right, ctx);
assign_expr.operator = AssignmentOperator::Assign;
}
fn revise_expression(
expr: &mut Expression<'a>,
mut temp_var_inits: ArenaVec<'a, Expression<'a>>,
span: Span,
ctx: &TraverseCtx<'a>,
) {
if !temp_var_inits.is_empty() {
temp_var_inits.reserve_exact(1);
temp_var_inits.push(expr.take_in(ctx.ast));
*expr = ctx.ast.expression_sequence(span, temp_var_inits);
}
}
fn math_pow(
span: Span,
left: Expression<'a>,
right: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let math = ctx.ast.ident("Math");
let math_symbol_id = ctx.scoping().find_binding(ctx.current_scope_id(), math);
let object = ctx.create_ident_expr(SPAN, math, math_symbol_id, ReferenceFlags::Read);
let property = ctx.ast.identifier_name(SPAN, "pow");
let callee =
Expression::from(ctx.ast.member_expression_static(span, object, property, false));
let arguments = ctx.ast.vec_from_array([Argument::from(left), Argument::from(right)]);
ctx.ast.expression_call(span, callee, NONE, arguments, false)
}
#[expect(clippy::unused_self)]
fn create_temp_var(
&self,
expr: Expression<'a>,
temp_var_inits: &mut ArenaVec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> BoundIdentifier<'a> {
let binding = VarDeclarationsStore::create_uid_var_based_on_node(&expr, ctx);
temp_var_inits.push(ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
binding.create_write_target(ctx),
expr,
));
binding
}
}