use super::ScanDiagnostics;
use crate::constants::FastHashSet;
use crate::models::ExtensionType;
use crate::models::TimeRange;
use crate::pricing::TierThresholds;
use crate::session::ParseMode;
use crate::session::diagnostics::partial_failure_reason;
use crate::session::parser::parse_session_file_typed_as_with_diagnostics;
use crate::summary_cache::{
CachedSourceSummary, CompactSourceSummary, SourceFingerprint, SummaryCacheKey, SummaryKind,
SummaryScanCache,
};
use crate::utils::directory::{FileInfo, collect_provider_files_diagnostics};
use anyhow::Result;
use rayon::prelude::*;
use std::path::Path;
pub(crate) struct LoadedCompactSummary {
pub(crate) summary: CompactSourceSummary,
pub(crate) parsed: bool,
pub(crate) failure: Option<String>,
}
pub(crate) trait CompactSink {
fn fold(&mut self, provider: ExtensionType, summary: &CompactSourceSummary);
}
pub(crate) fn load_compact_file_summary(
file: &FileInfo,
provider: ExtensionType,
tiers: Option<&TierThresholds>,
) -> Result<LoadedCompactSummary> {
let parsed = parse_session_file_typed_as_with_diagnostics(
&file.path,
provider,
ParseMode::UsageOnly,
tiers,
)?;
if parsed.diagnostics.is_complete_failure() {
let failure = if parsed.diagnostics.recognized_records == 0 {
"source contained no recognized provider records".to_string()
} else {
format!(
"none of {} analyzer-relevant provider records used a supported schema",
parsed.diagnostics.relevant_records
)
};
return Ok(LoadedCompactSummary {
summary: CompactSourceSummary::default(),
parsed: false,
failure: Some(failure),
});
}
let emit = parsed.diagnostics.should_emit_session();
if emit && parsed.analysis.records.is_empty() {
return Ok(LoadedCompactSummary {
summary: CompactSourceSummary::default(),
parsed: false,
failure: Some("normalized source produced no analysis records".to_string()),
});
}
let partial = parsed.diagnostics.partial_failure_count();
Ok(LoadedCompactSummary {
summary: CompactSourceSummary::from_file(parsed.analysis, file.modified_date.clone(), emit),
parsed: true,
failure: (partial > 0).then(|| partial_failure_reason(partial)),
})
}
pub(crate) fn fold_cached(
provider: ExtensionType,
source: &Path,
cached: &CachedSourceSummary,
sink: &mut impl CompactSink,
diagnostics: &mut ScanDiagnostics,
) {
if cached.parsed {
diagnostics.parsed += 1;
sink.fold(provider, &cached.summary);
}
if let Some(error) = &cached.failure {
diagnostics.record_failure(provider, source, error.clone());
}
}
pub(crate) fn fold_loaded(
provider: ExtensionType,
source: &Path,
loaded: &LoadedCompactSummary,
sink: &mut impl CompactSink,
diagnostics: &mut ScanDiagnostics,
) {
if loaded.parsed {
diagnostics.parsed += 1;
sink.fold(provider, &loaded.summary);
}
if let Some(error) = &loaded.failure {
diagnostics.record_failure(provider, source, error.clone());
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn scan_cached_files<F>(
dirs: &[&Path],
provider: ExtensionType,
filter: F,
time_range: TimeRange,
max_depth: Option<usize>,
cache: &mut SummaryScanCache,
seen: &mut FastHashSet<SummaryCacheKey>,
sink: &mut impl CompactSink,
diagnostics: &mut ScanDiagnostics,
tiers: Option<&TierThresholds>,
) -> Result<()>
where
F: Copy + Fn(&Path) -> bool + Sync + Send,
{
let discovery = collect_provider_files_diagnostics(dirs, filter, time_range, max_depth);
if !discovery.failures.is_empty() {
cache.preserve_provider_keys(seen, SummaryKind::File, provider);
}
diagnostics.candidates += discovery.failures.len();
for failure in discovery.failures {
diagnostics.record_hard_failure(provider, &failure.path, failure.error);
}
let mut files = discovery.files;
files.sort_unstable_by(|left, right| left.path.cmp(&right.path));
diagnostics.candidates += files.len();
let mut misses = Vec::new();
for file in files {
let key = SummaryCacheKey::new(SummaryKind::File, provider, &file.path, time_range);
seen.insert(key.clone());
match SourceFingerprint::file(&file.path, provider) {
Ok(fingerprint) => {
if let Some(cached) = cache.get(&key, &fingerprint) {
fold_cached(provider, &file.path, cached, sink, diagnostics);
} else {
misses.push((file, key, fingerprint));
}
}
Err(error) => diagnostics.record_hard_failure(provider, &file.path, error.to_string()),
}
}
let loaded: Vec<_> = misses
.into_par_iter()
.map(|(file, key, fingerprint)| {
let result = load_compact_file_summary(&file, provider, tiers);
(file.path, key, fingerprint, result)
})
.collect();
for (source, key, fingerprint, result) in loaded {
cache.record_parse();
match result {
Ok(loaded) => {
fold_loaded(provider, &source, &loaded, sink, diagnostics);
cache.insert(
key,
fingerprint,
loaded.summary,
loaded.parsed,
loaded.failure,
);
}
Err(error) => diagnostics.record_hard_failure(provider, &source, error.to_string()),
}
}
Ok(())
}