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 smells(&self) -> Vec<String> {
24        vec!["temporary_field".into()]
25    }
26
27    fn description(&self) -> &str {
28        "Fields used in too few methods"
29    }
30
31    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
32        let mut findings = Vec::new();
33        for class in &ctx.model.classes {
34            if class.field_names.is_empty() || class.method_count < self.min_methods {
35                continue;
36            }
37            let methods: Vec<_> = ctx
38                .model
39                .functions
40                .iter()
41                .filter(|f| f.start_line >= class.start_line && f.end_line <= class.end_line)
42                .collect();
43            if methods.len() < self.min_methods {
44                continue;
45            }
46            self.check_fields(ctx, class, &methods, &mut findings);
47        }
48        findings
49    }
50}
51
52impl TemporaryFieldAnalyzer {
53    fn check_fields(
54        &self,
55        ctx: &AnalysisContext,
56        class: &crate::ClassInfo,
57        methods: &[&crate::FunctionInfo],
58        findings: &mut Vec<Finding>,
59    ) {
60        for field in &class.field_names {
61            let usage = methods
62                .iter()
63                .filter(|m| m.referenced_fields.contains(field))
64                .count();
65            let ratio = usage as f64 / methods.len() as f64;
66            if ratio > 0.0 && ratio <= self.max_usage_ratio {
67                findings.push(Finding {
68                    smell_name: "temporary_field".into(),
69                    category: SmellCategory::OoAbusers,
70                    severity: Severity::Hint,
71                    location: Location {
72                        path: ctx.file.path.clone(),
73                        start_line: class.start_line,
74                        end_line: class.end_line,
75                        name: Some(format!("{}.{}", class.name, field)),
76                        ..Default::default()
77                    },
78                    message: format!(
79                        "Field `{}` in `{}` is only used in {}/{} methods, consider Extract Class",
80                        field,
81                        class.name,
82                        usage,
83                        methods.len()
84                    ),
85                    suggested_refactorings: vec!["Extract Class".into()],
86                    actual_value: Some(ratio),
87                    threshold: Some(self.max_usage_ratio),
88                });
89            }
90        }
91    }
92}