#![allow(
clippy::print_stderr,
reason = "the gate explains a non-zero exit on stderr, like the other CLI gates"
)]
use fallow_engine::baseline::{BaselineKind, BaselineStaleness, stale_baseline_gate_trips};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug)]
pub struct LoadedBaselineStaleness {
pub staleness: BaselineStaleness,
pub path: PathBuf,
pub scope_reasons: fallow_output::BaselineScopeReasons,
pub unrecognised_format: bool,
pub saved_by: Option<String>,
}
impl LoadedBaselineStaleness {
#[must_use]
pub fn to_envelope(&self, moved_entries: usize) -> fallow_output::BaselineStaleness {
self.staleness
.to_envelope(moved_entries, self.scope_reasons, self.unrecognised_format)
}
}
const fn entry_noun(expected: BaselineKind) -> &'static str {
match expected {
BaselineKind::DeadCode => "issue",
BaselineKind::Dupes => "clone group",
BaselineKind::Health => "finding",
}
}
pub fn gate_failed(
loaded: Option<&LoadedBaselineStaleness>,
enabled: bool,
expected: BaselineKind,
) -> bool {
if !enabled {
return false;
}
let Some(loaded) = loaded else {
return false;
};
report_gate(&GateReport {
entries: loaded.staleness.entries,
matched: loaded.staleness.matched,
change_scoped: loaded.staleness.change_scoped,
unrecognised_format: loaded.unrecognised_format,
saved_by: loaded.saved_by.as_deref(),
path: &loaded.path,
expected,
})
}
pub fn gate_failed_from_envelope(
staleness: &fallow_output::BaselineStaleness,
path: Option<&Path>,
enabled: bool,
saved_by: Option<&str>,
expected: BaselineKind,
) -> bool {
if !enabled {
return false;
}
let Some(path) = path else {
return false;
};
report_gate(&GateReport {
entries: staleness.baseline_entries,
matched: staleness.matched_entries,
change_scoped: staleness.change_scoped,
unrecognised_format: staleness.unrecognised_format,
saved_by,
path,
expected,
})
}
#[must_use]
pub fn refuse_save_before_analysis(
save_path: Option<&Path>,
saving: BaselineKind,
output: fallow_config::OutputFormat,
) -> Option<std::process::ExitCode> {
let refusal = fallow_engine::baseline::refuse_baseline_kind_overwrite(save_path?, saving)?;
Some(crate::error::emit_error(&refusal, 2, output))
}
pub fn note_stood_down(path: Option<&Path>, enabled: bool, reason: &str) {
if !enabled {
return;
}
let Some(path) = path else {
return;
};
eprintln!(
"Note: --fail-on-stale-baseline did not run: {reason}, so the baseline {} was not judged.",
path.display(),
);
}
pub fn note_unrecognised_baseline(
path: Option<&Path>,
unrecognised_format: bool,
saved_by: Option<&str>,
expected: BaselineKind,
flag: &str,
) {
if !unrecognised_format {
return;
}
let Some(path) = path else {
return;
};
if let Some(found) = saved_by {
eprintln!(
"Note: `fallow {found}` saved the baseline at {}. This run reads it as a \
`fallow {}` baseline, so it suppresses nothing. Point {flag} at this command's own \
baseline.",
path.display(),
expected.as_str(),
);
return;
}
eprintln!(
"Note: the baseline at {} has no entries this command recognises. It may be a baseline \
saved by another command, or an empty file. Either way it suppresses nothing. If another \
command saved it, point {flag} at this command's own baseline.",
path.display(),
);
}
struct GateReport<'a> {
entries: usize,
matched: usize,
change_scoped: bool,
unrecognised_format: bool,
saved_by: Option<&'a str>,
path: &'a Path,
expected: BaselineKind,
}
fn report_gate(report: &GateReport<'_>) -> bool {
let GateReport {
entries,
matched,
change_scoped,
unrecognised_format,
saved_by,
path,
expected,
} = *report;
let noun = entry_noun(expected);
if unrecognised_format {
match saved_by {
Some(found) => eprintln!(
"Baseline gate failed: `fallow {found}` saved the baseline {}. This run reads it \
as a `fallow {}` baseline, so it suppresses nothing. Point --baseline at this \
command's own baseline.",
path.display(),
expected.as_str(),
),
None => eprintln!(
"Baseline gate failed: the baseline {} has no entries this command recognises. It \
suppresses nothing. Point --baseline at this command's own baseline.",
path.display(),
),
}
return true;
}
if !stale_baseline_gate_trips(entries, matched, change_scoped) {
if change_scoped && entries > 0 {
eprintln!(
"Note: --fail-on-stale-baseline did not run: this analysis covered only part of \
the project, which cannot judge the whole-project baseline {}. Re-run over the \
whole project to gate on it.",
path.display(),
);
}
return false;
}
let stale = entries.saturating_sub(matched);
eprintln!(
"Baseline gate failed: {stale} of {entries} entries in {} matched no current {noun}. \
Re-save with: --save-baseline {}",
path.display(),
path.display(),
);
true
}