use std::collections::BTreeMap;
use oxc_coverage_types::{BranchEntry, FileCoverage, FnEntry, FunctionIdentity, Location};
use crate::transform::djb31_hex;
#[expect(
clippy::redundant_pub_crate,
reason = "crate-internal type intentionally; the explicit pub(crate) documents that this is not part of the public API even though the parent module is already private"
)]
pub(crate) struct CoverageMaps {
pub(crate) path: String,
pub(crate) statement_locs: Vec<Location>,
pub(crate) fn_entries: Vec<FnEntry>,
pub(crate) branch_entries: Vec<BranchEntry>,
pub(crate) logical_branch_ids: Vec<usize>,
}
#[expect(
clippy::redundant_pub_crate,
reason = "crate-internal function intentionally; the explicit pub(crate) documents that this is not part of the public API even though the parent module is already private"
)]
pub(crate) fn build_file_coverage(maps: CoverageMaps) -> FileCoverage {
let CoverageMaps { path, statement_locs, fn_entries, branch_entries, logical_branch_ids } =
maps;
let statement_map: BTreeMap<String, Location> =
statement_locs.into_iter().enumerate().map(|(i, loc)| (i.to_string(), loc)).collect();
let fn_map: BTreeMap<String, FnEntry> =
fn_entries.into_iter().enumerate().map(|(i, e)| (i.to_string(), e)).collect();
let branch_map: BTreeMap<String, BranchEntry> = branch_entries
.into_iter()
.enumerate()
.filter(|(_, entry)| !entry.locations.is_empty())
.map(|(i, entry)| (i.to_string(), entry))
.collect();
let s = statement_map.keys().map(|k| (k.clone(), 0)).collect();
let f = fn_map.keys().map(|k| (k.clone(), 0)).collect();
let b =
branch_map.iter().map(|(k, entry)| (k.clone(), vec![0; entry.locations.len()])).collect();
let b_t = if logical_branch_ids.is_empty() {
None
} else {
Some(
logical_branch_ids
.iter()
.filter_map(|&id| {
let key = id.to_string();
let len = branch_map.get(&key)?.locations.len();
Some((key, vec![0; len]))
})
.collect(),
)
};
FileCoverage {
path,
statement_map,
fn_map,
branch_map,
s,
f,
b,
b_t,
input_source_map: None,
x_fallow_function_map: None,
}
}
#[expect(
clippy::redundant_pub_crate,
reason = "crate-internal helper intentionally; the explicit pub(crate) documents that this is not part of the public API even though the parent module is already private"
)]
pub(crate) fn build_function_identity_map(
path: &str,
fn_map: &BTreeMap<String, FnEntry>,
) -> BTreeMap<String, FunctionIdentity> {
fn_map
.iter()
.map(|(key, entry)| {
(
key.clone(),
FunctionIdentity {
id: function_identity_id(path, entry),
name: entry.name.clone(),
path: path.to_string(),
decl: entry.decl.clone(),
loc: entry.loc.clone(),
},
)
})
.collect()
}
fn function_identity_id(path: &str, entry: &FnEntry) -> String {
let input = serde_json::to_string(&serde_json::json!([
path,
entry.name,
entry.decl.start.line,
entry.decl.start.column,
entry.decl.end.line,
entry.decl.end.column,
entry.loc.start.line,
entry.loc.start.column,
entry.loc.end.line,
entry.loc.end.column,
]))
.expect("FunctionIdentity hash input serializes to JSON infallibly");
format!("fallow:fn:{}", djb31_hex(&input))
}
#[cfg(test)]
mod tests {
use super::function_identity_id;
use oxc_coverage_types::{FnEntry, Location, Position};
fn entry(name: &str) -> FnEntry {
let pos = |line, column| Position { line, column };
FnEntry {
name: name.to_string(),
line: 1,
decl: Location { start: pos(1, 0), end: pos(1, 10) },
loc: Location { start: pos(1, 0), end: pos(1, 10) },
}
}
#[test]
fn function_identity_id_is_collision_resistant_against_pipe_in_name() {
let a = function_identity_id("a", &entry("b|c"));
let b = function_identity_id("a|b", &entry("c"));
assert_ne!(
a, b,
"JSON-encoded hash input must keep (path, name) boundaries unambiguous; \
got collision: {a}",
);
}
}