mollify_core/
complexity.rs1use crate::fingerprint::fingerprint;
6use mollify_graph::ModuleGraph;
7use mollify_types::{Action, Category, Confidence, Finding, Location, Severity};
8
9pub const DEFAULT_CYCLOMATIC: u32 = 10;
11pub const DEFAULT_COGNITIVE: u32 = 15;
12
13pub fn analyze(graph: &ModuleGraph) -> Vec<Finding> {
14 analyze_with(graph, DEFAULT_CYCLOMATIC, DEFAULT_COGNITIVE)
15}
16
17pub fn analyze_with(graph: &ModuleGraph, max_cyclo: u32, max_cog: u32) -> Vec<Finding> {
18 let mut findings = Vec::new();
19 for m in &graph.modules {
20 for f in &m.parsed.functions {
21 let over_cyclo = f.cyclomatic > max_cyclo;
22 let over_cog = f.cognitive > max_cog;
23 if !over_cyclo && !over_cog {
24 continue;
25 }
26 let rule = "high-complexity";
27 let reason = format!(
28 "function `{}` is complex (cyclomatic {}, cognitive {}); thresholds {}/{}",
29 f.name, f.cyclomatic, f.cognitive, max_cyclo, max_cog
30 );
31 findings.push(Finding {
32 fingerprint: fingerprint(rule, &[m.path.as_str(), &f.name]),
33 rule: rule.into(),
34 category: Category::Complexity,
35 severity: Severity::Warn,
36 confidence: Confidence::Certain,
39 attribution: None,
40 reason,
41 location: Location {
42 path: m.path.clone(),
43 line: f.line,
44 column: 0,
45 end_line: None,
46 },
47 actions: vec![Action {
48 kind: "refactor".into(),
49 description: format!(
50 "Refactor `{}` to reduce complexity (extract helpers, flatten nesting)",
51 f.name
52 ),
53 auto_fixable: false,
54 suppression_comment: Some("# mollify: ignore[high-complexity]".into()),
55 }],
56 });
57 }
58 }
59 findings
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use camino::{Utf8Path, Utf8PathBuf};
66 use mollify_graph::discover_python_files;
67
68 fn temp(tag: &str) -> Utf8PathBuf {
69 let base =
70 std::env::temp_dir().join(format!("mollify-core-cx-{}-{tag}", std::process::id()));
71 let _ = std::fs::remove_dir_all(&base);
72 Utf8PathBuf::from_path_buf(base).unwrap()
73 }
74 fn write(dir: &Utf8Path, rel: &str, src: &str) {
75 let p = dir.join(rel);
76 std::fs::create_dir_all(p.parent().unwrap()).unwrap();
77 std::fs::write(p, src).unwrap();
78 }
79
80 #[test]
81 fn flags_complex_function() {
82 let d = temp("cx");
83 let mut body = String::from("def big(x):\n");
85 for i in 0..12 {
86 body.push_str(&format!(" if x == {i} and x:\n x += {i}\n"));
87 }
88 body.push_str(" return x\n");
89 write(&d, "__init__.py", &body);
90 let files = discover_python_files(&d);
91 let g = ModuleGraph::build(&d, &files);
92 let f = analyze(&g);
93 assert!(
94 f.iter()
95 .any(|x| x.rule == "high-complexity" && x.reason.contains("big")),
96 "got {f:?}"
97 );
98 std::fs::remove_dir_all(&d).ok();
99 }
100
101 #[test]
102 fn ignores_simple_function() {
103 let d = temp("simple");
104 write(&d, "__init__.py", "def small(x):\n return x + 1\n");
105 let files = discover_python_files(&d);
106 let g = ModuleGraph::build(&d, &files);
107 assert!(analyze(&g).is_empty());
108 std::fs::remove_dir_all(&d).ok();
109 }
110}