use oxc::allocator::TakeIn;
use oxc::ast::ast::Expression;
use oxc::syntax::operator::LogicalOperator;
use oxc_traverse::TraverseCtx;
use crate::ast::{extract, query};
pub fn try_fold<'a>(
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a, ()>,
) -> Option<usize> {
let Expression::LogicalExpression(logical) = &*expr else {
return None;
};
let allocator = ctx.ast.allocator;
match logical.operator {
LogicalOperator::Or => {
let truthy = query::is_truthy(&logical.left)?;
let Expression::LogicalExpression(logical) = expr else { return None; };
*expr = if truthy {
logical.left.take_in(allocator)
} else {
logical.right.take_in(allocator)
};
}
LogicalOperator::And => {
let truthy = query::is_truthy(&logical.left)?;
let Expression::LogicalExpression(logical) = expr else { return None; };
*expr = if truthy {
logical.right.take_in(allocator)
} else {
logical.left.take_in(allocator)
};
}
LogicalOperator::Coalesce => {
let left_is_nullish = extract::is_null(&logical.left) || extract::is_undefined(&logical.left);
if !left_is_nullish {
if !is_definitely_non_nullish(&logical.left) {
return None;
}
}
let Expression::LogicalExpression(logical) = expr else { return None; };
*expr = if left_is_nullish {
logical.right.take_in(allocator)
} else {
logical.left.take_in(allocator)
};
}
}
Some(1)
}
fn is_definitely_non_nullish(expr: &Expression) -> bool {
match expr {
Expression::NumericLiteral(_)
| Expression::StringLiteral(_)
| Expression::BooleanLiteral(_)
| Expression::ArrayExpression(_)
| Expression::ObjectExpression(_)
| Expression::FunctionExpression(_)
| Expression::ArrowFunctionExpression(_) => true,
Expression::Identifier(id) => {
matches!(id.name.as_str(), "Infinity" | "NaN")
}
Expression::ParenthesizedExpression(p) => is_definitely_non_nullish(&p.expression),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::super::test_utils::fold;
#[test]
fn test_or() {
assert!(fold("true || x;").contains("true"));
assert!(fold("false || 42;").contains("42"));
assert!(fold("0 || \"fallback\";").contains("\"fallback\""));
assert!(fold("\"truthy\" || x;").contains("\"truthy\""));
}
#[test]
fn test_and() {
assert!(fold("true && 42;").contains("42"));
assert!(fold("false && x;").contains("false"));
assert!(fold("1 && \"yes\";").contains("\"yes\""));
assert!(fold("0 && x;").contains("0"));
}
#[test]
fn test_nullish() {
assert!(fold("null ?? \"default\";").contains("\"default\""));
assert!(fold("undefined ?? 42;").contains("42"));
assert!(fold("0 ?? \"default\";").contains("0"));
}
}