mod formatter;
mod strategies;
pub mod types;
#[cfg(test)]
mod tests;
pub use types::{DebugFormat, ExplainCoverageConfig, ExplainCoverageResult, StrategyAttempt};
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use crate::risk::coverage_index::CoverageIndex;
use crate::risk::lcov::{parse_lcov_file, LcovData};
use formatter::format_text_report;
use strategies::{search_all_files, try_exact_match, try_path_strategies};
pub fn explain_coverage(config: ExplainCoverageConfig) -> Result<()> {
let lcov_data = parse_lcov_file(&config.coverage_file).context("Failed to parse LCOV file")?;
let coverage_index = CoverageIndex::from_coverage(&lcov_data);
let result = run_coverage_detection(
&config.function_name,
config.file_path.as_ref(),
&coverage_index,
&lcov_data,
config.verbose,
);
output_result(&result, config.format)?;
Ok(())
}
fn run_coverage_detection(
function_name: &str,
file_path: Option<&PathBuf>,
coverage_index: &CoverageIndex,
lcov_data: &LcovData,
verbose: bool,
) -> ExplainCoverageResult {
let mut result = ExplainCoverageResult::new(function_name.to_string(), file_path.cloned());
collect_available_data(&mut result, lcov_data);
match file_path {
Some(path) => run_file_strategies(
&mut result,
path,
function_name,
coverage_index,
lcov_data,
verbose,
),
None => run_global_strategies(&mut result, function_name, lcov_data),
}
result
}
fn collect_available_data(result: &mut ExplainCoverageResult, lcov_data: &LcovData) {
for (file, functions) in lcov_data.functions.iter() {
result.available_files.push(file.display().to_string());
for func in functions.iter() {
result
.available_functions
.push(format!("{}::{}", file.display(), func.name));
}
}
}
fn run_file_strategies(
result: &mut ExplainCoverageResult,
file_path: &Path,
function_name: &str,
coverage_index: &CoverageIndex,
lcov_data: &LcovData,
verbose: bool,
) {
let attempt = try_exact_match(coverage_index, file_path, function_name);
result.add_attempt(attempt);
if !result.coverage_found && verbose {
let attempts = try_path_strategies(file_path, function_name, lcov_data);
for attempt in attempts {
result.add_attempt(attempt);
}
}
}
fn run_global_strategies(
result: &mut ExplainCoverageResult,
function_name: &str,
lcov_data: &LcovData,
) {
let attempts = search_all_files(function_name, lcov_data);
for attempt in attempts {
result.add_attempt(attempt);
}
}
fn output_result(result: &ExplainCoverageResult, format: DebugFormat) -> Result<()> {
match format {
DebugFormat::Json => {
println!("{}", serde_json::to_string_pretty(result)?);
}
DebugFormat::Text => {
let verbose = !result.coverage_found;
print!("{}", format_text_report(result, verbose));
}
}
Ok(())
}
pub fn normalize_path(path: &Path) -> PathBuf {
let path_str = path.to_string_lossy();
let cleaned = path_str.strip_prefix("./").unwrap_or(&path_str);
PathBuf::from(cleaned)
}