use std::collections::BTreeMap;
use oxc_coverage_types::{FileCoverage, Location};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct V8FunctionCoverage {
#[serde(rename = "functionName")]
pub function_name: String,
pub ranges: Vec<V8CoverageRange>,
#[serde(rename = "isBlockCoverage")]
pub is_block_coverage: bool,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct V8CoverageRange {
#[serde(rename = "startOffset")]
pub start_offset: u32,
#[serde(rename = "endOffset")]
pub end_offset: u32,
pub count: u32,
}
struct CoverageContext<'a> {
source: &'a str,
line_offsets: &'a [u32],
ranges: &'a [V8CoverageRange],
wrapper_length: u32,
}
struct CoverageInput<'a> {
source: &'a str,
functions: &'a [V8FunctionCoverage],
wrapper_length: u32,
arm_body_byte_spans: &'a BTreeMap<String, Vec<(u32, u32)>>,
}
impl CoverageContext<'_> {
fn count_for_location(&self, loc: &Location) -> u32 {
let start =
self.position_to_byte_offset(loc.start.line, loc.start.column) + self.wrapper_length;
let end = self.position_to_byte_offset(loc.end.line, loc.end.column) + self.wrapper_length;
smallest_containing_range_count(start, end, self.ranges)
}
fn arm_count_for_arm(&self, arm_loc: &Location, body_byte_span: Option<(u32, u32)>) -> u32 {
const TOLERANCE: u32 = 4;
let (arm_start, arm_end) = match body_byte_span {
Some((start, end)) if !(start == 0 && end == 0) => {
(start + self.wrapper_length, end + self.wrapper_length)
}
_ => (
self.position_to_byte_offset(arm_loc.start.line, arm_loc.start.column)
+ self.wrapper_length,
self.position_to_byte_offset(arm_loc.end.line, arm_loc.end.column)
+ self.wrapper_length,
),
};
let mut best: Option<(V8CoverageRange, u32)> = None;
for r in self.ranges {
let dist_start = r.start_offset.abs_diff(arm_start);
let dist_end = r.end_offset.abs_diff(arm_end);
if dist_start > TOLERANCE || dist_end > TOLERANCE {
continue;
}
let distance = dist_start + dist_end;
match best {
None => best = Some((*r, distance)),
Some((prev, prev_distance)) => {
let prev_width = prev.end_offset.saturating_sub(prev.start_offset);
let this_width = r.end_offset.saturating_sub(r.start_offset);
if distance < prev_distance
|| (distance == prev_distance && this_width < prev_width)
{
best = Some((*r, distance));
}
}
}
}
best.map_or(0, |(r, _)| r.count)
}
fn position_to_byte_offset(&self, line_1based: u32, col_utf16: u32) -> u32 {
if line_1based == 0 {
return 0;
}
let line_idx = (line_1based - 1) as usize;
if line_idx >= self.line_offsets.len() - 1 {
return *self.line_offsets.last().unwrap_or(&0);
}
let line_start = self.line_offsets[line_idx] as usize;
let line_end = self.line_offsets[line_idx + 1] as usize;
let line_bytes = self.source.get(line_start..line_end).unwrap_or("");
let mut utf16_remaining = col_utf16;
let mut byte_in_line = 0usize;
for ch in line_bytes.chars() {
if utf16_remaining == 0 {
break;
}
let units = ch.len_utf16() as u32;
if units > utf16_remaining {
break;
}
utf16_remaining -= units;
byte_in_line += ch.len_utf8();
}
u32::try_from(line_start + byte_in_line).unwrap_or(u32::MAX)
}
}
pub fn apply_v8_coverage(
file_coverage: &mut FileCoverage,
source: &str,
functions: &[V8FunctionCoverage],
wrapper_length: u32,
arm_body_byte_spans: &BTreeMap<String, Vec<(u32, u32)>>,
) {
let input = CoverageInput { source, functions, wrapper_length, arm_body_byte_spans };
apply_v8_coverage_inner(file_coverage, &input);
}
fn apply_v8_coverage_inner(file_coverage: &mut FileCoverage, input: &CoverageInput<'_>) {
let line_offsets = compute_line_offsets(input.source);
let ranges: Vec<V8CoverageRange> =
input.functions.iter().flat_map(|f| f.ranges.iter().copied()).collect();
let context = CoverageContext {
source: input.source,
line_offsets: &line_offsets,
ranges: &ranges,
wrapper_length: input.wrapper_length,
};
for (id, loc) in &file_coverage.statement_map {
let count = context.count_for_location(loc);
if let Some(slot) = file_coverage.s.get_mut(id) {
*slot = count;
}
}
for (id, fn_entry) in &file_coverage.fn_map {
let count = context.count_for_location(&fn_entry.loc);
if let Some(slot) = file_coverage.f.get_mut(id) {
*slot = count;
}
}
for (id, branch_entry) in &file_coverage.branch_map {
let body_spans = input.arm_body_byte_spans.get(id);
let arm_counts: Vec<u32> = branch_entry
.locations
.iter()
.enumerate()
.map(|(arm_idx, loc)| {
context.arm_count_for_arm(
loc,
body_spans.and_then(|spans| spans.get(arm_idx).copied()),
)
})
.collect();
if let Some(slot) = file_coverage.b.get_mut(id) {
*slot = arm_counts;
}
}
}
#[must_use]
pub fn extract_external_source_mapping_url(source: &str) -> Option<&str> {
const NEEDLE: &str = "//# sourceMappingURL=";
let idx = source.rfind(NEEDLE)?;
let after = &source[idx + NEEDLE.len()..];
let url = after.lines().next()?.trim();
if url.is_empty() || url.starts_with("data:") {
return None;
}
Some(url)
}
#[must_use]
pub fn extract_inline_source_map(source: &str) -> Option<serde_json::Value> {
const NEEDLE: &str = "//# sourceMappingURL=data:application/json";
let idx = source.rfind(NEEDLE)?;
let line = source[idx..].lines().next()?;
let comma = line.find(',')?;
let payload = &line[comma + 1..];
let is_base64 = line[..comma].contains(";base64");
let json = if is_base64 {
let bytes = decode_base64(payload).ok()?;
String::from_utf8(bytes).ok()?
} else {
urlencoding_decode(payload).ok()?
};
serde_json::from_str(&json).ok()
}
fn decode_base64(input: &str) -> Result<Vec<u8>, ()> {
fn value(c: u8) -> Result<u8, ()> {
match c {
b'A'..=b'Z' => Ok(c - b'A'),
b'a'..=b'z' => Ok(c - b'a' + 26),
b'0'..=b'9' => Ok(c - b'0' + 52),
b'+' | b'-' => Ok(62),
b'/' | b'_' => Ok(63),
_ => Err(()),
}
}
let trimmed: Vec<u8> =
input.bytes().filter(|b| *b != b'=' && !b.is_ascii_whitespace()).collect();
let mut out = Vec::with_capacity(trimmed.len() * 3 / 4);
for chunk in trimmed.chunks(4) {
let n0 = value(chunk[0])?;
let n1 = value(chunk[1])?;
out.push((n0 << 2) | (n1 >> 4));
if let Some(&c2) = chunk.get(2) {
let n2 = value(c2)?;
out.push((n1 << 4) | (n2 >> 2));
if let Some(&c3) = chunk.get(3) {
let n3 = value(c3)?;
out.push((n2 << 6) | n3);
}
}
}
Ok(out)
}
fn urlencoding_decode(input: &str) -> Result<String, ()> {
let mut out = Vec::with_capacity(input.len());
let bytes = input.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 2 < bytes.len() {
let hi = (bytes[i + 1] as char).to_digit(16).ok_or(())? as u8;
let lo = (bytes[i + 2] as char).to_digit(16).ok_or(())? as u8;
out.push((hi << 4) | lo);
i += 3;
} else {
out.push(bytes[i]);
i += 1;
}
}
String::from_utf8(out).map_err(|_| ())
}
fn compute_line_offsets(source: &str) -> Vec<u32> {
let mut offsets = vec![0u32];
for (i, b) in source.bytes().enumerate() {
if b == b'\n' {
let next = u32::try_from(i + 1).unwrap_or(u32::MAX);
offsets.push(next);
}
}
let end = u32::try_from(source.len()).unwrap_or(u32::MAX);
offsets.push(end);
offsets
}
fn smallest_containing_range_count(start: u32, end: u32, ranges: &[V8CoverageRange]) -> u32 {
let mut best: Option<V8CoverageRange> = None;
for r in ranges {
if r.start_offset <= start && r.end_offset >= end {
let width = r.end_offset.saturating_sub(r.start_offset);
match best {
None => best = Some(*r),
Some(prev) => {
let prev_width = prev.end_offset.saturating_sub(prev.start_offset);
if width < prev_width {
best = Some(*r);
}
}
}
}
}
best.map_or(0, |r| r.count)
}