use super::cognitive::cognitive_per_function;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ReadAnnotation {
pub line: usize,
pub name: String,
pub note: String,
}
pub(crate) fn annotations_for_file(source: &str, ext: &str, threshold: u32) -> Vec<ReadAnnotation> {
let mut out: Vec<ReadAnnotation> = Vec::new();
if let Some(fns) = cognitive_per_function(source, ext) {
for f in fns {
if f.cognitive > threshold {
out.push(ReadAnnotation {
line: f.line,
name: f.name,
note: format!("cc={}", f.cognitive),
});
}
}
}
out.sort_by(|a, b| a.line.cmp(&b.line).then_with(|| a.name.cmp(&b.name)));
out
}
pub(crate) fn by_name(annotations: &[ReadAnnotation]) -> HashMap<String, String> {
annotations
.iter()
.map(|a| (a.name.clone(), a.note.clone()))
.collect()
}
pub(crate) fn cognitive_for_symbol(
source: &str,
ext: &str,
name: &str,
start_line: usize,
) -> Option<u32> {
let fns = cognitive_per_function(source, ext)?;
fns.iter()
.filter(|f| f.name == name)
.min_by_key(|f| f.line.abs_diff(start_line))
.map(|f| f.cognitive)
}
#[cfg(all(test, feature = "tree-sitter"))]
mod tests {
use super::*;
#[test]
fn annotates_only_over_threshold() {
let src = "fn flat() {}\nfn deep(a: bool) { if a { if a { if a { if a {} } } } }\n";
let anns = annotations_for_file(src, "rs", 5);
assert_eq!(anns.len(), 1);
assert_eq!(anns[0].name, "deep");
assert_eq!(anns[0].note, "cc=10");
}
#[test]
fn nothing_when_under_threshold() {
let src = "fn small(a: bool) { if a {} }\n";
assert!(annotations_for_file(src, "rs", 15).is_empty());
}
#[test]
fn deterministic_across_runs() {
let src = "fn deep(a: bool) { if a { if a { if a { if a {} } } } }\n";
assert_eq!(
annotations_for_file(src, "rs", 5),
annotations_for_file(src, "rs", 5)
);
}
#[test]
fn by_name_lookup() {
let src = "fn deep(a: bool) { if a { if a { if a { if a {} } } } }\n";
let anns = annotations_for_file(src, "rs", 5);
let map = by_name(&anns);
assert_eq!(map.get("deep").map(String::as_str), Some("cc=10"));
}
#[test]
fn cognitive_for_symbol_reports_any_function() {
let src = "fn flat() {}\nfn deep(a: bool) { if a { if a { if a {} } } }\n";
assert_eq!(cognitive_for_symbol(src, "rs", "flat", 1), Some(0));
assert_eq!(cognitive_for_symbol(src, "rs", "deep", 2), Some(6));
assert_eq!(cognitive_for_symbol(src, "rs", "missing", 1), None);
}
#[test]
fn cognitive_for_symbol_disambiguates_by_line() {
let src = "fn dup(a: bool) { if a {} }\nfn other() {}\nfn dup(a: bool) { if a { if a { if a {} } } }\n";
assert_eq!(cognitive_for_symbol(src, "rs", "dup", 1), Some(1));
assert_eq!(cognitive_for_symbol(src, "rs", "dup", 3), Some(6));
}
}