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 a canonical param-type signature per function: the type names
35        // sorted alphabetically (we compare *sets*, not ordered tuples here,
36        // because data clumps is about recurring parameter *groups*).
37        let sigs: Vec<_> = ctx
38            .model
39            .functions
40            .iter()
41            .filter(|f| f.parameter_types.len() >= self.min_clump_size)
42            .map(|f| {
43                let mut names: Vec<&str> =
44                    f.parameter_types.iter().map(|t| t.name.as_str()).collect();
45                names.sort();
46                (f, names.join(","))
47            })
48            .collect();
49
50        // Count how many functions share the same type signature
51        let mut sig_counts: HashMap<&str, usize> = HashMap::new();
52        for (_, sig) in &sigs {
53            *sig_counts.entry(sig.as_str()).or_default() += 1;
54        }
55
56        sigs.iter()
57            .filter(|(_, sig)| {
58                sig_counts.get(sig.as_str()).copied().unwrap_or(0) >= self.min_occurrences
59            })
60            .map(|(f, sig)| Finding {
61                smell_name: "data_clumps".into(),
62                category: SmellCategory::Bloaters,
63                severity: Severity::Hint,
64                location: Location {
65                    path: ctx.file.path.clone(),
66                    start_line: f.start_line,
67                    start_col: f.name_col,
68                    end_line: f.start_line,
69                    end_col: f.name_end_col,
70                    name: Some(f.name.clone()),
71                },
72                message: format!(
73                    "Function `{}` shares parameter signature [{}] with {} other functions",
74                    f.name,
75                    sig,
76                    sig_counts[sig.as_str()] - 1
77                ),
78                suggested_refactorings: vec![
79                    "Extract Class".into(),
80                    "Introduce Parameter Object".into(),
81                ],
82                ..Default::default()
83            })
84            .collect()
85    }
86}