use std::path::Path;
use anyhow::Result;
use super::CheckArgs;
use super::input::Work;
use super::review_budget::{Budget, Claim, Reservation};
use crate::analysis::code_quality::CodeQualityAnalyzer;
use crate::analysis::result::{AnalysisResult, FailureReason};
use crate::config::site::Refusal;
pub(super) enum LiveReview {
Skip,
Unbounded,
Reserved(Reservation),
Denied { completed: u32, limit: u32 },
}
pub(super) enum Stage {
Deferred(AnalysisResult),
Complete(Box<Pass>),
}
pub(super) struct Pass {
pub(super) cached: AnalysisResult,
pub(super) live: AnalysisResult,
pub(super) live_review: LiveReview,
pub(super) budget: Option<Budget>,
pub(super) live_answered: bool,
}
#[derive(Clone, Copy)]
pub(super) struct Policy {
pub(super) authoritative: bool,
pub(super) limit: u32,
}
pub(super) async fn complete(
args: &CheckArgs,
root: &Path,
work: &Work,
analyzer: &CodeQualityAnalyzer,
policy: Policy,
mut cached: AnalysisResult,
allow_live: bool,
) -> Result<Pass> {
let misses: Vec<&[_]> = work
.by_file
.iter()
.filter(|hunks| {
hunks.first().is_some_and(|first| {
matches!(
cached.failed_files.get(&first.file_path),
Some(FailureReason::CacheMiss)
)
})
})
.map(Vec::as_slice)
.collect();
let should_review_live = allow_live && !args.cache_only && !misses.is_empty();
let served_before_live: usize = analyzer
.chain()
.providers()
.iter()
.map(|provider| provider.served())
.sum();
let mut budget = None;
let live_review = if !should_review_live {
LiveReview::Skip
} else if !policy.authoritative || args.unlimited_reviews {
LiveReview::Unbounded
} else {
let resolved = Budget::for_repo(root, policy.limit).await?;
let claim = resolved.claim()?;
budget = Some(resolved);
match claim {
Claim::Reserved(reservation) => LiveReview::Reserved(reservation),
Claim::LimitReached { completed, limit } => LiveReview::Denied { completed, limit },
}
};
let mut live = AnalysisResult::default();
match &live_review {
LiveReview::Unbounded | LiveReview::Reserved(_) => {
for hunks in &misses {
if let Some(first) = hunks.first() {
cached.failed_files.remove(&first.file_path);
}
}
live = analyzer.analyze_files_live(&misses).await;
}
LiveReview::Denied { completed, limit } => {
for hunks in &misses {
if let Some(first) = hunks.first() {
cached.failed_files.insert(
first.file_path.clone(),
FailureReason::ReviewLimit {
completed: *completed,
limit: *limit,
},
);
}
}
}
LiveReview::Skip => {}
}
let served_after_live: usize = analyzer
.chain()
.providers()
.iter()
.map(|provider| provider.served())
.sum();
Ok(Pass {
cached,
live,
live_review,
budget,
live_answered: fresh_answered(served_before_live, served_after_live),
})
}
fn fresh_answered(before: usize, after: usize) -> bool {
after > before
}
pub(super) fn refused(work: &Work, refusal: &Refusal) -> Pass {
let mut cached = AnalysisResult::default();
for hunks in &work.by_file {
if let Some(first) = hunks.first() {
cached.failed_files.insert(
first.file_path.clone(),
FailureReason::SitePolicyRefused {
marker: refusal.marker.clone(),
policy: refusal.policy.clone(),
},
);
}
}
Pass {
cached,
live: AnalysisResult::default(),
live_review: LiveReview::Skip,
budget: None,
live_answered: false,
}
}
#[cfg(test)]
mod tests {
#[test]
fn fresh_answer_requires_the_served_count_to_increase() {
assert!(super::fresh_answered(2, 3));
assert!(!super::fresh_answered(2, 2));
assert!(!super::fresh_answered(3, 2));
}
}