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