#![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 {
cyclomatic_sum: f64,
cyclomatic: f64,
n: usize,
cyclomatic_max: f64,
cyclomatic_min: f64,
cyclomatic_modified_sum: f64,
cyclomatic_modified: f64,
cyclomatic_modified_max: f64,
cyclomatic_modified_min: f64,
}
impl Default for Stats {
fn default() -> Self {
Self {
cyclomatic_sum: 0.,
cyclomatic: 1.,
n: 1,
cyclomatic_max: 0.,
cyclomatic_min: f64::MAX,
cyclomatic_modified_sum: 0.,
cyclomatic_modified: 1.,
cyclomatic_modified_max: 0.,
cyclomatic_modified_min: f64::MAX,
}
}
}
struct ModifiedStats<'a>(&'a Stats);
impl Serialize for ModifiedStats<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let s = self.0;
let mut st = serializer.serialize_struct("cyclomatic_modified", 4)?;
st.serialize_field("sum", &s.cyclomatic_modified_sum())?;
st.serialize_field("average", &s.cyclomatic_modified_average())?;
st.serialize_field("min", &s.cyclomatic_modified_min())?;
st.serialize_field("max", &s.cyclomatic_modified_max())?;
st.end()
}
}
impl Serialize for Stats {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut st = serializer.serialize_struct("cyclomatic", 5)?;
st.serialize_field("sum", &self.cyclomatic_sum())?;
st.serialize_field("average", &self.cyclomatic_average())?;
st.serialize_field("min", &self.cyclomatic_min())?;
st.serialize_field("max", &self.cyclomatic_max())?;
st.serialize_field("modified", &ModifiedStats(self))?;
st.end()
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"sum: {}, average: {}, min: {}, max: {}, \
modified_sum: {}, modified_average: {}, modified_min: {}, modified_max: {}",
self.cyclomatic_sum(),
self.cyclomatic_average(),
self.cyclomatic_min(),
self.cyclomatic_max(),
self.cyclomatic_modified_sum(),
self.cyclomatic_modified_average(),
self.cyclomatic_modified_min(),
self.cyclomatic_modified_max(),
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.cyclomatic_max = self.cyclomatic_max.max(other.cyclomatic_max);
self.cyclomatic_min = self.cyclomatic_min.min(other.cyclomatic_min);
self.cyclomatic_sum += other.cyclomatic_sum;
self.n += other.n;
self.cyclomatic_modified_max = self
.cyclomatic_modified_max
.max(other.cyclomatic_modified_max);
self.cyclomatic_modified_min = self
.cyclomatic_modified_min
.min(other.cyclomatic_modified_min);
self.cyclomatic_modified_sum += other.cyclomatic_modified_sum;
}
#[must_use]
pub fn cyclomatic(&self) -> f64 {
self.cyclomatic
}
#[must_use]
pub fn cyclomatic_sum(&self) -> f64 {
self.cyclomatic_sum
}
#[must_use]
pub fn cyclomatic_average(&self) -> f64 {
self.cyclomatic_sum() / self.n as f64
}
#[must_use]
pub fn cyclomatic_max(&self) -> f64 {
self.cyclomatic_max
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn cyclomatic_min(&self) -> f64 {
if self.cyclomatic_min == f64::MAX {
0.0
} else {
self.cyclomatic_min
}
}
#[must_use]
pub fn cyclomatic_modified(&self) -> f64 {
self.cyclomatic_modified
}
#[must_use]
pub fn cyclomatic_modified_sum(&self) -> f64 {
self.cyclomatic_modified_sum
}
#[must_use]
pub fn cyclomatic_modified_average(&self) -> f64 {
self.cyclomatic_modified_sum() / self.n as f64
}
#[must_use]
pub fn cyclomatic_modified_max(&self) -> f64 {
self.cyclomatic_modified_max
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn cyclomatic_modified_min(&self) -> f64 {
if self.cyclomatic_modified_min == f64::MAX {
0.0
} else {
self.cyclomatic_modified_min
}
}
#[inline]
pub(crate) fn compute_sum(&mut self) {
self.cyclomatic_sum += self.cyclomatic;
self.cyclomatic_modified_sum += self.cyclomatic_modified;
}
#[inline]
pub(crate) fn compute_minmax(&mut self) {
self.cyclomatic_max = self.cyclomatic_max.max(self.cyclomatic);
self.cyclomatic_min = self.cyclomatic_min.min(self.cyclomatic);
self.cyclomatic_modified_max = self.cyclomatic_modified_max.max(self.cyclomatic_modified);
self.cyclomatic_modified_min = self.cyclomatic_modified_min.min(self.cyclomatic_modified);
self.compute_sum();
}
}
#[doc(hidden)]
pub trait Cyclomatic
where
Self: Checker,
{
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats);
}
impl Cyclomatic for PythonCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Python::*;
match node.kind_id().into() {
If | Elif | For | While | Except | With | Assert | And | Or => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
CaseClause
if crate::metrics::npa::python_case_clause_counts(node, UNDERSCORE as u16) =>
{
stats.cyclomatic += 1.;
}
MatchStatement => {
stats.cyclomatic_modified += 1.;
}
Else if node.parent_grandparent_match(
|parent| parent.kind_id() == ElseClause,
|grand| {
matches!(
grand.kind_id().into(),
ForStatement | WhileStatement | TryStatement
)
},
) =>
{
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
macro_rules! impl_cyclomatic_c_family {
($code:ty, $lang:ident, $ternary:ident, [$($short_circuit:ident),+ $(,)?]) => {
impl Cyclomatic for $code {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use $lang::*;
match node.kind_id().into() {
Case => stats.cyclomatic += 1.,
SwitchStatement => stats.cyclomatic_modified += 1.,
If | For | While | Catch | $ternary $(| $short_circuit)+ => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
};
}
macro_rules! impl_cyclomatic_js_family {
($code:ty, $lang:ident, $opt_chain:ident) => {
impl_cyclomatic_c_family!(
$code,
$lang,
TernaryExpression,
[
AMPAMP,
PIPEPIPE,
QMARKQMARK,
AMPAMPEQ,
PIPEPIPEEQ,
QMARKQMARKEQ,
$opt_chain
]
);
};
}
impl_cyclomatic_js_family!(MozjsCode, Mozjs, OptionalChain);
impl_cyclomatic_js_family!(JavascriptCode, Javascript, OptionalChain);
impl_cyclomatic_js_family!(TypescriptCode, Typescript, QMARKDOT);
impl_cyclomatic_js_family!(TsxCode, Tsx, QMARKDOT);
impl Cyclomatic for RustCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Rust::*;
match node.kind_id().into() {
MatchArm | MatchArm2 => {
let is_bare_wildcard = node.child_by_field_name("pattern").is_some_and(|pat| {
crate::metrics::npa::pattern_is_bare_underscore(&pat, UNDERSCORE as u16)
});
if !is_bare_wildcard {
stats.cyclomatic += 1.;
}
}
MatchExpression => {
stats.cyclomatic_modified += 1.;
}
If | For | While | Loop | TryExpression | AMPAMP | PIPEPIPE => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl_cyclomatic_c_family!(CppCode, Cpp, ConditionalExpression, [AMPAMP, PIPEPIPE]);
macro_rules! impl_cyclomatic_java_like {
($code:ty, $lang:ident, [$($extra:ident),* $(,)?]) => {
impl Cyclomatic for $code {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use $lang::*;
match node.kind_id().into() {
Case => {
stats.cyclomatic += 1.;
}
Switch => {
stats.cyclomatic_modified += 1.;
}
If | For | While | Catch | TernaryExpression | AMPAMP | PIPEPIPE
$(| $extra)* => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
};
}
impl_cyclomatic_java_like!(JavaCode, Java, []);
impl_cyclomatic_java_like!(GroovyCode, Groovy, [Assert, QMARKCOLON]);
impl Cyclomatic for CsharpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Csharp::*;
match node.kind_id().into() {
Case => {
stats.cyclomatic += 1.;
}
SwitchExpressionArm if !csharp_switch_expression_arm_is_bare_discard(node) => {
stats.cyclomatic += 1.;
}
SwitchStatement | SwitchExpression => {
stats.cyclomatic_modified += 1.;
}
IfStatement
| ForStatement
| ForeachStatement
| WhileStatement
| DoStatement
| CatchClause
| ConditionalExpression
| ConditionalAccessExpression
| AMPAMP
| PIPEPIPE
| QMARKQMARK
| QMARKQMARKEQ => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for GoCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Go as G;
match node.kind_id().into() {
G::ExpressionCase | G::TypeCase | G::CommunicationCase => {
stats.cyclomatic += 1.;
}
G::ExpressionSwitchStatement | G::TypeSwitchStatement | G::SelectStatement => {
stats.cyclomatic_modified += 1.;
}
G::IfStatement | G::ForStatement | G::AMPAMP | G::PIPEPIPE => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for PerlCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Perl as P;
match node.kind_id().into() {
P::IfStatement
| P::UnlessStatement
| P::ElsifClause
| P::WhileStatement
| P::UntilStatement
| P::ForStatement1
| P::ForStatement2
| P::WhenSimpleStatement
| P::IfSimpleStatement
| P::UnlessSimpleStatement
| P::WhileSimpleStatement
| P::UntilSimpleStatement
| P::ForSimpleStatement
| P::AMPAMP
| P::PIPEPIPE
| P::SLASHSLASH
| P::AMPAMPEQ
| P::PIPEPIPEEQ
| P::SLASHSLASHEQ
| P::And
| P::Or
| P::TernaryExpression => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for KotlinCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Kotlin::*;
match node.kind_id().into() {
WhenEntry if !kotlin_when_entry_is_else(node) => {
stats.cyclomatic += 1.;
}
WhenExpression => {
stats.cyclomatic_modified += 1.;
}
IfExpression | ForStatement | WhileStatement | DoWhileStatement | CatchBlock
| AMPAMP | PIPEPIPE | QMARKCOLON => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for LuaCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
match node.kind_id().into() {
Lua::IfStatement
| Lua::ElseifStatement
| Lua::ForStatement
| Lua::WhileStatement
| Lua::RepeatStatement
| Lua::And
| Lua::Or => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for PhpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Php::*;
match node.kind_id().into() {
CaseStatement | MatchConditionalExpression => {
stats.cyclomatic += 1.;
}
SwitchStatement | MatchExpression => {
stats.cyclomatic_modified += 1.;
}
IfStatement
| ElseIfClause
| ElseIfClause2
| ForStatement
| ForeachStatement
| WhileStatement
| DoStatement
| ConditionalExpression
| CatchClause
| AMPAMP
| PIPEPIPE
| And
| Or
| Xor
| QMARKQMARK
| QMARKQMARKEQ => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
implement_metric_trait!(Cyclomatic, PreprocCode, CcommentCode);
impl Cyclomatic for RubyCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Ruby as R;
match node.kind_id().into() {
R::When | R::InClause => {
stats.cyclomatic += 1.;
}
R::Case | R::CaseMatch => {
stats.cyclomatic_modified += 1.;
}
R::If
| R::Unless
| R::Elsif
| R::IfModifier
| R::UnlessModifier
| R::While
| R::Until
| R::For
| R::WhileModifier
| R::UntilModifier
| R::Rescue
| R::RescueModifier
| R::RescueModifier2
| R::RescueModifier3
| R::Conditional
| R::AMPAMP
| R::PIPEPIPE
| R::And
| R::Or => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for ElixirCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
use Elixir as E;
match node.kind_id().into() {
E::StabClause => {
stats.cyclomatic += 1.;
}
E::AMPAMP | E::PIPEPIPE | E::And | E::Or => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
E::Call => {
if let Some(target) = node.child_by_field_name("target")
&& target.kind_id() == E::Identifier
&& let Some(name) = target.utf8_text(code)
{
match name {
"if" | "unless" | "for" | "while" => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
"case" | "cond" | "with" | "try" => {
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
_ => {}
}
}
}
fn csharp_switch_expression_arm_is_bare_discard(node: &Node) -> bool {
use Csharp::*;
enum PatternKind {
BareDiscard,
NotDiscard,
}
fn classify_pattern(child: &Node) -> PatternKind {
match child.kind_id().into() {
Discard => PatternKind::BareDiscard,
DeclarationPattern => {
let mut saw_discard = false;
let mut saw_implicit_type = false;
for sub in child.children().filter(Node::is_named) {
match sub.kind_id().into() {
Discard => saw_discard = true,
ImplicitType => saw_implicit_type = true,
_ => return PatternKind::NotDiscard,
}
}
if saw_discard && saw_implicit_type {
PatternKind::BareDiscard
} else {
PatternKind::NotDiscard
}
}
_ => PatternKind::NotDiscard,
}
}
let mut named = node.children().filter(Node::is_named);
let Some(pattern) = named.next() else {
return false;
};
let PatternKind::BareDiscard = classify_pattern(&pattern) else {
return false;
};
!named.any(|c| c.kind_id() == WhenClause)
}
fn kotlin_when_entry_is_else(node: &Node) -> bool {
node.child_by_field_name("condition").is_none()
}
fn bash_case_item_is_bare_wildcard(node: &Node, code: &[u8]) -> bool {
let mut cursor = node.0.walk();
if !cursor.goto_first_child() {
return false;
}
let mut value_count = 0usize;
let mut sole_value_is_star = false;
loop {
if cursor.field_name() == Some("value") {
value_count += 1;
if value_count > 1 {
return false;
}
sole_value_is_star = cursor.node().utf8_text(code).is_ok_and(|s| s.trim() == "*");
}
if !cursor.goto_next_sibling() {
break;
}
}
value_count == 1 && sole_value_is_star
}
impl Cyclomatic for BashCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
match node.kind_id().into() {
Bash::CaseItem | Bash::CaseItem2 if !bash_case_item_is_bare_wildcard(node, code) => {
stats.cyclomatic += 1.;
}
Bash::CaseStatement => {
stats.cyclomatic_modified += 1.;
}
Bash::IfStatement
| Bash::ElifClause
| Bash::ForStatement
| Bash::CStyleForStatement
| Bash::WhileStatement
| Bash::AMPAMP
| Bash::PIPEPIPE => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 1.;
}
_ => {}
}
}
}
impl Cyclomatic for TclCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
match node.kind_id().into() {
Tcl::If
| Tcl::Elseif
| Tcl::Foreach
| Tcl::While
| Tcl::Catch
| Tcl::TernaryExpr
| Tcl::AMPAMP
| Tcl::PIPEPIPE => {
stats.cyclomatic += 1.;
stats.cyclomatic_modified += 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 cyclomatic_empty_file_min_is_zero() {
let stats = Stats::default();
assert_eq!(stats.cyclomatic_min(), 0.0);
assert_eq!(stats.cyclomatic_modified_min(), 0.0);
}
#[test]
fn python_if_else_does_not_overcount_229() {
check_metrics::<PythonParser>(
"def f(x):
if x > 0:
y = 1
else:
y = 2
",
"foo.py",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn python_if_elif_else_chain_229() {
check_metrics::<PythonParser>(
"def f(x):
if x == 1:
return 10
elif x == 2:
return 20
elif x == 3:
return 30
else:
return 0
",
"foo.py",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
},
);
}
#[test]
fn python_for_else_still_counts_229() {
check_metrics::<PythonParser>(
"def f(xs):
for x in xs:
if x < 0:
break
else:
return True
return False
",
"foo.py",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
},
);
}
#[test]
fn python_while_else_still_counts_229() {
check_metrics::<PythonParser>(
"def f(n):
while n > 0:
n -= 1
else:
return True
return False
",
"foo.py",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn python_try_except_else_counts_229() {
check_metrics::<PythonParser>(
"def f():
try:
x = risky()
except ValueError:
x = -1
else:
x = x + 1
return x
",
"foo.py",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn python_simple_function() {
check_metrics::<PythonParser>(
"def f(a, b): # +2 (+1 unit space)
if a and b: # +2 (+1 and)
return 1
if c and d: # +2 (+1 and)
return 1",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0
}
}"###
);
},
);
}
#[test]
fn python_match_two_arm_wildcard() {
check_metrics::<PythonParser>(
"def f(x):
match x:
case 1:
return 'one'
case _:
return 'other'
",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn python_match_guarded_wildcard_counts() {
check_metrics::<PythonParser>(
"def f(x):
match x:
case 1:
return 'one'
case _ if x > 0:
return 'positive'
case _:
return 'other'
",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn python_1_level_nesting() {
check_metrics::<PythonParser>(
"def f(a, b): # +2 (+1 unit space)
if a: # +1
for i in range(b): # +1
return 1",
"foo.py",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn rust_1_level_nesting() {
check_metrics::<RustParser>(
"fn f() { // +2 (+1 unit space)
if true { // +1
match true {
true => println!(\"test\"), // +1
false => println!(\"test\"), // +1
}
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}
"#
);
},
);
}
#[test]
fn rust_match_modified() {
check_metrics::<RustParser>(
"fn f(x: u8) -> &'static str { // standard: +1 (unit) +1 (fn) +2 (arms 1,2) = 4; modified: +1 (unit) +1 (fn) +1 (MatchExpr) = 3
match x {
1 => \"one\",
2 => \"two\",
_ => \"other\",
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn c_switch() {
check_metrics::<CppParser>(
"void f() { // +2 (+1 unit space)
switch (1) {
case 1: // +1
printf(\"one\");
break;
case 2: // +1
printf(\"two\");
break;
case 3: // +1
printf(\"three\");
break;
default:
printf(\"all\");
break;
}
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn c_switch_modified() {
check_metrics::<CppParser>(
"void f() {
switch (x) {
case 1: break;
case 2: break;
case 3: break;
default: break;
}
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn c_real_function() {
check_metrics::<CppParser>(
"int sumOfPrimes(int max) { // +2 (+1 unit space)
int total = 0;
OUT: for (int i = 1; i <= max; ++i) { // +1
for (int j = 2; j < i; ++j) { // +1
if (i % j == 0) { // +1
continue OUT;
}
}
total += i;
}
return total;
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn c_unit_before() {
check_metrics::<CppParser>(
"
int a=42;
if(a==42) //+2(+1 unit space)
{
}
if(a==34) //+1
{
}
int sumOfPrimes(int max) { // +1
int total = 0;
OUT: for (int i = 1; i <= max; ++i) { // +1
for (int j = 2; j < i; ++j) { // +1
if (i % j == 0) { // +1
continue OUT;
}
}
total += i;
}
return total;
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 7.0,
"average": 3.5,
"min": 3.0,
"max": 4.0,
"modified": {
"sum": 7.0,
"average": 3.5,
"min": 3.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn c_unit_after() {
check_metrics::<CppParser>(
"
int sumOfPrimes(int max) { // +1
int total = 0;
OUT: for (int i = 1; i <= max; ++i) { // +1
for (int j = 2; j < i; ++j) { // +1
if (i % j == 0) { // +1
continue OUT;
}
}
total += i;
}
return total;
}
int a=42;
if(a==42) //+2(+1 unit space)
{
}
if(a==34) //+1
{
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 7.0,
"average": 3.5,
"min": 3.0,
"max": 4.0,
"modified": {
"sum": 7.0,
"average": 3.5,
"min": 3.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn java_simple_class() {
check_metrics::<JavaParser>(
"
public class Example { // +2 (+1 unit space)
int a = 10;
boolean b = (a > 5) ? true : false; // +1
boolean c = b && true; // +1
public void m1() { // +1
if (a % 2 == 0) { // +1
b = b || c; // +1
}
}
public void m2() { // +1
while (a > 3) { // +1
m1();
a--;
}
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 9.0,
"average": 2.25,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 9.0,
"average": 2.25,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn java_real_class() {
check_metrics::<JavaParser>(
"
public class Matrix { // +2 (+1 unit space)
private int[][] m = new int[5][5];
public void init() { // +1
for (int i = 0; i < m.length; i++) { // +1
for (int j = 0; j < m[i].length; j++) { // +1
m[i][j] = i * j;
}
}
}
public int compute(int i, int j) { // +1
try {
return m[i][j] / m[j][i];
} catch (ArithmeticException e) { // +1
return -1;
} catch (ArrayIndexOutOfBoundsException e) { // +1
return -2;
}
}
public void print(int result) { // +1
switch (result) {
case -1: // +1
System.out.println(\"Division by zero\");
break;
case -2: // +1
System.out.println(\"Wrong index number\");
break;
default:
System.out.println(\"The result is \" + result);
}
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 11.0,
"average": 2.2,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 10.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn java_switch_modified() {
check_metrics::<JavaParser>(
"public class A {
public void print(int result) {
switch (result) {
case -1:
System.out.println(\"minus one\");
break;
case -2:
System.out.println(\"minus two\");
break;
default:
System.out.println(\"other\");
}
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 1.6666666666666667,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 1.3333333333333333,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn csharp_simple_class() {
check_metrics::<CsharpParser>(
"public class Example {
int a = 10;
bool b = (a > 5) ? true : false;
bool c = b && true;
public void M1() {
if (a % 2 == 0) {
b = b || c;
}
}
public void M2() {
while (a > 3) {
M1();
a--;
}
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 9.0,
"average": 2.25,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 9.0,
"average": 2.25,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn csharp_real_class() {
check_metrics::<CsharpParser>(
"public class Matrix {
private int[,] m = new int[5, 5];
public void Init() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
m[i, j] = i * j;
}
}
}
public int Compute(int i, int j) {
try {
return m[i, j] / m[j, i];
} catch (System.DivideByZeroException) {
return -1;
} catch (System.IndexOutOfRangeException) {
return -2;
}
}
public void Print(int result) {
switch (result) {
case -1:
System.Console.WriteLine(\"Division by zero\");
break;
case -2:
System.Console.WriteLine(\"Wrong index number\");
break;
default:
System.Console.WriteLine(\"The result is \" + result);
break;
}
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 11.0,
"average": 2.2,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 10.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn csharp_anonymous_method() {
check_metrics::<CsharpParser>(
"public class A {
public void M() {
System.Action f = delegate(int x) {
if (x > 0) {
System.Console.WriteLine(x);
}
};
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 1.25,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 5.0,
"average": 1.25,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn csharp_switch_expression_arms() {
check_metrics::<CsharpParser>(
"public class A {
public string Name(int n) =>
n switch {
1 => \"one\",
2 => \"two\",
3 => \"three\",
_ => \"other\"
};
}",
"foo.cs",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 6.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 2.0,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 4.0,
"average": 1.3333333333333333,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn csharp_switch_expression_discard_arm_not_counted() {
check_metrics::<CsharpParser>(
"public class A {
public string Name(int n) =>
n switch {
1 => \"one\",
_ => \"other\"
};
}",
"foo.cs",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
},
);
}
#[test]
fn csharp_switch_expression_var_underscore_not_counted() {
check_metrics::<CsharpParser>(
"public class A {
public string Name(int n) =>
n switch {
1 => \"one\",
var _ => \"other\"
};
}",
"foo.cs",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
},
);
}
#[test]
fn csharp_switch_expression_guarded_discard_still_counts() {
check_metrics::<CsharpParser>(
"public class A {
public string Name(int n) =>
n switch {
1 => \"one\",
_ when n > 10 => \"big\",
_ => \"other\"
};
}",
"foo.cs",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn csharp_switch_expression_typed_discard_still_counts() {
check_metrics::<CsharpParser>(
"public class A {
public string Name(object n) =>
n switch {
1 => \"one\",
int _ => \"int\",
_ => \"other\"
};
}",
"foo.cs",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn csharp_switch_expression_guarded_var_underscore_still_counts() {
check_metrics::<CsharpParser>(
"public class A {
public string Name(int n) =>
n switch {
1 => \"one\",
var _ when n > 10 => \"big\",
_ => \"other\"
};
}",
"foo.cs",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn csharp_switch_modified() {
check_metrics::<CsharpParser>(
"public class A {
public string Describe(int n) {
switch (n) {
case 1:
return \"one\";
case 2:
return \"two\";
default:
return \"other\";
}
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 1.6666666666666667,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 1.3333333333333333,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn csharp_null_coalescing_and_conditional_access() {
check_metrics::<CsharpParser>(
"public class A {
public int? Get(string s, A b) {
return s?.Length ?? b?.Get(null, null) ?? 0;
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 7.0,
"average": 2.3333333333333335,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 7.0,
"average": 2.3333333333333335,
"min": 1.0,
"max": 5.0
}
}"###
);
},
);
}
#[test]
fn javascript_simple_function() {
check_metrics::<JavascriptParser>(
"function f(a, b) { // +2 (+1 unit space)
if (a) { // +1
return a;
} else if (b) { // +1
return b;
}
return 0;
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn javascript_switch() {
check_metrics::<JavascriptParser>(
"function f() { // +2 (+1 unit space)
switch (x) {
case 1: // +1
console.log(\"one\");
break;
case 2: // +1
console.log(\"two\");
break;
case 3: // +1
console.log(\"three\");
break;
default:
console.log(\"other\");
break;
}
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn javascript_switch_modified() {
check_metrics::<JavascriptParser>(
"function f(x) {
switch (x) {
case 1: return 'one';
case 2: return 'two';
case 3: return 'three';
}
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_simple_function() {
check_metrics::<GoParser>(
"package main
func f() {}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0,
"modified": {
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0
}
}"###
);
},
);
}
#[test]
fn go_if_else() {
check_metrics::<GoParser>(
"package main
func f(x bool) { // +2 (+1 unit)
if x { // +1
} else {
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_else_if_chain() {
check_metrics::<GoParser>(
"package main
func f(x int) { // +2 (+1 unit)
if x > 0 { // +1
} else if x < 0 { // +1 (nested if_statement)
} else if x == 0 { // +1 (nested if_statement)
} else {
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn go_for_loop() {
check_metrics::<GoParser>(
"package main
func f() { // +2 (+1 unit)
for i := 0; i < 10; i++ { // +1
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_for_range() {
check_metrics::<GoParser>(
"package main
func f(xs []int) { // +2 (+1 unit)
for _, v := range xs { // +1
_ = v
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_switch() {
check_metrics::<GoParser>(
"package main
func f(x int) { // +2 (+1 unit)
switch x {
case 1: // +1
case 2: // +1
default: // not counted
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_switch_modified() {
check_metrics::<GoParser>(
"package main
func f(x int) {
switch x {
case 1:
println(\"one\")
case 2:
println(\"two\")
case 3:
println(\"three\")
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_type_switch() {
check_metrics::<GoParser>(
"package main
func f(x interface{}) { // +2 (+1 unit)
switch x.(type) {
case int: // +1
case string: // +1
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_select() {
check_metrics::<GoParser>(
"package main
func f(c1, c2 chan int) { // +2 (+1 unit)
select {
case <-c1: // +1
case <-c2: // +1
default: // not counted
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn go_logical_operators() {
check_metrics::<GoParser>(
"package main
func f(a, b, c bool) { // +2 (+1 unit)
if a && b || c { // +1 if, +1 &&, +1 ||
}
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn go_defer_and_go_do_not_count() {
check_metrics::<GoParser>(
"package main
func f() { // +2 (+1 unit)
defer cleanup()
go work()
}",
"foo.go",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0,
"modified": {
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0
}
}"###
);
},
);
}
#[test]
fn java_anonymous_class() {
check_metrics::<JavaParser>(
"
abstract class A { // +2 (+1 unit space)
public abstract boolean m1(int n); // +1
public abstract boolean m2(int n); // +1
}
public class B { // +1
public void test() { // +1
A a = new A() {
public boolean m1(int n) { // +1
if (n % 2 == 0) { // +1
return true;
}
return false;
}
public boolean m2(int n) { // +1
if (n % 5 == 0) { // +1
return true;
}
return false;
}
};
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 10.0,
"average": 1.25,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 10.0,
"average": 1.25,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn java_do_statement_counts_in_cyclomatic() {
check_metrics::<JavaParser>(
"class Parity {
static void f() {
int i = 0;
do { // +1 (via inner `while` keyword)
++i;
} while (i < 10);
}
}",
"foo.java",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 4.0);
assert_eq!(s.cyclomatic_max(), 2.0);
assert_eq!(s.cyclomatic_modified_sum(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 1.3333333333333333,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 4.0,
"average": 1.3333333333333333,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn java_enhanced_for_statement_counts_in_cyclomatic() {
check_metrics::<JavaParser>(
"class Parity {
static void f(int[] xs) {
for (int x : xs) { // +1 (via `for` keyword)
g(x);
}
}
}",
"foo.java",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 4.0);
assert_eq!(s.cyclomatic_max(), 2.0);
assert_eq!(s.cyclomatic_modified_sum(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 1.3333333333333333,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 4.0,
"average": 1.3333333333333333,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn groovy_simple_class() {
check_metrics::<GroovyParser>(
"
class Example {
int a = 10
boolean b = (a > 5) ? true : false
boolean c = b && true
void m1() {
if (a % 2 == 0) {
b = b || c
}
}
void m2() {
while (a > 3) {
m1()
a--
}
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 9.0);
},
);
}
#[test]
fn groovy_nested_control_flow() {
check_metrics::<GroovyParser>(
"void f(int x) {
if (x > 0) {
while (x < 100) {
x = x + 1
}
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
},
);
}
#[test]
fn groovy_switch_with_cases() {
check_metrics::<GroovyParser>(
"void print(int result) {
switch (result) {
case -1:
println 'minus one'
break
case -2:
println 'minus two'
break
default:
println 'other'
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 3.0);
},
);
}
#[test]
fn groovy_try_catch() {
check_metrics::<GroovyParser>(
"void f() {
try {
risky()
} catch (Exception e) {
handle(e)
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
},
);
}
#[test]
fn groovy_closure_body_short_circuit() {
check_metrics::<GroovyParser>(
"def pred = { x -> x > 0 && x < 100 }",
"foo.groovy",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 2.0);
},
);
}
#[test]
fn groovy_assert_adds_branch() {
check_metrics::<GroovyParser>(
"void check(int x) {
assert x > 0
}",
"foo.groovy",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
},
);
}
#[test]
fn groovy_do_statement_counts_in_cyclomatic() {
check_metrics::<GroovyParser>(
"def f() {
int i = 0
do { // +1 (via inner `while` keyword)
++i
} while (i < 10)
}",
"foo.groovy",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 3.0);
assert_eq!(s.cyclomatic_max(), 2.0);
assert_eq!(s.cyclomatic_modified_sum(), 3.0);
},
);
}
#[test]
fn groovy_enhanced_for_statement_counts_in_cyclomatic() {
check_metrics::<GroovyParser>(
"def f(int[] xs) {
for (int x : xs) { // +1 (via `for` keyword)
println(x)
}
}",
"foo.groovy",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 3.0);
assert_eq!(s.cyclomatic_max(), 2.0);
assert_eq!(s.cyclomatic_modified_sum(), 3.0);
},
);
}
#[test]
fn perl_nested_control_flow() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
for my $i (1..10) { # +1 for_statement_2
if ($i % 2) { # +1 if_statement
print $i;
}
}
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}
"#
);
},
);
}
#[test]
fn perl_postfix_conditionals() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
return 1 if $_[0]; # +1 if_simple_statement
return 0 unless $_[1]; # +1 unless_simple_statement
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}
"#
);
},
);
}
#[test]
fn perl_unless_and_until() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
unless ($x) { # +1 unless_statement
print 'a';
}
until ($n == 0) { # +1 until_statement
$n--;
}
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}
"#
);
},
);
}
#[test]
fn perl_logical_operators_and_ternary() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
my $x = $a && $b; # +1 (&&)
my $y = $c || $d; # +1 (||)
my $z = $e // $f; # +1 (//)
my $t = $g ? 1 : 0; # +1 ternary
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0
}
}
"#
);
},
);
}
#[test]
fn perl_word_logical_operators() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
my $x = $a and $b; # +1 (and)
my $y = $c or $d; # +1 (or)
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}
"#
);
},
);
}
#[test]
fn perl_compound_short_circuit_assignment_249() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
my ($x, $y, $z) = @_;
$x ||= 1; # +1 (||=)
$y &&= 2; # +1 (&&=)
$z //= 3; # +1 (//=)
return $x;
}",
"foo.pl",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn perl_foreach_loop() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
foreach my $i (@list) { # +1 for_statement_2
print $i;
}
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(metric.cyclomatic, @r#"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}
"#);
},
);
}
#[test]
fn perl_else_does_not_count_but_elsif_does() {
check_metrics::<PerlParser>(
"sub f { # +1 (unit) +1 (sub)
if ($x) { # +1 if_statement
print 'a';
} elsif ($y) { # +1 elsif_clause
print 'b';
} else {
print 'c';
}
}",
"foo.pl",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}
"#
);
},
);
}
#[test]
fn tsx_simple_function() {
check_metrics::<TsxParser>(
"function f(a: number, b: number) { // +2 (+1 unit space)
if (a > 0) { // +1
return a;
} else if (b > 0) { // +1
return b;
}
return 0;
}",
"foo.tsx",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn typescript_if_else_and_switch() {
check_metrics::<TypescriptParser>(
"function classify(value: number): string {
if (value < 0) { // +1
return 'negative';
} else if (value === 0) { // +1
return 'zero';
}
switch (value) {
case 1: // +1
return 'one';
case 2: // +1
return 'two';
default:
return 'other';
}
}",
"foo.ts",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn typescript_switch_modified() {
check_metrics::<TypescriptParser>(
"function f(x: number): string {
switch (x) {
case 1: return 'one';
case 2: return 'two';
case 3: return 'three';
default: return 'other';
}
}",
"foo.ts",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn mozjs_if_else_and_switch() {
check_metrics::<MozjsParser>(
"function f(x) { // +2 (+1 unit space)
if (x > 0) { // +1
return 1;
} else if (x < 0) { // +1
return -1;
}
switch (x) {
case 0: // +1
return 0;
case 42: // +1
return 42;
default:
return -2;
}
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn mozjs_switch_modified() {
check_metrics::<MozjsParser>(
"function f(x) {
switch (x) {
case 1: return 1;
case 2: return 2;
}
}",
"foo.js",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn kotlin_cyclomatic_mixed() {
check_metrics::<KotlinParser>(
"class Calc {
fun compute(x: Int, y: Int): Int {
if (x > 0) { // +1
for (i in 1..x) { // +1
println(i)
}
}
when (y) {
1 -> println(\"one\") // +1 (WhenEntry)
2 -> println(\"two\") // +1
else -> println(\"?\") // skipped (else is default)
}
val ok = x > 0 && y > 0 // +1
try {
println(x / y)
} catch (e: Exception) { // +1
println(\"err\")
}
return x + y
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 9.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 7.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 9.0,
"average": 3.0,
"min": 1.0,
"max": 7.0,
"modified": {
"sum": 8.0,
"average": 2.6666666666666665,
"min": 1.0,
"max": 6.0
}
}
"###
);
},
);
}
#[test]
fn kotlin_when_modified() {
check_metrics::<KotlinParser>(
"fun describe(x: Int): String {
return when (x) {
1 -> \"one\"
2 -> \"two\"
3 -> \"three\"
else -> \"other\"
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn kotlin_when_else_arm_not_counted() {
check_metrics::<KotlinParser>(
"fun describe(x: Int): String {
return when (x) {
1 -> \"one\"
else -> \"other\"
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
},
);
}
#[test]
fn kotlin_when_multiple_explicit_arms_each_count() {
check_metrics::<KotlinParser>(
"fun describe(x: Int): String {
return when (x) {
1 -> \"one\"
2 -> \"two\"
3 -> \"three\"
else -> \"other\"
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
},
);
}
#[test]
fn lua_1_level_nesting() {
check_metrics::<LuaParser>(
"local function f(t)
for i = 1, #t do
if t[i] > 0 then
return t[i]
end
end
return 0
end",
"foo.lua",
|metric| {
insta::assert_json_snapshot!(metric.cyclomatic, @r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}
"###);
},
);
}
#[test]
fn lua_elseif_branches() {
check_metrics::<LuaParser>(
"local function classify(x)
if x > 0 then
return 1
elseif x < 0 then
return -1
elseif x == 0 then
return 0
else
return 0
end
end",
"foo.lua",
|metric| {
insta::assert_json_snapshot!(metric.cyclomatic, @r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}
"###);
},
);
}
#[test]
fn lua_logical_operators() {
check_metrics::<LuaParser>(
"local function f(a, b, c)
if a and b or c then
return 1
end
return 0
end",
"foo.lua",
|metric| {
insta::assert_json_snapshot!(metric.cyclomatic, @r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}
"###);
},
);
}
#[test]
fn bash_nested_control_flow() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
if [ $1 -eq 1 ]; then
for i in 1 2 3; do
echo $i
done
elif [ $1 -eq 2 ]; then
echo 'two'
fi
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
{".sum" => insta::rounded_redaction(2)}
);
},
);
}
#[test]
fn bash_case_modified() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
case $1 in
one) echo 1 ;;
two) echo 2 ;;
three) echo 3 ;;
esac
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn tcl_1_level_nesting() {
check_metrics::<TclParser>(
"proc f {x} {
while {$x > 0} {
if {$x > 10} {
set x [expr {$x - 1}]
}
}
}",
"foo.tcl",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tcl_elseif_branch() {
check_metrics::<TclParser>(
"proc f {x} {
if {$x > 10} {
puts big
} elseif {$x > 5} {
puts medium
} else {
puts small
}
}",
"foo.tcl",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tcl_logical_operators() {
check_metrics::<TclParser>(
"proc f {x y z} {
if {$x > 0 && $y > 0 || $z > 0} {
puts ok
}
}",
"foo.tcl",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tcl_catch_branch() {
check_metrics::<TclParser>(
"proc f {} {
catch {
expr {1 / 0}
} msg
}",
"foo.tcl",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tcl_try_no_branch() {
check_metrics::<TclParser>(
"proc f {} {
try {
expr {1 / 0}
} finally {
puts done
}
}",
"foo.tcl",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r#"
{
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0,
"modified": {
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0
}
}
"#
);
},
);
}
#[test]
fn mozjs_for_loop() {
check_metrics::<MozjsParser>(
"function f(n) { // +2 (+1 unit)
var s = 0;
for (var i = 0; i < n; i++) { // +1
s += i;
}
return s;
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn mozjs_logical_operators() {
check_metrics::<MozjsParser>(
"function f(a, b, c) { // +2 (+1 unit)
if (a && b || c) { // +1 if, +1 &&, +1 ||
return 1;
}
return 0;
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn javascript_nullish_coalescing_chain_226() {
check_metrics::<JavascriptParser>(
"function pick(a, b, c) { // +1 (entry)
return a ?? b ?? c; // +2 (two `??`)
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn typescript_nullish_coalescing_with_if_226() {
check_metrics::<TypescriptParser>(
"function classify(x: string | null, fallback: string | null): string { // +1 (entry)
if (x === \"y\") return \"yes\"; // +1 (if)
return x ?? fallback ?? \"unknown\"; // +2 (two `??`)
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn tsx_nullish_coalescing_chain_226() {
check_metrics::<TsxParser>(
"function pick(a: number | null, b: number | null, c: number): number { // +1 (entry)
return a ?? b ?? c; // +2 (two `??`)
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn mozjs_nullish_coalescing_chain_226() {
check_metrics::<MozjsParser>(
"function pick(a, b, c) { // +1 (entry)
return a ?? b ?? c; // +2 (two `??`)
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn javascript_nullish_coalescing_assignment_231() {
check_metrics::<JavascriptParser>(
"function pick(o) { // +1 (entry)
o.x ??= 1; // +1 (??=)
o.y ??= 2; // +1 (??=)
return o;
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn typescript_nullish_coalescing_assignment_231() {
check_metrics::<TypescriptParser>(
"function pick(o: { x?: number; y?: number }) { // +1 (entry)
o.x ??= 1; // +1 (??=)
o.y ??= 2; // +1 (??=)
return o;
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn tsx_nullish_coalescing_assignment_231() {
check_metrics::<TsxParser>(
"function pick(o: { x?: number; y?: number }) { // +1 (entry)
o.x ??= 1; // +1 (??=)
o.y ??= 2; // +1 (??=)
return o;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn mozjs_nullish_coalescing_assignment_231() {
check_metrics::<MozjsParser>(
"function pick(o) { // +1 (entry)
o.x ??= 1; // +1 (??=)
o.y ??= 2; // +1 (??=)
return o;
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn javascript_short_circuit_assignments_248() {
check_metrics::<JavascriptParser>(
"function f(x, y, z) { // +1 (entry)
x ??= 1; // +1 (??=)
y &&= 2; // +1 (&&=)
z ||= 3; // +1 (||=)
return x;
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn typescript_short_circuit_assignments_248() {
check_metrics::<TypescriptParser>(
"function f(x: number | null, y: number | null, z: number | null): number { // +1 (entry)
x ??= 1; // +1 (??=)
y &&= 2; // +1 (&&=)
z ||= 3; // +1 (||=)
return x ?? 0; // +1 (??)
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 6.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 5.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0
}
}"###
);
},
);
}
#[test]
fn tsx_short_circuit_assignments_248() {
check_metrics::<TsxParser>(
"function f(x: number | null, y: number | null, z: number | null): number { // +1 (entry)
x ??= 1; // +1 (??=)
y &&= 2; // +1 (&&=)
z ||= 3; // +1 (||=)
return x ?? 0; // +1 (??)
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 6.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 5.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0
}
}"###
);
},
);
}
#[test]
fn mozjs_short_circuit_assignments_248() {
check_metrics::<MozjsParser>(
"function f(x, y, z) { // +1 (entry)
x ??= 1; // +1 (??=)
y &&= 2; // +1 (&&=)
z ||= 3; // +1 (||=)
return x;
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0
}
}"###
);
},
);
}
#[test]
fn javascript_optional_chain_counted_in_cyclomatic_281() {
check_metrics::<JavascriptParser>(
"function pick(a) { // +1 (entry)
return a?.b?.c; // +2 (two `?.`)
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn mozjs_optional_chain_counted_in_cyclomatic_281() {
check_metrics::<MozjsParser>(
"function pick(a) { // +1 (entry)
return a?.b?.c; // +2 (two `?.`)
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn typescript_optional_chain_counted_in_cyclomatic_281() {
check_metrics::<TypescriptParser>(
"function pick(a: any) { // +1 (entry)
return a?.b?.c; // +2 (two `?.`)
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn tsx_optional_chain_counted_in_cyclomatic_281() {
check_metrics::<TsxParser>(
"function pick(a: any) { // +1 (entry)
return a?.b?.c; // +2 (two `?.`)
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn typescript_optional_chain_call_form_counted_281() {
check_metrics::<TypescriptParser>(
"function pick(a: any) { // +1 (entry)
return a?.b?.(); // +2 (member `?.` + call `?.`)
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn tsx_optional_chain_call_form_counted_281() {
check_metrics::<TsxParser>(
"function pick(a: any) { // +1 (entry)
return a?.b?.(); // +2 (member `?.` + call `?.`)
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
},
);
}
#[test]
fn csharp_nullish_coalescing_assignment_231() {
check_metrics::<CsharpParser>(
"public class A {
public int? x;
public int? y;
public void Pick() { // +1 (entry)
x ??= 1; // +1 (??=)
y ??= 2; // +1 (??=)
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 1.6666666666666667,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 5.0,
"average": 1.6666666666666667,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn mozjs_while_loop() {
check_metrics::<MozjsParser>(
"function f(n) { // +2 (+1 unit)
var i = 0;
while (i < n) { // +1
i++;
}
return i;
}",
"foo.js",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn bash_while_loop() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
local n=$1
while [ $n -gt 0 ]; do
echo $n
n=$((n - 1))
done
}",
"foo.sh",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn bash_case_statement() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
case $1 in
start) echo starting ;;
stop) echo stopping ;;
*) echo unknown ;;
esac
}",
"foo.sh",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn bash_case_bare_wildcard_excluded() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
case \"$1\" in
one) echo 1 ;;
*) echo 0 ;;
esac
}",
"foo.sh",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn bash_case_multi_value_with_star_counts() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
case \"$1\" in
a|*) echo any ;;
esac
}",
"foo.sh",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
},
);
}
#[test]
fn bash_simple_function() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
echo hello
}",
"foo.sh",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 2.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 1.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn kotlin_for_loop() {
check_metrics::<KotlinParser>(
"fun sum(n: Int): Int { // +2 (+1 unit)
var s = 0
for (i in 1..n) { // +1
s += i
}
return s
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn kotlin_while_loop() {
check_metrics::<KotlinParser>(
"fun countdown(n: Int): Int { // +2 (+1 unit)
var i = n
while (i > 0) { // +1
i--
}
return i
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn kotlin_logical_operators() {
check_metrics::<KotlinParser>(
"fun check(a: Boolean, b: Boolean, c: Boolean): Boolean { // +2 (+1 unit)
return a && b || c // +1 &&, +1 ||
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn kotlin_elvis_operator_239() {
check_metrics::<KotlinParser>(
"fun pick(a: String?, b: String?, c: String): String { // +2 (+1 unit)
return a ?: b ?: c // +2 (two ?: short-circuits)
}",
"foo.kt",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn typescript_for_loop() {
check_metrics::<TypescriptParser>(
"function sum(n: number): number { // +2 (+1 unit)
let s = 0;
for (let i = 0; i < n; i++) { // +1
s += i;
}
return s;
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn typescript_while_loop() {
check_metrics::<TypescriptParser>(
"function countdown(n: number): number { // +2 (+1 unit)
let i = n;
while (i > 0) { // +1
i--;
}
return i;
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn typescript_logical_operators() {
check_metrics::<TypescriptParser>(
"function check(a: boolean, b: boolean, c: boolean): boolean { // +2 (+1 unit)
return a && b || c; // +1 &&, +1 ||
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn typescript_try_catch() {
check_metrics::<TypescriptParser>(
"function safe(x: number): number { // +2 (+1 unit)
try {
return 1 / x;
} catch (e) { // +1
return 0;
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tsx_for_loop() {
check_metrics::<TsxParser>(
"function sum(n: number): number { // +2 (+1 unit)
let s = 0;
for (let i = 0; i < n; i++) { // +1
s += i;
}
return s;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tsx_while_loop() {
check_metrics::<TsxParser>(
"function countdown(n: number): number { // +2 (+1 unit)
let i = n;
while (i > 0) { // +1
i--;
}
return i;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tsx_logical_operators() {
check_metrics::<TsxParser>(
"function check(a: boolean, b: boolean, c: boolean): boolean { // +2 (+1 unit)
return a && b || c; // +1 &&, +1 ||
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tsx_try_catch() {
check_metrics::<TsxParser>(
"function safe(x: number): number { // +2 (+1 unit)
try {
return 1 / x;
} catch (e) { // +1
return 0;
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 2.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tsx_switch() {
check_metrics::<TsxParser>(
"function describe(x: number): string { // +2 (+1 unit)
switch (x) {
case 1: // +1
return 'one';
case 2: // +1
return 'two';
default:
return 'other';
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn tsx_switch_modified() {
check_metrics::<TsxParser>(
"function f(x: number): string {
switch (x) {
case 1: return 'one';
case 2: return 'two';
default: return 'other';
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn php_1_level_nesting() {
check_metrics::<PhpParser>(
"<?php
function f(int $a, int $b): bool {
if ($a > 0 && $b > 0) {
return true;
}
return false;
}",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn elixir_case_arms() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def classify(x) do\n case x do\n 1 -> :one\n 2 -> :two\n _ -> :other\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 6.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_logical_operators() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x, y) do\n x and y or (x && y) || x\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 7.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 7.0);
},
);
}
#[test]
fn elixir_try_rescue() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def safe do\n try do\n do_it()\n rescue\n ArgumentError -> :bad\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_if_else_counts() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) do\n if x > 0 do\n :pos\n else\n :neg\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_if_without_else_counts() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) do\n if x > 0 do\n :pos\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_unless_counts() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) do\n unless x > 0 do\n :nonpos\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_for_comprehension_counts() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(xs) do\n for x <- xs do\n x * 2\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_anonymous_fn_arms_count() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f do\n multi = fn 0 -> :zero; _ -> :other end\n multi.(0)\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 6.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_cond_arms() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) do\n cond do\n x < 0 -> :neg\n x == 0 -> :zero\n true -> :pos\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 6.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn elixir_with_else_only_counts_else_arms() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) do\n with {:ok, v} <- fetch(x),\n {:ok, w} <- fetch(v) do\n {:ok, w}\n else\n :error -> :nope\n other -> {:bad, other}\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 4.0);
},
);
}
#[test]
fn php_match_expression() {
check_metrics::<PhpParser>(
"<?php
function color(string $c): int {
return match ($c) {
'red' => 1,
'green' => 2,
'blue' => 3,
default => 0,
};
}",
"foo.php",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn php_switch_modified() {
check_metrics::<PhpParser>(
"<?php
function describe(int $n): string {
switch ($n) {
case 1:
return 'one';
case 2:
return 'two';
case 3:
return 'three';
default:
return 'other';
}
}",
"foo.php",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 4.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn php_null_coalescing() {
check_metrics::<PhpParser>(
"<?php
function pick($x, $y) {
$a = $x ?? $y;
$a ??= 0;
return $a;
}",
"foo.php",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
assert_eq!(metric.cyclomatic.cyclomatic_max(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn cpp_nested_switch_modified() {
check_metrics::<CppParser>(
"void f() {
switch (x) {
case 1:
switch (y) {
case 10: break;
case 20: break;
}
break;
case 2: break;
}
}",
"foo.c",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn rust_nested_match_modified() {
check_metrics::<RustParser>(
"fn f(x: u8) -> u8 {
match x {
1 => match x {
10 => 1,
20 => 2,
_ => 0,
},
_ => 0,
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn cpp_empty_switch_modified() {
check_metrics::<CppParser>("void f() { switch (x) {} }", "foo.c", |metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
});
}
#[test]
fn c_nested_loops() {
check_metrics::<CppParser>(
"void f() {
for (int i = 0; i < 10; ++i) { // +1
for (int j = 0; j < 10; ++j) { // +1
g(i, j);
}
}
}",
"foo.c",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 4.0);
assert_eq!(s.cyclomatic_max(), 3.0);
assert_eq!(s.cyclomatic_modified_sum(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn cpp_do_statement_counts_in_cyclomatic() {
check_metrics::<CppParser>(
"void f() {
int i = 0;
do { // +1 (via inner `while` keyword)
++i;
} while (i < 10);
}",
"foo.cpp",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 3.0);
assert_eq!(s.cyclomatic_max(), 2.0);
assert_eq!(s.cyclomatic_modified_sum(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn cpp_for_range_loop_counts_in_cyclomatic() {
check_metrics::<CppParser>(
"void f(std::vector<int> xs) {
for (auto x : xs) { // +1 (via `for` keyword)
g(x);
}
}",
"foo.cpp",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 3.0);
assert_eq!(s.cyclomatic_max(), 2.0);
assert_eq!(s.cyclomatic_modified_sum(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn c_ternary_chain() {
check_metrics::<CppParser>(
"int f(int a, int b, int c) {
return a > 0 ? a : (b > 0 ? b : c); // +2 ternaries (?: each)
}",
"foo.c",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 4.0);
assert_eq!(s.cyclomatic_max(), 3.0);
assert_eq!(s.cyclomatic_modified_sum(), 4.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn c_short_circuit_chain() {
check_metrics::<CppParser>(
"int f(int a, int b, int c, int d) {
if (a && b || c && d) { // 3 logical ops + 1 if = 4
return 1;
}
return 0;
}",
"foo.c",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 6.0);
assert_eq!(s.cyclomatic_max(), 5.0);
assert_eq!(s.cyclomatic_modified_sum(), 6.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0
}
}"###
);
},
);
}
#[test]
fn c_switch_fallthrough() {
check_metrics::<CppParser>(
"int f(int x) {
int r = 0;
switch (x) {
case 1: // +1
case 2: // +1
r = 10;
break;
case 3: // +1
r = 20;
break;
}
return r;
}",
"foo.c",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 5.0);
assert_eq!(s.cyclomatic_modified_sum(), 3.0);
assert!(s.cyclomatic_modified_sum() < s.cyclomatic_sum());
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn c_goto_not_counted() {
check_metrics::<CppParser>(
"int f(int n) {
int i = 0;
retry:
if (i < n) { // +1
++i;
goto retry; // ignored
}
return i;
}",
"foo.c",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_sum(), 3.0);
assert_eq!(s.cyclomatic_modified_sum(), 3.0);
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn cyclomatic_modified_accessors() {
check_metrics::<RustParser>(
"fn f(x: u8) -> u8 {
match x {
1 => 1,
2 => 2,
_ => 0,
}
}",
"foo.rs",
|metric| {
let s = &metric.cyclomatic;
assert_eq!(s.cyclomatic_modified_sum(), 3.0);
assert_eq!(s.cyclomatic_modified_min(), 1.0);
assert_eq!(s.cyclomatic_modified_max(), 2.0);
assert_eq!(s.cyclomatic_modified_average(), 1.5);
assert!(s.cyclomatic_modified_sum() <= s.cyclomatic_sum());
},
);
}
#[test]
fn rust_wildcard_only_match() {
check_metrics::<RustParser>(
"fn f(x: u8) -> &'static str {
match x {
_ => \"fallback\",
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn rust_wildcard_plus_explicit_arms() {
check_metrics::<RustParser>(
"fn f(x: u8) -> &'static str {
match x {
1 => \"one\",
2 => \"two\",
3 => \"three\",
_ => \"other\",
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn rust_some_wildcard_still_counts() {
check_metrics::<RustParser>(
"fn f(x: Option<u8>) -> u8 {
match x {
Some(_) => 1,
None => 0,
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn rust_tuple_wildcard_still_counts() {
check_metrics::<RustParser>(
"fn f(x: (u8, u8)) -> u8 {
match x {
(0, y) => y,
(_, y) => y + 1,
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn rust_guarded_wildcard_still_counts() {
check_metrics::<RustParser>(
"fn f(x: u8) -> &'static str {
match x {
1 => \"one\",
_ if x > 100 => \"big\",
_ => \"other\",
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 5.0,
"average": 2.5,
"min": 1.0,
"max": 4.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn bash_case_empty() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
case $1 in
esac
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 2.0,
"average": 1.0,
"min": 1.0,
"max": 1.0,
"modified": {
"sum": 3.0,
"average": 1.5,
"min": 1.0,
"max": 2.0
}
}"###
);
},
);
}
#[test]
fn bash_nested_case() {
check_metrics::<BashParser>(
"#!/bin/bash
f() {
case $1 in
a)
case $2 in
x) echo ax ;;
y) echo ay ;;
esac
;;
b) echo b ;;
esac
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 6.0,
"average": 3.0,
"min": 1.0,
"max": 5.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn rust_nested_match_with_wildcards() {
check_metrics::<RustParser>(
"fn f(x: u8, y: u8) -> &'static str {
match x {
1 => match y {
1 => \"one-one\",
_ => \"one-other\",
},
_ => \"other\",
}
}",
"foo.rs",
|metric| {
insta::assert_json_snapshot!(
metric.cyclomatic,
@r###"
{
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0,
"modified": {
"sum": 4.0,
"average": 2.0,
"min": 1.0,
"max": 3.0
}
}"###
);
},
);
}
#[test]
fn ruby_nested_branches() {
check_metrics::<RubyParser>(
"def foo(a)\n if a > 0\n while a > 0\n a -= 1\n end\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn ruby_case_when_arms() {
check_metrics::<RubyParser>(
"def foo(x)\n case x\n when 1 then 'one'\n when 2 then 'two'\n when 3 then 'three'\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 5.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
#[test]
fn ruby_ternary_conditional() {
check_metrics::<RubyParser>(
"def foo(x)\n x.positive? ? :pos : :nonpos\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
assert_eq!(metric.cyclomatic.cyclomatic_modified_sum(), 3.0);
},
);
}
#[test]
fn ruby_and_or_keywords() {
check_metrics::<RubyParser>(
"def foo(a, b, c)\n a and b or c\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 4.0);
},
);
}
#[test]
fn cyclomatic_if_elseif_else_chain_cross_language() {
check_metrics::<RubyParser>(
"def classify(x)\n if x > 0\n :pos\n elsif x < 0\n :neg\n else\n :zero\n end\nend\n",
"foo.rb",
|m| {
assert_eq!(m.cyclomatic.cyclomatic_max(), 3.0, "ruby");
},
);
check_metrics::<RustParser>(
"fn classify(x: i32) -> &'static str {\n if x > 0 { \"pos\" } else if x < 0 { \"neg\" } else { \"zero\" }\n}\n",
"foo.rs",
|m| {
assert_eq!(m.cyclomatic.cyclomatic_max(), 3.0, "rust");
},
);
check_metrics::<JavaParser>(
"class C {\n String classify(int x) {\n if (x > 0) return \"pos\";\n else if (x < 0) return \"neg\";\n else return \"zero\";\n }\n}\n",
"Foo.java",
|m| {
assert_eq!(m.cyclomatic.cyclomatic_max(), 3.0, "java");
},
);
}
#[test]
fn cyclomatic_java_groovy_parity_300() {
const JAVA_SRC: &str = "class C {\n\
int decide(int x, int y, int[] xs) {\n\
int r = 0;\n\
if (x > 0 && y > 0) r = 1;\n\
for (int i = 0; i < 3; i++) r++;\n\
while (x > 0) { x--; r++; }\n\
try { r += xs[0]; } catch (Exception e) { r = -1; }\n\
r = (x > 0 || y < 0) ? r : -r;\n\
switch (x) { case 1: r++; break; case 2: r--; break; default: break; }\n\
return r;\n\
}\n\
}\n";
const GROOVY_SRC: &str = "class C {\n\
int decide(int x, int y, int[] xs) {\n\
int r = 0\n\
if (x > 0 && y > 0) r = 1\n\
for (int i = 0; i < 3; i++) r++\n\
while (x > 0) { x--; r++ }\n\
try { r += xs[0] } catch (Exception e) { r = -1 }\n\
r = (x > 0 || y < 0) ? r : -r\n\
switch (x) { case 1: r++; break; case 2: r--; break; default: break }\n\
return r\n\
}\n\
}\n";
check_metrics::<JavaParser>(JAVA_SRC, "Foo.java", |m| {
assert_eq!(m.cyclomatic.cyclomatic_max(), 10.0, "java parity");
assert_eq!(
m.cyclomatic.cyclomatic_modified_max(),
9.0,
"java modified parity"
);
});
check_metrics::<GroovyParser>(GROOVY_SRC, "foo.groovy", |m| {
assert_eq!(m.cyclomatic.cyclomatic_max(), 10.0, "groovy parity");
assert_eq!(
m.cyclomatic.cyclomatic_modified_max(),
9.0,
"groovy modified parity"
);
});
}
#[test]
fn cyclomatic_groovy_assert_arm_300() {
check_metrics::<GroovyParser>("void check(int x) { assert x > 0 }", "foo.groovy", |m| {
assert_eq!(m.cyclomatic.cyclomatic_sum(), 3.0, "groovy assert sum");
assert_eq!(m.cyclomatic.cyclomatic_max(), 2.0, "groovy assert max");
assert_eq!(
m.cyclomatic.cyclomatic_modified_max(),
2.0,
"groovy assert modified max"
);
});
}
#[test]
fn cyclomatic_groovy_elvis_chain_246() {
check_metrics::<GroovyParser>(
"def pick(a, b, c) { return a ?: b ?: c }",
"foo.groovy",
|m| {
assert_eq!(m.cyclomatic.cyclomatic_sum(), 4.0, "groovy elvis sum");
assert_eq!(m.cyclomatic.cyclomatic_max(), 3.0, "groovy elvis max");
assert_eq!(
m.cyclomatic.cyclomatic_modified_max(),
3.0,
"groovy elvis modified max"
);
},
);
}
#[test]
fn ruby_rescue_modifier() {
check_metrics::<RubyParser>(
"def foo\n parse(x) rescue nil\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.cyclomatic.cyclomatic_sum(), 3.0);
insta::assert_json_snapshot!(metric.cyclomatic);
},
);
}
}