use oxc_allocator::TakeIn;
use oxc_ast::ast::*;
use oxc_semantic::ScopeFlags;
use oxc_span::GetSpan;
use crate::TraverseCtx;
use super::PeepholeOptimizations;
impl<'a> PeepholeOptimizations {
pub fn try_minimize_if(
if_stmt: &mut IfStatement<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
Self::wrap_to_avoid_ambiguous_else(if_stmt, ctx);
if let Statement::ExpressionStatement(expr_stmt) = &mut if_stmt.consequent {
if if_stmt.alternate.is_none() {
let (op, e) = match &mut if_stmt.test {
Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_not() => {
(LogicalOperator::Or, &mut unary_expr.argument)
}
e => (LogicalOperator::And, e),
};
let a = e.take_in(ctx.ast);
let b = expr_stmt.expression.take_in(ctx.ast);
let expr = Self::join_with_left_associative_op(if_stmt.span, op, a, b, ctx);
return Some(ctx.ast.statement_expression(if_stmt.span, expr));
} else if let Some(Statement::ExpressionStatement(alternate_expr_stmt)) =
&mut if_stmt.alternate
{
let test = if_stmt.test.take_in(ctx.ast);
let consequent = expr_stmt.expression.take_in(ctx.ast);
let alternate = alternate_expr_stmt.expression.take_in(ctx.ast);
let expr =
Self::minimize_conditional(if_stmt.span, test, consequent, alternate, ctx);
return Some(ctx.ast.statement_expression(if_stmt.span, expr));
}
} else if Self::is_statement_empty(&if_stmt.consequent) {
if if_stmt.alternate.is_none()
|| if_stmt.alternate.as_ref().is_some_and(Self::is_statement_empty)
{
let mut expr = if_stmt.test.take_in(ctx.ast);
Self::remove_unused_expression(&mut expr, ctx);
return Some(ctx.ast.statement_expression(if_stmt.span, expr));
} else if let Some(Statement::ExpressionStatement(expr_stmt)) = &mut if_stmt.alternate {
let (op, e) = match &mut if_stmt.test {
Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_not() => {
(LogicalOperator::And, &mut unary_expr.argument)
}
e => (LogicalOperator::Or, e),
};
let a = e.take_in(ctx.ast);
let b = expr_stmt.expression.take_in(ctx.ast);
let expr = Self::join_with_left_associative_op(if_stmt.span, op, a, b, ctx);
return Some(ctx.ast.statement_expression(if_stmt.span, expr));
} else if let Some(stmt) = &mut if_stmt.alternate {
match &mut if_stmt.test {
Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_not() => {
if_stmt.test = unary_expr.argument.take_in(ctx.ast);
if_stmt.consequent = stmt.take_in(ctx.ast);
if_stmt.alternate = None;
ctx.state.changed = true;
}
_ => {
if_stmt.test = Self::minimize_not(
if_stmt.test.span(),
if_stmt.test.take_in(ctx.ast),
ctx,
);
if_stmt.consequent = stmt.take_in(ctx.ast);
if_stmt.alternate = None;
Self::try_minimize_if(if_stmt, ctx);
ctx.state.changed = true;
}
}
}
} else {
if let Some(alternate) = &mut if_stmt.alternate {
if !matches!(alternate, Statement::IfStatement(_))
&& let Expression::UnaryExpression(unary_expr) = &mut if_stmt.test
&& unary_expr.operator.is_not()
{
if_stmt.test = unary_expr.argument.take_in(ctx.ast);
std::mem::swap(&mut if_stmt.consequent, alternate);
Self::wrap_to_avoid_ambiguous_else(if_stmt, ctx);
ctx.state.changed = true;
}
} else {
if let Statement::IfStatement(if2_stmt) = &mut if_stmt.consequent
&& if2_stmt.alternate.is_none()
{
let a = if_stmt.test.take_in(ctx.ast);
let b = if2_stmt.test.take_in(ctx.ast);
if_stmt.test = Self::join_with_left_associative_op(
if_stmt.test.span(),
LogicalOperator::And,
a,
b,
ctx,
);
if_stmt.consequent = if2_stmt.consequent.take_in(ctx.ast);
ctx.state.changed = true;
}
}
}
None
}
fn wrap_to_avoid_ambiguous_else(if_stmt: &mut IfStatement<'a>, ctx: &mut TraverseCtx<'a>) {
if let Statement::IfStatement(if2) = &mut if_stmt.consequent
&& if2.consequent.is_jump_statement()
&& if2.alternate.is_some()
{
let scope_id = ctx.create_child_scope_of_current(ScopeFlags::empty());
if_stmt.consequent =
Statement::BlockStatement(ctx.ast.alloc(ctx.ast.block_statement_with_scope_id(
if_stmt.consequent.span(),
ctx.ast.vec1(if_stmt.consequent.take_in(ctx.ast)),
scope_id,
)));
ctx.state.changed = true;
}
}
fn is_statement_empty(stmt: &Statement<'a>) -> bool {
match stmt {
Statement::BlockStatement(block_stmt) if block_stmt.body.is_empty() => true,
Statement::EmptyStatement(_) => true,
_ => false,
}
}
}