use oxc_allocator::TakeIn;
use oxc_ast::ast::*;
use oxc_semantic::ReferenceFlags;
use oxc_span::SPAN;
use oxc_traverse::Traverse;
use crate::{common::duplicate::duplicate_expression, context::TraverseCtx, state::TransformState};
pub struct LogicalAssignmentOperators;
impl LogicalAssignmentOperators {
pub fn new() -> Self {
Self
}
}
impl<'a> Traverse<'a, TransformState<'a>> for LogicalAssignmentOperators {
#[inline]
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let Expression::AssignmentExpression(assignment_expr) = expr else { return };
let Some(operator) = assignment_expr.operator.to_logical_operator() else { return };
self.transform_logical_assignment(expr, operator, ctx);
}
}
impl<'a> LogicalAssignmentOperators {
fn transform_logical_assignment(
&self,
expr: &mut Expression<'a>,
operator: LogicalOperator,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::AssignmentExpression(assignment_expr) = expr else { unreachable!() };
let (left_expr, assign_target) = match &mut assignment_expr.left {
AssignmentTarget::AssignmentTargetIdentifier(ident) => {
Self::convert_identifier(ident, ctx)
}
AssignmentTarget::StaticMemberExpression(static_expr) => {
self.convert_static_member_expression(static_expr, ctx)
}
AssignmentTarget::ComputedMemberExpression(computed_expr) => {
self.convert_computed_member_expression(computed_expr, ctx)
}
#[expect(clippy::match_same_arms)]
AssignmentTarget::PrivateFieldExpression(_) => return,
_ => return,
};
let span = assignment_expr.span;
let assign_op = AssignmentOperator::Assign;
let right = assignment_expr.right.take_in(ctx.ast);
let right = ctx.ast.expression_assignment(SPAN, assign_op, assign_target, right);
let logical_expr = ctx.ast.expression_logical(span, left_expr, operator, right);
*expr = logical_expr;
}
fn convert_identifier(
ident: &IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, AssignmentTarget<'a>) {
let reference = ctx.scoping_mut().get_reference_mut(ident.reference_id());
*reference.flags_mut() = ReferenceFlags::Read;
let symbol_id = reference.symbol_id();
let left_expr = Expression::Identifier(ctx.alloc(ident.clone()));
let ident = ctx.create_ident_reference(SPAN, ident.name, symbol_id, ReferenceFlags::Write);
let assign_target = AssignmentTarget::AssignmentTargetIdentifier(ctx.alloc(ident));
(left_expr, assign_target)
}
#[expect(clippy::unused_self)]
fn convert_static_member_expression(
&self,
static_expr: &mut StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, AssignmentTarget<'a>) {
let object = static_expr.object.take_in(ctx.ast);
let (object, object_ref) = duplicate_expression(object, true, ctx);
let left_expr = Expression::from(ctx.ast.member_expression_static(
static_expr.span,
object,
static_expr.property.clone(),
false,
));
let assign_expr = ctx.ast.member_expression_static(
static_expr.span,
object_ref,
static_expr.property.clone(),
false,
);
let assign_target = AssignmentTarget::from(assign_expr);
(left_expr, assign_target)
}
#[expect(clippy::unused_self)]
fn convert_computed_member_expression(
&self,
computed_expr: &mut ComputedMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, AssignmentTarget<'a>) {
let object = computed_expr.object.take_in(ctx.ast);
let (object, object_ref) = duplicate_expression(object, true, ctx);
let expression = computed_expr.expression.take_in(ctx.ast);
let (expression, expression_ref) = duplicate_expression(expression, true, ctx);
let left_expr = Expression::from(ctx.ast.member_expression_computed(
computed_expr.span,
object,
expression,
false,
));
let assign_target = AssignmentTarget::from(ctx.ast.member_expression_computed(
computed_expr.span,
object_ref,
expression_ref,
false,
));
(left_expr, assign_target)
}
}