Skip to main content

cha_core/plugins/
data_clumps.rs

1use std::collections::HashMap;
2
3use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
4
5/// Detect groups of parameters that repeatedly appear together across functions.
6pub struct DataClumpsAnalyzer {
7    pub min_clump_size: usize,
8    pub min_occurrences: usize,
9}
10
11impl Default for DataClumpsAnalyzer {
12    fn default() -> Self {
13        Self {
14            min_clump_size: 3,
15            min_occurrences: 3,
16        }
17    }
18}
19
20impl Plugin for DataClumpsAnalyzer {
21    fn name(&self) -> &str {
22        "data_clumps"
23    }
24
25    fn smells(&self) -> Vec<String> {
26        vec!["data_clumps".into()]
27    }
28
29    fn description(&self) -> &str {
30        "Repeated parameter type signatures"
31    }
32
33    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
34        // Build sorted param-type signatures per function
35        let sigs: Vec<_> = ctx
36            .model
37            .functions
38            .iter()
39            .filter(|f| f.parameter_types.len() >= self.min_clump_size)
40            .map(|f| (f, f.parameter_types.join(",")))
41            .collect();
42
43        // Count how many functions share the same type signature
44        let mut sig_counts: HashMap<&str, usize> = HashMap::new();
45        for (_, sig) in &sigs {
46            *sig_counts.entry(sig.as_str()).or_default() += 1;
47        }
48
49        sigs.iter()
50            .filter(|(_, sig)| {
51                sig_counts.get(sig.as_str()).copied().unwrap_or(0) >= self.min_occurrences
52            })
53            .map(|(f, sig)| Finding {
54                smell_name: "data_clumps".into(),
55                category: SmellCategory::Bloaters,
56                severity: Severity::Hint,
57                location: Location {
58                    path: ctx.file.path.clone(),
59                    start_line: f.start_line,
60                    start_col: f.name_col,
61                    end_line: f.start_line,
62                    end_col: f.name_end_col,
63                    name: Some(f.name.clone()),
64                },
65                message: format!(
66                    "Function `{}` shares parameter signature [{}] with {} other functions",
67                    f.name,
68                    sig,
69                    sig_counts[sig.as_str()] - 1
70                ),
71                suggested_refactorings: vec![
72                    "Extract Class".into(),
73                    "Introduce Parameter Object".into(),
74                ],
75                ..Default::default()
76            })
77            .collect()
78    }
79}