use rspack_core::DependencyRange;
use rspack_util::SpanExt;
use swc_core::{
common::Spanned,
ecma::ast::{BinExpr, BinaryOp},
};
use crate::{utils::eval::BasicEvaluatedExpression, visitors::JavascriptParser};
#[inline]
fn handle_template_string_compare<'a>(
left: &BasicEvaluatedExpression,
right: &BasicEvaluatedExpression,
mut res: BasicEvaluatedExpression<'a>,
eql: bool,
) -> Option<BasicEvaluatedExpression<'a>> {
let get_prefix = |parts: &Vec<BasicEvaluatedExpression>| {
let mut value = vec![];
for p in parts {
if let Some(s) = p.as_string() {
value.push(s);
} else {
break;
}
}
value.concat()
};
let get_suffix = |parts: &Vec<BasicEvaluatedExpression>| {
let mut value = vec![];
for p in parts.iter().rev() {
if let Some(s) = p.as_string() {
value.push(s);
} else {
break;
}
}
value.concat()
};
let prefix_res = {
let left_prefix = get_prefix(left.parts());
let right_prefix = get_prefix(right.parts());
let len_prefix = usize::min(left_prefix.len(), right_prefix.len());
len_prefix > 0 && left_prefix[0..len_prefix] != right_prefix[0..len_prefix]
};
if prefix_res {
res.set_bool(!eql);
res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects());
return Some(res);
}
let suffix_res = {
let left_suffix = get_suffix(left.parts());
let right_suffix = get_suffix(right.parts());
let len_suffix = usize::min(left_suffix.len(), right_suffix.len());
len_suffix > 0
&& left_suffix[left_suffix.len() - len_suffix..]
!= right_suffix[right_suffix.len() - len_suffix..]
};
if suffix_res {
res.set_bool(!eql);
res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects());
return Some(res);
}
None
}
#[inline]
fn is_always_different(a: Option<bool>, b: Option<bool>) -> bool {
match (a, b) {
(Some(a), Some(b)) => a != b,
_ => false,
}
}
#[inline]
fn handle_strict_equality_comparison<'a>(
eql: bool,
left: BasicEvaluatedExpression<'a>,
expr: &'a BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression<'a>> {
assert!(expr.op == BinaryOp::EqEqEq || expr.op == BinaryOp::NotEqEq);
let right = scanner.evaluate_expression(&expr.right);
let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.real_hi());
let left_const = left.is_compile_time_value();
let right_const = right.is_compile_time_value();
let common = |mut res: BasicEvaluatedExpression<'a>| {
res.set_bool(!eql);
res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects());
Some(res)
};
if left_const && right_const {
res.set_bool(eql == left.compare_compile_time_value(&right));
res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects());
Some(res)
} else if left.is_array() && right.is_array() {
common(res)
} else if left.is_template_string() && right.is_template_string() {
handle_template_string_compare(&left, &right, res, eql)
} else if is_always_different(left.as_bool(), right.as_bool())
|| is_always_different(left.as_nullish(), right.as_nullish())
{
common(res)
} else {
let left_primitive = left.is_primitive_type();
let right_primitive = right.is_primitive_type();
if left_primitive == Some(false) && (left_const || right_primitive == Some(true))
|| (right_primitive == Some(false) && (right_const || left_primitive == Some(true)))
{
common(res)
} else {
None
}
}
}
#[inline(always)]
fn handle_abstract_equality_comparison<'a>(
eql: bool,
left: BasicEvaluatedExpression<'a>,
expr: &'a BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression<'a>> {
assert!(expr.op == BinaryOp::EqEq || expr.op == BinaryOp::NotEq);
let right = scanner.evaluate_expression(&expr.right);
let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.real_hi());
let left_const = left.is_compile_time_value();
let right_const = right.is_compile_time_value();
if left_const && right_const {
res.set_bool(eql == left.compare_compile_time_value(&right));
res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects());
Some(res)
} else if left.is_array() && right.is_array() {
res.set_bool(!eql);
res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects());
Some(res)
} else if left.is_template_string() && right.is_template_string() {
handle_template_string_compare(&left, &right, res, eql)
} else {
None
}
}
#[inline(always)]
fn handle_nullish_coalescing<'a>(
left: BasicEvaluatedExpression<'a>,
expr: &'a BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression<'a>> {
let left_nullish = left.as_nullish();
match left_nullish {
Some(true) => {
let mut right = scanner.evaluate_expression(&expr.right);
if left.could_have_side_effects() {
right.set_side_effects(true)
}
right.set_range(expr.span.real_lo(), expr.span.real_hi());
Some(right)
}
Some(false) => {
let mut res = left.clone();
res.set_range(expr.span.real_lo(), expr.span.real_hi());
Some(res)
}
_ => None,
}
}
#[inline(always)]
fn handle_logical_or<'a>(
left: BasicEvaluatedExpression<'a>,
expr: &'a BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression<'a>> {
let left_bool = left.as_bool();
match left_bool {
Some(true) => {
let mut res = left.clone();
res.set_range(expr.span.real_lo(), expr.span.real_hi());
Some(res)
}
Some(false) => {
let mut right = scanner.evaluate_expression(&expr.right);
if left.could_have_side_effects() {
right.set_side_effects(true)
}
right.set_range(expr.span.real_lo(), expr.span.real_hi());
Some(right)
}
_ => {
let right_bool = scanner.evaluate_expression(&expr.right).as_bool();
if right_bool.is_some_and(|x| x) {
let mut res =
BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.real_hi());
res.set_truthy();
Some(res)
} else {
None
}
}
}
}
#[inline(always)]
fn handle_logical_and<'a>(
left: BasicEvaluatedExpression<'a>,
expr: &'a BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression<'a>> {
let left_bool = left.as_bool();
match left_bool {
Some(true) => {
let mut right = scanner.evaluate_expression(&expr.right);
if left.could_have_side_effects() {
right.set_side_effects(true)
}
right.set_range(expr.span.real_lo(), expr.span.real_hi());
Some(right)
}
Some(false) => {
let mut res = left.clone();
res.set_range(expr.span.real_lo(), expr.span.real_hi());
Some(res)
}
None => {
let right_bool = scanner.evaluate_expression(&expr.right).as_bool();
if right_bool.is_some_and(|x| !x) {
let mut res =
BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.real_hi());
res.set_falsy();
Some(res)
} else {
None
}
}
}
}
#[inline(always)]
fn handle_add<'a>(
left: BasicEvaluatedExpression<'a>,
expr: &'a BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression<'a>> {
assert_eq!(expr.op, BinaryOp::Add);
let right = scanner.evaluate_expression(&expr.right);
let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.real_hi());
if left.could_have_side_effects() || right.could_have_side_effects() {
res.set_side_effects(true)
}
if left.is_string() {
if right.is_string() {
res.set_string(format!("{}{}", left.string(), right.string()));
} else if right.is_number() {
res.set_string(format!("{}{}", left.string(), right.number()));
} else if right.is_wrapped() {
let (right_prefix, right_postfix, right_inner_expressions) =
right.into_wrapped().expect("right should be wrapped");
if let Some(prefix) = right_prefix.as_ref()
&& prefix.is_string()
{
let (start, end) = join_locations(left.range_ref(), prefix.range_ref());
let mut left_prefix = BasicEvaluatedExpression::with_range(start, end);
left_prefix.set_string(format!("{}{}", left.string(), prefix.string()));
res.set_wrapped(Some(left_prefix), right_postfix, right_inner_expressions)
} else {
res.set_wrapped(Some(left), right_postfix, right_inner_expressions);
}
} else {
res.set_wrapped(Some(left), None, vec![right])
}
} else if left.is_number() {
if right.is_string() {
res.set_string(format!("{}{}", left.number(), right.string()));
} else if right.is_number() {
res.set_number(left.number() + right.number())
} else {
return None;
}
} else if left.is_bigint() {
let had_side_effects = res.could_have_side_effects();
if let (Some(l), Some(r)) = (left.bigint(), right.bigint()) {
res.set_bigint(l.clone() + r.clone());
if had_side_effects {
res.set_side_effects(true);
}
return Some(res);
}
return None;
} else if left.is_wrapped() {
let (mut left_prefix, mut left_postfix, mut left_inner_expressions) =
left.into_wrapped().expect("left should be wrapped");
if let Some(postfix) = left_postfix.as_ref()
&& postfix.is_string()
&& right.is_string()
{
let range = join_locations(postfix.range_ref(), right.range_ref());
let mut right_postfix = BasicEvaluatedExpression::with_range(range.0, range.1);
right_postfix.set_string(format!("{}{}", postfix.string(), right.string()));
res.set_wrapped(
left_prefix.take(),
Some(right_postfix),
std::mem::take(&mut left_inner_expressions),
)
} else if let Some(postfix) = left_postfix.as_ref()
&& postfix.is_string()
&& right.is_number()
{
let range = join_locations(postfix.range_ref(), right.range_ref());
let mut right_postfix = BasicEvaluatedExpression::with_range(range.0, range.1);
right_postfix.set_string(format!("{}{}", postfix.string(), right.number()));
res.set_wrapped(
left_prefix.take(),
Some(right_postfix),
std::mem::take(&mut left_inner_expressions),
)
} else if right.is_string() {
res.set_wrapped(
left_prefix.take(),
Some(right),
std::mem::take(&mut left_inner_expressions),
);
} else if right.is_number() {
let range = right.range();
let mut postfix = BasicEvaluatedExpression::with_range(range.0, range.1);
postfix.set_string(right.number().to_string());
res.set_wrapped(
left_prefix.take(),
Some(postfix),
std::mem::take(&mut left_inner_expressions),
)
} else if right.is_wrapped() {
let (right_prefix, right_postfix, mut right_inner_expression) =
right.into_wrapped().expect("right should be wrapped");
let mut inner_expressions = std::mem::take(&mut left_inner_expressions);
if let Some(postfix) = left_postfix.take() {
inner_expressions.push(postfix);
}
if let Some(prefix) = right_prefix {
inner_expressions.push(prefix);
}
inner_expressions.append(&mut right_inner_expression);
res.set_wrapped(left_prefix.take(), right_postfix, inner_expressions);
} else {
let mut inner_expressions = std::mem::take(&mut left_inner_expressions);
if let Some(postfix) = left_postfix.take() {
inner_expressions.push(postfix);
}
inner_expressions.push(right);
res.set_wrapped(left_prefix.take(), None, inner_expressions)
}
} else if right.is_string() {
res.set_wrapped(None, Some(right), vec![left]);
} else if right.is_wrapped() {
let (right_prefix, right_postfix, mut right_inner_expressions) =
right.into_wrapped().expect("right should be wrapped");
let mut inner_expressions = if let Some(right_prefix) = right_prefix {
vec![left, right_prefix]
} else {
vec![left]
};
inner_expressions.append(&mut right_inner_expressions);
res.set_wrapped(None, right_postfix, inner_expressions);
} else {
return None;
}
Some(res)
}
#[inline(always)]
pub fn handle_const_operation<'a>(
left: BasicEvaluatedExpression<'a>,
expr: &'a BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression<'a>> {
if !left.is_compile_time_value() {
return None;
}
let right = scanner.evaluate_expression(&expr.right);
if !right.is_compile_time_value() {
return None;
}
let had_side_effects = left.could_have_side_effects() || right.could_have_side_effects();
let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.real_hi());
match expr.op {
BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod | BinaryOp::Exp => {
if let Some(left_number) = left.as_number()
&& let Some(right_number) = right.as_number()
{
res.set_number(match expr.op {
BinaryOp::Sub => left_number - right_number,
BinaryOp::Mul => left_number * right_number,
BinaryOp::Div => left_number / right_number,
BinaryOp::Mod => left_number % right_number,
BinaryOp::Exp => left_number.powf(right_number),
_ => unreachable!(),
});
if had_side_effects {
res.set_side_effects(true);
}
Some(res)
} else {
None
}
}
BinaryOp::LShift | BinaryOp::RShift | BinaryOp::ZeroFillRShift => {
if let Some(left_int) = left.as_int() {
let right_int = right.as_int()?;
let shift_bits = (right_int as u32) & 31;
let result = match expr.op {
BinaryOp::LShift => (left_int << shift_bits) as f64,
BinaryOp::RShift => (left_int >> shift_bits) as f64,
BinaryOp::ZeroFillRShift => (left_int as u32).wrapping_shr(shift_bits) as f64,
_ => unreachable!(),
};
res.set_number(result);
if had_side_effects {
res.set_side_effects(true);
}
Some(res)
} else {
None
}
}
BinaryOp::BitAnd | BinaryOp::BitXor | BinaryOp::BitOr => {
if let Some(left_number) = left.as_int()
&& let Some(right_number) = right.as_int()
{
res.set_number(match expr.op {
BinaryOp::BitAnd => left_number & right_number,
BinaryOp::BitXor => left_number ^ right_number,
BinaryOp::BitOr => left_number | right_number,
_ => unreachable!(),
} as f64);
if had_side_effects {
res.set_side_effects(true);
}
Some(res)
} else {
None
}
}
BinaryOp::Lt | BinaryOp::Gt | BinaryOp::LtEq | BinaryOp::GtEq => {
if left.is_string() && right.is_string() {
let left_str = left.string();
let right_str = right.string();
res.set_bool(match expr.op {
BinaryOp::Lt => left_str < right_str,
BinaryOp::LtEq => left_str <= right_str,
BinaryOp::Gt => left_str > right_str,
BinaryOp::GtEq => left_str >= right_str,
_ => unreachable!(),
});
Some(res)
} else if let Some(left_number) = left.as_number()
&& let Some(right_number) = right.as_number()
{
res.set_bool(match expr.op {
BinaryOp::Lt => left_number < right_number,
BinaryOp::LtEq => left_number <= right_number,
BinaryOp::Gt => left_number > right_number,
BinaryOp::GtEq => left_number >= right_number,
_ => unreachable!(),
});
Some(res)
} else {
None
}
}
_ => None,
}
}
pub fn eval_binary_expression<'a>(
scanner: &mut JavascriptParser,
expr: &'a BinExpr,
) -> Option<BasicEvaluatedExpression<'a>> {
let mut stack = vec![expr];
let mut expr = &*expr.left;
while let Some(bin) = expr.as_bin() {
stack.push(bin);
expr = &*bin.left;
}
let mut evaluated = None;
while let Some(expr) = stack.pop() {
let left = evaluated.unwrap_or_else(|| scanner.evaluate_expression(&expr.left));
evaluated = match expr.op {
BinaryOp::EqEq => handle_abstract_equality_comparison(true, left, expr, scanner),
BinaryOp::NotEq => handle_abstract_equality_comparison(false, left, expr, scanner),
BinaryOp::EqEqEq => handle_strict_equality_comparison(true, left, expr, scanner),
BinaryOp::NotEqEq => handle_strict_equality_comparison(false, left, expr, scanner),
BinaryOp::LogicalAnd => handle_logical_and(left, expr, scanner),
BinaryOp::LogicalOr => handle_logical_or(left, expr, scanner),
BinaryOp::NullishCoalescing => handle_nullish_coalescing(left, expr, scanner),
BinaryOp::Add => handle_add(left, expr, scanner),
_ => handle_const_operation(left, expr, scanner),
}
.or_else(|| {
Some(BasicEvaluatedExpression::with_range(
expr.span().real_lo(),
expr.span().real_hi(),
))
});
}
evaluated
}
fn join_locations(start: Option<&DependencyRange>, end: Option<&DependencyRange>) -> (u32, u32) {
match (start, end) {
(None, None) => unreachable!("invalid range"),
(None, Some(end)) => (end.start, end.end),
(Some(start), None) => (start.start, start.end),
(Some(start), Some(end)) => {
join_ranges(Some((start.start, start.end)), Some((end.start, end.end)))
}
}
}
fn join_ranges(start: Option<(u32, u32)>, end: Option<(u32, u32)>) -> (u32, u32) {
match (start, end) {
(None, None) => unreachable!("invalid range"),
(None, Some(end)) => end,
(Some(start), None) => start,
(Some(start), Some(end)) => {
assert!(start.0 <= end.1);
(start.0, end.1)
}
}
}