pub(crate) mod annotate;
#[cfg(feature = "tree-sitter")]
pub(crate) mod astutil;
#[allow(dead_code)]
pub(crate) mod cognitive;
#[allow(dead_code)]
pub(crate) mod coupling;
pub(crate) mod delta;
pub(crate) mod fabric;
pub(crate) mod gate;
pub(crate) mod naming;
pub(crate) mod persist;
pub(crate) mod report;
pub(crate) mod scan;
pub(crate) mod score;
pub(crate) use cognitive::{FunctionCognitive, cognitive_per_function};
pub(crate) use delta::{cognitive_delta, format_gate_notice, worst_regression};
pub(crate) use naming::{NamingFinding, naming_findings};
pub(crate) use scan::{ProjectHealth, scan_project};
pub(crate) use score::{Hotspot, NavigabilityInputs, NavigabilityScore, grade, navigability};
use serde::Serialize;
pub(crate) const DEFAULT_COGNITIVE_THRESHOLD: u32 = 15;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum GateMode {
Off,
#[default]
Warn,
Block,
}
impl GateMode {
pub(crate) fn parse(value: &str) -> Self {
match value.trim().to_ascii_lowercase().as_str() {
"off" | "false" | "none" | "disabled" => GateMode::Off,
"block" | "hard" | "error" => GateMode::Block,
_ => GateMode::Warn,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub(crate) struct FileHealth {
pub functions: Vec<FunctionCognitive>,
pub naming: Vec<NamingFinding>,
}
impl FileHealth {
pub(crate) fn over_threshold(
&self,
threshold: u32,
) -> impl Iterator<Item = &FunctionCognitive> {
self.functions
.iter()
.filter(move |f| f.cognitive > threshold)
}
pub(crate) fn worst_cognitive(&self) -> u32 {
self.functions
.iter()
.map(|f| f.cognitive)
.max()
.unwrap_or(0)
}
}
pub(crate) fn analyze_file(source: &str, extension: &str) -> Option<FileHealth> {
let functions = cognitive_per_function(source, extension)?;
let naming = naming_findings(source, extension).unwrap_or_default();
Some(FileHealth { functions, naming })
}
#[cfg(all(test, feature = "tree-sitter"))]
mod tests {
use super::*;
#[test]
fn analyze_file_combines_signals() {
let src = "fn _xfm_q2(a: bool, b: bool) { if a { if b {} } }\n";
let health = analyze_file(src, "rs").unwrap();
assert_eq!(health.functions.len(), 1);
assert_eq!(health.worst_cognitive(), 3);
assert_eq!(health.naming.len(), 1, "cryptic name flagged");
}
#[test]
fn analyze_file_unsupported_ext_is_none() {
assert!(analyze_file("plain text", "txt").is_none());
}
#[test]
fn over_threshold_filters() {
let src = "fn deep(a: bool) { if a { if a { if a { if a {} } } } }\n";
let health = analyze_file(src, "rs").unwrap();
assert_eq!(health.over_threshold(5).count(), 1);
assert_eq!(health.over_threshold(15).count(), 0);
}
}