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 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
27/// Group non-trivial functions by their body hash.
28fn 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
40/// Build findings for a group of structurally duplicate functions.
41fn 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}