Skip to main content

cha_core/plugins/
hub_like.rs

1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3/// Detect Hub-like modules with excessive fan-out (too many imports).
4///
5/// A module that imports too many other modules acts as a "hub" in the
6/// dependency graph, coupling itself to a large portion of the system.
7///
8/// ## References
9///
10/// [1] F. Arcelli Fontana, I. Pigazzini, R. Roveda, and M. Zanoni,
11///     "Architectural Smells Detected by Tools: a Catalogue Proposal,"
12///     in Proc. 13th European Conf. Software Architecture (ECSA), 2019.
13///     doi: 10.1145/3344948.3344982.
14///
15/// [2] R. C. Martin, "Agile Software Development: Principles, Patterns,
16///     and Practices," Prentice Hall, 2003. ISBN: 978-0135974445.
17///     Chapter 20: Stable Dependencies Principle.
18pub struct HubLikeDependencyAnalyzer {
19    pub max_imports: usize,
20}
21
22impl Default for HubLikeDependencyAnalyzer {
23    fn default() -> Self {
24        Self { max_imports: 20 }
25    }
26}
27
28impl Plugin for HubLikeDependencyAnalyzer {
29    fn name(&self) -> &str {
30        "hub_like_dependency"
31    }
32
33    fn description(&self) -> &str {
34        "Hub-like module with too many imports"
35    }
36
37    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
38        let count = ctx
39            .model
40            .imports
41            .iter()
42            .filter(|i| !i.is_module_decl)
43            .count();
44        if count <= self.max_imports {
45            return vec![];
46        }
47        vec![Finding {
48            smell_name: "hub_like_dependency".into(),
49            category: SmellCategory::Couplers,
50            severity: Severity::Warning,
51            location: Location {
52                path: ctx.file.path.clone(),
53                start_line: 1,
54                end_line: ctx.model.total_lines,
55                name: None,
56            },
57            message: format!(
58                "File has {} imports (threshold: {}), acting as a hub — consider splitting",
59                count, self.max_imports
60            ),
61            suggested_refactorings: vec!["Extract Module".into(), "Facade Pattern".into()],
62            actual_value: Some(count as f64),
63            threshold: Some(self.max_imports as f64),
64        }]
65    }
66}