Skip to main content

cha_core/plugins/
speculative_generality.rs

1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3/// Detect interfaces/traits with only one implementation (over-abstraction).
4pub struct SpeculativeGeneralityAnalyzer;
5
6impl Default for SpeculativeGeneralityAnalyzer {
7    fn default() -> Self {
8        Self
9    }
10}
11
12impl Plugin for SpeculativeGeneralityAnalyzer {
13    fn name(&self) -> &str {
14        "speculative_generality"
15    }
16
17    fn smells(&self) -> Vec<String> {
18        vec!["speculative_generality".into()]
19    }
20
21    fn description(&self) -> &str {
22        "Interface with too few implementations"
23    }
24
25    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
26        // Count how many classes extend/implement each interface in this file
27        let interfaces: Vec<_> = ctx
28            .model
29            .classes
30            .iter()
31            .filter(|c| c.is_interface)
32            .collect();
33        if interfaces.is_empty() {
34            return vec![];
35        }
36        let mut findings = Vec::new();
37        for iface in &interfaces {
38            let impl_count = ctx
39                .model
40                .classes
41                .iter()
42                .filter(|c| c.parent_name.as_deref() == Some(&iface.name))
43                .count();
44            // Only flag if exactly 0 or 1 implementations in the same file
45            if impl_count <= 1 {
46                findings.push(Finding {
47                    smell_name: "speculative_generality".into(),
48                    category: SmellCategory::Dispensables,
49                    severity: Severity::Hint,
50                    location: Location {
51                        path: ctx.file.path.clone(),
52                        start_line: iface.start_line,
53                        start_col: iface.name_col,
54                        end_line: iface.start_line,
55                        end_col: iface.name_end_col,
56                        name: Some(iface.name.clone()),
57                    },
58                    message: format!(
59                        "Interface `{}` has only {} implementation(s) in this file, consider Collapse Hierarchy",
60                        iface.name, impl_count
61                    ),
62                    suggested_refactorings: vec![
63                        "Collapse Hierarchy".into(),
64                        "Inline Class".into(),
65                    ],
66                    ..Default::default()
67                });
68            }
69        }
70        findings
71    }
72}