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#[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 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 pub fn exit(&self) -> f64 {
71 self.exit as f64
72 }
73 pub fn exit_sum(&self) -> f64 {
75 self.exit_sum as f64
76 }
77 pub fn exit_min(&self) -> f64 {
79 self.exit_min as f64
80 }
81 pub fn exit_max(&self) -> f64 {
83 self.exit_max as f64
84 }
85
86 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 JavascriptCode {
126 fn compute(node: &Node, stats: &mut Stats) {
127 if matches!(node.kind_id().into(), Javascript::ReturnStatement) {
128 stats.exit += 1;
129 }
130 }
131}
132
133impl Exit for TypescriptCode {
134 fn compute(node: &Node, stats: &mut Stats) {
135 if matches!(node.kind_id().into(), Typescript::ReturnStatement) {
136 stats.exit += 1;
137 }
138 }
139}
140
141impl Exit for TsxCode {
142 fn compute(node: &Node, stats: &mut Stats) {
143 if matches!(node.kind_id().into(), Tsx::ReturnStatement) {
144 stats.exit += 1;
145 }
146 }
147}
148
149impl Exit for RustCode {
150 fn compute(node: &Node, stats: &mut Stats) {
151 if matches!(
152 node.kind_id().into(),
153 Rust::ReturnExpression | Rust::TryExpression
154 ) || Self::is_func(node) && node.child_by_field_name("return_type").is_some()
155 {
156 stats.exit += 1;
157 }
158 }
159}
160
161impl Exit for CppCode {
162 fn compute(node: &Node, stats: &mut Stats) {
163 if matches!(node.kind_id().into(), Cpp::ReturnStatement) {
164 stats.exit += 1;
165 }
166 }
167}
168
169impl Exit for JavaCode {
170 fn compute(node: &Node, stats: &mut Stats) {
171 if matches!(node.kind_id().into(), Java::ReturnStatement) {
172 stats.exit += 1;
173 }
174 }
175}
176
177implement_metric_trait!(Exit, KotlinCode, PreprocCode, CcommentCode);
178
179#[cfg(test)]
180mod tests {
181 use crate::tools::check_metrics;
182
183 use super::*;
184
185 #[test]
186 fn python_no_exit() {
187 check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
188 insta::assert_json_snapshot!(
190 metric.nexits,
191 @r###"
192 {
193 "sum": 0.0,
194 "average": null,
195 "min": 0.0,
196 "max": 0.0
197 }"###
198 );
199 });
200 }
201
202 #[test]
203 fn rust_no_exit() {
204 check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
205 insta::assert_json_snapshot!(
207 metric.nexits,
208 @r###"
209 {
210 "sum": 0.0,
211 "average": null,
212 "min": 0.0,
213 "max": 0.0
214 }"###
215 );
216 });
217 }
218
219 #[test]
220 fn rust_question_mark() {
221 check_metrics::<RustParser>("let _ = a? + b? + c?;", "foo.rs", |metric| {
222 insta::assert_json_snapshot!(
224 metric.nexits,
225 @r###"
226 {
227 "sum": 3.0,
228 "average": null,
229 "min": 3.0,
230 "max": 3.0
231 }"###
232 );
233 });
234 }
235
236 #[test]
237 fn c_no_exit() {
238 check_metrics::<CppParser>("int a = 42;", "foo.c", |metric| {
239 insta::assert_json_snapshot!(
241 metric.nexits,
242 @r###"
243 {
244 "sum": 0.0,
245 "average": null,
246 "min": 0.0,
247 "max": 0.0
248 }"###
249 );
250 });
251 }
252
253 #[test]
254 fn javascript_no_exit() {
255 check_metrics::<JavascriptParser>("var a = 42;", "foo.js", |metric| {
256 insta::assert_json_snapshot!(
258 metric.nexits,
259 @r###"
260 {
261 "sum": 0.0,
262 "average": null,
263 "min": 0.0,
264 "max": 0.0
265 }"###
266 );
267 });
268 }
269
270 #[test]
271 fn python_simple_function() {
272 check_metrics::<PythonParser>(
273 "def f(a, b):
274 if a:
275 return a",
276 "foo.py",
277 |metric| {
278 println!("{:?}", metric.nexits);
279 insta::assert_json_snapshot!(
281 metric.nexits,
282 @r###"
283 {
284 "sum": 1.0,
285 "average": 1.0,
286 "min": 0.0,
287 "max": 1.0
288 }"###
289 );
290 },
291 );
292 }
293
294 #[test]
295 fn python_more_functions() {
296 check_metrics::<PythonParser>(
297 "def f(a, b):
298 if a:
299 return a
300 def f(a, b):
301 if b:
302 return b",
303 "foo.py",
304 |metric| {
305 insta::assert_json_snapshot!(
307 metric.nexits,
308 @r###"
309 {
310 "sum": 2.0,
311 "average": 1.0,
312 "min": 0.0,
313 "max": 1.0
314 }"###
315 );
316 },
317 );
318 }
319
320 #[test]
321 fn python_nested_functions() {
322 check_metrics::<PythonParser>(
323 "def f(a, b):
324 def foo(a):
325 if a:
326 return 1
327 bar = lambda a: lambda b: b or True or True
328 return bar(foo(a))(a)",
329 "foo.py",
330 |metric| {
331 insta::assert_json_snapshot!(
333 metric.nexits,
334 @r###"
335 {
336 "sum": 2.0,
337 "average": 0.5,
338 "min": 0.0,
339 "max": 1.0
340 }"###
341 );
342 },
343 );
344 }
345
346 #[test]
347 fn java_no_exit() {
348 check_metrics::<JavaParser>("int a = 42;", "foo.java", |metric| {
349 insta::assert_json_snapshot!(
351 metric.nexits,
352 @r###"
353 {
354 "sum": 0.0,
355 "average": null,
356 "min": 0.0,
357 "max": 0.0
358 }"###
359 );
360 });
361 }
362
363 #[test]
364 fn java_simple_function() {
365 check_metrics::<JavaParser>(
366 "class A {
367 public int sum(int x, int y) {
368 return x + y;
369 }
370 }",
371 "foo.java",
372 |metric| {
373 insta::assert_json_snapshot!(
375 metric.nexits,
376 @r###"
377 {
378 "sum": 1.0,
379 "average": 1.0,
380 "min": 0.0,
381 "max": 1.0
382 }"###
383 );
384 },
385 );
386 }
387
388 #[test]
389 fn java_split_function() {
390 check_metrics::<JavaParser>(
391 "class A {
392 public int multiply(int x, int y) {
393 if(x == 0 || y == 0){
394 return 0;
395 }
396 return x * y;
397 }
398 }",
399 "foo.java",
400 |metric| {
401 insta::assert_json_snapshot!(
403 metric.nexits,
404 @r###"
405 {
406 "sum": 2.0,
407 "average": 2.0,
408 "min": 0.0,
409 "max": 2.0
410 }"###
411 );
412 },
413 );
414 }
415}