use std::mem;
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_span::SPAN;
use oxc_syntax::operator::UpdateOperator;
use oxc_traverse::TraverseCtx;
use super::CoverageState;
#[derive(Clone, Copy)]
pub(super) enum CounterType {
Statement,
BranchLeft,
}
pub(super) struct PendingInsertion {
pub(super) target_start: u32,
pub(super) counter_id: usize,
pub(super) counter_type: CounterType,
}
pub(super) struct ClassFieldHoist {
pub(super) target_start: u32,
pub(super) counter_id: usize,
pub(super) is_static: bool,
}
#[derive(Clone, Copy)]
pub(super) enum CounterKind<'a> {
Slot { cov_fn_name: &'a str, kind: &'static str, id: usize },
Branch { cov_fn_name: &'a str, branch_id: usize, path_idx: usize },
}
impl<'a> CounterKind<'a> {
pub(super) const fn stmt(cov_fn_name: &'a str, id: usize) -> Self {
Self::Slot { cov_fn_name, kind: "s", id }
}
pub(super) const fn func(cov_fn_name: &'a str, id: usize) -> Self {
Self::Slot { cov_fn_name, kind: "f", id }
}
pub(super) const fn branch(cov_fn_name: &'a str, branch_id: usize, path_idx: usize) -> Self {
Self::Branch { cov_fn_name, branch_id, path_idx }
}
pub(super) fn from_pending(cov_fn_name: &'a str, pending: &PendingInsertion) -> Self {
match pending.counter_type {
CounterType::Statement => Self::stmt(cov_fn_name, pending.counter_id),
CounterType::BranchLeft => Self::branch(cov_fn_name, pending.counter_id, 0),
}
}
}
fn alloc_str<'a>(s: &str, ctx: &TraverseCtx<'a, CoverageState>) -> &'a str {
ctx.ast.allocator.alloc_str(s)
}
fn static_field<'a>(
base: Expression<'a>,
field: &'a str,
ctx: &TraverseCtx<'a, CoverageState>,
) -> MemberExpression<'a> {
MemberExpression::new_static_member_expression(
SPAN,
base,
IdentifierName::new(SPAN, field, ctx),
false,
ctx,
)
}
fn computed_index<'a>(
base: MemberExpression<'a>,
idx: usize,
ctx: &TraverseCtx<'a, CoverageState>,
) -> MemberExpression<'a> {
MemberExpression::new_computed_member_expression(
SPAN,
Expression::from(base),
index_literal(ctx, idx),
false,
ctx,
)
}
fn build_counter_expr<'a>(
kind: CounterKind<'a>,
ctx: &TraverseCtx<'a, CoverageState>,
) -> Expression<'a> {
let target = match kind {
CounterKind::Slot { cov_fn_name, kind, id } => {
let coverage = Expression::new_identifier(SPAN, cov_fn_name, ctx);
let field = static_field(coverage, alloc_str(kind, ctx), ctx);
computed_index(field, id, ctx)
}
CounterKind::Branch { cov_fn_name, branch_id, path_idx } => {
let coverage = Expression::new_identifier(SPAN, cov_fn_name, ctx);
let b = static_field(coverage, "b", ctx);
let outer = computed_index(b, branch_id, ctx);
computed_index(outer, path_idx, ctx)
}
};
Expression::new_update_expression(
SPAN,
UpdateOperator::Increment,
true,
SimpleAssignmentTarget::from(target),
ctx,
)
}
pub(super) fn build_counter_stmt<'a>(
kind: CounterKind<'a>,
ctx: &TraverseCtx<'a, CoverageState>,
) -> Statement<'a> {
let expr = build_counter_expr(kind, ctx);
Statement::new_expression_statement(SPAN, expr, ctx)
}
pub(super) fn prepend_counter<'a>(
target: &mut Expression<'a>,
kind: CounterKind<'a>,
ctx: &TraverseCtx<'a, CoverageState>,
) {
let counter = build_counter_expr(kind, ctx);
let orig = mem::replace(target, dummy_expr(ctx));
let mut items = ArenaVec::new_in(ctx);
items.push(counter);
items.push(orig);
*target = Expression::new_sequence_expression(SPAN, items, ctx);
}
pub(super) fn index_literal<'a>(
ctx: &TraverseCtx<'a, CoverageState>,
index: usize,
) -> Expression<'a> {
let index = f64::from(u32::try_from(index).unwrap_or(u32::MAX));
Expression::new_numeric_literal(SPAN, index, None, oxc_syntax::number::NumberBase::Decimal, ctx)
}
pub(super) fn dummy_expr<'a>(ctx: &TraverseCtx<'a, CoverageState>) -> Expression<'a> {
Expression::new_numeric_literal(SPAN, 0.0, None, oxc_syntax::number::NumberBase::Decimal, ctx)
}
pub(super) fn build_class_field_counter<'a>(
cov_fn: &'a str,
hoist: &ClassFieldHoist,
ctx: &TraverseCtx<'a, CoverageState>,
) -> ClassElement<'a> {
let counter = build_counter_expr(CounterKind::stmt(cov_fn, hoist.counter_id), ctx);
let key_name = alloc_str(&format!("__cov_{}_init_{}", cov_fn, hoist.counter_id), ctx);
let key = PropertyKey::StaticIdentifier(IdentifierName::boxed(SPAN, key_name, ctx));
ClassElement::new_property_definition(
SPAN,
PropertyDefinitionType::PropertyDefinition,
ArenaVec::new_in(ctx),
key,
None::<TSTypeAnnotation>,
Some(counter),
false,
hoist.is_static,
false,
false,
false,
false,
false,
None,
ctx,
)
}
pub(super) fn inject_branch_counter_into_statement<'a>(
stmt: &mut Statement<'a>,
kind: CounterKind<'a>,
ctx: &mut TraverseCtx<'a, CoverageState>,
) {
let counter_stmt = build_counter_stmt(kind, ctx);
if let Statement::BlockStatement(block) = stmt {
block.body.insert(0, counter_stmt);
return;
}
let scope_id = ctx.create_child_scope_of_current(oxc_syntax::scope::ScopeFlags::empty());
let original = mem::replace(stmt, Statement::new_empty_statement(SPAN, ctx));
let mut stmts = ArenaVec::new_in(ctx);
stmts.push(counter_stmt);
stmts.push(original);
*stmt = Statement::new_block_statement_with_scope_id(SPAN, stmts, scope_id, ctx);
}