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.model.imports.len();
39        if count <= self.max_imports {
40            return vec![];
41        }
42        vec![Finding {
43            smell_name: "hub_like_dependency".into(),
44            category: SmellCategory::Couplers,
45            severity: Severity::Warning,
46            location: Location {
47                path: ctx.file.path.clone(),
48                start_line: 1,
49                end_line: ctx.model.total_lines,
50                name: None,
51            },
52            message: format!(
53                "File has {} imports (threshold: {}), acting as a hub — consider splitting",
54                count, self.max_imports
55            ),
56            suggested_refactorings: vec!["Extract Module".into(), "Facade Pattern".into()],
57            actual_value: Some(count as f64),
58            threshold: Some(self.max_imports as f64),
59        }]
60    }
61}