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 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 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 }
72 })
73 .collect()
74 }
75}