use std::mem;
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_span::{GetSpan, SPAN, Span};
use oxc_syntax::operator::LogicalOperator;
use oxc_traverse::TraverseCtx;
use crate::pragma::{IgnoreType, PragmaMap};
use super::counters::{CounterKind, dummy_expr, index_literal, prepend_counter};
use super::coverage_map::{PendingArm, PendingBranch, is_synthetic_span};
use super::{CoverageState, CoverageTransform};
impl<'arena> CoverageTransform<'_, 'arena> {
pub(super) fn instrument_logical_expression(
&mut self,
expr: &mut LogicalExpression<'arena>,
ctx: &mut TraverseCtx<'arena, CoverageState>,
) {
if logical_expression_ignored(expr, ctx, self.in_ignored_subtree()) {
return;
}
match expr.operator {
LogicalOperator::And | LogicalOperator::Or | LogicalOperator::Coalesce => {
if is_parent_logical(ctx) {
return;
}
let leaf_spans = collect_logical_leaf_spans(expr, &ctx.state.pragmas);
if leaf_spans.is_empty() {
return;
}
let arms = leaf_spans.into_iter().map(PendingArm::new).collect();
let Some(reg) = self.register_branch(PendingBranch {
branch_type: "binary-expr",
umbrella_span: expr.span,
gate_arms: false,
arms,
}) else {
return;
};
if self.report_logic {
self.logical_branch_ids.push(reg.branch_id);
}
let mut state = LogicalWrapState::new(
self.cov_fn_name,
self.cov_fn_bt_name,
reg.branch_id,
self.report_logic,
);
wrap_logical_leaves(expr, &mut state, ctx);
}
}
}
}
pub(super) fn is_parent_logical(ctx: &TraverseCtx<'_, CoverageState>) -> bool {
use oxc_traverse::Ancestor;
for a in ctx.ancestors() {
match a {
Ancestor::ParenthesizedExpressionExpression(_) => {}
Ancestor::LogicalExpressionLeft(_) | Ancestor::LogicalExpressionRight(_) => {
return true;
}
_ => return false,
}
}
false
}
pub(super) fn logical_expression_ignored(
expr: &LogicalExpression,
ctx: &TraverseCtx<'_, CoverageState>,
parent_ignored: bool,
) -> bool {
parent_ignored
|| ctx.state.pragmas.get(expr.span.start) == Some(IgnoreType::Next)
|| is_synthetic_span(expr.span)
}
pub(super) fn collect_logical_leaf_spans(
expr: &LogicalExpression,
pragmas: &PragmaMap,
) -> Vec<Span> {
let mut spans = Vec::new();
collect_logical_leaves_inner(&expr.left, pragmas, &mut spans);
collect_logical_leaves_inner(&expr.right, pragmas, &mut spans);
spans
}
fn collect_logical_leaves_inner(expr: &Expression, pragmas: &PragmaMap, spans: &mut Vec<Span>) {
if let Expression::ParenthesizedExpression(paren) = expr {
collect_logical_leaves_inner(&paren.expression, pragmas, spans);
return;
}
if pragmas.get(expr.span().start) == Some(IgnoreType::Next) {
return;
}
if let Expression::LogicalExpression(logical) = expr {
collect_logical_leaves_inner(&logical.left, pragmas, spans);
collect_logical_leaves_inner(&logical.right, pragmas, spans);
} else {
spans.push(expr.span());
}
}
pub(super) struct LogicalWrapState<'b> {
cov_fn_name: &'b str,
cov_fn_bt_name: Option<&'b str>,
branch_id: usize,
report_logic: bool,
path_idx: usize,
}
impl<'b> LogicalWrapState<'b> {
pub(super) const fn new(
cov_fn_name: &'b str,
cov_fn_bt_name: Option<&'b str>,
branch_id: usize,
report_logic: bool,
) -> Self {
Self { cov_fn_name, cov_fn_bt_name, branch_id, report_logic, path_idx: 0 }
}
const fn current_path_idx(&self) -> usize {
self.path_idx
}
const fn advance_path(&mut self) {
self.path_idx += 1;
}
}
fn wrap_expression_with_branch_counter<'a>(
operand: &mut Expression<'a>,
state: &LogicalWrapState<'a>,
ctx: &TraverseCtx<'a, CoverageState>,
) {
prepend_counter(
operand,
CounterKind::branch(state.cov_fn_name, state.branch_id, state.current_path_idx()),
ctx,
);
}
fn build_bt_call<'a>(
inner: Expression<'a>,
state: &LogicalWrapState<'a>,
ctx: &TraverseCtx<'a, CoverageState>,
) -> Expression<'a> {
let bt_name = state.cov_fn_bt_name.expect("report_logic requires cov_fn_bt_name");
let callee = Expression::new_identifier(SPAN, bt_name, ctx);
let mut args = ArenaVec::new_in(ctx);
args.push(Argument::from(inner));
args.push(Argument::from(index_literal(ctx, state.branch_id)));
args.push(Argument::from(index_literal(ctx, state.current_path_idx())));
Expression::new_call_expression(
SPAN,
callee,
None::<TSTypeParameterInstantiation>,
args,
false,
ctx,
)
}
fn wrap_logical_leaf<'a>(
operand: &mut Expression<'a>,
state: &mut LogicalWrapState<'a>,
ctx: &TraverseCtx<'a, CoverageState>,
) {
wrap_expression_with_branch_counter(operand, state, ctx);
if state.report_logic {
let branch_wrapped = mem::replace(operand, dummy_expr(ctx));
*operand = build_bt_call(branch_wrapped, state, ctx);
}
state.advance_path();
}
pub(super) fn wrap_logical_leaves<'a>(
expr: &mut LogicalExpression<'a>,
state: &mut LogicalWrapState<'a>,
ctx: &mut TraverseCtx<'a, CoverageState>,
) {
wrap_logical_operand(&mut expr.left, state, ctx);
wrap_logical_operand(&mut expr.right, state, ctx);
}
fn wrap_logical_operand<'a>(
operand: &mut Expression<'a>,
state: &mut LogicalWrapState<'a>,
ctx: &mut TraverseCtx<'a, CoverageState>,
) {
if let Expression::ParenthesizedExpression(paren) = operand {
return wrap_logical_operand(&mut paren.expression, state, ctx);
}
if ctx.state.pragmas.get(operand.span().start) == Some(IgnoreType::Next) {
return;
}
if let Expression::LogicalExpression(inner) = operand {
wrap_logical_leaves(inner, state, ctx);
} else {
wrap_logical_leaf(operand, state, ctx);
}
}