use std::collections::BTreeMap;
use oxc_coverage_types::{BranchEntry, FileCoverage, FnEntry, FunctionIdentity, Location};
use sha2::{Digest, Sha256};
#[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 = build_truthy_hit_map(&branch_map, &logical_branch_ids);
FileCoverage {
path,
statement_map,
fn_map,
branch_map,
s,
f,
b,
b_t,
input_source_map: None,
x_fallow_function_map: None,
}
}
fn build_truthy_hit_map(
branch_map: &BTreeMap<String, BranchEntry>,
logical_branch_ids: &[usize],
) -> Option<BTreeMap<String, Vec<u32>>> {
if logical_branch_ids.is_empty() {
return None;
}
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(),
)
}
#[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 mut hasher = Sha256::new();
hasher.update(path.as_bytes());
hasher.update(entry.name.as_bytes());
hasher.update(entry.decl.start.line.to_string().as_bytes());
hasher.update(b"function");
let digest = hasher.finalize();
let mut hex = String::with_capacity("fallow:fn:".len() + 8);
hex.push_str("fallow:fn:");
for byte in &digest[..4] {
use std::fmt::Write;
write!(&mut hex, "{byte:02x}").expect("writing to String is infallible");
}
hex
}
#[cfg(test)]
mod tests {
use super::function_identity_id;
use oxc_coverage_types::{FnEntry, Location, Position};
use sha2::{Digest, Sha256};
fn entry(name: &str, start_line: u32) -> FnEntry {
let pos = |line, column| Position { line, column };
FnEntry {
name: name.to_string(),
line: start_line,
decl: Location { start: pos(start_line, 0), end: pos(start_line, 10) },
loc: Location { start: pos(start_line, 0), end: pos(start_line, 10) },
}
}
fn protocol_function_identity_id(file: &str, name: &str, start_line: u32) -> String {
let mut hasher = Sha256::new();
hasher.update(file.as_bytes());
hasher.update(name.as_bytes());
hasher.update(start_line.to_string().as_bytes());
hasher.update(b"function");
let digest = hasher.finalize();
let mut out = String::from("fallow:fn:");
for byte in &digest[..4] {
use std::fmt::Write;
write!(&mut out, "{byte:02x}").unwrap();
}
out
}
#[test]
fn function_identity_id_matches_fallow_cov_protocol() {
for (path, name, line) in [
("src/app.ts", "handler", 1u32),
("src/app.ts", "handler", 42),
("a/b/c.ts", "(anonymous_0)", 7),
("computed.ts", "x|y", 3),
] {
let ours = function_identity_id(path, &entry(name, line));
let theirs = protocol_function_identity_id(path, name, line);
assert_eq!(
ours, theirs,
"x_fallow_functionMap.id must match fallow-cov-protocol \
function_identity_id({path:?}, {name:?}, {line})",
);
}
}
#[test]
fn function_identity_id_has_fixed_8_hex_suffix() {
let id = function_identity_id("src/app.ts", &entry("handler", 1));
assert_eq!(id.len(), "fallow:fn:".len() + 8, "got {id:?}");
assert!(id.starts_with("fallow:fn:"));
let hex = &id["fallow:fn:".len()..];
assert!(
hex.chars().all(|c| c.is_ascii_hexdigit() && (c.is_ascii_digit() || c.is_lowercase())),
"suffix must be lowercase hex: {hex:?}",
);
}
}