use oxc_allocator::{ArenaVec, 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);
let b = expr_stmt.expression.take_in(ctx);
let expr = Self::join_with_left_associative_op(if_stmt.span, op, a, b, ctx);
return Some(Statement::new_expression_statement(if_stmt.span, expr, ctx));
} else if let Some(Statement::ExpressionStatement(alternate_expr_stmt)) =
&mut if_stmt.alternate
{
let test = if_stmt.test.take_in(ctx);
let consequent = expr_stmt.expression.take_in(ctx);
let alternate = alternate_expr_stmt.expression.take_in(ctx);
let expr =
Self::minimize_conditional(if_stmt.span, test, consequent, alternate, ctx);
return Some(Statement::new_expression_statement(if_stmt.span, expr, ctx));
}
} 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);
Self::remove_unused_expression(&mut expr, ctx);
return Some(Statement::new_expression_statement(if_stmt.span, expr, ctx));
} 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);
let b = expr_stmt.expression.take_in(ctx);
let expr = Self::join_with_left_associative_op(if_stmt.span, op, a, b, ctx);
return Some(Statement::new_expression_statement(if_stmt.span, expr, ctx));
} else if let Some(stmt) = &mut if_stmt.alternate {
match &mut if_stmt.test {
Expression::UnaryExpression(unary_expr) if unary_expr.operator.is_not() => {
let new_test = unary_expr.argument.take_in(ctx);
let new_consequent = stmt.take_in(ctx);
ctx.replace_expression(&mut if_stmt.test, new_test);
ctx.replace_statement(&mut if_stmt.consequent, new_consequent);
if_stmt.alternate = None;
}
_ => {
let new_test =
Self::minimize_not(if_stmt.test.span(), if_stmt.test.take_in(ctx), ctx);
let new_consequent = stmt.take_in(ctx);
ctx.replace_expression(&mut if_stmt.test, new_test);
ctx.replace_statement(&mut if_stmt.consequent, new_consequent);
if_stmt.alternate = None;
Self::try_minimize_if(if_stmt, ctx);
}
}
}
} 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()
{
let new_test = unary_expr.argument.take_in(ctx);
ctx.replace_expression(&mut if_stmt.test, new_test);
std::mem::swap(&mut if_stmt.consequent, alternate);
Self::wrap_to_avoid_ambiguous_else(if_stmt, ctx);
}
} else {
if let Statement::IfStatement(if2_stmt) = &mut if_stmt.consequent
&& if2_stmt.alternate.is_none()
{
let a = if_stmt.test.take_in(ctx);
let b = if2_stmt.test.take_in(ctx);
let new_test = Self::join_with_left_associative_op(
if_stmt.test.span(),
LogicalOperator::And,
a,
b,
ctx,
);
let new_consequent = if2_stmt.consequent.take_in(ctx);
ctx.replace_expression(&mut if_stmt.test, new_test);
ctx.replace_statement(&mut if_stmt.consequent, new_consequent);
}
}
}
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());
let new_consequent = Statement::new_block_statement_with_scope_id(
if_stmt.consequent.span(),
ArenaVec::from_value_in(if_stmt.consequent.take_in(ctx), ctx),
scope_id,
ctx,
);
ctx.replace_statement(&mut if_stmt.consequent, new_consequent);
}
}
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,
}
}
}