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
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 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}