use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::Path;
use eros::Context;
use crate::config::spex::Spex;
use crate::error::Result;
use crate::spec::{AcId, CoverageLayer, SpecId};
use crate::support::package_files::PackageFiles;
use crate::tracing::annotations::{self, Annotation};
#[cfg(test)]
#[path = "annotation_index_test.rs"]
mod annotation_index_test;
#[derive(Debug, Default)]
pub struct AnnotationIndex {
bare: HashMap<SpecId, HashSet<CoverageLayer>>,
by_ac: HashMap<(SpecId, AcId), HashSet<CoverageLayer>>,
}
impl AnnotationIndex {
pub fn new() -> Self {
Self::default()
}
pub fn has_bare(&self, spec_id: &SpecId, layer: CoverageLayer) -> bool {
self.bare
.get(spec_id)
.is_some_and(|layers| layers.contains(&layer))
}
pub fn has_ac(&self, spec_id: &SpecId, ac_id: &AcId, layer: CoverageLayer) -> bool {
self.by_ac
.get(&(spec_id.clone(), ac_id.clone()))
.is_some_and(|layers| layers.contains(&layer))
}
#[cfg(test)]
pub fn scan(content: &str) -> Self {
let mut index = Self::new();
index.record_all(annotations::parse(content));
index
}
pub fn scan_project(spex: &Spex) -> Result<Self> {
let mut index = Self::new();
for package in &spex.packages {
for contract_path in &package.contracts {
if let Ok(content) = fs::read_to_string(contract_path) {
index.record_all(annotations::parse(&content));
}
}
if !package.path.is_dir() {
continue;
}
index.scan_package(&package.path, &spex.dir)?;
}
Ok(index)
}
fn scan_package(&mut self, package_path: &Path, spex_dir: &Path) -> Result<()> {
for result in PackageFiles::new(package_path) {
let path = result.context("could not walk package files")?;
if path.starts_with(spex_dir) {
continue;
}
let Ok(content) = fs::read_to_string(path) else {
continue;
};
self.record_all(annotations::parse(&content));
}
Ok(())
}
fn record_all(&mut self, annotations: Vec<Annotation>) {
for annotation in annotations {
self.record(annotation);
}
}
fn record(&mut self, annotation: Annotation) {
let Annotation {
layer,
spec_id,
ac_id,
} = annotation;
let Some(ac_id) = ac_id else {
self.bare.entry(spec_id).or_default().insert(layer);
return;
};
if layer == CoverageLayer::Contract {
self.bare.entry(spec_id.clone()).or_default().insert(layer);
}
self.by_ac
.entry((spec_id, ac_id))
.or_default()
.insert(layer);
}
}