Skip to main content

cha_core/plugins/
temporary_field.rs

1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3/// Detect fields that are only used in a small fraction of methods.
4pub struct TemporaryFieldAnalyzer {
5    pub min_methods: usize,
6    pub max_usage_ratio: f64,
7}
8
9impl Default for TemporaryFieldAnalyzer {
10    fn default() -> Self {
11        Self {
12            min_methods: 3,
13            max_usage_ratio: 0.3,
14        }
15    }
16}
17
18impl Plugin for TemporaryFieldAnalyzer {
19    fn name(&self) -> &str {
20        "temporary_field"
21    }
22
23    fn description(&self) -> &str {
24        "Fields used in too few methods"
25    }
26
27    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
28        let mut findings = Vec::new();
29        for class in &ctx.model.classes {
30            if class.field_names.is_empty() || class.method_count < self.min_methods {
31                continue;
32            }
33            let methods: Vec<_> = ctx
34                .model
35                .functions
36                .iter()
37                .filter(|f| f.start_line >= class.start_line && f.end_line <= class.end_line)
38                .collect();
39            if methods.len() < self.min_methods {
40                continue;
41            }
42            self.check_fields(ctx, class, &methods, &mut findings);
43        }
44        findings
45    }
46}
47
48impl TemporaryFieldAnalyzer {
49    fn check_fields(
50        &self,
51        ctx: &AnalysisContext,
52        class: &crate::ClassInfo,
53        methods: &[&crate::FunctionInfo],
54        findings: &mut Vec<Finding>,
55    ) {
56        for field in &class.field_names {
57            let usage = methods
58                .iter()
59                .filter(|m| m.referenced_fields.contains(field))
60                .count();
61            let ratio = usage as f64 / methods.len() as f64;
62            if ratio > 0.0 && ratio <= self.max_usage_ratio {
63                findings.push(Finding {
64                    smell_name: "temporary_field".into(),
65                    category: SmellCategory::OoAbusers,
66                    severity: Severity::Hint,
67                    location: Location {
68                        path: ctx.file.path.clone(),
69                        start_line: class.start_line,
70                        end_line: class.end_line,
71                        name: Some(format!("{}.{}", class.name, field)),
72                    },
73                    message: format!(
74                        "Field `{}` in `{}` is only used in {}/{} methods, consider Extract Class",
75                        field,
76                        class.name,
77                        usage,
78                        methods.len()
79                    ),
80                    suggested_refactorings: vec!["Extract Class".into()],
81                    actual_value: Some(ratio),
82                    threshold: Some(self.max_usage_ratio),
83                });
84            }
85        }
86    }
87}