cha_core/plugins/
duplicate_code.rs1use std::collections::HashMap;
2
3use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
4
5pub struct DuplicateCodeAnalyzer;
7
8impl Plugin for DuplicateCodeAnalyzer {
9 fn name(&self) -> &str {
10 "duplicate_code"
11 }
12
13 fn smells(&self) -> Vec<String> {
14 vec!["duplicate_code".into()]
15 }
16
17 fn description(&self) -> &str {
18 "Duplicate code blocks (AST hash)"
19 }
20
21 fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
22 let hash_map = build_hash_groups(&ctx.model.functions);
23 hash_map
24 .values()
25 .filter(|g| g.len() >= 2)
26 .flat_map(|group| build_duplicate_findings(ctx, group))
27 .collect()
28 }
29}
30
31fn build_hash_groups(functions: &[crate::FunctionInfo]) -> HashMap<u64, Vec<&crate::FunctionInfo>> {
33 let mut map: HashMap<u64, Vec<&crate::FunctionInfo>> = HashMap::new();
34 for f in functions {
35 if let Some(hash) = f.body_hash
36 && f.line_count > 10
37 {
38 map.entry(hash).or_default().push(f);
39 }
40 }
41 map
42}
43
44fn build_duplicate_findings(ctx: &AnalysisContext, group: &[&crate::FunctionInfo]) -> Vec<Finding> {
46 let names: Vec<&str> = group.iter().map(|f| f.name.as_str()).collect();
47 group
48 .iter()
49 .map(|f| {
50 let peers = names
51 .iter()
52 .filter(|n| **n != f.name)
53 .copied()
54 .collect::<Vec<_>>()
55 .join(", ");
56 Finding {
57 smell_name: "duplicate_code".into(),
58 category: SmellCategory::Dispensables,
59 severity: Severity::Warning,
60 location: Location {
61 path: ctx.file.path.clone(),
62 start_line: f.start_line,
63 start_col: f.name_col,
64 end_line: f.start_line,
65 end_col: f.name_end_col,
66 name: Some(f.name.clone()),
67 },
68 message: format!(
69 "Function `{}` has duplicate structure with: {}",
70 f.name, peers
71 ),
72 suggested_refactorings: vec![
73 "Extract Method".into(),
74 "Form Template Method".into(),
75 ],
76 ..Default::default()
77 }
78 })
79 .collect()
80}