cha_core/plugins/
data_clumps.rs1use std::collections::HashMap;
2
3use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
4
5pub 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 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 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}