Skip to main content

cha_core/plugins/
duplicate_code.rs

1use std::collections::HashMap;
2
3use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
4
5/// Detect functions with identical AST structure (duplicate code).
6pub 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
31/// Group non-trivial functions by their body hash.
32fn 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
44/// Build findings for a group of structurally duplicate functions.
45fn 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}