1#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
8#![allow(
15 clippy::cast_precision_loss,
16 clippy::cast_possible_truncation,
17 clippy::cast_sign_loss
18)]
19
20use std::fmt;
21
22use crate::checker::Checker;
23use crate::macros::implement_metric_trait;
24use crate::*;
25
26#[derive(Debug, Clone, PartialEq)]
31#[non_exhaustive]
32pub struct Stats {
33 exit: usize,
34 exit_sum: usize,
35 total_space_functions: usize,
36 exit_min: usize,
37 exit_max: usize,
38}
39
40impl Default for Stats {
41 fn default() -> Self {
42 Self {
43 exit: 0,
44 exit_sum: 0,
45 total_space_functions: 1,
46 exit_min: usize::MAX,
47 exit_max: 0,
48 }
49 }
50}
51
52impl fmt::Display for Stats {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 write!(
55 f,
56 "sum: {}, average: {} min: {}, max: {}",
57 self.nexits_sum(),
58 self.nexits_average(),
59 self.nexits_min(),
60 self.nexits_max()
61 )
62 }
63}
64
65impl Stats {
66 pub fn merge(&mut self, other: &Stats) {
68 self.exit_max = self.exit_max.max(other.exit_max);
69 self.exit_min = self.exit_min.min(other.exit_min);
70 self.exit_sum += other.exit_sum;
71 }
72
73 #[must_use]
75 pub fn nexits(&self) -> u64 {
76 self.exit as u64
77 }
78 #[must_use]
80 pub fn nexits_sum(&self) -> u64 {
81 self.exit_sum as u64
82 }
83 #[must_use]
89 pub fn nexits_min(&self) -> u64 {
90 if self.exit_min == usize::MAX {
91 0
92 } else {
93 self.exit_min as u64
94 }
95 }
96 #[must_use]
98 pub fn nexits_max(&self) -> u64 {
99 self.exit_max as u64
100 }
101
102 #[must_use]
113 pub fn nexits_average(&self) -> f64 {
114 crate::metrics::average(self.nexits_sum() as f64, self.total_space_functions)
115 }
116 #[inline]
117 pub(crate) fn compute_sum(&mut self) {
118 self.exit_sum += self.exit;
119 }
120 #[inline]
121 pub(crate) fn compute_minmax(&mut self) {
122 self.exit_max = self.exit_max.max(self.exit);
123 self.exit_min = self.exit_min.min(self.exit);
124 self.compute_sum();
125 }
126 pub(crate) fn finalize(&mut self, total_space_functions: usize) {
127 self.total_space_functions = total_space_functions;
128 }
129}
130
131#[doc(hidden)]
132pub(crate) trait Exit
134where
135 Self: Checker,
136{
137 fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats);
140}
141
142macro_rules! impl_exit_match_kinds {
146 ($code:ty, $lang:ident, [$($kind:ident),+ $(,)?]) => {
147 impl Exit for $code {
148 fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
149 if matches!(node.kind_id().into(), $($lang::$kind)|+) {
150 stats.exit += 1;
151 }
152 }
153 }
154 };
155}
156
157impl_exit_match_kinds!(PythonCode, Python, [ReturnStatement, RaiseStatement, Yield]);
162impl_exit_match_kinds!(
165 MozjsCode,
166 Mozjs,
167 [ReturnStatement, ThrowStatement, YieldExpression]
168);
169impl_exit_match_kinds!(
170 JavascriptCode,
171 Javascript,
172 [ReturnStatement, ThrowStatement, YieldExpression]
173);
174impl_exit_match_kinds!(
175 TypescriptCode,
176 Typescript,
177 [ReturnStatement, ThrowStatement, YieldExpression]
178);
179impl_exit_match_kinds!(
180 TsxCode,
181 Tsx,
182 [ReturnStatement, ThrowStatement, YieldExpression]
183);
184impl_exit_match_kinds!(CppCode, Cpp, [ReturnStatement, ThrowStatement]);
185impl_exit_match_kinds!(MozcppCode, Mozcpp, [ReturnStatement, ThrowStatement]);
186impl_exit_match_kinds!(CCode, C, [ReturnStatement]);
188impl_exit_match_kinds!(ObjcCode, Objc, [ReturnStatement, ThrowStatement]);
191impl_exit_match_kinds!(
197 JavaCode,
198 Java,
199 [ReturnStatement, ThrowStatement, YieldStatement]
200);
201impl_exit_match_kinds!(
205 GroovyCode,
206 Groovy,
207 [ReturnStatement, ThrowStatement, YieldStatement]
208);
209
210impl Exit for RustCode {
211 fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
212 if matches!(
218 node.kind_id().into(),
219 Rust::ReturnExpression | Rust::TryExpression
220 ) {
221 stats.exit += 1;
222 }
223 }
224}
225
226impl Exit for CsharpCode {
227 fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
228 if matches!(
229 node.kind_id().into(),
230 Csharp::ReturnStatement
231 | Csharp::YieldStatement
232 | Csharp::ThrowStatement
233 | Csharp::ThrowExpression
234 ) {
235 stats.exit += 1;
236 }
237 }
238}
239
240impl Exit for GoCode {
241 fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
250 if matches!(node.kind_id().into(), Go::ReturnStatement) {
251 stats.exit += 1;
252 } else if node.kind_id() == Go::CallExpression
253 && let Some(function) = node.child_by_field_name("function")
254 && function.kind_id() == Go::Identifier
255 && function.utf8_text(code) == Some("panic")
256 {
257 stats.exit += 1;
258 }
259 }
260}
261
262impl Exit for PerlCode {
263 fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
264 if node.kind_id() == Perl::ReturnExpression {
265 stats.exit += 1;
266 }
267 }
268}
269
270impl Exit for KotlinCode {
271 fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
272 if matches!(
273 node.kind_id().into(),
274 Kotlin::ReturnExpression | Kotlin::ThrowExpression
275 ) {
276 stats.exit += 1;
277 }
278 }
279}
280
281impl Exit for LuaCode {
282 fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
291 if node.kind_id() == Lua::ReturnStatement {
292 stats.exit += 1;
293 } else if node.kind_id() == Lua::FunctionCall
294 && let Some(name) = node.child_by_field_name("name")
295 && matches!(name.utf8_text(code), Some("error" | "os.exit"))
296 {
297 stats.exit += 1;
298 }
299 }
300}
301
302impl Exit for BashCode {
303 fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
304 if matches!(node.kind_id().into(), Bash::Command)
309 && let Some(name) = node.child_by_field_name("name")
310 && matches!(name.utf8_text(code), Some("return" | "exit"))
311 {
312 stats.exit += 1;
313 }
314 }
315}
316
317impl Exit for TclCode {
318 fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
319 if node.kind_id() == Tcl::Command
322 && let Some(name) = node.child_by_field_name("name")
323 && name.kind_id() == Tcl::SimpleWord
324 && name.utf8_text(code) == Some("return")
325 {
326 stats.exit += 1;
327 }
328 }
329}
330
331impl Exit for IrulesCode {
332 fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
333 if node.kind_id() == Irules::Command
342 && let Some(name) = node.child_by_field_name("name")
343 && name.utf8_text(code) == Some("return")
344 {
345 stats.exit += 1;
346 }
347 }
348}
349
350impl Exit for PhpCode {
351 fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
359 if matches!(
360 node.kind_id().into(),
361 Php::ReturnStatement | Php::YieldExpression | Php::ThrowExpression | Php::ExitStatement
362 ) {
363 stats.exit += 1;
364 }
365 }
366}
367
368implement_metric_trait!(Exit, PreprocCode, CcommentCode);
370
371impl Exit for RubyCode {
372 fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
380 if matches!(node.kind_id().into(), Ruby::Return | Ruby::Return2) {
381 stats.exit += 1;
382 }
383 }
384}
385
386impl Exit for ElixirCode {
387 fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
393 if node.kind_id() == Elixir::Call
394 && let Some(target) = node.child_by_field_name("target")
395 && target.kind_id() == Elixir::Identifier
396 && matches!(
397 target.utf8_text(code),
398 Some("throw" | "raise" | "reraise" | "exit")
399 )
400 {
401 stats.exit += 1;
402 }
403 }
404}
405
406#[cfg(test)]
407#[allow(
408 clippy::float_cmp,
409 clippy::cast_precision_loss,
410 clippy::cast_possible_truncation,
411 clippy::cast_sign_loss,
412 clippy::similar_names,
413 clippy::doc_markdown,
414 clippy::needless_raw_string_hashes,
415 clippy::too_many_lines
416)]
417mod tests {
418 use crate::tools::check_metrics;
419
420 use super::*;
421
422 #[test]
427 fn exit_empty_file_min_is_zero() {
428 let stats = Stats::default();
429 assert_eq!(stats.nexits_min(), 0);
430 }
431
432 #[test]
433 fn python_no_exit() {
434 check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
435 insta::assert_json_snapshot!(
437 metric.nexits,
438 @r#"
439 {
440 "sum": 0,
441 "average": 0.0,
442 "min": 0,
443 "max": 0
444 }
445 "#
446 );
447 });
448 }
449
450 #[test]
451 fn rust_no_exit() {
452 check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
453 insta::assert_json_snapshot!(
455 metric.nexits,
456 @r#"
457 {
458 "sum": 0,
459 "average": 0.0,
460 "min": 0,
461 "max": 0
462 }
463 "#
464 );
465 });
466 }
467
468 #[test]
469 fn rust_question_mark() {
470 check_metrics::<RustParser>("let _ = a? + b? + c?;", "foo.rs", |metric| {
471 insta::assert_json_snapshot!(
473 metric.nexits,
474 @r#"
475 {
476 "sum": 3,
477 "average": 3.0,
478 "min": 3,
479 "max": 3
480 }
481 "#
482 );
483 });
484 }
485
486 #[test]
494 fn rust_explicit_return_with_return_type() {
495 check_metrics::<RustParser>("fn foo() -> i32 { return 1; }", "foo.rs", |metric| {
496 insta::assert_json_snapshot!(
498 metric.nexits,
499 @r#"
500 {
501 "sum": 1,
502 "average": 1.0,
503 "min": 0,
504 "max": 1
505 }
506 "#
507 );
508 });
509 }
510
511 #[test]
515 fn rust_implicit_return_not_counted() {
516 check_metrics::<RustParser>("fn foo() -> i32 { 0 }", "foo.rs", |metric| {
517 insta::assert_json_snapshot!(
519 metric.nexits,
520 @r#"
521 {
522 "sum": 0,
523 "average": 0.0,
524 "min": 0,
525 "max": 0
526 }
527 "#
528 );
529 });
530 }
531
532 #[test]
536 fn rust_mixed_explicit_and_implicit_return() {
537 check_metrics::<RustParser>(
538 "fn foo(x: bool) -> i32 { if x { return 1; } 0 }",
539 "foo.rs",
540 |metric| {
541 insta::assert_json_snapshot!(
543 metric.nexits,
544 @r#"
545 {
546 "sum": 1,
547 "average": 1.0,
548 "min": 0,
549 "max": 1
550 }
551 "#
552 );
553 },
554 );
555 }
556
557 #[test]
561 fn rust_question_mark_in_function() {
562 check_metrics::<RustParser>(
563 "fn foo() -> Result<i32, ()> { Ok(do_thing()?) }",
564 "foo.rs",
565 |metric| {
566 insta::assert_json_snapshot!(
568 metric.nexits,
569 @r#"
570 {
571 "sum": 1,
572 "average": 1.0,
573 "min": 0,
574 "max": 1
575 }
576 "#
577 );
578 },
579 );
580 }
581
582 #[test]
585 fn rust_unit_return_no_exit() {
586 check_metrics::<RustParser>("fn foo() { let _x = 1; }", "foo.rs", |metric| {
587 insta::assert_json_snapshot!(
589 metric.nexits,
590 @r#"
591 {
592 "sum": 0,
593 "average": 0.0,
594 "min": 0,
595 "max": 0
596 }
597 "#
598 );
599 });
600 }
601
602 #[test]
603 fn c_no_exit() {
604 check_metrics::<CParser>("int a = 42;", "foo.c", |metric| {
605 insta::assert_json_snapshot!(
607 metric.nexits,
608 @r#"
609 {
610 "sum": 0,
611 "average": 0.0,
612 "min": 0,
613 "max": 0
614 }
615 "#
616 );
617 });
618 }
619
620 #[test]
623 fn c_multiple_returns_in_branches() {
624 check_metrics::<CParser>(
625 "int f(int x) {
626 if (x < 0) {
627 return -1;
628 } else if (x == 0) {
629 return 0;
630 } else {
631 return 1;
632 }
633 }",
634 "foo.c",
635 |metric| {
636 assert_eq!(metric.nexits.nexits_sum(), 3);
638 assert_eq!(metric.nexits.nexits_max(), 3);
639 insta::assert_json_snapshot!(
640 metric.nexits,
641 @r#"
642 {
643 "sum": 3,
644 "average": 3.0,
645 "min": 0,
646 "max": 3
647 }
648 "#
649 );
650 },
651 );
652 }
653
654 #[test]
663 fn c_keyword_identifiers_parse_and_returns_count() {
664 use std::path::PathBuf;
665
666 let source = "int process(int new, int class) {
667 int delete = new + class;
668 if (delete > 0) {
669 return delete;
670 }
671 return 0;
672 }";
673 let parser = CParser::new(source.as_bytes().to_vec(), &PathBuf::from("foo.c"), None);
674 assert!(
675 !parser.root().has_error(),
676 "C grammar must parse C++-keyword identifiers without an error cascade"
677 );
678
679 check_metrics::<CParser>(source, "foo.c", |metric| {
680 assert_eq!(metric.nom.functions_sum(), 1);
681 assert_eq!(metric.nexits.nexits_sum(), 2);
682 });
683 }
684
685 #[test]
689 fn cpp_return_in_try_catch() {
690 check_metrics::<CppParser>(
691 "int f(int x) {
692 try {
693 if (x == 0) {
694 return 1;
695 }
696 return 2;
697 } catch (...) {
698 return -1;
699 }
700 }",
701 "foo.cpp",
702 |metric| {
703 assert_eq!(metric.nexits.nexits_sum(), 3);
706 assert_eq!(metric.nexits.nexits_max(), 3);
707 insta::assert_json_snapshot!(
708 metric.nexits,
709 @r#"
710 {
711 "sum": 3,
712 "average": 3.0,
713 "min": 0,
714 "max": 3
715 }
716 "#
717 );
718 },
719 );
720 }
721
722 #[test]
725 fn c_early_return_in_loop() {
726 check_metrics::<CParser>(
727 "int find(int* a, int n, int target) {
728 for (int i = 0; i < n; ++i) {
729 if (a[i] == target) {
730 return i;
731 }
732 }
733 return -1;
734 }",
735 "foo.c",
736 |metric| {
737 assert_eq!(metric.nexits.nexits_sum(), 2);
739 assert_eq!(metric.nexits.nexits_max(), 2);
740 insta::assert_json_snapshot!(
741 metric.nexits,
742 @r#"
743 {
744 "sum": 2,
745 "average": 2.0,
746 "min": 0,
747 "max": 2
748 }
749 "#
750 );
751 },
752 );
753 }
754
755 #[test]
758 fn c_void_no_explicit_return() {
759 check_metrics::<CParser>(
760 "void greet(const char* who) {
761 printf(\"hi %s\\n\", who);
762 }",
763 "foo.c",
764 |metric| {
765 assert_eq!(metric.nexits.nexits_sum(), 0);
767 assert_eq!(metric.nexits.nexits_max(), 0);
768 insta::assert_json_snapshot!(
769 metric.nexits,
770 @r#"
771 {
772 "sum": 0,
773 "average": 0.0,
774 "min": 0,
775 "max": 0
776 }
777 "#
778 );
779 },
780 );
781 }
782
783 #[test]
784 fn javascript_no_exit() {
785 check_metrics::<JavascriptParser>("var a = 42;", "foo.js", |metric| {
786 insta::assert_json_snapshot!(
788 metric.nexits,
789 @r#"
790 {
791 "sum": 0,
792 "average": 0.0,
793 "min": 0,
794 "max": 0
795 }
796 "#
797 );
798 });
799 }
800
801 #[test]
802 fn javascript_simple_function() {
803 check_metrics::<JavascriptParser>(
804 "function f(a, b) {
805 if (a) {
806 return a;
807 }
808 return b;
809 }",
810 "foo.js",
811 |metric| {
812 insta::assert_json_snapshot!(
814 metric.nexits,
815 @r#"
816 {
817 "sum": 2,
818 "average": 2.0,
819 "min": 0,
820 "max": 2
821 }
822 "#
823 );
824 },
825 );
826 }
827
828 #[test]
829 fn javascript_nested_functions() {
830 check_metrics::<JavascriptParser>(
831 "function outer() {
832 function inner() {
833 return 1;
834 }
835 return inner();
836 }",
837 "foo.js",
838 |metric| {
839 insta::assert_json_snapshot!(
841 metric.nexits,
842 @r#"
843 {
844 "sum": 2,
845 "average": 1.0,
846 "min": 0,
847 "max": 1
848 }
849 "#
850 );
851 },
852 );
853 }
854
855 #[test]
856 fn python_simple_function() {
857 check_metrics::<PythonParser>(
858 "def f(a, b):
859 if a:
860 return a",
861 "foo.py",
862 |metric| {
863 insta::assert_json_snapshot!(
865 metric.nexits,
866 @r#"
867 {
868 "sum": 1,
869 "average": 1.0,
870 "min": 0,
871 "max": 1
872 }
873 "#
874 );
875 },
876 );
877 }
878
879 #[test]
880 fn python_more_functions() {
881 check_metrics::<PythonParser>(
882 "def f(a, b):
883 if a:
884 return a
885 def f(a, b):
886 if b:
887 return b",
888 "foo.py",
889 |metric| {
890 insta::assert_json_snapshot!(
892 metric.nexits,
893 @r#"
894 {
895 "sum": 2,
896 "average": 1.0,
897 "min": 0,
898 "max": 1
899 }
900 "#
901 );
902 },
903 );
904 }
905
906 #[test]
907 fn python_nested_functions() {
908 check_metrics::<PythonParser>(
909 "def f(a, b):
910 def foo(a):
911 if a:
912 return 1
913 bar = lambda a: lambda b: b or True or True
914 return bar(foo(a))(a)",
915 "foo.py",
916 |metric| {
917 insta::assert_json_snapshot!(
919 metric.nexits,
920 @r#"
921 {
922 "sum": 2,
923 "average": 0.5,
924 "min": 0,
925 "max": 1
926 }
927 "#
928 );
929 },
930 );
931 }
932
933 #[test]
934 fn java_no_exit() {
935 check_metrics::<JavaParser>("int a = 42;", "foo.java", |metric| {
936 insta::assert_json_snapshot!(
938 metric.nexits,
939 @r#"
940 {
941 "sum": 0,
942 "average": 0.0,
943 "min": 0,
944 "max": 0
945 }
946 "#
947 );
948 });
949 }
950
951 #[test]
952 fn java_simple_function() {
953 check_metrics::<JavaParser>(
954 "class A {
955 public int sum(int x, int y) {
956 return x + y;
957 }
958 }",
959 "foo.java",
960 |metric| {
961 insta::assert_json_snapshot!(
963 metric.nexits,
964 @r#"
965 {
966 "sum": 1,
967 "average": 1.0,
968 "min": 0,
969 "max": 1
970 }
971 "#
972 );
973 },
974 );
975 }
976
977 #[test]
978 fn go_no_return() {
979 check_metrics::<GoParser>(
980 "package main
981 func f() {
982 x := 1
983 _ = x
984 }",
985 "foo.go",
986 |metric| {
987 insta::assert_json_snapshot!(
989 metric.nexits,
990 @r#"
991 {
992 "sum": 0,
993 "average": 0.0,
994 "min": 0,
995 "max": 0
996 }
997 "#
998 );
999 },
1000 );
1001 }
1002
1003 #[test]
1004 fn go_single_return() {
1005 check_metrics::<GoParser>(
1006 "package main
1007 func f() int {
1008 return 1
1009 }",
1010 "foo.go",
1011 |metric| {
1012 insta::assert_json_snapshot!(
1013 metric.nexits,
1014 @r#"
1015 {
1016 "sum": 1,
1017 "average": 1.0,
1018 "min": 0,
1019 "max": 1
1020 }
1021 "#
1022 );
1023 },
1024 );
1025 }
1026
1027 #[test]
1028 fn go_multiple_returns() {
1029 check_metrics::<GoParser>(
1030 "package main
1031 func f(x int) int {
1032 if x > 0 {
1033 return 1
1034 }
1035 if x < 0 {
1036 return -1
1037 }
1038 return 0
1039 }",
1040 "foo.go",
1041 |metric| {
1042 insta::assert_json_snapshot!(
1044 metric.nexits,
1045 @r#"
1046 {
1047 "sum": 3,
1048 "average": 3.0,
1049 "min": 0,
1050 "max": 3
1051 }
1052 "#
1053 );
1054 },
1055 );
1056 }
1057
1058 #[test]
1059 fn go_naked_return() {
1060 check_metrics::<GoParser>(
1061 "package main
1062 func f() (x int) {
1063 x = 1
1064 return
1065 }",
1066 "foo.go",
1067 |metric| {
1068 insta::assert_json_snapshot!(
1070 metric.nexits,
1071 @r#"
1072 {
1073 "sum": 1,
1074 "average": 1.0,
1075 "min": 0,
1076 "max": 1
1077 }
1078 "#
1079 );
1080 },
1081 );
1082 }
1083
1084 #[test]
1085 fn go_multivalue_return() {
1086 check_metrics::<GoParser>(
1087 "package main
1088 func f() (int, error) {
1089 return 0, nil
1090 }",
1091 "foo.go",
1092 |metric| {
1093 insta::assert_json_snapshot!(
1095 metric.nexits,
1096 @r#"
1097 {
1098 "sum": 1,
1099 "average": 1.0,
1100 "min": 0,
1101 "max": 1
1102 }
1103 "#
1104 );
1105 },
1106 );
1107 }
1108
1109 #[test]
1110 fn go_panic_counts_as_exit() {
1111 check_metrics::<GoParser>(
1112 "package main
1113 func f() {
1114 panic(\"boom\")
1115 }",
1116 "foo.go",
1117 |metric| {
1118 insta::assert_json_snapshot!(
1121 metric.nexits,
1122 @r#"
1123 {
1124 "sum": 1,
1125 "average": 1.0,
1126 "min": 0,
1127 "max": 1
1128 }
1129 "#
1130 );
1131 },
1132 );
1133 }
1134
1135 #[test]
1136 fn go_panic_and_return_both_count() {
1137 check_metrics::<GoParser>(
1138 "package main
1139 func f(x int) int {
1140 if x < 0 {
1141 panic(\"negative\")
1142 }
1143 return x
1144 }",
1145 "foo.go",
1146 |metric| {
1147 insta::assert_json_snapshot!(
1149 metric.nexits,
1150 @r#"
1151 {
1152 "sum": 2,
1153 "average": 2.0,
1154 "min": 0,
1155 "max": 2
1156 }
1157 "#
1158 );
1159 },
1160 );
1161 }
1162
1163 #[test]
1164 fn go_package_qualified_panic_is_not_exit() {
1165 check_metrics::<GoParser>(
1166 "package main
1167 func f() {
1168 foo.panic()
1169 }",
1170 "foo.go",
1171 |metric| {
1172 insta::assert_json_snapshot!(
1176 metric.nexits,
1177 @r#"
1178 {
1179 "sum": 0,
1180 "average": 0.0,
1181 "min": 0,
1182 "max": 0
1183 }
1184 "#
1185 );
1186 },
1187 );
1188 }
1189
1190 #[test]
1191 fn java_split_function() {
1192 check_metrics::<JavaParser>(
1193 "class A {
1194 public int multiply(int x, int y) {
1195 if(x == 0 || y == 0){
1196 return 0;
1197 }
1198 return x * y;
1199 }
1200 }",
1201 "foo.java",
1202 |metric| {
1203 insta::assert_json_snapshot!(
1205 metric.nexits,
1206 @r#"
1207 {
1208 "sum": 2,
1209 "average": 2.0,
1210 "min": 0,
1211 "max": 2
1212 }
1213 "#
1214 );
1215 },
1216 );
1217 }
1218
1219 #[test]
1220 fn csharp_no_exit() {
1221 check_metrics::<CsharpParser>("int a = 42;", "foo.cs", |metric| {
1222 insta::assert_json_snapshot!(
1223 metric.nexits,
1224 @r#"
1225 {
1226 "sum": 0,
1227 "average": 0.0,
1228 "min": 0,
1229 "max": 0
1230 }
1231 "#
1232 );
1233 });
1234 }
1235
1236 #[test]
1237 fn csharp_simple_function() {
1238 check_metrics::<CsharpParser>(
1239 "class A {
1240 public int Sum(int x, int y) {
1241 return x + y;
1242 }
1243 }",
1244 "foo.cs",
1245 |metric| {
1246 insta::assert_json_snapshot!(
1247 metric.nexits,
1248 @r#"
1249 {
1250 "sum": 1,
1251 "average": 1.0,
1252 "min": 0,
1253 "max": 1
1254 }
1255 "#
1256 );
1257 },
1258 );
1259 }
1260
1261 #[test]
1262 fn csharp_split_function() {
1263 check_metrics::<CsharpParser>(
1264 "class A {
1265 public int Multiply(int x, int y) {
1266 if (x == 0 || y == 0) {
1267 return 0;
1268 }
1269 return x * y;
1270 }
1271 }",
1272 "foo.cs",
1273 |metric| {
1274 insta::assert_json_snapshot!(
1275 metric.nexits,
1276 @r#"
1277 {
1278 "sum": 2,
1279 "average": 2.0,
1280 "min": 0,
1281 "max": 2
1282 }
1283 "#
1284 );
1285 },
1286 );
1287 }
1288
1289 #[test]
1290 fn csharp_yield_and_throw() {
1291 check_metrics::<CsharpParser>(
1292 "class A {
1293 public IEnumerable<int> Gen() {
1294 yield return 1;
1295 yield break;
1296 }
1297 public int Bad(int x) {
1298 if (x < 0) throw new System.Exception();
1299 return x;
1300 }
1301 }",
1302 "foo.cs",
1303 |metric| {
1304 insta::assert_json_snapshot!(
1306 metric.nexits,
1307 @r#"
1308 {
1309 "sum": 4,
1310 "average": 2.0,
1311 "min": 0,
1312 "max": 2
1313 }
1314 "#
1315 );
1316 },
1317 );
1318 }
1319
1320 #[test]
1321 fn perl_no_exit() {
1322 check_metrics::<PerlParser>(
1323 "sub f {
1324 print 'hi';
1325 }",
1326 "foo.pl",
1327 |metric| {
1328 insta::assert_json_snapshot!(
1329 metric.nexits,
1330 @r#"
1331 {
1332 "sum": 0,
1333 "average": 0.0,
1334 "min": 0,
1335 "max": 0
1336 }
1337 "#
1338 );
1339 },
1340 );
1341 }
1342
1343 #[test]
1344 fn perl_no_function_no_exit() {
1345 check_metrics::<PerlParser>("my $x = 1;\nprint $x;\n", "foo.pl", |metric| {
1346 insta::assert_json_snapshot!(metric.nexits, @r#"
1347 {
1348 "sum": 0,
1349 "average": 0.0,
1350 "min": 0,
1351 "max": 0
1352 }
1353 "#);
1354 });
1355 }
1356
1357 #[test]
1358 fn perl_multiple_returns() {
1359 check_metrics::<PerlParser>(
1360 "sub f {
1361 return 1 if $_[0];
1362 return 0;
1363 }",
1364 "foo.pl",
1365 |metric| {
1366 insta::assert_json_snapshot!(
1367 metric.nexits,
1368 @r#"
1369 {
1370 "sum": 2,
1371 "average": 2.0,
1372 "min": 0,
1373 "max": 2
1374 }
1375 "#
1376 );
1377 },
1378 );
1379 }
1380
1381 #[test]
1382 fn tsx_function_with_returns() {
1383 check_metrics::<TsxParser>(
1384 "function clamp(val: number, min: number, max: number) {
1385 if (val < min) {
1386 return min;
1387 }
1388 if (val > max) {
1389 return max;
1390 }
1391 return val;
1392 }",
1393 "foo.tsx",
1394 |metric| {
1395 insta::assert_json_snapshot!(
1396 metric.nexits,
1397 @r#"
1398 {
1399 "sum": 3,
1400 "average": 3.0,
1401 "min": 0,
1402 "max": 3
1403 }
1404 "#
1405 );
1406 },
1407 );
1408 }
1409
1410 #[test]
1411 fn typescript_no_exit() {
1412 check_metrics::<TypescriptParser>("const x: number = 42;", "foo.ts", |metric| {
1413 insta::assert_json_snapshot!(
1414 metric.nexits,
1415 @r#"
1416 {
1417 "sum": 0,
1418 "average": 0.0,
1419 "min": 0,
1420 "max": 0
1421 }
1422 "#
1423 );
1424 });
1425 }
1426
1427 #[test]
1428 fn typescript_function_with_returns() {
1429 check_metrics::<TypescriptParser>(
1430 "function safeDivide(a: number, b: number): number | null {
1431 if (b === 0) {
1432 return null;
1433 }
1434 return a / b;
1435 }",
1436 "foo.ts",
1437 |metric| {
1438 insta::assert_json_snapshot!(
1439 metric.nexits,
1440 @r#"
1441 {
1442 "sum": 2,
1443 "average": 2.0,
1444 "min": 0,
1445 "max": 2
1446 }
1447 "#
1448 );
1449 },
1450 );
1451 }
1452
1453 #[test]
1454 fn mozjs_no_exit() {
1455 check_metrics::<MozjsParser>("var a = 42;", "foo.js", |metric| {
1456 insta::assert_json_snapshot!(
1457 metric.nexits,
1458 @r#"
1459 {
1460 "sum": 0,
1461 "average": 0.0,
1462 "min": 0,
1463 "max": 0
1464 }
1465 "#
1466 );
1467 });
1468 }
1469
1470 #[test]
1471 fn mozjs_function_with_returns() {
1472 check_metrics::<MozjsParser>(
1473 "function f(a, b) {
1474 if (a) {
1475 return a;
1476 }
1477 return b;
1478 }",
1479 "foo.js",
1480 |metric| {
1481 insta::assert_json_snapshot!(
1482 metric.nexits,
1483 @r#"
1484 {
1485 "sum": 2,
1486 "average": 2.0,
1487 "min": 0,
1488 "max": 2
1489 }
1490 "#
1491 );
1492 },
1493 );
1494 }
1495
1496 #[test]
1497 fn kotlin_exit_return_and_throw() {
1498 check_metrics::<KotlinParser>(
1499 "fun divide(a: Int, b: Int): Int {
1500 if (b == 0) {
1501 throw IllegalArgumentException(\"zero\")
1502 }
1503 return a / b
1504 }",
1505 "foo.kt",
1506 |metric| {
1507 insta::assert_json_snapshot!(
1508 metric.nexits,
1509 @r#"
1510 {
1511 "sum": 2,
1512 "average": 2.0,
1513 "min": 0,
1514 "max": 2
1515 }
1516 "#
1517 );
1518 },
1519 );
1520 }
1521
1522 #[test]
1523 fn lua_no_exit() {
1524 check_metrics::<LuaParser>(
1525 "local function f(x)
1526 local y = x + 1
1527end",
1528 "foo.lua",
1529 |metric| {
1530 insta::assert_json_snapshot!(
1531 metric.nexits,
1532 @r#"
1533 {
1534 "sum": 0,
1535 "average": 0.0,
1536 "min": 0,
1537 "max": 0
1538 }
1539 "#
1540 );
1541 },
1542 );
1543 }
1544
1545 #[test]
1546 fn lua_return() {
1547 check_metrics::<LuaParser>(
1548 "local function f(x)
1549 if x > 0 then
1550 return x
1551 end
1552 return 0
1553end",
1554 "foo.lua",
1555 |metric| {
1556 insta::assert_json_snapshot!(
1557 metric.nexits,
1558 @r#"
1559 {
1560 "sum": 2,
1561 "average": 2.0,
1562 "min": 0,
1563 "max": 2
1564 }
1565 "#
1566 );
1567 },
1568 );
1569 }
1570
1571 #[test]
1572 fn lua_error_counts_as_exit() {
1573 check_metrics::<LuaParser>(
1574 "local function f(x)
1575 error(\"bad\")
1576end",
1577 "foo.lua",
1578 |metric| {
1579 insta::assert_json_snapshot!(
1582 metric.nexits,
1583 @r#"
1584 {
1585 "sum": 1,
1586 "average": 1.0,
1587 "min": 0,
1588 "max": 1
1589 }
1590 "#
1591 );
1592 },
1593 );
1594 }
1595
1596 #[test]
1597 fn lua_os_exit_counts_as_exit() {
1598 check_metrics::<LuaParser>(
1599 "local function f()
1600 os.exit(1)
1601end",
1602 "foo.lua",
1603 |metric| {
1604 insta::assert_json_snapshot!(
1607 metric.nexits,
1608 @r#"
1609 {
1610 "sum": 1,
1611 "average": 1.0,
1612 "min": 0,
1613 "max": 1
1614 }
1615 "#
1616 );
1617 },
1618 );
1619 }
1620
1621 #[test]
1622 fn lua_error_and_return_both_count() {
1623 check_metrics::<LuaParser>(
1624 "local function f(x)
1625 if x < 0 then
1626 error(\"negative\")
1627 end
1628 return x
1629end",
1630 "foo.lua",
1631 |metric| {
1632 insta::assert_json_snapshot!(
1634 metric.nexits,
1635 @r#"
1636 {
1637 "sum": 2,
1638 "average": 2.0,
1639 "min": 0,
1640 "max": 2
1641 }
1642 "#
1643 );
1644 },
1645 );
1646 }
1647
1648 #[test]
1649 fn lua_user_call_is_not_exit() {
1650 check_metrics::<LuaParser>(
1651 "local function f()
1652 foo()
1653 myError(\"x\")
1654end",
1655 "foo.lua",
1656 |metric| {
1657 insta::assert_json_snapshot!(
1660 metric.nexits,
1661 @r#"
1662 {
1663 "sum": 0,
1664 "average": 0.0,
1665 "min": 0,
1666 "max": 0
1667 }
1668 "#
1669 );
1670 },
1671 );
1672 }
1673
1674 #[test]
1675 fn bash_no_exit() {
1676 check_metrics::<BashParser>("echo \"no exits\"", "foo.sh", |metric| {
1677 insta::assert_json_snapshot!(
1678 metric.nexits,
1679 @r#"
1680 {
1681 "sum": 0,
1682 "average": 0.0,
1683 "min": 0,
1684 "max": 0
1685 }
1686 "#
1687 );
1688 });
1689 }
1690
1691 #[test]
1692 fn bash_explicit_return() {
1693 check_metrics::<BashParser>(
1694 "f() {
1695 if [ -z \"$1\" ]; then
1696 return 1
1697 fi
1698 echo ok
1699 }",
1700 "foo.sh",
1701 |metric| {
1702 insta::assert_json_snapshot!(
1703 metric.nexits,
1704 @r#"
1705 {
1706 "sum": 1,
1707 "average": 1.0,
1708 "min": 0,
1709 "max": 1
1710 }
1711 "#
1712 );
1713 },
1714 );
1715 }
1716
1717 #[test]
1718 fn bash_explicit_exit() {
1719 check_metrics::<BashParser>(
1720 "f() {
1721 exit 0
1722 }",
1723 "foo.sh",
1724 |metric| {
1725 insta::assert_json_snapshot!(
1726 metric.nexits,
1727 @r#"
1728 {
1729 "sum": 1,
1730 "average": 1.0,
1731 "min": 0,
1732 "max": 1
1733 }
1734 "#
1735 );
1736 },
1737 );
1738 }
1739
1740 #[test]
1741 fn bash_multiple_exits() {
1742 check_metrics::<BashParser>(
1743 "f() {
1744 if [ \"$1\" = die ]; then
1745 exit 1
1746 fi
1747 return 0
1748 }",
1749 "foo.sh",
1750 |metric| {
1751 insta::assert_json_snapshot!(
1752 metric.nexits,
1753 @r#"
1754 {
1755 "sum": 2,
1756 "average": 2.0,
1757 "min": 0,
1758 "max": 2
1759 }
1760 "#
1761 );
1762 },
1763 );
1764 }
1765
1766 #[test]
1767 fn bash_returnish_names_are_not_exits() {
1768 check_metrics::<BashParser>(
1773 "returncode=1
1774 returns() {
1775 echo named
1776 }
1777 returns",
1778 "foo.sh",
1779 |metric| {
1780 insta::assert_json_snapshot!(
1781 metric.nexits,
1782 @r#"
1783 {
1784 "sum": 0,
1785 "average": 0.0,
1786 "min": 0,
1787 "max": 0
1788 }
1789 "#
1790 );
1791 },
1792 );
1793 }
1794
1795 #[test]
1796 fn tcl_no_exit() {
1797 check_metrics::<TclParser>(
1798 "proc f {x} {
1799 puts $x
1800}",
1801 "foo.tcl",
1802 |metric| {
1803 insta::assert_json_snapshot!(
1804 metric.nexits,
1805 @r#"
1806 {
1807 "sum": 0,
1808 "average": 0.0,
1809 "min": 0,
1810 "max": 0
1811 }
1812 "#
1813 );
1814 },
1815 );
1816 }
1817
1818 #[test]
1819 fn tcl_return() {
1820 check_metrics::<TclParser>(
1821 "proc f {x} {
1822 return $x
1823}",
1824 "foo.tcl",
1825 |metric| {
1826 assert_eq!(metric.nexits.nexits_sum(), 1);
1827 assert_eq!(metric.nexits.nexits_max(), 1);
1828 insta::assert_json_snapshot!(metric.nexits);
1829 },
1830 );
1831 }
1832
1833 #[test]
1834 fn tcl_multiple_returns() {
1835 check_metrics::<TclParser>(
1836 "proc f {x} {
1837 if {$x > 0} {
1838 return positive
1839 }
1840 return nonpositive
1841}",
1842 "foo.tcl",
1843 |metric| {
1844 assert_eq!(metric.nexits.nexits_sum(), 2);
1845 assert_eq!(metric.nexits.nexits_max(), 2);
1846 insta::assert_json_snapshot!(metric.nexits);
1847 },
1848 );
1849 }
1850
1851 #[test]
1852 fn typescript_multiple_returns() {
1853 check_metrics::<TypescriptParser>(
1854 "function classify(n: number): string {
1855 if (n > 0) {
1856 return 'positive';
1857 } else if (n < 0) {
1858 return 'negative';
1859 }
1860 return 'zero';
1861 }",
1862 "foo.ts",
1863 |metric| {
1864 assert_eq!(metric.nexits.nexits_sum(), 3);
1865 assert_eq!(metric.nexits.nexits_max(), 3);
1866 insta::assert_json_snapshot!(metric.nexits);
1867 },
1868 );
1869 }
1870
1871 #[test]
1872 fn typescript_nested_functions() {
1873 check_metrics::<TypescriptParser>(
1874 "function outer(): number {
1875 function inner(): number {
1876 return 42;
1877 }
1878 return inner();
1879 }",
1880 "foo.ts",
1881 |metric| {
1882 assert_eq!(metric.nexits.nexits_sum(), 2);
1884 assert_eq!(metric.nexits.nexits_max(), 1);
1885 insta::assert_json_snapshot!(metric.nexits);
1886 },
1887 );
1888 }
1889
1890 #[test]
1891 fn tsx_no_exit() {
1892 check_metrics::<TsxParser>(
1893 "function f(): void {
1894 console.log('hello');
1895 }",
1896 "foo.tsx",
1897 |metric| {
1898 assert_eq!(metric.nexits.nexits_sum(), 0);
1899 assert_eq!(metric.nexits.nexits_max(), 0);
1900 insta::assert_json_snapshot!(metric.nexits);
1901 },
1902 );
1903 }
1904
1905 #[test]
1906 fn tsx_multiple_returns() {
1907 check_metrics::<TsxParser>(
1908 "function classify(n: number): string {
1909 if (n > 0) {
1910 return 'positive';
1911 } else if (n < 0) {
1912 return 'negative';
1913 }
1914 return 'zero';
1915 }",
1916 "foo.tsx",
1917 |metric| {
1918 assert_eq!(metric.nexits.nexits_sum(), 3);
1919 assert_eq!(metric.nexits.nexits_max(), 3);
1920 insta::assert_json_snapshot!(metric.nexits);
1921 },
1922 );
1923 }
1924
1925 #[test]
1926 fn kotlin_multiple_returns() {
1927 check_metrics::<KotlinParser>(
1928 "fun classify(n: Int): String {
1929 if (n > 0) {
1930 return \"positive\"
1931 } else if (n < 0) {
1932 return \"negative\"
1933 }
1934 return \"zero\"
1935 }",
1936 "foo.kt",
1937 |metric| {
1938 assert_eq!(metric.nexits.nexits_sum(), 3);
1939 assert_eq!(metric.nexits.nexits_max(), 3);
1940 insta::assert_json_snapshot!(metric.nexits);
1941 },
1942 );
1943 }
1944
1945 #[test]
1946 fn kotlin_no_exit() {
1947 check_metrics::<KotlinParser>(
1948 "fun f(): Unit {
1949 println(\"hello\")
1950 }",
1951 "foo.kt",
1952 |metric| {
1953 assert_eq!(metric.nexits.nexits_sum(), 0);
1954 assert_eq!(metric.nexits.nexits_max(), 0);
1955 insta::assert_json_snapshot!(metric.nexits);
1956 },
1957 );
1958 }
1959
1960 #[test]
1961 fn mozjs_nested_functions() {
1962 check_metrics::<MozjsParser>(
1963 "function outer() {
1964 function inner() {
1965 return 42;
1966 }
1967 return inner();
1968 }",
1969 "foo.js",
1970 |metric| {
1971 assert_eq!(metric.nexits.nexits_sum(), 2);
1973 assert_eq!(metric.nexits.nexits_max(), 1);
1974 insta::assert_json_snapshot!(metric.nexits);
1975 },
1976 );
1977 }
1978
1979 #[test]
1980 fn php_no_exit() {
1981 check_metrics::<PhpParser>("<?php $a = 42;", "foo.php", |metric| {
1982 insta::assert_json_snapshot!(
1983 metric.nexits,
1984 @r#"
1985 {
1986 "sum": 0,
1987 "average": 0.0,
1988 "min": 0,
1989 "max": 0
1990 }
1991 "#
1992 );
1993 });
1994 }
1995
1996 #[test]
1997 fn php_yield_throw() {
1998 check_metrics::<PhpParser>(
2001 "<?php
2002 function gen() {
2003 yield 1;
2004 yield 2;
2005 throw new \\Exception('x');
2006 }",
2007 "foo.php",
2008 |metric| {
2009 insta::assert_json_snapshot!(
2011 metric.nexits,
2012 @r#"
2013 {
2014 "sum": 3,
2015 "average": 3.0,
2016 "min": 0,
2017 "max": 3
2018 }
2019 "#
2020 );
2021 },
2022 );
2023 }
2024
2025 #[test]
2026 fn php_exit_statement() {
2027 check_metrics::<PhpParser>(
2032 "<?php
2033 function bail(int $code): void {
2034 if ($code === 1) {
2035 exit(1);
2036 }
2037 exit;
2038 }",
2039 "foo.php",
2040 |metric| {
2041 insta::assert_json_snapshot!(
2043 metric.nexits,
2044 @r#"
2045 {
2046 "sum": 2,
2047 "average": 2.0,
2048 "min": 0,
2049 "max": 2
2050 }
2051 "#
2052 );
2053 },
2054 );
2055 }
2056
2057 #[test]
2058 fn elixir_no_exit() {
2059 check_metrics::<ElixirParser>(
2064 "defmodule Foo do\n def add(a, b) do\n a + b\n end\nend\n",
2065 "foo.ex",
2066 |metric| {
2067 assert_eq!(metric.nexits.nexits_sum(), 0);
2068 insta::assert_json_snapshot!(
2069 metric.nexits,
2070 @r#"
2071 {
2072 "sum": 0,
2073 "average": 0.0,
2074 "min": 0,
2075 "max": 0
2076 }
2077 "#
2078 );
2079 },
2080 );
2081 }
2082
2083 #[test]
2084 fn elixir_raise_throw_exit() {
2085 check_metrics::<ElixirParser>(
2088 "defmodule Foo do\n def bad(x) do\n raise \"first\"\n throw(:second)\n exit(:third)\n end\nend\n",
2089 "foo.ex",
2090 |metric| {
2091 assert_eq!(metric.nexits.nexits_sum(), 3);
2092 insta::assert_json_snapshot!(
2093 metric.nexits,
2094 @r#"
2095 {
2096 "sum": 3,
2097 "average": 3.0,
2098 "min": 0,
2099 "max": 3
2100 }
2101 "#
2102 );
2103 },
2104 );
2105 }
2106
2107 #[test]
2108 fn elixir_reraise_counts() {
2109 check_metrics::<ElixirParser>(
2113 "defmodule Foo do\n def wrap(stack) do\n reraise(\"oops\", stack)\n end\nend\n",
2114 "foo.ex",
2115 |metric| {
2116 assert_eq!(metric.nexits.nexits_sum(), 1);
2117 },
2118 );
2119 }
2120
2121 #[test]
2122 fn elixir_lookalike_call_is_not_exit() {
2123 check_metrics::<ElixirParser>(
2127 "defmodule Foo do\n def f do\n throw_event(:click)\n Logger.raise_alert()\n exit_code = 0\n exit_code\n end\nend\n",
2128 "foo.ex",
2129 |metric| {
2130 assert_eq!(metric.nexits.nexits_sum(), 0);
2131 },
2132 );
2133 }
2134
2135 #[test]
2136 fn ruby_no_exit() {
2137 check_metrics::<RubyParser>("def foo\n a = 1\n a + 1\nend\n", "foo.rb", |metric| {
2139 assert_eq!(metric.nexits.nexits_sum(), 0);
2140 });
2141 }
2142
2143 #[test]
2144 fn ruby_multiple_returns() {
2145 check_metrics::<RubyParser>(
2148 "def kind(x)\n return :zero if x == 0\n if x > 0\n return :pos\n elsif x < 0\n return :neg\n end\n return :unknown\nend\n",
2149 "foo.rb",
2150 |metric| {
2151 assert_eq!(metric.nexits.nexits_sum(), 4);
2152 },
2153 );
2154 }
2155
2156 #[test]
2157 fn ruby_explicit_returns() {
2158 check_metrics::<RubyParser>(
2162 "def foo(x)\n return 0 if x.nil?\n yield x\n return x * 2\nend\n",
2163 "foo.rb",
2164 |metric| {
2165 assert_eq!(metric.nexits.nexits_sum(), 2);
2166 insta::assert_json_snapshot!(metric.nexits);
2167 },
2168 );
2169 }
2170
2171 #[test]
2172 fn python_return_and_raise() {
2173 check_metrics::<PythonParser>(
2177 "def parse(s):
2178 if not s:
2179 raise ValueError(\"empty\")
2180 return int(s)",
2181 "foo.py",
2182 |metric| {
2183 assert_eq!(metric.nexits.nexits_sum(), 2);
2184 insta::assert_json_snapshot!(
2185 metric.nexits,
2186 @r#"
2187 {
2188 "sum": 2,
2189 "average": 2.0,
2190 "min": 0,
2191 "max": 2
2192 }
2193 "#
2194 );
2195 },
2196 );
2197 }
2198
2199 #[test]
2200 fn javascript_return_and_throw() {
2201 check_metrics::<JavascriptParser>(
2203 "function parseLength(s) {
2204 if (s === null) throw new Error('null');
2205 return s.length;
2206 }",
2207 "foo.js",
2208 |metric| {
2209 assert_eq!(metric.nexits.nexits_sum(), 2);
2210 insta::assert_json_snapshot!(
2211 metric.nexits,
2212 @r#"
2213 {
2214 "sum": 2,
2215 "average": 2.0,
2216 "min": 0,
2217 "max": 2
2218 }
2219 "#
2220 );
2221 },
2222 );
2223 }
2224
2225 #[test]
2226 fn mozjs_return_and_throw() {
2227 check_metrics::<MozjsParser>(
2229 "function parseLength(s) {
2230 if (s === null) throw new Error('null');
2231 return s.length;
2232 }",
2233 "foo.js",
2234 |metric| {
2235 assert_eq!(metric.nexits.nexits_sum(), 2);
2236 insta::assert_json_snapshot!(
2237 metric.nexits,
2238 @r#"
2239 {
2240 "sum": 2,
2241 "average": 2.0,
2242 "min": 0,
2243 "max": 2
2244 }
2245 "#
2246 );
2247 },
2248 );
2249 }
2250
2251 #[test]
2252 fn typescript_return_and_throw() {
2253 check_metrics::<TypescriptParser>(
2254 "function parseLength(s: string | null): number {
2255 if (s === null) throw new Error('null');
2256 return s.length;
2257 }",
2258 "foo.ts",
2259 |metric| {
2260 assert_eq!(metric.nexits.nexits_sum(), 2);
2261 insta::assert_json_snapshot!(
2262 metric.nexits,
2263 @r#"
2264 {
2265 "sum": 2,
2266 "average": 2.0,
2267 "min": 0,
2268 "max": 2
2269 }
2270 "#
2271 );
2272 },
2273 );
2274 }
2275
2276 #[test]
2277 fn tsx_return_and_throw() {
2278 check_metrics::<TsxParser>(
2279 "function parseLength(s: string | null): number {
2280 if (s === null) throw new Error('null');
2281 return s.length;
2282 }",
2283 "foo.tsx",
2284 |metric| {
2285 assert_eq!(metric.nexits.nexits_sum(), 2);
2286 insta::assert_json_snapshot!(
2287 metric.nexits,
2288 @r#"
2289 {
2290 "sum": 2,
2291 "average": 2.0,
2292 "min": 0,
2293 "max": 2
2294 }
2295 "#
2296 );
2297 },
2298 );
2299 }
2300
2301 #[test]
2302 fn java_return_and_throw() {
2303 check_metrics::<JavaParser>(
2305 "class A {
2306 int parseLength(String s) {
2307 if (s == null) throw new NullPointerException();
2308 return s.length();
2309 }
2310 }",
2311 "foo.java",
2312 |metric| {
2313 assert_eq!(metric.nexits.nexits_sum(), 2);
2314 insta::assert_json_snapshot!(
2315 metric.nexits,
2316 @r#"
2317 {
2318 "sum": 2,
2319 "average": 2.0,
2320 "min": 0,
2321 "max": 2
2322 }
2323 "#
2324 );
2325 },
2326 );
2327 }
2328
2329 #[test]
2330 fn java_yield_in_switch_expression() {
2331 check_metrics::<JavaParser>(
2334 "class A {
2335 int describe(int n) {
2336 return switch (n) {
2337 case 0: yield 100;
2338 default: yield 200;
2339 };
2340 }
2341 }",
2342 "foo.java",
2343 |metric| {
2344 assert_eq!(metric.nexits.nexits_sum(), 3);
2345 },
2346 );
2347 }
2348
2349 #[test]
2350 fn groovy_no_exit() {
2351 check_metrics::<GroovyParser>("int a = 42", "foo.groovy", |metric| {
2353 assert_eq!(metric.nexits.nexits_sum(), 0);
2354 });
2355 }
2356
2357 #[test]
2358 fn groovy_simple_function() {
2359 check_metrics::<GroovyParser>(
2361 "int answer() {
2362 return 42
2363 }",
2364 "foo.groovy",
2365 |metric| {
2366 assert_eq!(metric.nexits.nexits_sum(), 1);
2367 },
2368 );
2369 }
2370
2371 #[test]
2372 fn groovy_return_and_throw() {
2373 check_metrics::<GroovyParser>(
2374 "class A {
2375 int parseLength(String s) {
2376 if (s == null) throw new NullPointerException()
2377 return s.length()
2378 }
2379 }",
2380 "foo.groovy",
2381 |metric| {
2382 assert_eq!(metric.nexits.nexits_sum(), 2);
2383 },
2384 );
2385 }
2386
2387 #[test]
2388 fn groovy_yield_in_switch_expression() {
2389 check_metrics::<GroovyParser>(
2392 "class A {
2393 int describe(int n) {
2394 return switch (n) {
2395 case 0: yield 100;
2396 default: yield 200;
2397 }
2398 }
2399 }",
2400 "foo.groovy",
2401 |metric| {
2402 assert_eq!(metric.nexits.nexits_sum(), 3);
2403 },
2404 );
2405 }
2406
2407 #[test]
2408 fn groovy_implicit_return_not_counted() {
2409 check_metrics::<GroovyParser>("int identity(int x) { x }", "foo.groovy", |metric| {
2414 assert_eq!(metric.nexits.nexits_sum(), 0);
2415 });
2416 }
2417
2418 #[test]
2419 fn cpp_return_and_throw() {
2420 check_metrics::<CppParser>(
2422 "int parseLength(const char* s) {
2423 if (s == nullptr) throw std::invalid_argument(\"null\");
2424 return 0;
2425 }",
2426 "foo.cpp",
2427 |metric| {
2428 assert_eq!(metric.nexits.nexits_sum(), 2);
2429 insta::assert_json_snapshot!(
2430 metric.nexits,
2431 @r#"
2432 {
2433 "sum": 2,
2434 "average": 2.0,
2435 "min": 0,
2436 "max": 2
2437 }
2438 "#
2439 );
2440 },
2441 );
2442 }
2443
2444 #[test]
2445 fn python_yield_counts_as_exit() {
2446 check_metrics::<PythonParser>(
2451 "def gen():
2452 yield 1
2453 yield 2
2454 return",
2455 "foo.py",
2456 |metric| {
2457 assert_eq!(metric.nexits.nexits_sum(), 3);
2458 insta::assert_json_snapshot!(
2459 metric.nexits,
2460 @r#"
2461 {
2462 "sum": 3,
2463 "average": 3.0,
2464 "min": 0,
2465 "max": 3
2466 }
2467 "#
2468 );
2469 },
2470 );
2471 }
2472
2473 #[test]
2474 fn javascript_yield_counts_as_exit() {
2475 check_metrics::<JavascriptParser>(
2478 "function* gen() {
2479 yield 1;
2480 yield 2;
2481 return;
2482 }",
2483 "foo.js",
2484 |metric| {
2485 assert_eq!(metric.nexits.nexits_sum(), 3);
2486 insta::assert_json_snapshot!(
2487 metric.nexits,
2488 @r#"
2489 {
2490 "sum": 3,
2491 "average": 3.0,
2492 "min": 0,
2493 "max": 3
2494 }
2495 "#
2496 );
2497 },
2498 );
2499 }
2500
2501 #[test]
2502 fn mozjs_yield_counts_as_exit() {
2503 check_metrics::<MozjsParser>(
2505 "function* gen() {
2506 yield 1;
2507 yield 2;
2508 return;
2509 }",
2510 "foo.js",
2511 |metric| {
2512 assert_eq!(metric.nexits.nexits_sum(), 3);
2513 insta::assert_json_snapshot!(
2514 metric.nexits,
2515 @r#"
2516 {
2517 "sum": 3,
2518 "average": 3.0,
2519 "min": 0,
2520 "max": 3
2521 }
2522 "#
2523 );
2524 },
2525 );
2526 }
2527
2528 #[test]
2529 fn typescript_yield_counts_as_exit() {
2530 check_metrics::<TypescriptParser>(
2531 "function* gen(): Generator<number> {
2532 yield 1;
2533 yield 2;
2534 return;
2535 }",
2536 "foo.ts",
2537 |metric| {
2538 assert_eq!(metric.nexits.nexits_sum(), 3);
2539 insta::assert_json_snapshot!(
2540 metric.nexits,
2541 @r#"
2542 {
2543 "sum": 3,
2544 "average": 3.0,
2545 "min": 0,
2546 "max": 3
2547 }
2548 "#
2549 );
2550 },
2551 );
2552 }
2553
2554 #[test]
2555 fn tsx_yield_counts_as_exit() {
2556 check_metrics::<TsxParser>(
2557 "function* gen(): Generator<number> {
2558 yield 1;
2559 yield 2;
2560 return;
2561 }",
2562 "foo.tsx",
2563 |metric| {
2564 assert_eq!(metric.nexits.nexits_sum(), 3);
2565 insta::assert_json_snapshot!(
2566 metric.nexits,
2567 @r#"
2568 {
2569 "sum": 3,
2570 "average": 3.0,
2571 "min": 0,
2572 "max": 3
2573 }
2574 "#
2575 );
2576 },
2577 );
2578 }
2579
2580 #[test]
2581 fn python_yield_forms_count_as_exit() {
2582 check_metrics::<PythonParser>(
2587 "def gen():
2588 yield
2589 yield 1
2590 yield from range(3)",
2591 "foo.py",
2592 |metric| {
2593 assert_eq!(metric.nexits.nexits_sum(), 3);
2594 insta::assert_json_snapshot!(
2595 metric.nexits,
2596 @r#"
2597 {
2598 "sum": 3,
2599 "average": 3.0,
2600 "min": 0,
2601 "max": 3
2602 }
2603 "#
2604 );
2605 },
2606 );
2607 }
2608
2609 #[test]
2610 fn javascript_yield_delegate_counts_as_exit() {
2611 check_metrics::<JavascriptParser>(
2616 "function* gen() {
2617 yield 1;
2618 yield* other();
2619 yield 2;
2620 }",
2621 "foo.js",
2622 |metric| {
2623 assert_eq!(metric.nexits.nexits_sum(), 3);
2624 insta::assert_json_snapshot!(
2625 metric.nexits,
2626 @r#"
2627 {
2628 "sum": 3,
2629 "average": 3.0,
2630 "min": 0,
2631 "max": 3
2632 }
2633 "#
2634 );
2635 },
2636 );
2637 }
2638
2639 #[test]
2640 fn mozjs_yield_delegate_counts_as_exit() {
2641 check_metrics::<MozjsParser>(
2642 "function* gen() {
2643 yield 1;
2644 yield* other();
2645 yield 2;
2646 }",
2647 "foo.js",
2648 |metric| {
2649 assert_eq!(metric.nexits.nexits_sum(), 3);
2650 insta::assert_json_snapshot!(
2651 metric.nexits,
2652 @r#"
2653 {
2654 "sum": 3,
2655 "average": 3.0,
2656 "min": 0,
2657 "max": 3
2658 }
2659 "#
2660 );
2661 },
2662 );
2663 }
2664
2665 #[test]
2666 fn typescript_yield_delegate_counts_as_exit() {
2667 check_metrics::<TypescriptParser>(
2668 "function* gen(): Generator<number> {
2669 yield 1;
2670 yield* other();
2671 yield 2;
2672 }",
2673 "foo.ts",
2674 |metric| {
2675 assert_eq!(metric.nexits.nexits_sum(), 3);
2676 insta::assert_json_snapshot!(
2677 metric.nexits,
2678 @r#"
2679 {
2680 "sum": 3,
2681 "average": 3.0,
2682 "min": 0,
2683 "max": 3
2684 }
2685 "#
2686 );
2687 },
2688 );
2689 }
2690
2691 #[test]
2692 fn tsx_yield_delegate_counts_as_exit() {
2693 check_metrics::<TsxParser>(
2694 "function* gen(): Generator<number> {
2695 yield 1;
2696 yield* other();
2697 yield 2;
2698 }",
2699 "foo.tsx",
2700 |metric| {
2701 assert_eq!(metric.nexits.nexits_sum(), 3);
2702 insta::assert_json_snapshot!(
2703 metric.nexits,
2704 @r#"
2705 {
2706 "sum": 3,
2707 "average": 3.0,
2708 "min": 0,
2709 "max": 3
2710 }
2711 "#
2712 );
2713 },
2714 );
2715 }
2716
2717 #[test]
2720 fn irules_no_exit() {
2721 check_metrics::<IrulesParser>(
2722 "when HTTP_REQUEST {
2723 set x 1
2724 log local0. $x
2725}
2726",
2727 "foo.irule",
2728 |metric| {
2729 assert_eq!(metric.nexits.nexits_sum(), 0);
2730 },
2731 );
2732 }
2733
2734 #[test]
2736 fn irules_return() {
2737 check_metrics::<IrulesParser>(
2738 "when HTTP_REQUEST {
2739 if { [HTTP::uri] eq \"/\" } {
2740 return
2741 }
2742 log local0. \"served\"
2743}
2744",
2745 "foo.irule",
2746 |metric| {
2747 assert_eq!(metric.nexits.nexits_sum(), 1);
2748 },
2749 );
2750 }
2751
2752 #[test]
2755 fn irules_multi_value_return_counts_once() {
2756 check_metrics::<IrulesParser>(
2757 "proc pair { a b } {
2758 return [list $a $b]
2759}
2760",
2761 "foo.irule",
2762 |metric| {
2763 assert_eq!(metric.nexits.nexits_sum(), 1);
2764 },
2765 );
2766 }
2767
2768 #[test]
2771 fn objc_no_exit() {
2772 check_metrics::<ObjcParser>(
2773 "@implementation Foo
2774- (void)bar {
2775 [self doWork];
2776}
2777@end
2778",
2779 "foo.m",
2780 |metric| {
2781 assert_eq!(metric.nexits.nexits_sum(), 0);
2782 insta::assert_json_snapshot!(metric.nexits, @r#"
2783 {
2784 "sum": 0,
2785 "average": 0.0,
2786 "min": 0,
2787 "max": 0
2788 }
2789 "#);
2790 },
2791 );
2792 }
2793
2794 #[test]
2797 fn objc_return_and_throw() {
2798 check_metrics::<ObjcParser>(
2799 "@implementation Foo
2800- (int)bar:(int)x {
2801 if (x < 0) {
2802 @throw [NSException exceptionWithName:@\"e\" reason:@\"r\" userInfo:nil];
2803 }
2804 return x;
2805}
2806@end
2807",
2808 "foo.m",
2809 |metric| {
2810 assert_eq!(metric.nexits.nexits_sum(), 2);
2811 insta::assert_json_snapshot!(metric.nexits, @r#"
2812 {
2813 "sum": 2,
2814 "average": 2.0,
2815 "min": 0,
2816 "max": 2
2817 }
2818 "#);
2819 },
2820 );
2821 }
2822}