#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use serde::Serialize;
use serde::ser::{SerializeStruct, Serializer};
use std::fmt;
use crate::checker::Checker;
use crate::macros::implement_metric_trait;
use crate::*;
#[derive(Debug, Clone)]
pub struct Stats {
exit: usize,
exit_sum: usize,
total_space_functions: usize,
exit_min: usize,
exit_max: usize,
}
impl Default for Stats {
fn default() -> Self {
Self {
exit: 0,
exit_sum: 0,
total_space_functions: 1,
exit_min: usize::MAX,
exit_max: 0,
}
}
}
impl Serialize for Stats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut st = serializer.serialize_struct("nexits", 4)?;
st.serialize_field("sum", &self.exit_sum())?;
st.serialize_field("average", &self.exit_average())?;
st.serialize_field("min", &self.exit_min())?;
st.serialize_field("max", &self.exit_max())?;
st.end()
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"sum: {}, average: {} min: {}, max: {}",
self.exit_sum(),
self.exit_average(),
self.exit_min(),
self.exit_max()
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.exit_max = self.exit_max.max(other.exit_max);
self.exit_min = self.exit_min.min(other.exit_min);
self.exit_sum += other.exit_sum;
}
#[must_use]
pub fn exit(&self) -> f64 {
self.exit as f64
}
#[must_use]
pub fn exit_sum(&self) -> f64 {
self.exit_sum as f64
}
#[must_use]
pub fn exit_min(&self) -> f64 {
if self.exit_min == usize::MAX {
0.0
} else {
self.exit_min as f64
}
}
#[must_use]
pub fn exit_max(&self) -> f64 {
self.exit_max as f64
}
#[must_use]
pub fn exit_average(&self) -> f64 {
self.exit_sum() / self.total_space_functions as f64
}
#[inline]
pub(crate) fn compute_sum(&mut self) {
self.exit_sum += self.exit;
}
#[inline]
pub(crate) fn compute_minmax(&mut self) {
self.exit_max = self.exit_max.max(self.exit);
self.exit_min = self.exit_min.min(self.exit);
self.compute_sum();
}
pub(crate) fn finalize(&mut self, total_space_functions: usize) {
self.total_space_functions = total_space_functions;
}
}
#[doc(hidden)]
pub trait Exit
where
Self: Checker,
{
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats);
}
macro_rules! impl_exit_match_kinds {
($code:ty, $lang:ident, [$($kind:ident),+ $(,)?]) => {
impl Exit for $code {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if matches!(node.kind_id().into(), $($lang::$kind)|+) {
stats.exit += 1;
}
}
}
};
}
impl_exit_match_kinds!(PythonCode, Python, [ReturnStatement, RaiseStatement, Yield]);
impl_exit_match_kinds!(
MozjsCode,
Mozjs,
[ReturnStatement, ThrowStatement, YieldExpression]
);
impl_exit_match_kinds!(
JavascriptCode,
Javascript,
[ReturnStatement, ThrowStatement, YieldExpression]
);
impl_exit_match_kinds!(
TypescriptCode,
Typescript,
[ReturnStatement, ThrowStatement, YieldExpression]
);
impl_exit_match_kinds!(
TsxCode,
Tsx,
[ReturnStatement, ThrowStatement, YieldExpression]
);
impl_exit_match_kinds!(CppCode, Cpp, [ReturnStatement, ThrowStatement]);
impl_exit_match_kinds!(JavaCode, Java, [ReturnStatement, ThrowStatement]);
impl_exit_match_kinds!(
GroovyCode,
Groovy,
[ReturnStatement, ThrowStatement, YieldStatement]
);
impl Exit for RustCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if matches!(
node.kind_id().into(),
Rust::ReturnExpression | Rust::TryExpression
) {
stats.exit += 1;
}
}
}
impl Exit for CsharpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if matches!(
node.kind_id().into(),
Csharp::ReturnStatement
| Csharp::YieldStatement
| Csharp::ThrowStatement
| Csharp::ThrowExpression
) {
stats.exit += 1;
}
}
}
impl Exit for GoCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if matches!(node.kind_id().into(), Go::ReturnStatement) {
stats.exit += 1;
}
}
}
impl Exit for PerlCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if node.kind_id() == Perl::ReturnExpression {
stats.exit += 1;
}
}
}
impl Exit for KotlinCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if matches!(
node.kind_id().into(),
Kotlin::ReturnExpression | Kotlin::ThrowExpression
) {
stats.exit += 1;
}
}
}
impl Exit for LuaCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if node.kind_id() == Lua::ReturnStatement {
stats.exit += 1;
}
}
}
impl Exit for BashCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
if matches!(node.kind_id().into(), Bash::Command)
&& let Some(name) = node.child_by_field_name("name")
&& matches!(name.utf8_text(code), Some("return" | "exit"))
{
stats.exit += 1;
}
}
}
impl Exit for TclCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
if node.kind_id() == Tcl::Command
&& let Some(name) = node.child_by_field_name("name")
&& name.kind_id() == Tcl::SimpleWord
&& name.utf8_text(code) == Some("return")
{
stats.exit += 1;
}
}
}
impl Exit for PhpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if matches!(
node.kind_id().into(),
Php::ReturnStatement | Php::YieldExpression | Php::ThrowExpression | Php::ExitStatement
) {
stats.exit += 1;
}
}
}
implement_metric_trait!(Exit, PreprocCode, CcommentCode);
impl Exit for RubyCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
if matches!(node.kind_id().into(), Ruby::Return | Ruby::Return2) {
stats.exit += 1;
}
}
}
impl Exit for ElixirCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
if node.kind_id() == Elixir::Call
&& let Some(target) = node.child_by_field_name("target")
&& target.kind_id() == Elixir::Identifier
&& matches!(
target.utf8_text(code),
Some("throw" | "raise" | "reraise" | "exit")
)
{
stats.exit += 1;
}
}
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::doc_markdown,
clippy::needless_raw_string_hashes,
clippy::too_many_lines
)]
mod tests {
use crate::tools::check_metrics;
use super::*;
#[test]
fn exit_empty_file_min_is_zero() {
let stats = Stats::default();
assert_eq!(stats.exit_min(), 0.0);
}
#[test]
fn python_no_exit() {
check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn rust_no_exit() {
check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn rust_question_mark() {
check_metrics::<RustParser>("let _ = a? + b? + c?;", "foo.rs", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": null,
"min": 3.0,
"max": 3.0
}"###
);
});
}
#[test]
fn rust_explicit_return_with_return_type() {
check_metrics::<RustParser>("fn foo() -> i32 { return 1; }", "foo.rs", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
});
}
#[test]
fn rust_implicit_return_not_counted() {
check_metrics::<RustParser>("fn foo() -> i32 { 0 }", "foo.rs", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn rust_mixed_explicit_and_implicit_return() {
check_metrics::<RustParser>(
"fn foo(x: bool) -> i32 { if x { return 1; } 0 }",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn rust_question_mark_in_function() {
check_metrics::<RustParser>(
"fn foo() -> Result<i32, ()> { Ok(do_thing()?) }",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn rust_unit_return_no_exit() {
check_metrics::<RustParser>("fn foo() { let _x = 1; }", "foo.rs", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn c_no_exit() {
check_metrics::<CppParser>("int a = 42;", "foo.c", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn c_multiple_returns_in_branches() {
check_metrics::<CppParser>(
"int f(int x) {
if (x < 0) {
return -1;
} else if (x == 0) {
return 0;
} else {
return 1;
}
}",
"foo.c",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
assert_eq!(metric.nexits.exit_max(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}"###
);
},
);
}
#[test]
fn cpp_return_in_try_catch() {
check_metrics::<CppParser>(
"int f(int x) {
try {
if (x == 0) {
return 1;
}
return 2;
} catch (...) {
return -1;
}
}",
"foo.cpp",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
assert_eq!(metric.nexits.exit_max(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}"###
);
},
);
}
#[test]
fn c_early_return_in_loop() {
check_metrics::<CppParser>(
"int find(int* a, int n, int target) {
for (int i = 0; i < n; ++i) {
if (a[i] == target) {
return i;
}
}
return -1;
}",
"foo.c",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
assert_eq!(metric.nexits.exit_max(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn c_void_no_explicit_return() {
check_metrics::<CppParser>(
"void greet(const char* who) {
printf(\"hi %s\\n\", who);
}",
"foo.c",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
assert_eq!(metric.nexits.exit_max(), 0.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}"###
);
},
);
}
#[test]
fn javascript_no_exit() {
check_metrics::<JavascriptParser>("var a = 42;", "foo.js", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn javascript_simple_function() {
check_metrics::<JavascriptParser>(
"function f(a, b) {
if (a) {
return a;
}
return b;
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn javascript_nested_functions() {
check_metrics::<JavascriptParser>(
"function outer() {
function inner() {
return 1;
}
return inner();
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn python_simple_function() {
check_metrics::<PythonParser>(
"def f(a, b):
if a:
return a",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn python_more_functions() {
check_metrics::<PythonParser>(
"def f(a, b):
if a:
return a
def f(a, b):
if b:
return b",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn python_nested_functions() {
check_metrics::<PythonParser>(
"def f(a, b):
def foo(a):
if a:
return 1
bar = lambda a: lambda b: b or True or True
return bar(foo(a))(a)",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 0.5,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn java_no_exit() {
check_metrics::<JavaParser>("int a = 42;", "foo.java", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn java_simple_function() {
check_metrics::<JavaParser>(
"class A {
public int sum(int x, int y) {
return x + y;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn go_no_return() {
check_metrics::<GoParser>(
"package main
func f() {
x := 1
_ = x
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}"###
);
},
);
}
#[test]
fn go_single_return() {
check_metrics::<GoParser>(
"package main
func f() int {
return 1
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn go_multiple_returns() {
check_metrics::<GoParser>(
"package main
func f(x int) int {
if x > 0 {
return 1
}
if x < 0 {
return -1
}
return 0
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}"###
);
},
);
}
#[test]
fn go_naked_return() {
check_metrics::<GoParser>(
"package main
func f() (x int) {
x = 1
return
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn go_multivalue_return() {
check_metrics::<GoParser>(
"package main
func f() (int, error) {
return 0, nil
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn java_split_function() {
check_metrics::<JavaParser>(
"class A {
public int multiply(int x, int y) {
if(x == 0 || y == 0){
return 0;
}
return x * y;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn csharp_no_exit() {
check_metrics::<CsharpParser>("int a = 42;", "foo.cs", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn csharp_simple_function() {
check_metrics::<CsharpParser>(
"class A {
public int Sum(int x, int y) {
return x + y;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn csharp_split_function() {
check_metrics::<CsharpParser>(
"class A {
public int Multiply(int x, int y) {
if (x == 0 || y == 0) {
return 0;
}
return x * y;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn csharp_yield_and_throw() {
check_metrics::<CsharpParser>(
"class A {
public IEnumerable<int> Gen() {
yield return 1;
yield break;
}
public int Bad(int x) {
if (x < 0) throw new System.Exception();
return x;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r#"
{
"sum": 4.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"#
);
},
);
}
#[test]
fn perl_no_exit() {
check_metrics::<PerlParser>(
"sub f {
print 'hi';
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r#"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}
"#
);
},
);
}
#[test]
fn perl_no_function_no_exit() {
check_metrics::<PerlParser>("my $x = 1;\nprint $x;\n", "foo.pl", |metric| {
insta::assert_json_snapshot!(metric.nexits, @r#"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}
"#);
});
}
#[test]
fn perl_multiple_returns() {
check_metrics::<PerlParser>(
"sub f {
return 1 if $_[0];
return 0;
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r#"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"#
);
},
);
}
#[test]
fn tsx_function_with_returns() {
check_metrics::<TsxParser>(
"function clamp(val: number, min: number, max: number) {
if (val < min) {
return min;
}
if (val > max) {
return max;
}
return val;
}",
"foo.tsx",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}"###
);
},
);
}
#[test]
fn typescript_no_exit() {
check_metrics::<TypescriptParser>("const x: number = 42;", "foo.ts", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn typescript_function_with_returns() {
check_metrics::<TypescriptParser>(
"function safeDivide(a: number, b: number): number | null {
if (b === 0) {
return null;
}
return a / b;
}",
"foo.ts",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn mozjs_no_exit() {
check_metrics::<MozjsParser>("var a = 42;", "foo.js", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn mozjs_function_with_returns() {
check_metrics::<MozjsParser>(
"function f(a, b) {
if (a) {
return a;
}
return b;
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn kotlin_exit_return_and_throw() {
check_metrics::<KotlinParser>(
"fun divide(a: Int, b: Int): Int {
if (b == 0) {
throw IllegalArgumentException(\"zero\")
}
return a / b
}",
"foo.kt",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn lua_no_exit() {
check_metrics::<LuaParser>(
"local function f(x)
local y = x + 1
end",
"foo.lua",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}
"###
);
},
);
}
#[test]
fn lua_return() {
check_metrics::<LuaParser>(
"local function f(x)
if x > 0 then
return x
end
return 0
end",
"foo.lua",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn bash_no_exit() {
check_metrics::<BashParser>("echo \"no exits\"", "foo.sh", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn bash_explicit_return() {
check_metrics::<BashParser>(
"f() {
if [ -z \"$1\" ]; then
return 1
fi
echo ok
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn bash_explicit_exit() {
check_metrics::<BashParser>(
"f() {
exit 0
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 1.0,
"average": 1.0,
"min": 0.0,
"max": 1.0
}"###
);
},
);
}
#[test]
fn bash_multiple_exits() {
check_metrics::<BashParser>(
"f() {
if [ \"$1\" = die ]; then
exit 1
fi
return 0
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn bash_returnish_names_are_not_exits() {
check_metrics::<BashParser>(
"returncode=1
returns() {
echo named
}
returns",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}"###
);
},
);
}
#[test]
fn tcl_no_exit() {
check_metrics::<TclParser>(
"proc f {x} {
puts $x
}",
"foo.tcl",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r#"
{
"sum": 0.0,
"average": 0.0,
"min": 0.0,
"max": 0.0
}
"#
);
},
);
}
#[test]
fn tcl_return() {
check_metrics::<TclParser>(
"proc f {x} {
return $x
}",
"foo.tcl",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 1.0);
assert_eq!(metric.nexits.exit_max(), 1.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn tcl_multiple_returns() {
check_metrics::<TclParser>(
"proc f {x} {
if {$x > 0} {
return positive
}
return nonpositive
}",
"foo.tcl",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
assert_eq!(metric.nexits.exit_max(), 2.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn typescript_multiple_returns() {
check_metrics::<TypescriptParser>(
"function classify(n: number): string {
if (n > 0) {
return 'positive';
} else if (n < 0) {
return 'negative';
}
return 'zero';
}",
"foo.ts",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
assert_eq!(metric.nexits.exit_max(), 3.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn typescript_nested_functions() {
check_metrics::<TypescriptParser>(
"function outer(): number {
function inner(): number {
return 42;
}
return inner();
}",
"foo.ts",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
assert_eq!(metric.nexits.exit_max(), 1.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn tsx_no_exit() {
check_metrics::<TsxParser>(
"function f(): void {
console.log('hello');
}",
"foo.tsx",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
assert_eq!(metric.nexits.exit_max(), 0.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn tsx_multiple_returns() {
check_metrics::<TsxParser>(
"function classify(n: number): string {
if (n > 0) {
return 'positive';
} else if (n < 0) {
return 'negative';
}
return 'zero';
}",
"foo.tsx",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
assert_eq!(metric.nexits.exit_max(), 3.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn kotlin_multiple_returns() {
check_metrics::<KotlinParser>(
"fun classify(n: Int): String {
if (n > 0) {
return \"positive\"
} else if (n < 0) {
return \"negative\"
}
return \"zero\"
}",
"foo.kt",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
assert_eq!(metric.nexits.exit_max(), 3.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn kotlin_no_exit() {
check_metrics::<KotlinParser>(
"fun f(): Unit {
println(\"hello\")
}",
"foo.kt",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
assert_eq!(metric.nexits.exit_max(), 0.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn mozjs_nested_functions() {
check_metrics::<MozjsParser>(
"function outer() {
function inner() {
return 42;
}
return inner();
}",
"foo.js",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
assert_eq!(metric.nexits.exit_max(), 1.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn php_no_exit() {
check_metrics::<PhpParser>("<?php $a = 42;", "foo.php", |metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
});
}
#[test]
fn php_yield_throw() {
check_metrics::<PhpParser>(
"<?php
function gen() {
yield 1;
yield 2;
throw new \\Exception('x');
}",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}"###
);
},
);
}
#[test]
fn php_exit_statement() {
check_metrics::<PhpParser>(
"<?php
function bail(int $code): void {
if ($code === 1) {
exit(1);
}
exit;
}",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}"###
);
},
);
}
#[test]
fn elixir_no_exit() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def add(a, b) do\n a + b\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 0.0,
"average": null,
"min": 0.0,
"max": 0.0
}"###
);
},
);
}
#[test]
fn elixir_raise_throw_exit() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def bad(x) do\n raise \"first\"\n throw(:second)\n exit(:third)\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r#"
{
"sum": 3.0,
"average": null,
"min": 0.0,
"max": 3.0
}
"#
);
},
);
}
#[test]
fn elixir_reraise_counts() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def wrap(stack) do\n reraise(\"oops\", stack)\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 1.0);
},
);
}
#[test]
fn elixir_lookalike_call_is_not_exit() {
check_metrics::<ElixirParser>(
"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",
"foo.ex",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
},
);
}
#[test]
fn ruby_no_exit() {
check_metrics::<RubyParser>("def foo\n a = 1\n a + 1\nend\n", "foo.rb", |metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
});
}
#[test]
fn ruby_multiple_returns() {
check_metrics::<RubyParser>(
"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",
"foo.rb",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 4.0);
},
);
}
#[test]
fn ruby_explicit_returns() {
check_metrics::<RubyParser>(
"def foo(x)\n return 0 if x.nil?\n yield x\n return x * 2\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(metric.nexits);
},
);
}
#[test]
fn python_return_and_raise() {
check_metrics::<PythonParser>(
"def parse(s):
if not s:
raise ValueError(\"empty\")
return int(s)",
"foo.py",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn javascript_return_and_throw() {
check_metrics::<JavascriptParser>(
"function parseLength(s) {
if (s === null) throw new Error('null');
return s.length;
}",
"foo.js",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn mozjs_return_and_throw() {
check_metrics::<MozjsParser>(
"function parseLength(s) {
if (s === null) throw new Error('null');
return s.length;
}",
"foo.js",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn typescript_return_and_throw() {
check_metrics::<TypescriptParser>(
"function parseLength(s: string | null): number {
if (s === null) throw new Error('null');
return s.length;
}",
"foo.ts",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn tsx_return_and_throw() {
check_metrics::<TsxParser>(
"function parseLength(s: string | null): number {
if (s === null) throw new Error('null');
return s.length;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn java_return_and_throw() {
check_metrics::<JavaParser>(
"class A {
int parseLength(String s) {
if (s == null) throw new NullPointerException();
return s.length();
}
}",
"foo.java",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn groovy_no_exit() {
check_metrics::<GroovyParser>("int a = 42", "foo.groovy", |metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
});
}
#[test]
fn groovy_simple_function() {
check_metrics::<GroovyParser>(
"int answer() {
return 42
}",
"foo.groovy",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 1.0);
},
);
}
#[test]
fn groovy_return_and_throw() {
check_metrics::<GroovyParser>(
"class A {
int parseLength(String s) {
if (s == null) throw new NullPointerException()
return s.length()
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
},
);
}
#[test]
fn groovy_yield_in_switch_expression() {
check_metrics::<GroovyParser>(
"class A {
int describe(int n) {
return switch (n) {
case 0: yield 100;
default: yield 200;
}
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
},
);
}
#[test]
fn groovy_implicit_return_not_counted() {
check_metrics::<GroovyParser>("int identity(int x) { x }", "foo.groovy", |metric| {
assert_eq!(metric.nexits.exit_sum(), 0.0);
});
}
#[test]
fn cpp_return_and_throw() {
check_metrics::<CppParser>(
"int parseLength(const char* s) {
if (s == nullptr) throw std::invalid_argument(\"null\");
return 0;
}",
"foo.cpp",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 2.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 2.0,
"average": 2.0,
"min": 0.0,
"max": 2.0
}
"###
);
},
);
}
#[test]
fn python_yield_counts_as_exit() {
check_metrics::<PythonParser>(
"def gen():
yield 1
yield 2
return",
"foo.py",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn javascript_yield_counts_as_exit() {
check_metrics::<JavascriptParser>(
"function* gen() {
yield 1;
yield 2;
return;
}",
"foo.js",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn mozjs_yield_counts_as_exit() {
check_metrics::<MozjsParser>(
"function* gen() {
yield 1;
yield 2;
return;
}",
"foo.js",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn typescript_yield_counts_as_exit() {
check_metrics::<TypescriptParser>(
"function* gen(): Generator<number> {
yield 1;
yield 2;
return;
}",
"foo.ts",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn tsx_yield_counts_as_exit() {
check_metrics::<TsxParser>(
"function* gen(): Generator<number> {
yield 1;
yield 2;
return;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn python_yield_forms_count_as_exit() {
check_metrics::<PythonParser>(
"def gen():
yield
yield 1
yield from range(3)",
"foo.py",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn javascript_yield_delegate_counts_as_exit() {
check_metrics::<JavascriptParser>(
"function* gen() {
yield 1;
yield* other();
yield 2;
}",
"foo.js",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn mozjs_yield_delegate_counts_as_exit() {
check_metrics::<MozjsParser>(
"function* gen() {
yield 1;
yield* other();
yield 2;
}",
"foo.js",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn typescript_yield_delegate_counts_as_exit() {
check_metrics::<TypescriptParser>(
"function* gen(): Generator<number> {
yield 1;
yield* other();
yield 2;
}",
"foo.ts",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
#[test]
fn tsx_yield_delegate_counts_as_exit() {
check_metrics::<TsxParser>(
"function* gen(): Generator<number> {
yield 1;
yield* other();
yield 2;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.nexits.exit_sum(), 3.0);
insta::assert_json_snapshot!(
metric.nexits,
@r###"
{
"sum": 3.0,
"average": 3.0,
"min": 0.0,
"max": 3.0
}
"###
);
},
);
}
}