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 description(&self) -> &str {
26 "Repeated parameter type signatures"
27 }
28
29 fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
30 let sigs: Vec<_> = ctx
32 .model
33 .functions
34 .iter()
35 .filter(|f| f.parameter_types.len() >= self.min_clump_size)
36 .map(|f| (f, f.parameter_types.join(",")))
37 .collect();
38
39 let mut sig_counts: HashMap<&str, usize> = HashMap::new();
41 for (_, sig) in &sigs {
42 *sig_counts.entry(sig.as_str()).or_default() += 1;
43 }
44
45 sigs.iter()
46 .filter(|(_, sig)| {
47 sig_counts.get(sig.as_str()).copied().unwrap_or(0) >= self.min_occurrences
48 })
49 .map(|(f, sig)| Finding {
50 smell_name: "data_clumps".into(),
51 category: SmellCategory::Bloaters,
52 severity: Severity::Hint,
53 location: Location {
54 path: ctx.file.path.clone(),
55 start_line: f.start_line,
56 end_line: f.end_line,
57 name: Some(f.name.clone()),
58 },
59 message: format!(
60 "Function `{}` shares parameter signature [{}] with {} other functions",
61 f.name,
62 sig,
63 sig_counts[sig.as_str()] - 1
64 ),
65 suggested_refactorings: vec![
66 "Extract Class".into(),
67 "Introduce Parameter Object".into(),
68 ],
69 ..Default::default()
70 })
71 .collect()
72 }
73}