rust_code_analysis/metrics/
exit.rs

1use serde::ser::{SerializeStruct, Serializer};
2use serde::Serialize;
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::*;
7
8/// The `NExit` metric.
9///
10/// This metric counts the number of possible exit points
11/// from a function/method.
12#[derive(Debug, Clone)]
13pub struct Stats {
14    exit: usize,
15    exit_sum: usize,
16    total_space_functions: usize,
17    exit_min: usize,
18    exit_max: usize,
19}
20
21impl Default for Stats {
22    fn default() -> Self {
23        Self {
24            exit: 0,
25            exit_sum: 0,
26            total_space_functions: 1,
27            exit_min: usize::MAX,
28            exit_max: 0,
29        }
30    }
31}
32
33impl Serialize for Stats {
34    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
35    where
36        S: Serializer,
37    {
38        let mut st = serializer.serialize_struct("nexits", 4)?;
39        st.serialize_field("sum", &self.exit())?;
40        st.serialize_field("average", &self.exit_average())?;
41        st.serialize_field("min", &self.exit_min())?;
42        st.serialize_field("max", &self.exit_max())?;
43        st.end()
44    }
45}
46
47impl fmt::Display for Stats {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        write!(
50            f,
51            "sum: {}, average: {} min: {}, max: {}",
52            self.exit_sum(),
53            self.exit_average(),
54            self.exit_min(),
55            self.exit_max()
56        )
57    }
58}
59
60impl Stats {
61    /// Merges a second `NExit` metric into the first one
62    pub fn merge(&mut self, other: &Stats) {
63        self.exit_max = self.exit_max.max(other.exit_max);
64        self.exit_min = self.exit_min.min(other.exit_min);
65        self.exit_sum += other.exit_sum;
66    }
67
68    /// Returns the `NExit` metric value
69    pub fn exit(&self) -> f64 {
70        self.exit as f64
71    }
72    /// Returns the `NExit` metric sum value
73    pub fn exit_sum(&self) -> f64 {
74        self.exit_sum as f64
75    }
76    /// Returns the `NExit` metric  minimum value
77    pub fn exit_min(&self) -> f64 {
78        self.exit_min as f64
79    }
80    /// Returns the `NExit` metric maximum value
81    pub fn exit_max(&self) -> f64 {
82        self.exit_max as f64
83    }
84
85    /// Returns the `NExit` metric average value
86    ///
87    /// This value is computed dividing the `NExit` value
88    /// for the total number of functions/closures in a space.
89    ///
90    /// If there are no functions in a code, its value is `NAN`.
91    pub fn exit_average(&self) -> f64 {
92        self.exit_sum() / self.total_space_functions as f64
93    }
94    #[inline(always)]
95    pub(crate) fn compute_sum(&mut self) {
96        self.exit_sum += self.exit;
97    }
98    #[inline(always)]
99    pub(crate) fn compute_minmax(&mut self) {
100        self.exit_max = self.exit_max.max(self.exit);
101        self.exit_min = self.exit_min.min(self.exit);
102        self.compute_sum();
103    }
104    pub(crate) fn finalize(&mut self, total_space_functions: usize) {
105        self.total_space_functions = total_space_functions;
106    }
107}
108
109#[doc(hidden)]
110pub trait Exit
111where
112    Self: Checker,
113{
114    fn compute(_node: &Node, _stats: &mut Stats) {}
115}
116
117impl Exit for PythonCode {
118    fn compute(node: &Node, stats: &mut Stats) {
119        if matches!(node.object().kind_id().into(), Python::ReturnStatement) {
120            stats.exit += 1;
121        }
122    }
123}
124
125impl Exit for MozjsCode {
126    fn compute(node: &Node, stats: &mut Stats) {
127        if matches!(node.object().kind_id().into(), Mozjs::ReturnStatement) {
128            stats.exit += 1;
129        }
130    }
131}
132
133impl Exit for JavascriptCode {
134    fn compute(node: &Node, stats: &mut Stats) {
135        if matches!(node.object().kind_id().into(), Javascript::ReturnStatement) {
136            stats.exit += 1;
137        }
138    }
139}
140
141impl Exit for TypescriptCode {
142    fn compute(node: &Node, stats: &mut Stats) {
143        if matches!(node.object().kind_id().into(), Typescript::ReturnStatement) {
144            stats.exit += 1;
145        }
146    }
147}
148
149impl Exit for TsxCode {
150    fn compute(node: &Node, stats: &mut Stats) {
151        if matches!(node.object().kind_id().into(), Tsx::ReturnStatement) {
152            stats.exit += 1;
153        }
154    }
155}
156
157impl Exit for RustCode {
158    fn compute(node: &Node, stats: &mut Stats) {
159        if matches!(node.object().kind_id().into(), Rust::ReturnExpression)
160            || Self::is_func(node) && node.object().child_by_field_name("return_type").is_some()
161        {
162            stats.exit += 1;
163        }
164    }
165}
166
167impl Exit for CppCode {
168    fn compute(node: &Node, stats: &mut Stats) {
169        if matches!(node.object().kind_id().into(), Cpp::ReturnStatement) {
170            stats.exit += 1;
171        }
172    }
173}
174
175impl Exit for JavaCode {
176    fn compute(node: &Node, stats: &mut Stats) {
177        if matches!(node.object().kind_id().into(), Java::ReturnStatement) {
178            stats.exit += 1;
179        }
180    }
181}
182
183impl Exit for PreprocCode {}
184impl Exit for CcommentCode {}
185
186#[cfg(test)]
187mod tests {
188    use std::path::PathBuf;
189
190    use super::*;
191
192    #[test]
193    fn python_no_exit() {
194        check_metrics!(
195            "a = 42",
196            "foo.py",
197            PythonParser,
198            nexits,
199            [
200                (exit_sum, 0, usize),
201                (exit_min, 0, usize),
202                (exit_max, 0, usize)
203            ],
204            [(exit_average, f64::NAN)] // 0 functions
205        );
206    }
207
208    #[test]
209    fn rust_no_exit() {
210        check_metrics!(
211            "let a = 42;",
212            "foo.rs",
213            RustParser,
214            nexits,
215            [
216                (exit_sum, 0, usize),
217                (exit_min, 0, usize),
218                (exit_max, 0, usize)
219            ],
220            [(exit_average, f64::NAN)] // 0 functions
221        );
222    }
223
224    #[test]
225    fn c_no_exit() {
226        check_metrics!(
227            "int a = 42;",
228            "foo.c",
229            CppParser,
230            nexits,
231            [
232                (exit_sum, 0, usize),
233                (exit_min, 0, usize),
234                (exit_max, 0, usize)
235            ],
236            [(exit_average, f64::NAN)] // 0 functions
237        );
238    }
239
240    #[test]
241    fn javascript_no_exit() {
242        check_metrics!(
243            "var a = 42;",
244            "foo.js",
245            JavascriptParser,
246            nexits,
247            [
248                (exit_sum, 0, usize),
249                (exit_min, 0, usize),
250                (exit_max, 0, usize)
251            ],
252            [(exit_average, f64::NAN)] // 0 functions
253        );
254    }
255
256    #[test]
257    fn python_simple_function() {
258        check_metrics!(
259            "def f(a, b):
260                 if a:
261                     return a",
262            "foo.py",
263            PythonParser,
264            nexits,
265            [
266                (exit_sum, 1, usize),
267                (exit_min, 0, usize),
268                (exit_max, 1, usize)
269            ],
270            [(exit_average, 1.0)] // 1 function
271        );
272    }
273
274    #[test]
275    fn python_more_functions() {
276        check_metrics!(
277            "def f(a, b):
278                 if a:
279                     return a
280            def f(a, b):
281                 if b:
282                     return b",
283            "foo.py",
284            PythonParser,
285            nexits,
286            [
287                (exit_sum, 2, usize),
288                (exit_min, 0, usize),
289                (exit_max, 1, usize)
290            ],
291            [(exit_average, 1.0)] // 2 functions
292        );
293    }
294
295    #[test]
296    fn python_nested_functions() {
297        check_metrics!(
298            "def f(a, b):
299                 def foo(a):
300                     if a:
301                         return 1
302                 bar = lambda a: lambda b: b or True or True
303                 return bar(foo(a))(a)",
304            "foo.py",
305            PythonParser,
306            nexits,
307            [
308                (exit_sum, 2, usize),
309                (exit_min, 0, usize),
310                (exit_max, 1, usize)
311            ],
312            [(exit_average, 0.5)] // 2 functions + 2 lambdas = 4
313        );
314    }
315
316    #[test]
317    fn java_no_exit() {
318        check_metrics!(
319            "int a = 42;",
320            "foo.java",
321            JavaParser,
322            nexits,
323            [
324                (exit_sum, 0, usize),
325                (exit_min, 0, usize),
326                (exit_max, 0, usize)
327            ],
328            [(exit_average, f64::NAN)] // 0 functions
329        );
330    }
331
332    #[test]
333    fn java_simple_function() {
334        check_metrics!(
335            "class A {
336              public int sum(int x, int y) {
337                return x + y;
338              }
339            }",
340            "foo.java",
341            JavaParser,
342            nexits,
343            [
344                (exit_sum, 1, usize),
345                (exit_min, 0, usize),
346                (exit_max, 1, usize)
347            ],
348            [(exit_average, 1.0)] // 1 exit / 1 space
349        );
350    }
351
352    #[test]
353    fn java_split_function() {
354        check_metrics!(
355            "class A {
356              public int multiply(int x, int y) {
357                if(x == 0 || y == 0){
358                    return 0;
359                }
360                return x * y;
361              }
362            }",
363            "foo.java",
364            JavaParser,
365            nexits,
366            [
367                (exit_sum, 2, usize),
368                (exit_min, 0, usize),
369                (exit_max, 2, usize)
370            ],
371            [(exit_average, 2.0)] // 2 exit / space 1
372        );
373    }
374}