Skip to main content

rust_code_analysis_code_split/metrics/
exit.rs

1use serde::Serialize;
2use serde::ser::{SerializeStruct, Serializer};
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::macros::implement_metric_trait;
7use crate::*;
8
9/// The `NExit` metric.
10///
11/// This metric counts the number of possible exit points
12/// from a function/method.
13#[derive(Debug, Clone)]
14pub struct Stats {
15    exit: usize,
16    exit_sum: usize,
17    total_space_functions: usize,
18    exit_min: usize,
19    exit_max: usize,
20}
21
22impl Default for Stats {
23    fn default() -> Self {
24        Self {
25            exit: 0,
26            exit_sum: 0,
27            total_space_functions: 1,
28            exit_min: usize::MAX,
29            exit_max: 0,
30        }
31    }
32}
33
34impl Serialize for Stats {
35    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
36    where
37        S: Serializer,
38    {
39        let mut st = serializer.serialize_struct("nexits", 4)?;
40        st.serialize_field("sum", &self.exit_sum())?;
41        st.serialize_field("average", &self.exit_average())?;
42        st.serialize_field("min", &self.exit_min())?;
43        st.serialize_field("max", &self.exit_max())?;
44        st.end()
45    }
46}
47
48impl fmt::Display for Stats {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        write!(
51            f,
52            "sum: {}, average: {} min: {}, max: {}",
53            self.exit_sum(),
54            self.exit_average(),
55            self.exit_min(),
56            self.exit_max()
57        )
58    }
59}
60
61impl Stats {
62    /// Merges a second `NExit` metric into the first one
63    pub fn merge(&mut self, other: &Stats) {
64        self.exit_max = self.exit_max.max(other.exit_max);
65        self.exit_min = self.exit_min.min(other.exit_min);
66        self.exit_sum += other.exit_sum;
67    }
68
69    /// Returns the `NExit` metric value
70    pub fn exit(&self) -> f64 {
71        self.exit as f64
72    }
73    /// Returns the `NExit` metric sum value
74    pub fn exit_sum(&self) -> f64 {
75        self.exit_sum as f64
76    }
77    /// Returns the `NExit` metric  minimum value
78    pub fn exit_min(&self) -> f64 {
79        self.exit_min as f64
80    }
81    /// Returns the `NExit` metric maximum value
82    pub fn exit_max(&self) -> f64 {
83        self.exit_max as f64
84    }
85
86    /// Returns the `NExit` metric average value
87    ///
88    /// This value is computed dividing the `NExit` value
89    /// for the total number of functions/closures in a space.
90    ///
91    /// If there are no functions in a code, its value is `NAN`.
92    pub fn exit_average(&self) -> f64 {
93        self.exit_sum() / self.total_space_functions as f64
94    }
95    #[inline(always)]
96    pub(crate) fn compute_sum(&mut self) {
97        self.exit_sum += self.exit;
98    }
99    #[inline(always)]
100    pub(crate) fn compute_minmax(&mut self) {
101        self.exit_max = self.exit_max.max(self.exit);
102        self.exit_min = self.exit_min.min(self.exit);
103        self.compute_sum();
104    }
105    pub(crate) fn finalize(&mut self, total_space_functions: usize) {
106        self.total_space_functions = total_space_functions;
107    }
108}
109
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.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.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.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.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.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!(
160            node.kind_id().into(),
161            Rust::ReturnExpression | Rust::TryExpression
162        ) || Self::is_func(node) && node.child_by_field_name("return_type").is_some()
163        {
164            stats.exit += 1;
165        }
166    }
167}
168
169impl Exit for CppCode {
170    fn compute(node: &Node, stats: &mut Stats) {
171        if matches!(node.kind_id().into(), Cpp::ReturnStatement) {
172            stats.exit += 1;
173        }
174    }
175}
176
177impl Exit for JavaCode {
178    fn compute(node: &Node, stats: &mut Stats) {
179        if matches!(node.kind_id().into(), Java::ReturnStatement) {
180            stats.exit += 1;
181        }
182    }
183}
184
185implement_metric_trait!(Exit, KotlinCode, PreprocCode, CcommentCode);
186
187#[cfg(test)]
188mod tests {
189    use crate::tools::check_metrics;
190
191    use super::*;
192
193    #[test]
194    fn python_no_exit() {
195        check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
196            // 0 functions
197            insta::assert_json_snapshot!(
198                metric.nexits,
199                @r###"
200                    {
201                      "sum": 0.0,
202                      "average": null,
203                      "min": 0.0,
204                      "max": 0.0
205                    }"###
206            );
207        });
208    }
209
210    #[test]
211    fn rust_no_exit() {
212        check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
213            // 0 functions
214            insta::assert_json_snapshot!(
215                metric.nexits,
216                @r###"
217                    {
218                      "sum": 0.0,
219                      "average": null,
220                      "min": 0.0,
221                      "max": 0.0
222                    }"###
223            );
224        });
225    }
226
227    #[test]
228    fn rust_question_mark() {
229        check_metrics::<RustParser>("let _ = a? + b? + c?;", "foo.rs", |metric| {
230            // 0 functions
231            insta::assert_json_snapshot!(
232                metric.nexits,
233                @r###"
234                    {
235                      "sum": 3.0,
236                      "average": null,
237                      "min": 3.0,
238                      "max": 3.0
239                    }"###
240            );
241        });
242    }
243
244    #[test]
245    fn c_no_exit() {
246        check_metrics::<CppParser>("int a = 42;", "foo.c", |metric| {
247            // 0 functions
248            insta::assert_json_snapshot!(
249                metric.nexits,
250                @r###"
251                    {
252                      "sum": 0.0,
253                      "average": null,
254                      "min": 0.0,
255                      "max": 0.0
256                    }"###
257            );
258        });
259    }
260
261    #[test]
262    fn javascript_no_exit() {
263        check_metrics::<JavascriptParser>("var a = 42;", "foo.js", |metric| {
264            // 0 functions
265            insta::assert_json_snapshot!(
266                metric.nexits,
267                @r###"
268                    {
269                      "sum": 0.0,
270                      "average": null,
271                      "min": 0.0,
272                      "max": 0.0
273                    }"###
274            );
275        });
276    }
277
278    #[test]
279    fn python_simple_function() {
280        check_metrics::<PythonParser>(
281            "def f(a, b):
282                 if a:
283                     return a",
284            "foo.py",
285            |metric| {
286                println!("{:?}", metric.nexits);
287                // 1 function
288                insta::assert_json_snapshot!(
289                    metric.nexits,
290                    @r###"
291                    {
292                      "sum": 1.0,
293                      "average": 1.0,
294                      "min": 0.0,
295                      "max": 1.0
296                    }"###
297                );
298            },
299        );
300    }
301
302    #[test]
303    fn python_more_functions() {
304        check_metrics::<PythonParser>(
305            "def f(a, b):
306                 if a:
307                     return a
308            def f(a, b):
309                 if b:
310                     return b",
311            "foo.py",
312            |metric| {
313                // 2 functions
314                insta::assert_json_snapshot!(
315                    metric.nexits,
316                    @r#"
317                {
318                  "sum": 2.0,
319                  "average": 1.0,
320                  "min": 0.0,
321                  "max": 2.0
322                }
323                "#
324                );
325            },
326        );
327    }
328
329    #[test]
330    fn python_nested_functions() {
331        check_metrics::<PythonParser>(
332            "def f(a, b):
333                 def foo(a):
334                     if a:
335                         return 1
336                 bar = lambda a: lambda b: b or True or True
337                 return bar(foo(a))(a)",
338            "foo.py",
339            |metric| {
340                // 2 functions + 2 lambdas = 4
341                insta::assert_json_snapshot!(
342                    metric.nexits,
343                    @r#"
344                {
345                  "sum": 2.0,
346                  "average": 1.0,
347                  "min": 0.0,
348                  "max": 2.0
349                }
350                "#
351                );
352            },
353        );
354    }
355
356    #[test]
357    fn java_no_exit() {
358        check_metrics::<JavaParser>("int a = 42;", "foo.java", |metric| {
359            // 0 functions
360            insta::assert_json_snapshot!(
361                metric.nexits,
362                @r###"
363                    {
364                      "sum": 0.0,
365                      "average": null,
366                      "min": 0.0,
367                      "max": 0.0
368                    }"###
369            );
370        });
371    }
372
373    #[test]
374    fn java_simple_function() {
375        check_metrics::<JavaParser>(
376            "class A {
377              public int sum(int x, int y) {
378                return x + y;
379              }
380            }",
381            "foo.java",
382            |metric| {
383                // 1 exit / 1 space
384                insta::assert_json_snapshot!(
385                    metric.nexits,
386                    @r###"
387                    {
388                      "sum": 1.0,
389                      "average": 1.0,
390                      "min": 0.0,
391                      "max": 1.0
392                    }"###
393                );
394            },
395        );
396    }
397
398    #[test]
399    fn java_split_function() {
400        check_metrics::<JavaParser>(
401            "class A {
402              public int multiply(int x, int y) {
403                if(x == 0 || y == 0){
404                    return 0;
405                }
406                return x * y;
407              }
408            }",
409            "foo.java",
410            |metric| {
411                // 2 exit / space 1
412                insta::assert_json_snapshot!(
413                    metric.nexits,
414                    @r###"
415                    {
416                      "sum": 2.0,
417                      "average": 2.0,
418                      "min": 0.0,
419                      "max": 2.0
420                    }"###
421                );
422            },
423        );
424    }
425}