Skip to main content

cha_core/plugins/
feature_envy.rs

1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3/// Detect methods that reference external objects more than their own.
4pub struct FeatureEnvyAnalyzer {
5    pub min_refs: usize,
6    pub external_ratio: f64,
7}
8
9impl Default for FeatureEnvyAnalyzer {
10    fn default() -> Self {
11        Self {
12            min_refs: 3,
13            external_ratio: 0.7,
14        }
15    }
16}
17
18impl Plugin for FeatureEnvyAnalyzer {
19    fn name(&self) -> &str {
20        "feature_envy"
21    }
22
23    fn description(&self) -> &str {
24        "Method uses external data more than its own"
25    }
26
27    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
28        ctx.model
29            .functions
30            .iter()
31            .filter(|f| {
32                let total = f.external_refs.len();
33                total >= self.min_refs
34            })
35            .map(|f| {
36                // Find the most-referenced external object
37                let mut counts: std::collections::HashMap<&str, usize> =
38                    std::collections::HashMap::new();
39                for r in &f.external_refs {
40                    *counts.entry(r.as_str()).or_default() += 1;
41                }
42                let top = counts.values().max().copied().unwrap_or(0);
43                let total = f.external_refs.len();
44                (f, top, total)
45            })
46            .filter(|(_, top, total)| (*top as f64 / *total as f64) >= self.external_ratio)
47            .map(|(f, top, total)| {
48                let ratio = top as f64 / total as f64;
49                Finding {
50                    smell_name: "feature_envy".into(),
51                    category: SmellCategory::Couplers,
52                    severity: Severity::Hint,
53                    location: Location {
54                        path: ctx.file.path.clone(),
55                        start_line: f.start_line,
56                        end_line: f.end_line,
57                        name: Some(f.name.clone()),
58                    },
59                    message: format!(
60                        "Function `{}` references external objects more than its own data",
61                        f.name
62                    ),
63                    suggested_refactorings: vec!["Move Method".into()],
64                    actual_value: Some(ratio),
65                    threshold: Some(self.external_ratio),
66                }
67            })
68            .collect()
69    }
70}