rust_diff_analyzer/classifier/
attr_classifier.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use crate::{config::Config, types::SemanticUnit};
5
6/// Checks if unit is a test function
7///
8/// # Arguments
9///
10/// * `unit` - Semantic unit to check
11///
12/// # Returns
13///
14/// `true` if unit has #[test] attribute
15///
16/// # Examples
17///
18/// ```
19/// use rust_diff_analyzer::{
20///     classifier::attr_classifier::is_test_unit,
21///     types::{LineSpan, SemanticUnit, SemanticUnitKind, Visibility},
22/// };
23///
24/// let unit = SemanticUnit::new(
25///     SemanticUnitKind::Function,
26///     "test_it".to_string(),
27///     Visibility::Private,
28///     LineSpan::new(1, 10),
29///     vec!["test".to_string()],
30/// );
31///
32/// assert!(is_test_unit(&unit));
33/// ```
34pub fn is_test_unit(unit: &SemanticUnit) -> bool {
35    unit.has_attribute("test")
36}
37
38/// Checks if unit is a benchmark function
39///
40/// # Arguments
41///
42/// * `unit` - Semantic unit to check
43///
44/// # Returns
45///
46/// `true` if unit has #[bench] attribute
47///
48/// # Examples
49///
50/// ```
51/// use rust_diff_analyzer::{
52///     classifier::attr_classifier::is_bench_unit,
53///     types::{LineSpan, SemanticUnit, SemanticUnitKind, Visibility},
54/// };
55///
56/// let unit = SemanticUnit::new(
57///     SemanticUnitKind::Function,
58///     "bench_it".to_string(),
59///     Visibility::Private,
60///     LineSpan::new(1, 10),
61///     vec!["bench".to_string()],
62/// );
63///
64/// assert!(is_bench_unit(&unit));
65/// ```
66pub fn is_bench_unit(unit: &SemanticUnit) -> bool {
67    unit.has_attribute("bench")
68}
69
70/// Checks if unit is inside a #[cfg(test)] module
71///
72/// # Arguments
73///
74/// * `unit` - Semantic unit to check
75///
76/// # Returns
77///
78/// `true` if unit has cfg_test marker
79///
80/// # Examples
81///
82/// ```
83/// use rust_diff_analyzer::{
84///     classifier::attr_classifier::is_in_test_module,
85///     types::{LineSpan, SemanticUnit, SemanticUnitKind, Visibility},
86/// };
87///
88/// let unit = SemanticUnit::new(
89///     SemanticUnitKind::Function,
90///     "helper".to_string(),
91///     Visibility::Private,
92///     LineSpan::new(1, 10),
93///     vec!["cfg_test".to_string()],
94/// );
95///
96/// assert!(is_in_test_module(&unit));
97/// ```
98pub fn is_in_test_module(unit: &SemanticUnit) -> bool {
99    unit.has_attribute("cfg_test")
100}
101
102/// Checks if unit has a test-related feature attribute
103///
104/// # Arguments
105///
106/// * `unit` - Semantic unit to check
107/// * `config` - Configuration with test features
108///
109/// # Returns
110///
111/// `true` if unit has a test feature attribute
112///
113/// # Examples
114///
115/// ```
116/// use rust_diff_analyzer::{
117///     classifier::attr_classifier::has_test_feature,
118///     config::Config,
119///     types::{LineSpan, SemanticUnit, SemanticUnitKind, Visibility},
120/// };
121///
122/// let unit = SemanticUnit::new(
123///     SemanticUnitKind::Function,
124///     "mock_fn".to_string(),
125///     Visibility::Private,
126///     LineSpan::new(1, 10),
127///     vec!["cfg".to_string()],
128/// );
129///
130/// let config = Config::default();
131/// let result = has_test_feature(&unit, &config);
132/// ```
133pub fn has_test_feature(unit: &SemanticUnit, config: &Config) -> bool {
134    let test_features = config.test_features_set();
135
136    for attr in &unit.attributes {
137        if attr.starts_with("cfg") {
138            for feature in &test_features {
139                if attr.contains(feature) {
140                    return true;
141                }
142            }
143        }
144    }
145
146    false
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::types::{LineSpan, SemanticUnitKind, Visibility};
153
154    fn make_unit(attrs: Vec<&str>) -> SemanticUnit {
155        SemanticUnit::new(
156            SemanticUnitKind::Function,
157            "test".to_string(),
158            Visibility::Private,
159            LineSpan::new(1, 10),
160            attrs.into_iter().map(String::from).collect(),
161        )
162    }
163
164    #[test]
165    fn test_is_test_unit() {
166        assert!(is_test_unit(&make_unit(vec!["test"])));
167        assert!(!is_test_unit(&make_unit(vec!["inline"])));
168    }
169
170    #[test]
171    fn test_is_bench_unit() {
172        assert!(is_bench_unit(&make_unit(vec!["bench"])));
173        assert!(!is_bench_unit(&make_unit(vec!["test"])));
174    }
175
176    #[test]
177    fn test_is_in_test_module() {
178        assert!(is_in_test_module(&make_unit(vec!["cfg_test"])));
179        assert!(!is_in_test_module(&make_unit(vec!["test"])));
180    }
181}