use oxc_span::Span;
use oxc_coverage_types::{BranchEntry, FnEntry, Location, Position};
use super::CoverageTransform;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct EagerMergeKey {
source: u32,
start_line: u32,
start_column: u32,
end_line: u32,
end_column: u32,
}
impl From<(u32, Location)> for EagerMergeKey {
fn from((source, location): (u32, Location)) -> Self {
Self {
source,
start_line: location.start.line,
start_column: location.start.column,
end_line: location.end.line,
end_column: location.end.column,
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct BranchKey {
source: u32,
arms: Vec<(u32, u32, u32, u32)>,
}
pub(super) struct PendingArm {
pub(super) location_span: Span,
pub(super) body_span: Span,
}
impl PendingArm {
pub(super) const fn new(span: Span) -> Self {
Self { location_span: span, body_span: span }
}
pub(super) const fn with_body(location_span: Span, body_span: Span) -> Self {
Self { location_span, body_span }
}
}
pub(super) struct PendingBranch {
pub(super) branch_type: &'static str,
pub(super) umbrella_span: Span,
pub(super) gate_arms: bool,
pub(super) arms: Vec<PendingArm>,
}
pub(super) struct BranchRegistration {
pub(super) branch_id: usize,
path_indices: Vec<Option<usize>>,
}
impl BranchRegistration {
pub(super) fn slot(&self, arm: usize) -> Option<usize> {
self.path_indices[arm]
}
}
const fn location_parts(loc: &Location) -> (u32, u32, u32, u32) {
(loc.start.line, loc.start.column, loc.end.line, loc.end.column)
}
impl CoverageTransform<'_, '_> {
fn location_maps(&self, loc: &Location) -> bool {
self.eager_remapper.as_ref().is_none_or(|r| r.location_maps(loc))
}
fn eager_merge_key(&self, loc: &Location) -> Option<EagerMergeKey> {
self.eager_remapper.as_ref().and_then(|r| r.remap_location(loc)).map(EagerMergeKey::from)
}
fn span_to_location(&self, span: Span) -> Location {
Location {
start: self.offset_to_position(span.start),
end: self.offset_to_position(span.end),
}
}
fn offset_to_position(&self, offset: u32) -> Position {
let line = self.line_offsets.partition_point(|&o| o <= offset).saturating_sub(1);
let line_start = self.line_offsets[line] as usize;
let end = (offset as usize).min(self.source.len());
let column = if self.source_is_ascii {
end - line_start
} else {
self.source[line_start..end].chars().map(char::len_utf16).sum::<usize>()
};
Position {
line: u32::try_from(line + 1).unwrap_or(u32::MAX),
column: u32::try_from(column).unwrap_or(u32::MAX),
}
}
pub(super) fn add_function(
&mut self,
name: String,
decl_span: Span,
body_span: Span,
) -> Option<usize> {
let decl = self.span_to_location(decl_span);
let loc = self.span_to_location(body_span);
if !self.location_maps(&decl) || !self.location_maps(&loc) {
return None;
}
let key = self.eager_merge_key(&decl);
if let Some(key) = &key
&& let Some(&id) = self.eager_function_ids.get(key)
{
if let Some(existing) = self.fn_map.get(id)
&& (existing.name != name
|| location_parts(&existing.decl) != location_parts(&decl)
|| location_parts(&existing.loc) != location_parts(&loc))
{
self.eager_function_overlay_conflict = true;
}
return Some(id);
}
let id_num = self.fn_map.len();
if let Some(key) = key {
self.eager_function_ids.insert(key, id_num);
}
let line = decl.start.line;
self.fn_map.push(FnEntry { name, line, decl, loc });
Some(id_num)
}
pub(super) fn add_statement(&mut self, span: Span) -> Option<usize> {
let loc = self.span_to_location(span);
if !self.location_maps(&loc) {
return None;
}
let key = self.eager_merge_key(&loc);
if let Some(key) = &key
&& let Some(&id) = self.eager_statement_ids.get(key)
{
return Some(id);
}
let id_num = self.statement_map.len();
if let Some(key) = key {
self.eager_statement_ids.insert(key, id_num);
}
self.statement_map.push(loc);
Some(id_num)
}
fn eager_branch_key(&self, surviving_locs: &[Location]) -> Option<BranchKey> {
let remapper = self.eager_remapper.as_ref()?;
let mut source = None;
let mut arms = Vec::with_capacity(surviving_locs.len());
for loc in surviving_locs {
let (arm_source, mapped) = remapper.remap_location(loc)?;
if *source.get_or_insert(arm_source) != arm_source {
return None;
}
arms.push(location_parts(&mapped));
}
Some(BranchKey { source: source?, arms })
}
pub(super) fn register_branch(&mut self, pending: PendingBranch) -> Option<BranchRegistration> {
let PendingBranch { branch_type, umbrella_span, gate_arms, arms } = pending;
let umbrella = self.span_to_location(umbrella_span);
let mut path_indices = Vec::with_capacity(arms.len());
let mut surviving_locs = Vec::new();
let mut body_spans = Vec::new();
for arm in &arms {
let loc = self.span_to_location(arm.location_span);
if gate_arms && !self.location_maps(&loc) {
path_indices.push(None);
continue;
}
path_indices.push(Some(surviving_locs.len()));
surviving_locs.push(loc);
body_spans.push((arm.body_span.start, arm.body_span.end));
}
if self.eager_remapper.is_some() && surviving_locs.is_empty() {
return None;
}
let entry_loc = if self.location_maps(&umbrella) {
umbrella
} else {
surviving_locs.iter().find(|loc| self.location_maps(loc)).cloned()?
};
let key = self.eager_branch_key(&surviving_locs);
if let Some(key) = &key
&& let Some(&branch_id) = self.eager_branch_ids.get(key)
{
return Some(BranchRegistration { branch_id, path_indices });
}
let branch_id = self.branch_map.len();
if let Some(key) = key {
self.eager_branch_ids.insert(key, branch_id);
}
let line = entry_loc.start.line;
self.branch_map.push(BranchEntry {
loc: entry_loc,
line,
branch_type: branch_type.to_string(),
locations: surviving_locs,
});
self.branch_arm_body_byte_spans.push(body_spans);
Some(BranchRegistration { branch_id, path_indices })
}
}
pub(super) fn is_synthetic_span(span: Span) -> bool {
span.start == 0 && span.end == 0
}