cha_core/plugins/
feature_envy.rs1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3pub 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 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}