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 smells(&self) -> Vec<String> {
24        vec!["feature_envy".into()]
25    }
26
27    fn description(&self) -> &str {
28        "Method uses external data more than its own"
29    }
30
31    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
32        ctx.model
33            .functions
34            .iter()
35            .filter(|f| {
36                let total = f.external_refs.len();
37                total >= self.min_refs
38            })
39            .map(|f| {
40                // Find the most-referenced external object
41                let mut counts: std::collections::HashMap<&str, usize> =
42                    std::collections::HashMap::new();
43                for r in &f.external_refs {
44                    *counts.entry(r.as_str()).or_default() += 1;
45                }
46                let top = counts.values().max().copied().unwrap_or(0);
47                let total = f.external_refs.len();
48                (f, top, total)
49            })
50            .filter(|(_, top, total)| (*top as f64 / *total as f64) >= self.external_ratio)
51            .map(|(f, top, total)| {
52                let ratio = top as f64 / total as f64;
53                Finding {
54                    smell_name: "feature_envy".into(),
55                    category: SmellCategory::Couplers,
56                    severity: Severity::Hint,
57                    location: Location {
58                        path: ctx.file.path.clone(),
59                        start_line: f.start_line,
60                        end_line: f.end_line,
61                        name: Some(f.name.clone()),
62                        ..Default::default()
63                    },
64                    message: format!(
65                        "Function `{}` references external objects more than its own data",
66                        f.name
67                    ),
68                    suggested_refactorings: vec!["Move Method".into()],
69                    actual_value: Some(ratio),
70                    threshold: Some(self.external_ratio),
71                    risk_score: None,
72                }
73            })
74            .collect()
75    }
76}