lean_ctx/core/code_health/
annotate.rs1use super::cognitive::cognitive_per_function;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ReadAnnotation {
15 pub line: usize,
17 pub name: String,
18 pub note: String,
20}
21
22pub fn annotations_for_file(source: &str, ext: &str, threshold: u32) -> Vec<ReadAnnotation> {
26 let mut out: Vec<ReadAnnotation> = Vec::new();
27 if let Some(fns) = cognitive_per_function(source, ext) {
28 for f in fns {
29 if f.cognitive > threshold {
30 out.push(ReadAnnotation {
31 line: f.line,
32 name: f.name,
33 note: format!("cc={}", f.cognitive),
34 });
35 }
36 }
37 }
38 out.sort_by(|a, b| a.line.cmp(&b.line).then_with(|| a.name.cmp(&b.name)));
39 out
40}
41
42pub fn by_name(annotations: &[ReadAnnotation]) -> HashMap<String, String> {
45 annotations
46 .iter()
47 .map(|a| (a.name.clone(), a.note.clone()))
48 .collect()
49}
50
51pub fn cognitive_for_symbol(source: &str, ext: &str, name: &str, start_line: usize) -> Option<u32> {
57 let fns = cognitive_per_function(source, ext)?;
58 fns.iter()
59 .filter(|f| f.name == name)
60 .min_by_key(|f| f.line.abs_diff(start_line))
61 .map(|f| f.cognitive)
62}
63
64#[cfg(all(test, feature = "tree-sitter"))]
65pub mod tests {
66 use super::*;
67
68 #[test]
69 fn annotates_only_over_threshold() {
70 let src = "fn flat() {}\nfn deep(a: bool) { if a { if a { if a { if a {} } } } }\n";
72 let anns = annotations_for_file(src, "rs", 5);
73 assert_eq!(anns.len(), 1);
74 assert_eq!(anns[0].name, "deep");
75 assert_eq!(anns[0].note, "cc=10");
76 }
77
78 #[test]
79 fn nothing_when_under_threshold() {
80 let src = "fn small(a: bool) { if a {} }\n";
81 {
82 let found = &annotations_for_file(src, "rs", 15);
83 assert!(found.is_empty(), "{found:?}");
84 }
85 }
86
87 #[test]
88 fn deterministic_across_runs() {
89 let src = "fn deep(a: bool) { if a { if a { if a { if a {} } } } }\n";
90 assert_eq!(
91 annotations_for_file(src, "rs", 5),
92 annotations_for_file(src, "rs", 5)
93 );
94 }
95
96 #[test]
97 fn by_name_lookup() {
98 let src = "fn deep(a: bool) { if a { if a { if a { if a {} } } } }\n";
99 let anns = annotations_for_file(src, "rs", 5);
100 let map = by_name(&anns);
101 assert_eq!(map.get("deep").map(String::as_str), Some("cc=10"));
102 }
103
104 #[test]
105 fn cognitive_for_symbol_reports_any_function() {
106 let src = "fn flat() {}\nfn deep(a: bool) { if a { if a { if a {} } } }\n";
107 assert_eq!(cognitive_for_symbol(src, "rs", "flat", 1), Some(0));
109 assert_eq!(cognitive_for_symbol(src, "rs", "deep", 2), Some(6));
110 assert_eq!(cognitive_for_symbol(src, "rs", "missing", 1), None);
111 }
112
113 #[test]
114 fn cognitive_for_symbol_disambiguates_by_line() {
115 let src = "fn dup(a: bool) { if a {} }\nfn other() {}\nfn dup(a: bool) { if a { if a { if a {} } } }\n";
117 assert_eq!(cognitive_for_symbol(src, "rs", "dup", 1), Some(1));
118 assert_eq!(cognitive_for_symbol(src, "rs", "dup", 3), Some(6));
119 }
120}