#![allow(
clippy::enum_glob_use,
clippy::too_many_lines,
clippy::wildcard_imports
)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use std::fmt;
use crate::checker::Checker;
use crate::macros::implement_metric_trait;
use crate::*;
mod bash;
mod c;
mod cpp;
mod csharp;
mod elixir;
mod go;
mod groovy;
mod irules;
mod java;
mod js_family;
mod kotlin;
mod lua;
mod mozcpp;
mod objc;
mod perl;
mod php;
mod python;
mod ruby;
mod rust;
mod tcl;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Stats {
pub(super) assignments: f64,
assignments_sum: f64,
assignments_min: f64,
assignments_max: f64,
pub(super) branches: f64,
branches_sum: f64,
branches_min: f64,
branches_max: f64,
pub(super) conditions: f64,
conditions_sum: f64,
conditions_min: f64,
conditions_max: f64,
space_count: usize,
}
impl Default for Stats {
fn default() -> Self {
Self {
assignments: 0.,
assignments_sum: 0.,
assignments_min: f64::MAX,
assignments_max: 0.,
branches: 0.,
branches_sum: 0.,
branches_min: f64::MAX,
branches_max: 0.,
conditions: 0.,
conditions_sum: 0.,
conditions_min: f64::MAX,
conditions_max: 0.,
space_count: 1,
}
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"assignments: {}, branches: {}, conditions: {}, magnitude: {}, \
assignments_average: {}, branches_average: {}, conditions_average: {}, \
assignments_min: {}, assignments_max: {}, \
branches_min: {}, branches_max: {}, \
conditions_min: {}, conditions_max: {}",
self.assignments_sum(),
self.branches_sum(),
self.conditions_sum(),
self.magnitude_sum(),
self.assignments_average(),
self.branches_average(),
self.conditions_average(),
self.assignments_min(),
self.assignments_max(),
self.branches_min(),
self.branches_max(),
self.conditions_min(),
self.conditions_max()
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.assignments_min = self.assignments_min.min(other.assignments_min);
self.assignments_max = self.assignments_max.max(other.assignments_max);
self.branches_min = self.branches_min.min(other.branches_min);
self.branches_max = self.branches_max.max(other.branches_max);
self.conditions_min = self.conditions_min.min(other.conditions_min);
self.conditions_max = self.conditions_max.max(other.conditions_max);
self.assignments_sum += other.assignments_sum;
self.branches_sum += other.branches_sum;
self.conditions_sum += other.conditions_sum;
self.space_count += other.space_count;
}
#[must_use]
pub fn assignments(&self) -> u64 {
self.assignments as u64
}
#[must_use]
pub fn assignments_sum(&self) -> u64 {
self.assignments_sum as u64
}
#[must_use]
pub fn assignments_average(&self) -> f64 {
crate::metrics::average(self.assignments_sum() as f64, self.space_count)
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn assignments_min(&self) -> u64 {
if self.assignments_min == f64::MAX {
0
} else {
self.assignments_min as u64
}
}
#[must_use]
pub fn assignments_max(&self) -> u64 {
self.assignments_max as u64
}
#[must_use]
pub fn branches(&self) -> u64 {
self.branches as u64
}
#[must_use]
pub fn branches_sum(&self) -> u64 {
self.branches_sum as u64
}
#[must_use]
pub fn branches_average(&self) -> f64 {
crate::metrics::average(self.branches_sum() as f64, self.space_count)
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn branches_min(&self) -> u64 {
if self.branches_min == f64::MAX {
0
} else {
self.branches_min as u64
}
}
#[must_use]
pub fn branches_max(&self) -> u64 {
self.branches_max as u64
}
#[must_use]
pub fn conditions(&self) -> u64 {
self.conditions as u64
}
#[must_use]
pub fn conditions_sum(&self) -> u64 {
self.conditions_sum as u64
}
#[must_use]
pub fn conditions_average(&self) -> f64 {
crate::metrics::average(self.conditions_sum() as f64, self.space_count)
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn conditions_min(&self) -> u64 {
if self.conditions_min == f64::MAX {
0
} else {
self.conditions_min as u64
}
}
#[must_use]
pub fn conditions_max(&self) -> u64 {
self.conditions_max as u64
}
#[must_use]
pub fn magnitude(&self) -> f64 {
(self.assignments.powi(2) + self.branches.powi(2) + self.conditions.powi(2)).sqrt()
}
#[must_use]
pub fn magnitude_sum(&self) -> f64 {
(self.assignments_sum.powi(2) + self.branches_sum.powi(2) + self.conditions_sum.powi(2))
.sqrt()
}
#[inline]
pub(crate) fn compute_sum(&mut self) {
self.assignments_sum += self.assignments;
self.branches_sum += self.branches;
self.conditions_sum += self.conditions;
}
#[inline]
pub(crate) fn compute_minmax(&mut self) {
self.assignments_min = self.assignments_min.min(self.assignments);
self.assignments_max = self.assignments_max.max(self.assignments);
self.branches_min = self.branches_min.min(self.branches);
self.branches_max = self.branches_max.max(self.branches);
self.conditions_min = self.conditions_min.min(self.conditions);
self.conditions_max = self.conditions_max.max(self.conditions);
self.compute_sum();
}
}
#[doc(hidden)]
pub(crate) trait Abc
where
Self: Checker,
{
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
);
}
pub(super) fn for_each_named_child(
list: &Node,
conditions: &mut f64,
f: fn(&Node, &Node, &mut f64),
) {
let mut cursor = list.cursor();
if cursor.goto_first_child() {
loop {
let child = cursor.node();
if child.is_named() {
f(&child, list, conditions);
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
implement_metric_trait!(Abc, PreprocCode, CcommentCode);
#[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 {
#[cfg(feature = "csharp")]
use crate::test_support::assert_csharp_fixture_spells;
use crate::test_support::{
assert_fixture_spells, ast_has_kind_id, check_func_space_only_shim,
check_metrics_only_shim, child_space, metrics_verbatim,
};
#[cfg(any(
feature = "csharp",
feature = "groovy",
feature = "kotlin",
feature = "php",
feature = "ruby",
feature = "rust",
feature = "typescript",
))]
use crate::traits::ParserTrait;
use super::*;
check_metrics_only_shim!(check_metrics, Abc);
check_func_space_only_shim!(check_func_space, Abc, Cyclomatic);
#[cfg(any(
feature = "c",
feature = "cpp",
feature = "go",
feature = "javascript",
feature = "mozcpp",
feature = "mozjs",
feature = "objc",
feature = "perl",
feature = "typescript",
))]
fn abc_conditions(lang: LANG, src: &str) -> u64 {
metrics_verbatim(
lang,
src.as_bytes(),
MetricsOptions::default().with_only(&[crate::Metric::Abc]),
)
.abc
.conditions_sum()
}
#[cfg(any(
feature = "cpp",
feature = "csharp",
feature = "groovy",
feature = "java",
feature = "perl",
feature = "php",
feature = "typescript",
))]
fn assert_deepest_conditions_match_cyclomatic(space: &crate::FuncSpace, expected: u64) {
let mut deepest = space;
while let Some(child) = deepest.spaces.last() {
deepest = child;
}
let decisions = deepest.metrics.cyclomatic.cyclomatic() - 1;
assert_eq!(decisions, expected);
assert_eq!(deepest.metrics.abc.conditions(), decisions);
}
#[cfg(any(feature = "csharp", feature = "elixir"))]
fn assert_every_member_scores(
container: &crate::FuncSpace,
members: usize,
expected: u64,
why: &str,
) {
assert_eq!(
container.spaces.len(),
members,
"member count changed — the fixture moved, not the metric"
);
for member in &container.spaces {
let name = member.name.as_deref().unwrap_or("?");
assert_eq!(member.metrics.abc.conditions(), expected, "{name}: {why}");
assert_eq!(
member.metrics.abc.conditions(),
member.metrics.cyclomatic.cyclomatic() - 1,
"{name}: §8 parity with the cyclomatic decision count"
);
}
}
#[cfg(any(
feature = "csharp",
feature = "elixir",
feature = "groovy",
feature = "java",
feature = "kotlin",
feature = "python",
feature = "ruby",
feature = "rust",
))]
#[track_caller]
fn assert_members_score(container: &crate::FuncSpace, expected: &[(&str, u64, u64)]) {
assert_eq!(
container.spaces.len(),
expected.len(),
"member count changed — the fixture moved, not the metric"
);
for (name, conditions, cyclomatic) in expected {
let member = child_space(container, name);
assert_eq!(
member.metrics.abc.conditions(),
*conditions,
"{name}: abc.conditions"
);
assert_eq!(
member.metrics.cyclomatic.cyclomatic(),
*cyclomatic,
"{name}: cyclomatic"
);
}
}
#[test]
fn abc_empty_file_min_is_zero() {
let stats = Stats::default();
assert_eq!(stats.assignments_min(), 0);
assert_eq!(stats.branches_min(), 0);
assert_eq!(stats.conditions_min(), 0);
}
#[cfg(feature = "java")]
#[test]
fn java_eq_arm_counts_outside_final_declarations() {
check_metrics::<JavaParser>(
"class A { void m() { int x = 0; x = 1; x = 2; x = 3; } }",
"foo.java",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_eq_arm_skips_final_initializers() {
check_metrics::<JavaParser>(
"class A {
final int X = 1;
@Deprecated private final int Y = 2;
void m() { final int Z = 3; }
}",
"foo.java",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_final_initializer_does_not_suppress_the_assignments_inside_it() {
let cases = [
("void m() { final Runnable r = () -> { x = 1; }; }", 1, 0),
(
"void m() { final Runnable r = () -> { x = 1; y = 2; }; }",
2,
1,
),
("void m() { final Runnable r = () -> x = 1; }", 1, 0),
("void m() { final int[] a = { x = 1 }; }", 1, 0),
("private final Runnable f = () -> { x = 1; };", 1, 0),
("void m() { final int q = 1; x = 1; }", 1, 1),
];
let mut ran = 0;
for (body, expected, before) in cases {
let src = format!("class K {{ int x, y; {body} }}\n");
let assignments = metrics_verbatim(
LANG::Java,
src.as_bytes(),
MetricsOptions::default().with_only(&[crate::Metric::Abc]),
)
.abc
.assignments_sum();
assert_eq!(assignments, expected, "`{body}` (pre-fix {before})");
ran += 1;
}
assert_eq!(ran, cases.len());
assert!(cases.iter().any(|&(_, now, before)| now != before));
}
#[cfg(feature = "java")]
#[test]
fn java_constant_declarations() {
check_metrics::<JavaParser>(
"class A {
private final int X1 = 0, Y1 = 0;
public final float PI = 3.14f;
final static String HELLO = \"Hello,\";
protected String world = \" world!\"; // +1a
public float e = 2.718f; // +1a
private int x2 = 1, y2 = 2; // +2a
void m() {
final int Z1 = 0, Z2 = 0, Z3 = 0;
final float T = 0.0f;
int z1 = 1, z2 = 2, z3 = 3; // +3a
float t = 60.0f; // +1a
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 8,
"branches": 0,
"conditions": 0,
"magnitude": 8.0,
"value": 0.0,
"assignments_average": 2.6666666666666665,
"branches_average": 0.0,
"conditions_average": 0.0,
"assignments_min": 0,
"assignments_max": 4,
"branches_min": 0,
"branches_max": 0,
"conditions_min": 0,
"conditions_max": 0
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_declarations_with_conditions() {
check_metrics::<JavaParser>(
"
boolean a = (1 > 2); // +1a +1c
boolean b = 3 > 4; // +1a +1c
boolean c = (1 > 2) && 3 > 4; // +1a +2c
boolean d = b && (x > 5) || c; // +1a +3c
boolean e = !d; // +1a +1c
boolean f = ((!false)); // +1a +1c
boolean g = !(!(true)); // +1a +1c
boolean h = true; // +1a
boolean i = (false); // +1a
boolean j = (((((true))))); // +1a
boolean k = (((((m()))))); // +1a +1b
boolean l = (((((!m()))))); // +1a +1b +1c
boolean m = (!(!((m())))); // +1a +1b +1c
List<String> n = null; // +1a (< and > used for generic types are not counted as conditions)
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 14,
"branches": 3,
"conditions": 12,
"magnitude": 18.681541692269406,
"value": 18.681541692269406,
"assignments_average": 14.0,
"branches_average": 3.0,
"conditions_average": 12.0,
"assignments_min": 14,
"assignments_max": 14,
"branches_min": 3,
"branches_max": 3,
"conditions_min": 12,
"conditions_max": 12
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_assignments_with_conditions() {
check_metrics::<JavaParser>(
"
a = 2 < 1; // +1a +1c
b = (4 >= 3) && 2 <= 1; // +1a +2c
c = a || (x != 10) && b; // +1a +3c
d = !false; // +1a +1c
e = (!false); // +1a +1c
f = !(false); // +1a +1c
g = (!(((true)))); // +1a +1c
h = ((true)); // +1a
i = !m(); // +1a +1b +1c
j = !((m())); // +1a +1b +1c
k = (!(m())); // +1a +1b +1c
l = ((!(m()))); // +1a +1b +1c
m = !B.<Integer>m(2); // +1a +1b +1c
n = !((B.<Integer>m(4))); // +1a +1b +1c
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 14,
"branches": 6,
"conditions": 16,
"magnitude": 22.090722034374522,
"value": 22.090722034374522,
"assignments_average": 14.0,
"branches_average": 6.0,
"conditions_average": 16.0,
"assignments_min": 14,
"assignments_max": 14,
"branches_min": 6,
"branches_max": 6,
"conditions_min": 16,
"conditions_max": 16
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_methods_arguments_with_conditions() {
check_metrics::<JavaParser>(
"
m1(a); // +1b
m2(a, b); // +1b
m3(true, (false), (((true)))); // +1b
m3(m1(false), m1(true), m1(false)); // +4b
m1(!a); // +1b +1c
m2((((a))), (!b)); // +1b +1c
m3(!(a), b, !!!c); // +1b +2c
m3(a, !b, m2(!a, !m2(!b, !m1(!c)))); // +4b +6c
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 14,
"conditions": 10,
"magnitude": 17.204650534085253,
"value": 17.204650534085253,
"assignments_average": 0.0,
"branches_average": 14.0,
"conditions_average": 10.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 14,
"branches_max": 14,
"conditions_min": 10,
"conditions_max": 10
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_if_single_conditions() {
check_metrics::<JavaParser>(
"
if ( a < 0 ) {} // +1c
if ( ((a != 0)) ) {} // +1c
if ( !(a > 0) ) {} // +1c
if ( !(((a == 0))) ) {} // +1c
if ( b.m1() ) {} // +1b +1c
if ( !b.m1() ) {} // +1b +1c
if ( !!b.m2() ) {} // +1b +1c
if ( (!(b.m1())) ) {} // +1b +1c
if ( (!(!b.m1())) ) {} // +1b +1c
if ( ((b.m2())) ) {} // +1b +1c
if ( ((b.m().m1())) ) {} // +2b +1c
if ( c ) {} // +1c
if ( !c ) {} // +1c
if ( !!!!!!!!!!c ) {} // +1c
if ( (((c))) ) {} // +1c
if ( (((!c))) ) {} // +1c
if ( ((!(c))) ) {} // +1c
if ( true ) {} // +1c
if ( !true ) {} // +1c
if ( ((false)) ) {} // +1c
if ( !(!(false)) ) {} // +1c
if ( !!!false ) {} // +1c
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 8,
"conditions": 22,
"magnitude": 23.40939982143925,
"value": 23.40939982143925,
"assignments_average": 0.0,
"branches_average": 8.0,
"conditions_average": 22.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 8,
"branches_max": 8,
"conditions_min": 22,
"conditions_max": 22
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_if_multiple_conditions() {
check_metrics::<JavaParser>(
"
if ( a || b || c || d ) {} // +4c
if ( a || b && c && d ) {} // +4c
if ( x < y && a == b ) {} // +2c
if ( ((z < (x + y))) ) {} // +1c
if ( a || ((((b))) && c) ) {} // +3c
if ( a && ((((a == b))) && c) ) {} // +3c
if ( a || ((((a == b))) || ((c))) ) {} // +3c
if ( x < y && B.m() ) {} // +1b +2c
if ( x < y && !(((B.m()))) ) {} // +1b +2c
if ( !(x < y) && !B.m() ) {} // +1b +2c
if ( !!!(!!!(a)) && B.m() || // +1b +2c
!B.m() && (((x > 4))) ) {} // +1b +2c
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 5,
"conditions": 30,
"magnitude": 30.4138126514911,
"value": 30.4138126514911,
"assignments_average": 0.0,
"branches_average": 5.0,
"conditions_average": 30.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 5,
"branches_max": 5,
"conditions_min": 30,
"conditions_max": 30
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_while_and_do_while_conditions() {
check_metrics::<JavaParser>(
"
while ( (!(!(!(a)))) ) {} // +1c
while ( b || 1 > 2 ) {} // +2c
while ( x.m() && (((c))) ) {} // +1b +2c
do {} while ( !!!(((!!!a))) ); // +1c
do {} while ( a || (b && c) ); // +3c
do {} while ( !x.m() && 1 > 2 || !true ); // +1b +3c
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 2,
"conditions": 12,
"magnitude": 12.165525060596439,
"value": 12.165525060596439,
"assignments_average": 0.0,
"branches_average": 2.0,
"conditions_average": 12.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 2,
"branches_max": 2,
"conditions_min": 12,
"conditions_max": 12
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_return_with_conditions() {
check_metrics::<JavaParser>(
"class A {
boolean m1() {
return !(z >= 0); // +1c
}
boolean m2() {
return (((!x))); // +1c
}
boolean m3() {
return x && y; // +2c
}
boolean m4() {
return y || (z < 0); // +2c
}
boolean m5() {
return x || y ? // +3c (two unary conditions and one ?)
true : false;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 0,
"conditions": 9,
"magnitude": 9.0,
"value": 0.0,
"assignments_average": 0.0,
"branches_average": 0.0,
"conditions_average": 1.2857142857142858,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 0,
"branches_max": 0,
"conditions_min": 0,
"conditions_max": 3
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_return_without_conditions() {
check_metrics::<JavaParser>(
"class A {
boolean m1() {
return x;
}
boolean m2() {
return (x);
}
boolean m3() {
return y.m(); // +1b
}
boolean m4() {
return false;
}
void m5() {
return;
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 1,
"conditions": 0,
"magnitude": 1.0,
"value": 0.0,
"assignments_average": 0.0,
"branches_average": 0.14285714285714285,
"conditions_average": 0.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 0,
"branches_max": 1,
"conditions_min": 0,
"conditions_max": 0
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_lambda_expressions_return_with_conditions() {
check_metrics::<JavaParser>(
"
Predicate<Boolean> p1 = a -> a; // +1a
Predicate<Boolean> p2 = b -> true; // +1a
Predicate<Boolean> p3 = c -> m(); // +1a
Predicate<Integer> p4 = d -> d > 10; // +1a +1c
Predicate<Boolean> p5 = (e) -> !e; // +1a +1c
Predicate<Boolean> p6 = (f) -> !((!f)); // +1a +1c
Predicate<Boolean> p7 = (g) -> !g && true; // +1a +2c
BiPredicate<Boolean, Boolean> bp1 = (h, i) -> !h && !i; // +1a +2c
BiPredicate<Boolean, Boolean> bp2 = (j, k) -> {
return j || k; // +1a +2c
};
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 9,
"branches": 1,
"conditions": 9,
"magnitude": 12.767145334803704,
"value": 12.767145334803704,
"assignments_average": 9.0,
"branches_average": 1.0,
"conditions_average": 9.0,
"assignments_min": 9,
"assignments_max": 9,
"branches_min": 1,
"branches_max": 1,
"conditions_min": 9,
"conditions_max": 9
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_for_with_variable_declaration() {
check_metrics::<JavaParser>(
"
for ( int i1 = 0; !(!(!(!a))); i1++ ) {} // +2a +1c
for ( int i2 = 0; !B.m(); i2++ ) {} // +2a +1b +1c
for ( int i3 = 0; a || false; i3++ ) {} // +2a +2c
for ( int i4 = 0; a && B.m() ? true : false; i4++ ) {} // +2a +1b +3c
for ( int i5 = 0; true; i5++ ) {} // +2a +1c
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 10,
"branches": 2,
"conditions": 8,
"magnitude": 12.96148139681572,
"value": 12.96148139681572,
"assignments_average": 10.0,
"branches_average": 2.0,
"conditions_average": 8.0,
"assignments_min": 10,
"assignments_max": 10,
"branches_min": 2,
"branches_max": 2,
"conditions_min": 8,
"conditions_max": 8
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_for_without_variable_declaration() {
check_metrics::<JavaParser>(
"class A{
void m1() {
for (i = 0; x < y; i++) {} // +2a +1c
for (i = 0; ((x < y)); i++) {} // +2a +1c
for (i = 0; !(!(x < y)); i++) {} // +2a +1c
for (i = 0; true; i++) {} // +2a +1c
}
void m2() {
for ( ; true; ) {} // +1c
}
void m3() {
for ( ; ; ) {} // +0c — no condition to count (#1276)
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 8,
"branches": 0,
"conditions": 5,
"magnitude": 9.433981132056603,
"value": 0.0,
"assignments_average": 1.6,
"branches_average": 0.0,
"conditions_average": 1.0,
"assignments_min": 0,
"assignments_max": 8,
"branches_min": 0,
"branches_max": 0,
"conditions_min": 0,
"conditions_max": 4
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_empty_for_condition_counts_nothing() {
check_metrics::<JavaParser>(
"class A { void m() { for (;;) { break; } } }",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 0),
);
check_metrics::<JavaParser>(
"class A { void m() { int i; for (i = 0; ; i++) { break; } } }",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 0),
);
}
#[cfg(feature = "java")]
#[test]
fn java_for_condition_survives_a_header_comment() {
check_metrics::<JavaParser>(
"class A { void m(boolean a) { for (; /* n */ a; ) { break; } } }",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<JavaParser>(
"class A { void m(boolean a) { for (; a; ) { break; } } }",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
}
#[cfg(feature = "java")]
#[test]
fn java_ternary_conditions() {
check_metrics::<JavaParser>(
"
a = true; // +1a
b = a ? true : false; // +1a +2c
c = ((((a)))) ? !false : !b; // +1a +4c
d = !this.m() ? !!a : (false); // +1a +1b +3c
e = !(a) && b ? ((c)) : !d; // +1a +4c
if ( this.m() ? a : !this.m() ) {} // +2b +3c
if ( x > 0 ? !(false) : this.m() ) {} // +1b +3c
if ( x > 0 && x != 3 ? !(a) : (!(b)) ) {} // +5c
",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 5,
"branches": 4,
"conditions": 24,
"magnitude": 24.839484696748443,
"value": 24.839484696748443,
"assignments_average": 5.0,
"branches_average": 4.0,
"conditions_average": 24.0,
"assignments_min": 5,
"assignments_max": 5,
"branches_min": 4,
"branches_max": 4,
"conditions_min": 24,
"conditions_max": 24
}
"#
);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_assignments_only() {
check_metrics::<BashParser>(
"f() {
a=1
b=2
c+=3
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 3,
"branches": 0,
"conditions": 0,
"magnitude": 3.0,
"value": 0.0,
"assignments_average": 1.5,
"branches_average": 0.0,
"conditions_average": 0.0,
"assignments_min": 0,
"assignments_max": 3,
"branches_min": 0,
"branches_max": 0,
"conditions_min": 0,
"conditions_max": 0
}
"#
);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_commands_only() {
check_metrics::<BashParser>(
"f() {
echo a
ls
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 2,
"conditions": 0,
"magnitude": 2.0,
"value": 0.0,
"assignments_average": 0.0,
"branches_average": 1.0,
"conditions_average": 0.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 0,
"branches_max": 2,
"conditions_min": 0,
"conditions_max": 0
}
"#
);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_control_flow_counts_conditions() {
check_metrics::<BashParser>(
"f() {
if cmd; then
echo a
elif other; then
echo b
fi
while running; do
echo c
done
case \"$x\" in
a) echo d ;;
*) echo e ;;
esac
}",
"foo.sh",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_conditions_mix() {
check_metrics::<BashParser>(
"f() {
if [[ \"$a\" == \"$b\" ]]; then
echo eq
fi
if [[ \"$x\" != \"$y\" ]]; then
echo ne
fi
if (( $a < $b )); then
echo lt
fi
if [ -z \"$x\" ]; then
echo empty
fi
}",
"foo.sh",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 8);
assert_eq!(metric.abc.branches_sum(), 4);
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 4,
"conditions": 8,
"magnitude": 8.94427190999916,
"value": 0.0,
"assignments_average": 0.0,
"branches_average": 2.0,
"conditions_average": 4.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 0,
"branches_max": 4,
"conditions_min": 0,
"conditions_max": 8
}
"#
);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_redirection_is_not_a_condition() {
check_metrics::<BashParser>(
"f() {\n echo hi > out.txt\n read x < in.txt\n}\n",
"foo.sh",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 2);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_comparison_inside_an_arithmetic_or_test_context_is_a_condition() {
check_metrics::<BashParser>(
"f() {\n if [[ $a > $b ]]; then :; fi\n}\ng() {\n if (( a > b )); then :; fi\n}\n",
"foo.sh",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_arithmetic_ternary_is_a_condition() {
check_metrics::<BashParser>(
"f() {\n local m=$(( a > b ? a : b ))\n}\n",
"foo.sh",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "bash")]
#[test]
fn bash_magnitude() {
check_metrics::<BashParser>(
"f() {
a=1
b=2
if [[ \"$a\" == \"$b\" ]]; then
echo eq
fi
}",
"foo.sh",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 2,
"branches": 1,
"conditions": 2,
"magnitude": 3.0,
"value": 0.0,
"assignments_average": 1.0,
"branches_average": 0.5,
"conditions_average": 1.0,
"assignments_min": 0,
"assignments_max": 2,
"branches_min": 0,
"branches_max": 1,
"conditions_min": 0,
"conditions_max": 2
}
"#
);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_malformed_parenthesized_no_panic() {
check_metrics::<JavaParser>("class A { void m() { if (( }) }", "foo.java", |metric| {
assert_eq!(metric.abc.assignments(), 0);
assert_eq!(metric.abc.branches(), 0);
assert_eq!(metric.abc.conditions(), 0);
assert_eq!(metric.abc.magnitude(), 0.0);
});
}
#[cfg(feature = "java")]
#[test]
fn java_bool_returning_terminal_kinds_count() {
check_metrics::<JavaParser>(
"class Cfg { boolean flag; }
class A {
void m(Object v, boolean[] flags, Cfg cfg) {
if (cfg.flag) { }
if ((boolean) v) { }
if (v instanceof Cfg) { }
if (flags[0]) { }
}
}",
"foo.java",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_generic_declarations_are_not_conditions() {
check_metrics::<JavaParser>(
"class Gen<T extends Comparable<T>> {
<U> U ident(U x) { return x; }
List<T> pick(Map<String, List<T>> m) { return m.get(\"k\"); }
}",
"foo.java",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 1);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_generic_wildcard_is_not_a_condition() {
check_metrics::<JavaParser>(
"class A {
int m(List<? extends Number> xs, Map<String, ? extends Number> ys,
int a, int b) { return a < b ? 1 : 2; }
}",
"foo.java",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_comparison_operators_still_count_alongside_generics() {
check_func_space::<JavaParser, _>(
"class A {
int m(List<String> xs, int a, int b) {
if (a < b) { return 1; }
if (a > b) { return 2; }
return 0;
}
}",
"foo.java",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
}
#[cfg(feature = "java")]
#[test]
fn java_constructor_delegation_is_a_branch() {
check_metrics::<JavaParser>(
"class Sub extends Base {
Sub() { super(1); }
Sub(int a) { this(); }
}",
"foo.java",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
},
);
}
#[cfg(feature = "java")]
#[test]
fn java_constructor_delegation_does_not_double_count_arguments() {
check_metrics::<JavaParser>(
"class Sub extends Base {
Sub() { super(f()); }
}",
"foo.java",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_constructor_initializer_is_a_branch() {
check_metrics::<CsharpParser>(
"class Sub : Base {
Sub() : base(1) { }
Sub(int a) : this() { }
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_primary_constructor_base_call_is_a_branch() {
let src = "class Sub(int x) : Base(x) { }
record R1(int x) : Base(x);
record class R2(int x) : Base(x);
class G<T>(int x) : GBase<T>(x) where T : class { }
class Multi(int x) : Base(x), IBase { }
struct S(int x) : IBase { }
record R3(int x) : Base;";
assert_csharp_fixture_spells(
src,
&[
(
Csharp::PrimaryConstructorBaseType as u16,
2,
"`record` base calls",
),
(Csharp::ArgumentList as u16, 5, "base-call argument lists"),
(Csharp::BaseList2 as u16, 7, "base lists"),
(Csharp::BaseList as u16, 0, "unaliased `base_list` nodes"),
(Csharp::COMMA as u16, 1, "`Multi`'s trailing interface"),
(Csharp::TypeArgumentList as u16, 1, "`G`'s generic base"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
for name in ["Sub", "R1", "R2", "G", "Multi"] {
assert_eq!(
child_space(&space, name).metrics.abc.branches(),
1,
"{name}: the primary-constructor base call is one branch"
);
}
for name in ["S", "R3"] {
assert_eq!(
child_space(&space, name).metrics.abc.branches(),
0,
"{name}: a base type with no argument list is not a call"
);
}
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_base_list_gate_excludes_other_argument_lists() {
let src = "[System.Obsolete(\"why\")]
class A(int x) : Base(x) {
void M(int y) { Helper(y); }
object N() { return new Foo(1); }
int P(System.Collections.Generic.List<int> l, int i) { return l[i]; }
}";
assert_csharp_fixture_spells(
src,
&[
(
Csharp::AttributeArgumentList as u16,
1,
"attribute argument lists",
),
(
Csharp::BracketedArgumentList as u16,
1,
"bracketed argument lists",
),
(Csharp::TypeArgumentList as u16, 1, "type argument lists"),
(Csharp::ArgumentList as u16, 3, "argument lists"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
let class = child_space(&space, "A");
assert_eq!(
class.metrics.abc.branches(),
1,
"`: Base(x)` alone — the attribute's arguments are not a call"
);
assert_eq!(
child_space(class, "M").metrics.abc.branches(),
1,
"`Helper(y)` is one branch, not one per argument list"
);
assert_eq!(
child_space(class, "N").metrics.abc.branches(),
1,
"`new Foo(1)` is one branch, not one per argument list"
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_base_call_and_constructor_initializer_do_not_double_count() {
let src = "class Both(int x) : Base(x) {
public Both(int a, int b) : this(a) { }
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::ConstructorInitializer as u16, 1, "`: this(a)`"),
(Csharp::BaseList2 as u16, 1, "the primary-constructor base"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
let class = child_space(&space, "Both");
assert_eq!(
class.metrics.abc.branches(),
1,
"the class space owns `: Base(x)` and nothing else"
);
assert_eq!(
child_space(class, "Both").metrics.abc.branches(),
1,
"the secondary constructor owns `: this(a)` and nothing else"
);
});
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_constructor_delegation_is_a_branch() {
check_metrics::<KotlinParser>(
"class Sub : Base {
constructor(x: Int) : super(x) { }
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_primary_constructor_superclass_call_is_a_branch() {
check_func_space::<KotlinParser, _>(
"class Sub : Base(1, 2) { }
class Plain : Marker { }
fun make(): Any = object : Base(3) { }
class Classic : Base {
constructor(x: Int) : super(x) { }
}",
"foo.kt",
|space| {
assert_eq!(space.metrics.abc.branches_sum(), 3);
assert_eq!(
child_space(&space, "Sub").metrics.abc.branches(),
1,
"the primary-constructor superclass call is the class space's"
);
assert_eq!(
child_space(&space, "Plain").metrics.abc.branches(),
0,
"a delegation specifier with no argument list is not a call"
);
let make = child_space(&space, "make");
assert_eq!(
make.metrics.abc.branches(),
0,
"`make` owns no branch; `object : Base(3)` is its own space"
);
assert_eq!(
child_space(make, "<anonymous>").metrics.abc.branches(),
1,
"the object expression's superclass call is that space's"
);
let classic = child_space(&space, "Classic");
assert_eq!(
classic.metrics.abc.branches(),
0,
"a secondary constructor's `: super(x)` is not the class's"
);
assert_eq!(
child_space(classic, "<anonymous>").metrics.abc.branches(),
1,
"it belongs to the constructor's own function space"
);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_annotation_arguments_are_not_a_branch() {
let src = r#"@file:Suppress("unused")
@Mark("a")
class Ann : Base(1) {
@get:Mark("b")
val v: Int = 0
}"#;
let parser = KotlinParser::new(
src.as_bytes().to_vec(),
&std::path::PathBuf::from("foo.kt"),
None,
);
assert_eq!(
parser
.root()
.preorder()
.filter(|n| n.kind_id() == Kotlin::ConstructorInvocation as u16)
.count(),
4,
"fixture must keep three annotations plus the superclass call"
);
assert!(ast_has_kind_id(&parser, Kotlin::FileAnnotation as u16));
check_metrics::<KotlinParser>(src, "foo.kt", |metric| {
assert_eq!(metric.abc.branches_sum(), 1);
});
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_constructor_delegation_is_a_branch() {
check_metrics::<GroovyParser>(
"class Sub extends Base {
Sub() { super(1) }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
},
);
}
#[cfg(any(feature = "groovy", feature = "java", feature = "kotlin"))]
fn assert_enum_branches<P: MetricSuite>(
src: &str,
path: &str,
kinds: &[(u16, usize, &str)],
branches: u64,
) {
assert_fixture_spells::<P>(src, path, kinds);
check_func_space::<P, _>(src, path, |space| {
assert_eq!(space.metrics.abc.branches_sum(), branches);
});
}
#[cfg(feature = "java")]
#[test]
fn java_enum_constant_with_arguments_is_a_branch() {
let src = "class Outer {
static int f() { return 1; }
enum E {
A(1),
@SuppressWarnings(\"x\") B,
K(2),
D(f());
E(int v) {}
}
}";
assert_enum_branches::<JavaParser>(
src,
"foo.java",
&[
(Java::EnumConstant as u16, 4, "enum constants"),
(Java::ArgumentList as u16, 4, "argument lists"),
(
Java::AnnotationArgumentList as u16,
1,
"`@SuppressWarnings`'s argument list",
),
(Java::MethodInvocation as u16, 1, "`D`'s nested `f()` call"),
],
4,
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_enum_entry_with_arguments_is_a_branch() {
let src = "fun g(): Int = 1
enum class E(val v: Int = 0) { A(1), B, C(g()) }
enum class P { X, Y }";
assert_enum_branches::<KotlinParser>(
src,
"foo.kt",
&[
(Kotlin::EnumEntry as u16, 5, "enum entries"),
(Kotlin::ValueArguments as u16, 3, "argument lists"),
(Kotlin::CallExpression as u16, 1, "`C`'s nested `g()` call"),
],
3,
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_enum_constant_with_arguments_is_a_branch() {
let src = "class Outer { static int f() { 1 } }
enum E { A(1), B, D(Outer.f()) }";
assert_enum_branches::<GroovyParser>(
src,
"foo.groovy",
&[
(Groovy::EnumConstant as u16, 3, "enum constants"),
(Groovy::ArgumentList as u16, 3, "argument lists"),
(
Groovy::MethodInvocation as u16,
1,
"`D`'s nested `Outer.f()` call",
),
],
3,
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_no_abc() {
check_metrics::<GroovyParser>(
"// just a comment, no executable code",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_single_assignment() {
check_metrics::<GroovyParser>("int x = 1", "foo.groovy", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
});
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_assignments() {
check_metrics::<GroovyParser>(
"void f() {
int a = 1
int b = 2
a = 3
b = 4
a += 1
b -= 1
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 6);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_branches() {
check_metrics::<GroovyParser>(
"void f() {
doStuff()
helper.invoke()
new Worker()
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_conditions_in_if() {
check_metrics::<GroovyParser>(
"void f(int a) {
if (a == 0) { println(a) }
if (a >= 1) { println(a) }
if (a != 2) { println(a) }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_branches_with_juxt_call() {
check_metrics::<GroovyParser>(
"void f() {
println 'hi'
println 'bye'
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_try_catch_conditions() {
check_metrics::<GroovyParser>(
"void f() {
try {
risky()
} catch (Exception e) {
handle(e)
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_ternary_conditions() {
check_metrics::<GroovyParser>(
"void f(int x) {
def y = x > 0 ? 1 : 2
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_constant_excluded_from_assignments() {
check_metrics::<GroovyParser>(
"class A {
final int CONST = 42
int field = 0
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_malformed_parenthesized_no_panic() {
check_metrics::<GroovyParser>("def x = (((", "foo.groovy", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
});
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_bool_returning_terminal_kinds_count() {
check_metrics::<GroovyParser>(
"class Cfg { boolean flag }
class A {
void m(Object v, Cfg cfg) {
if (cfg.flag) { }
if ((boolean) v) { }
if (v as Boolean) { }
if (v instanceof Cfg) { }
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_generic_declarations_are_not_conditions() {
check_metrics::<GroovyParser>(
"class Gen<T extends Comparable<T>> {
List<T> pick(Map<String, List<T>> m) { m.get(\"k\") }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 1);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_generic_wildcard_is_not_a_condition() {
check_metrics::<GroovyParser>(
"class A {
int m(List<? extends Number> xs, Map<String, ? extends Number> ys,
int a, int b) { a < b ? 1 : 2 }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_method_type_parameters_are_not_conditions() {
check_metrics::<GroovyParser>(
"class A {
def <U> U ident(U x, int a, int b) { a < b ? x : null }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_elvis_counts_one_condition_per_token() {
check_func_space::<GroovyParser, _>(
"class K { def f(a, c) { return a ?: c } }",
"foo.groovy",
|space| assert_deepest_conditions_match_cyclomatic(&space, 1),
);
check_func_space::<GroovyParser, _>(
"class K { def g(a, b, c) { return a ?: b ?: c } }",
"foo.groovy",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_comparison_operators_still_count_alongside_generics() {
check_func_space::<GroovyParser, _>(
"class A {
int m(List<String> xs, int a, int b) {
if (a < b) { return 1 }
if (a > b) { return 2 }
return 0
}
}",
"foo.groovy",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_if_multiple_conditions() {
check_metrics::<GroovyParser>(
"void f(boolean a, boolean b, boolean c) {
if (a || b || c) { println(a) }
if (a && b && c) { println(a) }
if (!a && !b) { println(a) }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 8);
assert_eq!(metric.abc.branches_sum(), 3);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_while_and_do_while_conditions() {
check_metrics::<GroovyParser>(
"void f(boolean a, boolean b) {
while (a) {
a = false
}
do {
b = !b
} while (b)
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_if_while_boolean_literal_condition() {
check_metrics::<GroovyParser>(
"void m() {
if (true) { println 'a' }
if (false) { println 'b' }
while (true) { break }
int t = true ? 1 : 0
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 5);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_return_unary_boolean_literal() {
check_metrics::<GroovyParser>(
"boolean f() {
return !true
}
boolean g() {
return !false
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_short_circuit_with_boolean_literal_operand() {
check_metrics::<GroovyParser>(
"void m(boolean x) {
if (x && true) { println 'a' }
if (false || x) { println 'b' }
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_methods_arguments_with_conditions() {
check_metrics::<GroovyParser>(
"void f(boolean a, boolean b, boolean c) {
m1(a)
m1(!a)
m2(!a, !b)
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_return_with_conditions() {
check_metrics::<GroovyParser>(
"boolean f(boolean a) {
return (a)
}
boolean g(boolean a) {
return !a
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_for_with_variable_declaration() {
check_metrics::<GroovyParser>(
"void f() {
for (int i = 0; i < 10; i++) {
println(i)
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_for_with_bare_identifier_condition() {
check_metrics::<GroovyParser>(
"void f(boolean go) {
for (int i = 0; go; i++) {
println(i)
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_for_with_empty_initializer_counts_the_condition() {
check_metrics::<GroovyParser>(
"void f(boolean go) {
int i = 0
for (; go; i++) {
println(i)
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_empty_for_condition_counts_nothing() {
check_metrics::<GroovyParser>("void f() { for (;;) { break } }", "foo.groovy", |metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
});
check_metrics::<GroovyParser>(
"void f() { for (int i = 0; ; i++) { break } }",
"foo.groovy",
|metric| assert_eq!(metric.abc.conditions_sum(), 0),
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_for_condition_survives_a_header_comment() {
check_metrics::<GroovyParser>(
"void f(boolean go) { for (; /* n */ go; ) { break } }",
"foo.groovy",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<GroovyParser>(
"void f(boolean go) { for (; go; ) { break } }",
"foo.groovy",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_for_with_negated_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(bool done) {
for (int i = 0; !done; i++) { System.Console.WriteLine(i); }
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 1);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_eq_arm_counts_outside_final_declarations() {
check_metrics::<GroovyParser>(
"void f(int x) {
x = 42
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_final_field_initializer_does_not_suppress_the_closure_body() {
check_metrics::<GroovyParser>(
"class K {
int x
final Closure c = { x = 1 }
Closure d = { x = 2 }
final int q = 3
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
},
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_final_local_is_an_error_at_the_pinned_grammar() {
let source = b"class K { int x; def m() { final int a = 0; x = 1 } }";
let parser = GroovyParser::new(
source.to_vec(),
&std::path::PathBuf::from("foo.groovy"),
None,
);
assert!(
ast_has_kind_id(&parser, u16::MAX),
"a `final` local should still parse to an ERROR node; if the grammar \
now accepts it, re-derive the local half of the predicate",
);
check_metrics::<GroovyParser>(
"class K { int x; def m() { final int a = 0; x = 1 } }",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_const_initializer_shapes() {
check_metrics::<CsharpParser>(
"class K {
int x;
private const int Q = 1;
public static readonly int R = 2;
void M() { const int q = 3; System.Action a = () => { x = 4; }; }
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_constant_declarations() {
check_metrics::<CsharpParser>(
"class A {
private const int X1 = 0, Y1 = 0;
public const float PI = 3.14f;
const string HELLO = \"Hello,\";
protected string world = \" world!\";
public float e = 2.718f;
private int x2 = 1, y2 = 2;
void M() {
const int Z1 = 0, Z2 = 0, Z3 = 0;
const float T = 0.0f;
int z1 = 1, z2 = 2, z3 = 3;
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_declarations_with_conditions() {
check_metrics::<CsharpParser>(
"class A {
bool a = (1 == 2);
bool b = (1 < 2);
bool c = !true;
bool d = !false;
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_assignments_with_conditions() {
check_metrics::<CsharpParser>(
"class A {
void M() {
int a = 0;
a += 1;
a -= 2;
a *= 3;
a /= 4;
a %= 5;
a++;
a--;
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_methods_arguments_with_conditions() {
check_metrics::<CsharpParser>(
"class A {
void M(int x, int y) {
F(x == y, x < y, !x.Equals(y));
}
void F(bool a, bool b, bool c) {}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_if_single_conditions() {
check_metrics::<CsharpParser>(
"class A {
void M(int x) {
if (x > 0) { System.Console.WriteLine(\"a\"); }
if (x < 0) { System.Console.WriteLine(\"b\"); }
if (x == 0) { System.Console.WriteLine(\"c\"); }
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_if_multiple_conditions() {
check_metrics::<CsharpParser>(
"class A {
void M(int x, int y) {
if (x > 0 && y > 0) { System.Console.WriteLine(\"a\"); }
if (x < 0 || y < 0) { System.Console.WriteLine(\"b\"); }
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_while_and_do_while_conditions() {
check_metrics::<CsharpParser>(
"class A {
void M(int x) {
while (x > 0) { x--; }
do { x++; } while (x < 10);
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_return_with_conditions() {
check_metrics::<CsharpParser>(
"class A {
bool M(int x) {
return (x > 0);
}
bool N(int x) {
return !(x < 0);
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_switch_expression_arm_counts_condition() {
check_metrics::<CsharpParser>(
"class A {
int M(int x) {
return x switch { 1 => 10, 2 => 20, _ => 0 };
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(all(feature = "csharp", feature = "java"))]
#[test]
fn csharp_java_switch_arm_abc_parity() {
check_metrics::<CsharpParser>(
"class A {
int M(int x) {
return x switch { 1 => 10, 2 => 20 };
}
}",
"foo.cs",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
check_metrics::<JavaParser>(
"class A {
int m(int x) {
return switch (x) { case 1 -> 10; case 2 -> 20; };
}
}",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "java")]
#[test]
fn java_switch_default_not_a_condition() {
check_metrics::<JavaParser>(
"class A {
int m(int x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}
}",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
check_metrics::<JavaParser>(
"class A {
int m(int x) {
return switch (x) { case 1 -> 1; case 2 -> 2; default -> 0; };
}
}",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "java")]
#[test]
fn java_switch_without_default_counts_all_cases() {
check_metrics::<JavaParser>(
"class A {
int m(int x) {
switch (x) { case 1: return 1; case 2: return 2; }
return -1;
}
}",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_switch_default_not_a_condition() {
check_metrics::<CsharpParser>(
"class A {
int M(int x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}
}",
"foo.cs",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_goto_case_is_not_a_condition() {
let src = "class A {
int ctl(int x) { switch (x) { case 1: return 1; case 2: return 2; default: return 0; } }
int jmp(int x) { switch (x) { case 1: goto case 2; case 2: return 2; default: return 0; } }
int dfl(int x) { switch (x) { case 1: goto default; case 2: return 2; default: return 0; } }
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::Case as u16, 7, "`case` tokens (6 arms + 1 jump)"),
(Csharp::SwitchSection as u16, 9, "switch arms"),
(Csharp::GotoStatement as u16, 2, "`goto` statements"),
(
Csharp::Default as u16,
4,
"`default` tokens (3 arms + 1 jump)",
),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_every_member_scores(
&space.spaces[0],
3,
2,
"one condition per `case` arm, none for a `goto` into one",
);
});
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_switch_default_not_a_condition() {
check_metrics::<CppParser>(
"void f(int x) {
switch (x) { case 1: return; case 2: return; default: return; }
}",
"foo.cpp",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "objc")]
#[test]
fn objc_abc() {
check_metrics::<ObjcParser>(
"@implementation Foo\n\
- (int)bar:(int)n {\n\
int total = 0;\n\
for (int i = 0; i < n; i++) {\n\
total = total + [self valueAt:i];\n\
}\n\
@try {\n\
[self risky];\n\
} @catch (NSException *e) {\n\
total = -1;\n\
}\n\
if (total >= 0) {\n\
return total;\n\
}\n\
return 0;\n\
}\n\
@end\n",
"foo.m",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 5);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "objc")]
#[test]
fn objc_abc_conditions() {
check_metrics::<ObjcParser>(
"@implementation Foo\n\
- (int)g:(int *)p {\n\
while (p != 0 && *p > 0) {\n\
p++;\n\
}\n\
do {\n\
p--;\n\
} while (*p < 9);\n\
return *p == 0;\n\
}\n\
@end\n",
"foo.m",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "objc")]
#[test]
fn objc_abc_message_send_unary_condition() {
check_metrics::<ObjcParser>(
"@implementation Foo\n\
- (void)bar:(int)a {\n\
[self use:!a];\n\
cFunc(!a);\n\
}\n\
@end\n",
"foo.m",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "objc")]
#[test]
fn objc_message_send_is_a_bool_terminal_in_condition_slots() {
let cases = [
("if ([self ok]) {}", 1, 1),
("while (![self ok]) {}", 1, 1),
("do {} while ([self ok]);", 1, 1),
("for (; [self ok]; ) {}", 1, 1),
("x = [self ok] ? 1 : 2;", 2, 1),
("if ([self ok] && [o ok]) {}", 2, 2),
("return [self ok];", 0, 1),
("[self use:[self ok]];", 0, 2),
("[self use:![self ok]];", 1, 2),
];
let mut ran = 0;
for (body, conditions, branches) in cases {
let src = format!("@implementation Foo\n- (int)bar {{\n {body}\n}}\n@end\n");
let abc = metrics_verbatim(
LANG::Objc,
src.as_bytes(),
MetricsOptions::default().with_only(&[crate::Metric::Abc]),
)
.abc;
assert_eq!(abc.conditions_sum(), conditions, "`{body}` conditions");
assert_eq!(abc.branches_sum(), branches, "`{body}` branches");
ran += 1;
}
assert_eq!(ran, cases.len());
assert!(cases.iter().any(|&(_, c, _)| c == 0));
assert!(cases.iter().any(|&(_, c, _)| c == 2));
}
#[cfg(feature = "objc")]
#[test]
fn objc_message_send_condition_agrees_with_c_call() {
for (send, call) in [
("if ([a ok]) {}", "if (ok()) {}"),
("for (; [a ok]; ) {}", "for (; ok(); ) {}"),
("x = [a ok] ? 1 : 2;", "x = ok() ? 1 : 2;"),
] {
let wrap =
|body: &str| format!("@implementation Foo\n- (int)bar {{\n {body}\n}}\n@end\n");
let reference = abc_conditions(LANG::Objc, &wrap(call));
assert!(reference >= 1, "`{call}` must count at least the slot");
assert_eq!(
abc_conditions(LANG::Objc, &wrap(send)),
reference,
"`{send}`"
);
}
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_switch_default_not_a_condition() {
check_metrics::<GroovyParser>(
"class A {
int m(int x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0 }
}
}",
"foo.groovy",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "javascript")]
#[test]
fn js_switch_default_not_a_condition() {
check_metrics::<JavascriptParser>(
"function f(x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}",
"foo.js",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "typescript")]
#[test]
fn ts_switch_default_not_a_condition() {
check_metrics::<TypescriptParser>(
"function f(x: number): number {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}",
"foo.ts",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(all(feature = "cpp", feature = "csharp", feature = "java"))]
#[test]
fn java_csharp_cpp_switch_default_abc_parity() {
check_metrics::<JavaParser>(
"class A {
int m(int x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}
}",
"foo.java",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
check_metrics::<CsharpParser>(
"class A {
int M(int x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}
}",
"foo.cs",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
check_metrics::<CppParser>(
"void f(int x) {
switch (x) { case 1: return; case 2: return; default: return; }
}",
"foo.cpp",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(all(feature = "cpp", feature = "csharp", feature = "java"))]
#[test]
fn java_csharp_cpp_switch_default_cyclomatic_parity() {
check_func_space::<JavaParser, _>(
"class A {
int m(int x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}
}",
"foo.java",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
check_func_space::<CsharpParser, _>(
"class A {
int M(int x) {
switch (x) { case 1: return 1; case 2: return 2; default: return 0; }
}
}",
"foo.cs",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
check_func_space::<CppParser, _>(
"void f(int x) {
switch (x) { case 1: return; case 2: return; default: return; }
}",
"foo.cpp",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
}
#[cfg(feature = "php")]
#[test]
fn php_switch_default_not_a_condition() {
check_func_space::<PhpParser, _>(
"<?php
function f($x) {
switch ($x) {
case 1: return 1;
case 2: return 2;
default: return 0;
}
}",
"foo.php",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
}
#[cfg(feature = "php")]
#[test]
fn php_match_default_not_a_condition() {
check_func_space::<PhpParser, _>(
"<?php
function g($x) {
return match ($x) {
1 => \"a\",
2 => \"b\",
default => \"z\",
};
}",
"foo.php",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_if_bare_identifier_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(bool x) {
if (x) { System.Console.WriteLine(\"a\"); }
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_while_bare_identifier_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(bool x) {
while (x) { x = false; }
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_do_while_bare_identifier_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(bool x) {
do { x = true; } while (x);
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_if_unary_not_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(bool x) {
if (!x) { System.Console.WriteLine(\"a\"); }
}
bool N(bool x) {
return !x;
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_if_double_parenthesized_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(bool x) {
if ((x)) { System.Console.WriteLine(\"a\"); }
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_bool_returning_terminal_kinds_count() {
check_metrics::<CsharpParser>(
"using System.Threading.Tasks;
class A {
async Task M(object v, bool[] flags, Cfg cfg, C c) {
if (cfg.flag) { }
if (await c.Check()) { }
if ((bool)v) { }
if (v is not null) { }
if (flags[0]) { }
}
}
class Cfg { public bool flag; }
class C { public Task<bool> Check() => null; }",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 5);
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 1);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_if_method_call_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(string s) {
if (s.StartsWith(\"x\")) { System.Console.WriteLine(\"a\"); }
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_if_while_boolean_literal_condition() {
check_metrics::<CsharpParser>(
"class A {
void M() {
if (true) { System.Console.WriteLine(\"a\"); }
if (false) { System.Console.WriteLine(\"b\"); }
while (true) { break; }
do { break; } while (false);
int t = true ? 1 : 0;
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 6);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_short_circuit_with_boolean_literal_operand() {
check_metrics::<CsharpParser>(
"class A {
void M(bool x) {
if (x && true) { System.Console.WriteLine(\"a\"); }
if (false || x) { System.Console.WriteLine(\"b\"); }
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.assignments_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_return_without_conditions() {
check_metrics::<CsharpParser>(
"class A {
int M() { return 42; }
string N() { return \"hi\"; }
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_lambda_expressions_return_with_conditions() {
check_metrics::<CsharpParser>(
"class A {
public void M() {
System.Func<int, bool> f = x => (x > 0);
System.Func<int, bool> g = x => !(x < 0);
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_for_with_variable_declaration() {
check_metrics::<CsharpParser>(
"class A {
void M() {
for (int i = 0; i < 10; i++) {
System.Console.WriteLine(i);
}
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_for_without_variable_declaration() {
check_metrics::<CsharpParser>(
"class A {
void M() {
int i;
for (i = 0; i < 10; i++) {
System.Console.WriteLine(i);
}
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_for_identifier_condition() {
check_metrics::<CsharpParser>(
"class A {
void M(bool ready) {
for (; ready ;) { }
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 0,
"conditions": 1,
"magnitude": 1.0,
"value": 0.0,
"assignments_average": 0.0,
"branches_average": 0.0,
"conditions_average": 0.3333333333333333,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 0,
"branches_max": 0,
"conditions_min": 0,
"conditions_max": 1
}
"#
);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_for_invocation_condition() {
check_metrics::<CsharpParser>(
"class A {
bool Ok() { return true; }
void M() {
for (; Ok() ;) { }
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 1,
"conditions": 1,
"magnitude": 1.4142135623730951,
"value": 0.0,
"assignments_average": 0.0,
"branches_average": 0.25,
"conditions_average": 0.25,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 0,
"branches_max": 1,
"conditions_min": 0,
"conditions_max": 1
}
"#
);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_for_boolean_literal_condition() {
check_metrics::<CsharpParser>(
"class A {
void M() {
for (; true ;) { }
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_for_empty_condition() {
check_metrics::<CsharpParser>(
"class A {
void M() {
for (; ;) { }
}
}",
"foo.cs",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r#"
{
"assignments": 0,
"branches": 0,
"conditions": 0,
"magnitude": 0.0,
"value": 0.0,
"assignments_average": 0.0,
"branches_average": 0.0,
"conditions_average": 0.0,
"assignments_min": 0,
"assignments_max": 0,
"branches_min": 0,
"branches_max": 0,
"conditions_min": 0,
"conditions_max": 0
}
"#
);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_ternary_conditions() {
check_metrics::<CsharpParser>(
"class A {
int Sign(int x) {
return (x > 0) ? 1 : (x < 0 ? -1 : 0);
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_malformed_parenthesized_no_panic() {
check_metrics::<CsharpParser>("class A { void M() { if (( }) }", "foo.cs", |metric| {
assert_eq!(metric.abc.assignments(), 0);
assert_eq!(metric.abc.branches(), 0);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_function_pointer_type_no_double_count() {
check_metrics::<CsharpParser>(
"unsafe class A {
public delegate*<int, int, int> Adder;
public delegate*<string, void> Logger;
}",
"foo.cs",
|metric| {
assert_eq!(
metric.abc.conditions(),
0,
"function-pointer-type angle brackets must not count"
);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_generic_type_args_no_double_count() {
check_metrics::<CsharpParser>(
"class A {
void M(System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<int>> d) {
System.Console.WriteLine(d);
}
}",
"foo.cs",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_operator_declaration_is_not_a_condition() {
check_func_space::<CsharpParser, _>(
"class V {
public static bool operator <(V a, V b) { return true; }
public static bool operator >(V a, V b) { return true; }
int m(int a, int b, int x) {
if (a < b) { return 1; }
return x is > 0 ? 2 : 3;
}
}",
"foo.cs",
|space| {
let class = &space.spaces[0];
assert_eq!(class.spaces.len(), 3, "two overloads plus `m`");
for (i, member) in class.spaces.iter().enumerate().take(2) {
assert_eq!(
member.metrics.abc.conditions(),
0,
"operator declaration {i} must score no condition"
);
}
assert_eq!(class.spaces[2].metrics.abc.conditions(), 3);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_comparison_operator_overloads_are_not_conditions() {
let src = "class V {
public static bool operator <=(V a, V b) { return true; }
public static bool operator >=(V a, V b) { return true; }
public static bool operator ==(V a, V b) { return true; }
public static bool operator !=(V a, V b) { return true; }
int n(int a, int b) {
if (a <= b) { return 1; }
if (a >= b) { return 2; }
if (a == b) { return 3; }
if (a != b) { return 4; }
return 0;
}
}";
assert_csharp_fixture_spells(
src,
&[
(
Csharp::OperatorDeclaration as u16,
4,
"operator declarations",
),
(Csharp::LTEQ as u16, 2, "`<=`"),
(Csharp::GTEQ as u16, 2, "`>=`"),
(Csharp::EQEQ as u16, 2, "`==`"),
(Csharp::BANGEQ as u16, 2, "`!=`"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
let class = &space.spaces[0];
assert_eq!(class.spaces.len(), 5, "four overloads plus `n`");
for (member, spelling) in class.spaces.iter().zip(["<=", ">=", "==", "!="]) {
assert_eq!(
member.metrics.abc.conditions(),
0,
"`operator {spelling}` declares an operator, it does not apply one"
);
assert_eq!(
member.metrics.abc.conditions(),
member.metrics.cyclomatic.cyclomatic() - 1,
"`operator {spelling}`: §8 parity with the cyclomatic decision count"
);
}
let control = child_space(class, "n");
assert_eq!(
control.metrics.abc.conditions(),
4,
"`n` applies all four tokens: one condition each"
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_preproc_equality_counts_through_the_binary_expression_alias() {
let src = "class P {
#if A == B
int x;
#endif
#if C != D
int y;
#endif
}";
let parser = CsharpParser::new(
src.as_bytes().to_vec(),
std::path::Path::new("foo.cs"),
None,
);
assert!(
ast_has_kind_id(&parser, Csharp::BinaryExpression as u16),
"the `#if` expressions must parse as `binary_expression` — \
the alias is what the arm's allowlist matches"
);
assert!(
!ast_has_kind_id(&parser, Csharp::BinaryExpression2 as u16),
"`BinaryExpression2` is the pre-alias symbol and must stay \
unreachable; if the grammar starts emitting it, re-derive the \
allowlist rather than trusting this arm"
);
check_metrics::<CsharpParser>(src, "foo.cs", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_relational_pattern_does_not_double_count_its_arm() {
let src = "class A {
int n(int x) => x switch { > 5 => 1, < 0 => 2, _ => 3 };
int g(int x) => x switch { >= 5 => 1, <= 0 => 2, _ => 3 };
int c(int x) => x switch { 5 => 1, 0 => 2, _ => 3 };
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::RelationalPattern as u16, 4, "relational patterns"),
(Csharp::GT as u16, 1, "`>`"),
(Csharp::LT as u16, 1, "`<`"),
(Csharp::GTEQ as u16, 1, "`>=`"),
(Csharp::LTEQ as u16, 1, "`<=`"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_every_member_scores(&space.spaces[0], 3, 2, "one condition per non-discard arm");
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_is_pattern_and_combinators_score_one_decision() {
let src = "class A {
int m(int x) { if (x is > 0) { return 1; } return 0; }
int f(int x) { if (x > 0) { return 1; } return 0; }
int a(int x) { if (x is > 0 and < 9) { return 1; } return 0; }
int o(int x) { if (x is < 0 or >= 100) { return 1; } return 0; }
int n(int x) { if (x is not > 5) { return 1; } return 0; }
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::RelationalPattern as u16, 6, "relational patterns"),
(Csharp::IsPatternExpression as u16, 4, "`is` pattern tests"),
(Csharp::AndPattern as u16, 1, "`and`"),
(Csharp::OrPattern as u16, 1, "`or`"),
(Csharp::NegatedPattern as u16, 1, "`not`"),
(Csharp::GTEQ as u16, 1, "`>=` (only `o` carries one)"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_every_member_scores(&space.spaces[0], 5, 1, "the `if` is the only decision");
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_relational_pattern_scores_one_wherever_it_is_written() {
let src = "class A {
static bool M(bool b) { return b; }
bool p(int x) { bool b = x is > 5; return b; }
bool q(int x) { bool b = x > 5; return b; }
bool r(int x) { return x is > 5; }
bool s(int x) { return M(x is > 5); }
bool t(object x) { bool b = x is int; return b; }
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::RelationalPattern as u16, 3, "relational patterns"),
(Csharp::IsPatternExpression as u16, 3, "`is` pattern tests"),
(Csharp::IsExpression as u16, 1, "the `is int` control"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
let class = &space.spaces[0];
assert_eq!(class.spaces.len(), 6, "`M` plus five probes");
let by_name = |n: &str| {
class
.spaces
.iter()
.find(|s| s.name.as_deref() == Some(n))
.unwrap_or_else(|| panic!("fixture lost `{n}`"))
.metrics
.abc
.conditions()
};
for probe in ["p", "r", "s"] {
assert_eq!(by_name(probe), 1, "`{probe}`: the `is` test, once");
}
assert_eq!(by_name("t"), 1, "the bare type test, the same once");
assert_eq!(by_name("q"), 1, "the comparison it is sugar for");
assert_eq!(by_name("M"), 0, "no relational operator anywhere");
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_switch_arm_guard_operator_still_counts() {
let src = "class A {
int w(int x) => x switch { > 0 when x % 2 == 0 => 1, > 0 => 2, _ => 3 };
int g(int x) => x switch { int n when n is > 5 => 1, _ => 0 };
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::WhenClause as u16, 2, "`when` guards"),
(Csharp::EQEQ as u16, 1, "the guard's `==`"),
(Csharp::RelationalPattern as u16, 3, "relational patterns"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
let m = &space.spaces[0].spaces[0];
assert_eq!(m.name.as_deref(), Some("w"));
assert_eq!(
m.metrics.abc.conditions(),
3,
"two arms plus the guard's `==`"
);
assert_eq!(m.metrics.cyclomatic.cyclomatic(), 4);
let g = &space.spaces[0].spaces[1];
assert_eq!(g.name.as_deref(), Some("g"));
assert_eq!(
g.metrics.abc.conditions(),
2,
"one arm plus the guard slot, which scores the `is` test"
);
assert_eq!(g.metrics.cyclomatic.cyclomatic(), 3);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_switch_arm_guard_scores_one_condition_however_spelled() {
let src = "class A {
static bool IsEven(int x) { return true; }
int tok(int x) => x switch { > 0 when x % 2 == 0 => 1, > 0 => 2, _ => 3 };
int cmp(int x) => x switch { > 0 when x > 2 => 1, > 0 => 2, _ => 3 };
int call(int x) => x switch { > 0 when IsEven(x) => 1, > 0 => 2, _ => 3 };
int paren(int x) => x switch { > 0 when (IsEven(x)) => 1, > 0 => 2, _ => 3 };
int nullc(int x, bool? b) => x switch { > 0 when b ?? false => 1, > 0 => 2, _ => 3 };
int none(int x) => x switch { > 2 => 1, > 0 => 2, _ => 3 };
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::WhenClause as u16, 5, "`when` guards"),
(Csharp::EQEQ as u16, 1, "`tok`'s `==`"),
(Csharp::QMARKQMARK as u16, 1, "`nullc`'s `??`"),
(
Csharp::ParenthesizedExpression as u16,
1,
"`paren`'s parenthesised guard",
),
(Csharp::ArgumentList as u16, 2, "the `IsEven` calls"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[
("IsEven", 0, 1),
("tok", 3, 4),
("cmp", 3, 4),
("call", 3, 4),
("paren", 3, 4),
("nullc", 3, 5),
("none", 2, 3),
],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_bare_is_type_test_scores_one_condition() {
let src = "class A {
static bool IsEven(int x) { return true; }
int g(object x) => x switch { object o when x is int => 1, _ => 0 };
int gq(object x) => x switch { object o when x is int y => 1, _ => 0 };
int gc(object x) => x switch { object o when IsEven(1) => 1, _ => 0 };
int p(object x) { if (x is int) { return 1; } return 0; }
int q(object x) { if (x is int y) { return 1; } return 0; }
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::IsExpression as u16, 2, "the bare `is` type tests"),
(
Csharp::IsPatternExpression as u16,
2,
"the designation-pattern twins",
),
(Csharp::WhenClause as u16, 3, "`when` guards"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[
("IsEven", 0, 1),
("g", 2, 3),
("gq", 2, 3),
("gc", 2, 3),
("p", 1, 2),
("q", 1, 2),
],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_null_forgiving_operand_scores_like_its_operand() {
let src = "class A {
int p(bool b) { if (b) { return 1; } return 0; }
int pn(bool b) { if (b!) { return 1; } return 0; }
int pp(bool b) { if ((b!)) { return 1; } return 0; }
int pd(bool b) { if (b!!) { return 1; } return 0; }
int pb(bool b) { if (!b!) { return 1; } return 0; }
int c(bool b) { if (b && b) { return 1; } return 0; }
int cn(bool b) { if (b! && b) { return 1; } return 0; }
int cp(bool b) { if ((b!) && b) { return 1; } return 0; }
}";
assert_csharp_fixture_spells(
src,
&[
(
Csharp::PostfixUnaryExpression as u16,
7,
"the null-forgiving suffixes",
),
(
Csharp::ParenthesizedExpression as u16,
2,
"the explicit parenthesised spellings",
),
(Csharp::PrefixUnaryExpression as u16, 1, "`pb`'s `!`"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[
("p", 1, 2),
("pn", 1, 2),
("pp", 1, 2),
("pd", 1, 2),
("pb", 1, 2),
("c", 2, 3),
("cn", 2, 3),
("cp", 2, 3),
],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_null_forgiving_operand_survives_an_interposed_comment() {
let src = "class A {
int p(bool b) { if (b!) { return 1; } return 0; }
int pc(bool b) { if (b /*c*/ !) { return 1; } return 0; }
int n(bool b) { if (!b) { return 1; } return 0; }
int nc(bool b) { if (! /*c*/ b) { return 1; } return 0; }
int r(bool b) { if ((b)) { return 1; } return 0; }
int rc(bool b) { if (( /*c*/ b)) { return 1; } return 0; }
}";
assert_csharp_fixture_spells(
src,
&[
(
Csharp::Comment as u16,
3,
"the interposed comments — without them every row is its own control",
),
(Csharp::PostfixUnaryExpression as u16, 2, "`p` and `pc`"),
(Csharp::PrefixUnaryExpression as u16, 2, "`n` and `nc`"),
(
Csharp::ParenthesizedExpression as u16,
2,
"`r` and `rc` — the `if` parens are anonymous tokens",
),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[
("p", 1, 2),
("pc", 1, 2),
("n", 1, 2),
("nc", 0, 2),
("r", 1, 2),
("rc", 0, 2),
],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_statement_switch_section_guard_counts() {
let src = "class A {
static bool IsEven(int x) { return true; }
int guarded(int x) {
switch (x) {
case > 0 when IsEven(x): return 1;
case > 0: return 2;
default: return 3;
}
}
int plain(int x) {
switch (x) {
case > 2: return 1;
case > 0: return 2;
default: return 3;
}
}
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::WhenClause as u16, 1, "the `case … when` guard"),
(Csharp::SwitchSection as u16, 6, "switch sections"),
(
Csharp::SwitchExpressionArm as u16,
0,
"expression arms (this fixture is the statement form)",
),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[("IsEven", 0, 1), ("guarded", 3, 4), ("plain", 2, 3)],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_catch_filter_guard_scores_one_condition_however_spelled() {
let src = "class A {
static bool IsEven(int x) { return true; }
int tok(int x) { try { return 1; } catch (System.Exception e) when (x % 2 == 0) { return 2; } }
int cmp(int x) { try { return 1; } catch (System.Exception e) when (x > 0) { return 2; } }
int call(int x) { try { return 1; } catch (System.Exception e) when (IsEven(x)) { return 2; } }
int paren(int x) { try { return 1; } catch (System.Exception e) when ((IsEven(x))) { return 2; } }
int none(int x) { try { return 1; } catch (System.Exception e) { return 2; } }
}";
assert_csharp_fixture_spells(
src,
&[
(
Csharp::CatchFilterClause as u16,
4,
"`catch … when` filters",
),
(Csharp::CatchClause as u16, 5, "catch clauses"),
(Csharp::WhenClause as u16, 0, "switch guards (none here)"),
(
Csharp::ParenthesizedExpression as u16,
1,
"`paren`'s inner parentheses",
),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[
("IsEven", 0, 1),
("tok", 3, 3),
("cmp", 3, 3),
("call", 3, 3),
("paren", 3, 3),
("none", 2, 2),
],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_guarded_discard_arm_scores_arm_and_guard() {
let src = "class A {
static bool IsEven(int x) { return true; }
int guarded(int x) => x switch { > 0 => 1, _ when IsEven(x) => 2, _ => 3 };
int bare(int x) => x switch { > 0 => 1, _ => 3 };
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::WhenClause as u16, 1, "the guarded discard"),
(Csharp::Discard as u16, 3, "discard patterns"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[("IsEven", 0, 1), ("guarded", 3, 4), ("bare", 1, 2)],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_guard_keeps_its_condition_across_a_comment() {
let src = "class A {
static bool IsEven(int x) => true;
int plain(int x) => x switch { > 0 when IsEven(x) => 1, _ => 0 };
int cmt(int x) => x switch { > 0 when /*c*/ IsEven(x) => 1, _ => 0 };
int cplain(int x) { try { return 1; } catch (System.Exception e) when (IsEven(x)) { return 2; } }
int ccmt(int x) { try { return 1; } catch (System.Exception e) when (/*c*/ IsEven(x)) { return 2; } }
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::Comment as u16, 2, "the in-guard comments"),
(Csharp::WhenClause as u16, 2, "switch guards"),
(Csharp::CatchFilterClause as u16, 2, "catch filters"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[
("IsEven", 0, 1),
("plain", 2, 3),
("cmt", 2, 3),
("cplain", 3, 3),
("ccmt", 3, 3),
],
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_compound_guard_keeps_its_sub_structure() {
let src = "class A {
int compound(int a, int b) => a switch { > 0 when a > 1 && b < 2 => 1, _ => 0 };
int single(int a, int b) => a switch { > 0 when a > 1 => 1, _ => 0 };
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::WhenClause as u16, 2, "`when` guards"),
(Csharp::AMPAMP as u16, 1, "`compound`'s `&&`"),
(Csharp::LT as u16, 1, "`compound`'s `<`"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(&space.spaces[0], &[("compound", 3, 4), ("single", 2, 3)]);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_nullable_type_syntax_is_not_a_condition() {
check_metrics::<CsharpParser>(
"class A {
int M<T>(int? p, int a, int b) where T : class? {
int? q = null;
return a > b ? 1 : 2;
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_conditional_access_still_counts_as_a_condition() {
let src = "class A {
object M(string s, int[] xs) {
return s?.Length ?? xs?[0];
}
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::QMARK as u16, 2, "the `?.` and `?[` operators"),
(Csharp::QMARKQMARK as u16, 1, "the `??`"),
],
);
check_metrics::<CsharpParser>(src, "foo.cs", |metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_null_coalescing_is_a_condition() {
let src = "class A {
int coalesce(int? x) { return x ?? 0; }
int plain(int x) { return x; }
}";
assert_csharp_fixture_spells(src, &[(Csharp::QMARKQMARK as u16, 1, "the `??`")]);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(&space.spaces[0], &[("coalesce", 1, 2), ("plain", 0, 1)]);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_ternary_still_counts_alongside_nullable_types() {
check_metrics::<CsharpParser>(
"class A {
int M(int? n, bool c) {
return c ? 1 : 2;
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_aliased_invocation_expression_branches() {
check_metrics::<CsharpParser>(
"class A {
void M() {
System.Console.WriteLine(1);
System.Console.WriteLine(2);
System.Console.WriteLine(3);
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.abc.branches_max(), 3);
assert_eq!(metric.abc.conditions_max(), 0);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_zero_abc() {
check_metrics::<PhpParser>("<?php\n", "foo.php", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "php")]
#[test]
fn php_simple_assignment() {
check_metrics::<PhpParser>(
"<?php
function f(): void {
$a = 1;
$b = 2;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_augmented_assignment() {
check_metrics::<PhpParser>(
"<?php
function f(int $x): int {
$a = 0;
$a += $x;
$a -= 1;
$a *= 2;
return $a;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_const_excluded() {
check_metrics::<PhpParser>(
"<?php
class A {
const PI = 3.14;
const E = 2.71;
}
enum Color {
case Red;
case Green;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_function_call() {
check_metrics::<PhpParser>(
"<?php
function f(): void {
foo();
bar(1, 2);
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_method_call() {
check_metrics::<PhpParser>(
"<?php
function f($obj): void {
$obj->m1();
$obj->m2(1);
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_static_call() {
check_metrics::<PhpParser>(
"<?php
function f(): void {
Foo::bar();
Foo::baz(1);
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_nullsafe_call() {
check_metrics::<PhpParser>(
"<?php
function f($obj): void {
$obj?->m1();
$obj?->m2(1);
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_object_creation() {
check_metrics::<PhpParser>(
"<?php
function f(): void {
new Foo();
new Bar(1);
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_comparison_eq() {
check_metrics::<PhpParser>(
"<?php
function f(int $a, int $b): bool {
return $a == $b || $a != $b;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_comparison_strict() {
check_metrics::<PhpParser>(
"<?php
function f(int $a, int $b): bool {
return $a === $b || $a !== $b;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_spaceship() {
check_metrics::<PhpParser>(
"<?php
function f(int $a, int $b): int {
return $a <=> $b;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_instanceof() {
check_metrics::<PhpParser>(
"<?php
function f($x): bool {
return $x instanceof Foo;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_complex_function() {
check_metrics::<PhpParser>(
"<?php
function f(int $a, int $b): int {
$sum = $a + $b;
$prod = $a * $b;
if ($sum > 0 && $prod === 0) {
return foo($sum);
}
return bar()->double();
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.abc),
);
}
#[cfg(feature = "php")]
#[test]
fn php_if_boolean_literal_condition() {
check_metrics::<PhpParser>(
"<?php\n\
function f() {\n\
\x20 if (true) {} // +1c\n\
\x20 if (!false) {} // +1c\n\
\x20 while (true) {} // +1c\n\
\x20 do {} while (false); // +1c\n\
}\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_methods_arguments_with_conditions() {
check_metrics::<PhpParser>(
"<?php\n\
function f($a, $b) {\n\
\x20 m($a, $b); // +1b\n\
\x20 m(!$a, !$b); // +1b +2c\n\
}\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_return_with_conditions() {
check_metrics::<PhpParser>(
"<?php\n\
function m1($z) { return !($z >= 0); }\n\
function m2($x) { return (((!$x))); }\n\
function m3($x, $y) { return $x && $y; }\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_name2_hidden_rule_drift_marker() {
let src = "<?php\nfunction f($x) { if ($x) { foo($x); } }\n";
let parser = PhpParser::new(
src.as_bytes().to_vec(),
&std::path::PathBuf::from("foo.php"),
None,
);
assert!(!ast_has_kind_id(&parser, Php::Name2 as u16));
}
#[cfg(feature = "php")]
#[test]
fn php_scoped_property_access_condition_counts() {
check_metrics::<PhpParser>(
"<?php\n\
class Config { public static $enabled = true; }\n\
function f() { if (Config::$enabled) { } }\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_named_argument_unary_conditional_counts() {
check_metrics::<PhpParser>(
"<?php\nfunction f($a) { m(name: !$a); }\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_low_precedence_keyword_logical_ops_trigger_walker() {
check_metrics::<PhpParser>(
"<?php\n\
function f($a, $b) {\n\
\x20 return $a or $b;\n\
}\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_if_multiple_conditions() {
check_metrics::<PhpParser>(
"<?php\n\
function f($a, $b, $c, $d) {\n\
\x20 if ($a || $b || $c || $d) {} // +4c\n\
\x20 if ($a && $b && $c) {} // +3c\n\
\x20 if (!$a && !$b) {} // +2c\n\
}\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_while_and_do_while_conditions() {
check_metrics::<PhpParser>(
"<?php\n\
function f($a, $b) {\n\
\x20 while ($a || $b) {} // +2c\n\
\x20 do {} while ($a && !$b); // +2c\n\
}\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_short_circuit_with_boolean_literal_operand() {
check_metrics::<PhpParser>(
"<?php\nfunction f($a) { return $a && true; }\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "php")]
#[test]
fn php_ternary_operand_slots_count_as_unary_conditions() {
check_metrics::<PhpParser>(
"<?php\nfunction f() { $x = $a ? !$b : !$c; }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 4),
);
check_metrics::<PhpParser>(
"<?php\nfunction f() { $x = ($a > 0) ? $b : -$b; }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
check_metrics::<PhpParser>(
"<?php\nfunction f() { $x = $a ? ($b ? $c : $d) : $e; }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 4),
);
check_metrics::<PhpParser>(
"<?php\nfunction f() { $x = !$a ? $b : $c; }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "php")]
#[test]
fn php_elided_ternary_body_still_walks_the_alternative() {
check_metrics::<PhpParser>(
"<?php\nfunction f() { $x = $a ?: !$b; }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 3),
);
}
#[cfg(feature = "php")]
#[test]
fn php_for_condition_slot_counts_unary_conditions() {
check_metrics::<PhpParser>(
"<?php\nfunction f($a) { for (; $a; ) {} }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<PhpParser>(
"<?php\nfunction f($a) { for (; !$a; ) {} }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<PhpParser>(
"<?php\nfunction f($a) { for (; ($a); ) {} }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<PhpParser>(
"<?php\nfunction f($n) { for ($i = 0; $i < $n; $i++) {} }\n",
"foo.php",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
check_metrics::<PhpParser>(
"<?php\nfunction f() { for (;;) { break; } }\n",
"foo.php",
|metric| assert_eq!(metric.abc.conditions_sum(), 0),
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_empty_class() {
check_metrics::<KotlinParser>("class C {}", "foo.kt", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_val_declarations_are_not_assignments() {
check_metrics::<KotlinParser>(
"class C {
val a: Int = 1
val b: Int = 2
val c: Int = 3
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_var_declarations_count_assignment() {
check_metrics::<KotlinParser>(
"class C {
var a: Int = 1
var b: Int = 2
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_val_then_assignments_count() {
check_metrics::<KotlinParser>(
"fun f() {
val cfg = 0
a = 1
b = 2
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_var_then_assignments_count() {
check_metrics::<KotlinParser>(
"fun f() {
var cfg = 0
a = 1
b = 2
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_augmented_assignments_count() {
check_metrics::<KotlinParser>(
"fun m() {
var x = 0
x += 1
x -= 2
x *= 3
x++
--x
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_branches_call_expression() {
check_metrics::<KotlinParser>(
"fun m() {
println(\"a\")
println(\"b\")
println(\"c\")
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_object_construction_branch() {
check_metrics::<KotlinParser>(
"class P(val x: Int)
fun m(): P = P(1)",
"foo.kt",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_comparisons_count_conditions() {
check_metrics::<KotlinParser>(
"fun m(a: Int, b: Int): Boolean {
val r1 = a < b
val r2 = a > b
val r3 = a <= b
val r4 = a >= b
val r5 = a == b
val r6 = a != b
return r1 || r2 || r3 || r4 || r5 || r6
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 12);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_identity_equality_conditions() {
check_metrics::<KotlinParser>(
"fun m(a: Any, b: Any): Boolean {
return a === b || a !== b
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_else_branch_counts() {
check_metrics::<KotlinParser>(
"fun m(x: Int): Int {
return if (x > 0) 1 else -1
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_when_entries_count() {
check_metrics::<KotlinParser>(
"fun m(x: Int): Int {
return when (x) {
1 -> 10
2 -> 20
else -> 0
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_when_else_not_a_condition() {
check_metrics::<KotlinParser>(
"fun m(x: Int): Int {
return when (x) { 1 -> 10; 2 -> 20; else -> 0 }
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "kotlin")]
#[track_caller]
fn assert_kotlin_class_members(
src: &str,
kinds: &[(u16, usize, &str)],
expected: &[(&str, u64, u64)],
) {
assert_fixture_spells::<KotlinParser>(src, "foo.kt", kinds);
check_func_space::<KotlinParser, _>(src, "foo.kt", |space| {
assert_members_score(&space.spaces[0], expected);
});
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_subjectless_when_arm_counts_its_condition_once() {
let src = "class K {
fun f(): Boolean = true
fun two(x: Int): Int = when { x > 5 -> 1; x < 0 -> 2; else -> 0 }
fun bare(x: Boolean, y: Boolean): Int = when { x -> 1; y -> 2; else -> 0 }
fun ge(x: Int): Int = when { x >= 5 -> 1; else -> 0 }
fun eq(x: Int): Int = when { x == 5 -> 1; else -> 0 }
fun andd(a: Int, b: Int): Int = when { a > 1 && b < 2 -> 1; else -> 0 }
fun call(): Int = when { f() -> 1; else -> 0 }
fun iff(x: Int): Int = if (x > 5) 1 else 0
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::WhenSubject as u16, 0, "`when` subjects"),
(Kotlin::WhenEntry as u16, 14, "`when` entries"),
(
Kotlin::BinaryExpression as u16,
8,
"comparison / chain expressions",
),
(Kotlin::AMPAMP as u16, 1, "`andd`'s `&&`"),
],
&[
("f", 0, 1),
("two", 2, 3),
("bare", 2, 3),
("ge", 1, 2),
("eq", 1, 2),
("andd", 2, 3),
("call", 1, 2),
("iff", 2, 2),
],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_condition_slot_peels_null_assertions_and_casts() {
let src = "class K {
fun g(x: Boolean): Boolean = true
fun bare(a: Boolean): Int = when { a -> 1; else -> 0 }
fun cmp(a: Int): Int = when { a > 5 -> 1; else -> 0 }
fun call(a: Boolean): Int = when { g(a) -> 1; else -> 0 }
fun bang(a: Boolean?): Int = when { a!! -> 1; else -> 0 }
fun paren(a: Boolean?): Int = when { (a!!) -> 1; else -> 0 }
fun cast(a: Any): Int = when { a as Boolean -> 1; else -> 0 }
fun chain(a: Any?): Int = when { a!! as Boolean -> 1; else -> 0 }
fun safe(a: Any): Int = when { (a as? Boolean)!! -> 1; else -> 0 }
fun cmt(a: Boolean): Int = when { ! /*c*/ a -> 1; else -> 0 }
fun iff(a: Boolean?): Int { if (a!!) { return 1 }; return 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::WhenSubject as u16, 0, "`when` subjects"),
(
Kotlin::BANGBANG as u16,
5,
"the `!!` null assertions of `bang` / `paren` / `chain` / `safe` / `iff`",
),
(
Kotlin::AsExpression as u16,
3,
"the casts of `cast` / `chain` / `safe`",
),
(Kotlin::AsQMARK as u16, 1, "`safe`'s safe cast"),
(
Kotlin::BlockComment as u16,
1,
"`cmt`'s interposed comment, spaces and all",
),
(
Kotlin::ParenthesizedExpression as u16,
2,
"the parenthesised operands of `paren` / `safe`",
),
],
&[
("g", 0, 1),
("bare", 1, 2),
("cmp", 1, 2),
("call", 1, 2),
("bang", 1, 2),
("paren", 1, 2),
("cast", 1, 2),
("chain", 1, 2),
("safe", 1, 2),
("cmt", 1, 2),
("iff", 1, 2),
],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_subjectful_when_arms_keep_the_per_entry_count() {
let src = "class K {
fun inn(a: Int): Int = when (a) { in 1..2 -> 1; else -> 0 }
fun notin(a: Int): Int = when (a) { !in 1..2 -> 1; else -> 0 }
fun iss(a: Any): Int = when (a) { is String -> 1; else -> 0 }
fun konst(a: Int): Int = when (a) { 1 -> 10; 2 -> 20; else -> 0 }
fun cmp(x: Boolean, y: Int): Int = when (x) { y > 5 -> 1; else -> 0 }
fun nested(x: Boolean, y: Int): Int { if (x == (y > 5)) { return 1 }; return 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::WhenSubject as u16, 5, "`when` subjects"),
(Kotlin::WhenEntry as u16, 11, "`when` entries"),
(Kotlin::RangeTest as u16, 2, "`in` / `!in` patterns"),
(Kotlin::TypeTest as u16, 1, "the `is` pattern"),
(
Kotlin::BinaryExpression as u16,
3,
"`cmp`'s and `nested`'s comparisons",
),
],
&[
("inn", 1, 2),
("notin", 1, 2),
("iss", 1, 2),
("konst", 2, 3),
("cmp", 2, 2),
("nested", 2, 2),
],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_when_subject_scan_clears_a_comment_before_the_brace() {
let src = "class K {
fun cmtSubject(x: Int): Int = when /*c*/ (x) { 1 -> 1; else -> 0 }
fun cmtNone(a: Boolean): Int = when /*c*/ { a -> 1; else -> 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::BlockComment as u16, 2, "the two header comments"),
(Kotlin::WhenSubject as u16, 1, "`cmtSubject`'s subject"),
(Kotlin::WhenEntry as u16, 4, "`when` entries"),
],
&[("cmtSubject", 1, 2), ("cmtNone", 1, 2)],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_subjectless_when_condition_wrappers_count_once() {
let src = "class K {
fun notted(a: Boolean): Int = when { !a -> 1; else -> 0 }
fun paren(a: Boolean): Int = when { (a) -> 1; else -> 0 }
fun notCmp(a: Int): Int = when { !(a > 1) -> 1; else -> 0 }
fun parenCmp(a: Int): Int = when { (a > 1) -> 1; else -> 0 }
fun nest(x: Int, y: Int): Int =
when { x > 1 -> when { y > 2 -> 1; else -> 2 }; else -> 0 }
fun blockBody(x: Int): Int { when { x > 5 -> { return 1 } }; return 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::WhenSubject as u16, 0, "`when` subjects"),
(Kotlin::WhenEntry as u16, 13, "`when` entries"),
(Kotlin::UnaryExpression as u16, 2, "the two `!` operands"),
(
Kotlin::ParenthesizedExpression as u16,
3,
"the three parenthesised operands",
),
(Kotlin::BinaryExpression as u16, 5, "comparisons"),
],
&[
("notted", 1, 2),
("paren", 1, 2),
("notCmp", 1, 2),
("parenCmp", 1, 2),
("nest", 2, 3),
("blockBody", 1, 2),
],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_subjectless_when_multi_alternative_entry() {
let src = "class K {
fun alts(x: Int, y: Int): Int = when { x > 5, y < 0 -> 1; else -> 0 }
fun bareAlts(x: Boolean, y: Boolean): Int = when { x, y -> 1; else -> 0 }
fun subjAlts(a: Int): Int = when (a) { 1, 2 -> 10; else -> 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::WhenSubject as u16, 1, "`subjAlts`'s subject"),
(Kotlin::WhenEntry as u16, 6, "`when` entries"),
(
Kotlin::BinaryExpression as u16,
2,
"`alts`'s two comparisons",
),
],
&[
("alts", 2, 2),
("bareAlts", 1, 2),
("subjAlts", 1, 2),
],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_is_and_in_expressions_are_unary_conditions() {
let src = "class K {
fun ifIs(a: Any): Int { if (a is String) { return 1 }; return 0 }
fun ifIn(a: Int): Int { if (a in 1..2) { return 1 }; return 0 }
fun whenIs(a: Any): Int = when { a is String -> 1; else -> 0 }
fun whenIn(a: Int): Int = when { a in 1..2 -> 1; else -> 0 }
fun isAnd(a: Any, b: Boolean): Int = when { a is String && b -> 1; else -> 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::WhenSubject as u16, 0, "`when` subjects"),
(Kotlin::WhenEntry as u16, 6, "`when` entries"),
(Kotlin::IsExpression as u16, 3, "`is` tests"),
(Kotlin::InExpression as u16, 2, "`in` tests"),
(Kotlin::AMPAMP as u16, 1, "`isAnd`'s `&&`"),
],
&[
("ifIs", 1, 2),
("ifIn", 1, 2),
("whenIs", 1, 2),
("whenIn", 1, 2),
("isAnd", 2, 3),
],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_infix_boolean_functions_are_unary_conditions() {
let src = "class K {
fun whenAnd(a: Boolean, b: Boolean): Int = when { a and b -> 1; else -> 0 }
fun whenOr(a: Boolean, b: Boolean): Int = when { a or b -> 1; else -> 0 }
fun whenXor(a: Boolean, b: Boolean): Int = when { a xor b -> 1; else -> 0 }
fun whenAmp(a: Boolean, b: Boolean): Int = when { a && b -> 1; else -> 0 }
fun ifAnd(a: Boolean, b: Boolean): Int { if (a and b) { return 1 }; return 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::WhenSubject as u16, 0, "`when` subjects"),
(Kotlin::InfixExpression as u16, 4, "the infix calls"),
(Kotlin::AMPAMP as u16, 1, "`whenAmp`'s `&&`"),
],
&[
("whenAnd", 1, 2),
("whenOr", 1, 2),
("whenXor", 1, 2),
("whenAmp", 2, 3),
("ifAnd", 1, 2),
],
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_catch_block_counts() {
check_metrics::<KotlinParser>(
"fun m() {
try {
println(\"ok\")
} catch (e: Exception) {
println(\"err\")
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_elvis_and_safe_cast() {
check_metrics::<KotlinParser>(
"fun m(s: String?): Int {
val n = (s as? Int) ?: 0
return n
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_generic_brackets_not_conditions() {
check_metrics::<KotlinParser>(
"class Box<T>(val v: T)
fun <T> wrap(x: T): Box<T> = Box(x)",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_super_type_argument_is_not_a_condition() {
check_metrics::<KotlinParser>(
"class B : A() {
override fun g(a: Int, b: Int): Int {
if (a < b) { return super<A>.g(a, b) }
return 0
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 2);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_class_with_methods_and_branches() {
check_metrics::<KotlinParser>(
"class C {
var counter: Int = 0
fun bump() {
counter += 1
println(counter)
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_object_singleton_abc() {
check_metrics::<KotlinParser>(
"object Util {
fun work(x: Int): Int {
var y = x
y += 1
if (y > 0) {
return y
}
return -1
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_interface_abc() {
check_metrics::<KotlinParser>(
"interface I {
fun work(): Int
fun describe(): String
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_nested_class_abc() {
check_metrics::<KotlinParser>(
"class Outer {
var o: Int = 0
class Nested {
var n: Int = 0
fun bump() { n += 1 }
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_data_class_abc() {
check_metrics::<KotlinParser>(
"data class Point(val x: Int, val y: Int)",
"foo.kt",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_primary_constructor_default_value_not_assignment() {
check_metrics::<KotlinParser>("class C(val a: Int = 5)", "foo.kt", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_unary_conditions_in_chain() {
check_metrics::<KotlinParser>(
"fun f(a: Boolean, b: Boolean, c: Boolean) {
if (a && b || c) { println(\"x\") }
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_comparison_operands_add_nothing() {
check_metrics::<KotlinParser>(
"fun g(x: Int, y: Int) {
if (x > 0 && y > 0) { println(\"x\") }
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_negated_operand_is_unary_condition() {
check_metrics::<KotlinParser>(
"fun f(a: Boolean, b: Boolean) {
if (a && !b) { println(\"x\") }
}",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_chain_operands_peel_null_assertions_and_casts() {
for (chain, spelling, anchors) in [
(
"a && b!!",
"null assertion",
&[(Kotlin::BANGBANG as u16, 1usize, "the `!!`")][..],
),
(
"a && b as Boolean",
"cast",
&[(Kotlin::AsExpression as u16, 1, "the `as` cast")][..],
),
(
"a && (b!!)",
"parenthesised null assertion",
&[
(Kotlin::BANGBANG as u16, 1, "the `!!`"),
(Kotlin::ParenthesizedExpression as u16, 1, "the parens"),
][..],
),
] {
let src = format!("fun f(a: Boolean, b: Any) {{ if ({chain}) {{ println(\"x\") }} }}");
assert_fixture_spells::<KotlinParser>(&src, "foo.kt", anchors);
check_func_space::<KotlinParser, _>(&src, "foo.kt", |space| {
let f = child_space(&space, "f");
assert_eq!(
f.metrics.abc.conditions(),
2,
"`{chain}`: the identifier plus the {spelling} operand"
);
assert_eq!(f.metrics.cyclomatic.cyclomatic(), 3, "`{chain}`: decisions");
});
}
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_bare_if_predicate_is_one_condition() {
check_metrics::<KotlinParser>(
"fun m(flag: Boolean): Int { return if (flag) 1 else -1 }",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_bare_while_predicate_is_one_condition() {
check_metrics::<KotlinParser>(
"fun m(running: Boolean) { while (running) { println(\"x\") } }",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_bare_do_while_predicate_is_one_condition() {
check_metrics::<KotlinParser>(
"fun m(ok: Boolean) { do { println(\"x\") } while (ok) }",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_comparison_predicate_not_double_counted() {
check_metrics::<KotlinParser>(
"fun m(a: Int, b: Int): Int { return if (a == b) 1 else -1 }",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_short_circuit_predicate_not_double_counted() {
check_metrics::<KotlinParser>(
"fun m(x: Boolean, y: Boolean): Int { return if (x && y) 1 else -1 }",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_parenthesised_bare_predicate_is_one_condition() {
check_metrics::<KotlinParser>(
"fun m(flag: Boolean) { if ((flag)) { println(\"x\") } }",
"foo.kt",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_assignments_basic() {
check_metrics::<TypescriptParser>(
"class C {
m(): void {
let x = 0; // +1 — only a `const` initializer is suppressed
x = 1; // +1
x += 2; // +1
x++; // +1
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_const_excluded_from_assignments() {
check_metrics::<TypescriptParser>(
"class C {
m(): void {
const a = 1; // suppressed (`const` initializer)
const b = 2; // suppressed
let c = 3; // +1 — `let` initializers count
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_asi_const_does_not_suppress_later_assignments() {
check_metrics::<TypescriptParser>(
"function f() {
const a = 1
x = 2
y = 3
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_semicolon_const_does_not_suppress_later_assignments() {
check_metrics::<TypescriptParser>(
"function f() {
const a = 1;
x = 2;
y = 3;
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_as_const_does_not_suppress_later_assignments() {
check_metrics::<TypescriptParser>(
"function f(x: number) {
let y = x as const
w = 3
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_const_declarator_shapes_stay_suppressed() {
check_metrics::<TypescriptParser>(
"function f(o: any, xs: number[]) {
const a = 1, b = 2
const {c = 5, d: {e = 6} = {}} = o
const [g = 7, ...[h = 8]] = xs
let i = 3
var j = 4
for (const x of xs) { k(x) }
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_const_initializer_value_assignments_still_count() {
check_metrics::<TypescriptParser>(
"function m(o: any, a: any, b: any) { const x = (o.p = 1); const y = a || (b = 2); }",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_branches_function_calls() {
check_metrics::<TypescriptParser>(
"class C {
m(): void {
foo(); // +1
bar(1, 2); // +1
new Date(); // +1
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_conditions_comparison_operators() {
check_metrics::<TypescriptParser>(
"class C {
m(x: number, y: number): boolean {
return x == y // +1
|| x === y // +1
|| x != y // +1
|| x !== y // +1
|| x < y // +1
|| x <= y // +1
|| x > y // +1
|| x >= y; // +1
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 8);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_conditions_control_flow_arms() {
check_metrics::<TypescriptParser>(
"class C {
m(x: number): number {
try { // +1 (try)
if (x > 0) { // +1 (>)
return 1;
} else { // +1 (else)
return -1;
}
} catch (e) { // +1 (catch)
return 0;
}
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_conditions_switch_case() {
check_metrics::<TypescriptParser>(
"class C {
m(x: number): number {
switch (x) {
case 1: // +1
return 1;
case 2: // +1
return 2;
default: // +0 (fallthrough, #469)
return 0;
}
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_ternary_and_nullish() {
check_metrics::<TypescriptParser>(
"class C {
m(x: number | null): number {
return x !== null // +1 (!==)
? x // +1 (ternary ?)
: 0;
}
n(x: number | null): number {
return x ?? 0; // +1 (??)
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_instanceof_counts_as_condition() {
check_metrics::<TypescriptParser>(
"class C {
m(o: unknown): boolean {
return o instanceof C; // +1
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_generic_lt_gt_not_a_condition() {
check_metrics::<TypescriptParser>(
"class C<T> {
xs: Array<number> = [];
m(): void {
const arr: Array<string> = []; // suppressed const
void arr;
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_comparison_operators_still_count_alongside_generics() {
check_func_space::<TypescriptParser, _>(
"function m(xs: Array<string>, a: number, b: number): number {
if (a < b) { return 1; }
if (a > b) { return 2; }
void xs;
return 0;
}",
"foo.ts",
|space| assert_deepest_conditions_match_cyclomatic(&space, 2),
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_optional_type_syntax_is_not_a_condition() {
check_metrics::<TypescriptParser>(
"interface I { a?: string; m?(x: number): void; }
abstract class K { f?: number; abstract g?(): void; }
type Tup = [number, string?];
function h(x?: number, y: number = 0): number { return y > 1 ? 1 : 2; }",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_conditional_type_is_not_a_condition() {
check_metrics::<TypescriptParser>(
"type Cond<T> = T extends string ? number : boolean;
function pick(a: number, b: number): number { return a > b ? 1 : 2; }",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_ternary_qmark_alias_stays_unreachable() {
let parser = TypescriptParser::new(
"function f(a: boolean): number { return a ? 1 : 2; }\n"
.as_bytes()
.to_vec(),
std::path::Path::new("foo.ts"),
None,
);
assert!(ast_has_kind_id(&parser, Typescript::QMARK as u16));
assert!(!ast_has_kind_id(&parser, Typescript::QMARK2 as u16));
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_abstract_class_abc() {
check_metrics::<TypescriptParser>(
"abstract class C {
abstract a(): void;
m(x: number): number {
if (x > 0) return 1; // +1 condition
return 0;
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_interface_abc_zero() {
check_metrics::<TypescriptParser>(
"interface I {
a(): void;
b(): number;
p: string;
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_arrow_field_contributes_abc() {
check_metrics::<TypescriptParser>(
"class C {
arrow = (x: number) => {
if (x > 0) { // +1 condition
return foo(); // +1 branch
}
return 0;
};
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_parameter_property_init_not_assignment() {
check_metrics::<TypescriptParser>(
"class C {
f: number = 0;
constructor(public x: number, private y: string) {
let z = 0;
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_assignments_basic() {
check_metrics::<TsxParser>(
"class C {
m(): void {
let x = 0;
x = 1;
x += 2;
x++;
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_const_excluded_from_assignments() {
check_metrics::<TsxParser>(
"class C {
m(): void {
const a = 1;
let b = 2;
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_branches_function_calls() {
check_metrics::<TsxParser>(
"class C {
m(): void {
foo();
new Date();
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_conditions_comparison_operators() {
check_metrics::<TsxParser>(
"class C {
m(x: number, y: number): boolean {
return x == y || x < y || x >= y;
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_conditions_control_flow_arms() {
check_metrics::<TsxParser>(
"class C {
m(x: number): number {
try {
if (x > 0) return 1;
else return -1;
} catch (e) {
return 0;
}
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_conditions_switch_case() {
check_metrics::<TsxParser>(
"class C {
m(x: number): number {
switch (x) {
case 1: return 1; // +1
case 2: return 2; // +1
default: return 0; // +0 (fallthrough, #469)
}
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_ternary_and_nullish() {
check_metrics::<TsxParser>(
"class C {
m(x: number | null): number {
return x !== null ? x : 0;
}
n(x: number | null): number { return x ?? 0; }
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_instanceof_counts_as_condition() {
check_metrics::<TsxParser>(
"class C { m(o: unknown): boolean { return o instanceof C; } }",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_generic_lt_gt_not_a_condition() {
check_metrics::<TsxParser>(
"class C<T> { xs: Array<number> = []; }",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_jsx_elements_are_not_conditions() {
check_metrics::<TsxParser>(
"function f(a: number, b: number) {
if (a < b) { return <div className=\"x\"><span>hi</span></div>; }
return <br />;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_optional_type_syntax_is_not_a_condition() {
check_metrics::<TsxParser>(
"interface I { a?: string; m?(x: number): void; }
class K { f?: number; }
function h(x?: number, y: number = 0): number { return y > 1 ? 1 : 2; }",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_conditional_type_is_not_a_condition() {
check_metrics::<TsxParser>(
"type Cond<T> = T extends string ? number : boolean;
function pick(a: number, b: number): number { return a > b ? 1 : 2; }",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_ternary_qmark_alias_stays_unreachable() {
let parser = TsxParser::new(
"function f(a: boolean): number { return a ? 1 : 2; }\n"
.as_bytes()
.to_vec(),
std::path::Path::new("foo.tsx"),
None,
);
assert!(ast_has_kind_id(&parser, Tsx::QMARK as u16));
assert!(!ast_has_kind_id(&parser, Tsx::QMARK2 as u16));
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_abstract_class_abc() {
check_metrics::<TsxParser>(
"abstract class C {
abstract a(): void;
m(x: number): number {
if (x > 0) return 1;
return 0;
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_interface_abc_zero() {
check_metrics::<TsxParser>(
"interface I { a(): void; p: string; }",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_arrow_field_contributes_abc() {
check_metrics::<TsxParser>(
"class C {
arrow = (x: number) => {
if (x > 0) return foo();
return 0;
};
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_asi_const_does_not_suppress_later_assignments() {
check_metrics::<TsxParser>(
"function f() {
const a = 1
x = 2
y = 3
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_const_declarator_shapes_stay_suppressed() {
check_metrics::<TsxParser>(
"function f(o: any, xs: number[]) {
const a = 1, b = 2
const {c = 5, d: {e = 6} = {}} = o
const [g = 7, ...[h = 8]] = xs
let i = 3
var j = 4
for (const x of xs) { k(x) }
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_parameter_property_init_not_assignment() {
check_metrics::<TsxParser>(
"class C {
f: number = 0;
constructor(public x: number) { let z = 0; }
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_zero_abc() {
check_metrics::<RubyParser>("\n", "foo.rb", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_simple_assignment() {
check_metrics::<RubyParser>("def f\n a = 1\n b = 2\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_augmented_assignment() {
check_metrics::<RubyParser>(
"def f(x)\n a = 0\n a += x\n a -= 1\n a *= 2\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_logical_augmented_assignment() {
check_metrics::<RubyParser>("def f\n @x ||= 0\n @x &&= 1\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_method_call_branch() {
check_metrics::<RubyParser>(
"def f(obj)\n foo()\n obj.bar(1)\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_super_and_yield_branches() {
check_metrics::<RubyParser>("def f\n super\n yield\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.assignments_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_attr_macro_is_branch() {
check_metrics::<RubyParser>("class A\n attr_accessor :x\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.branches_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_comparison_conditions() {
check_metrics::<RubyParser>(
"def f(a, b)\n a == b\n a != b\n a < b\n a > b\n a <= b\n a >= b\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_superclass_clause_is_not_a_condition() {
check_metrics::<RubyParser>(
"class Foo < Bar\n def plain\n x = 1\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_operator_method_name_is_not_a_condition() {
check_metrics::<RubyParser>("def <(other)\n @v < other\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_every_comparison_operator_method_name_is_not_a_condition() {
check_metrics::<RubyParser>(
"def ==(o)\n 1\nend\n\
def !=(o)\n 1\nend\n\
def <=(o)\n 1\nend\n\
def >=(o)\n 1\nend\n\
def <=>(o)\n 1\nend\n\
def =~(o)\n 1\nend\n\
def <(o)\n 1\nend\n\
def >(o)\n 1\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
},
);
check_metrics::<RubyParser>(
"def cmp(a, b)\n a == b || a <= b || a <=> b\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_case_match_in_arms_are_conditions() {
check_metrics::<RubyParser>(
"def f(x)\n case x\n in 1 then :one\n in 2 then :two\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_case_match_guarded_wildcard_is_a_condition() {
check_metrics::<RubyParser>(
"def f(x)\n case x\n in _ if x then :y\n in _ then :default\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_case_match_bare_wildcard_is_not_a_condition() {
check_metrics::<RubyParser>(
"def f(x)\n case x\n in _ then :default\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_bare_predicate_control_flow_counts_one_condition() {
check_metrics::<RubyParser>(
"def f(flag)\n if flag\n a\n end\n unless flag\n b\n end\n while flag\n c\n end\n until flag\n d\n end\n a if flag\n b unless flag\n c while flag\n d until flag\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 8);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_bare_predicate_does_not_double_count_comparison_or_chain() {
check_metrics::<RubyParser>(
"def f(a, b)\n if a == b\n x\n end\n if a && b\n y\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_spaceship_and_case_equality() {
check_metrics::<RubyParser>(
"def f(a, b)\n a <=> b\n a === b\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_ternary_condition() {
check_metrics::<RubyParser>("def f(x)\n x == 0 ? :z : :nz\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_ternary_operand_slots_count_as_unary_conditions() {
check_metrics::<RubyParser>("def f\n x = a ? !b : !c\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
});
check_metrics::<RubyParser>("def f\n x = (a > 0) ? b : -b\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
check_metrics::<RubyParser>(
"def f\n x = a ? (b ? c : d) : e\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
check_metrics::<RubyParser>("def f\n x = (a) ? !b : !c\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
});
check_metrics::<RubyParser>("def f\n x = !a ? b : c\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_ternary_branch_operands_are_not_double_counted() {
check_metrics::<RubyParser>("def f\n x = a ? (b) : c\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
check_metrics::<RubyParser>("def f\n x = a ? b : (c)\nend\n", "foo.rb", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
check_metrics::<RubyParser>(
"def f\n x = a ?\n # note\n (b) : c\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
check_metrics::<RubyParser>(
"def f\n x = a ? b :\n # note\n (c)\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_case_when_arms() {
check_metrics::<RubyParser>(
"def f(x)\n case x\n when 1 then 'one'\n when 2 then 'two'\n else 'other'\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_elsif_and_else() {
check_metrics::<RubyParser>(
"def f(x)\n if x > 0\n 1\n elsif x < 0\n -1\n else\n 0\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_rescue_clause_condition() {
check_metrics::<RubyParser>(
"def f\n begin\n do_it\n rescue StandardError => e\n handle(e)\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_class_complex_function() {
check_metrics::<RubyParser>(
"class A\n def f(a, b)\n sum = a + b\n if sum > 0 && b == 0\n foo(sum)\n end\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_unary_conditions_in_chain() {
check_metrics::<RubyParser>(
"def f(a, b, c)\n if a && b || c\n puts \"x\"\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_keyword_and_or_chain_counts_operands() {
check_metrics::<RubyParser>(
"def f(a, b, c)\n if a and b or c\n puts \"x\"\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_negated_operand_is_unary_condition() {
check_metrics::<RubyParser>(
"def f(a, b)\n if a && !b\n puts \"x\"\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_comparison_operands_add_nothing() {
check_metrics::<RubyParser>(
"def f(x, y)\n if x > 0 && y > 0\n puts \"x\"\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_empty_module_zero() {
check_metrics::<PythonParser>("", "empty.py", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_plain_assignments_count() {
check_metrics::<PythonParser>("x = 1\ny = 2\nz = x\n", "foo.py", |metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_typed_assignment_counts_bare_annotation_does_not() {
check_metrics::<PythonParser>("x: int = 1\ny: int\n", "foo.py", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_augmented_assignments_count() {
check_metrics::<PythonParser>("x = 0\nx += 1\nx -= 1\nx *= 2\n", "foo.py", |metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_walrus_counts_as_assignment() {
check_metrics::<PythonParser>("if (n := 10) > 5:\n pass\n", "foo.py", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_calls_are_branches() {
check_metrics::<PythonParser>(
"def foo():\n pass\ndef bar():\n pass\nclass Baz:\n pass\nfoo()\nbar()\nBaz()\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.assignments_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_comparisons_count_conditions() {
check_metrics::<PythonParser>(
"def f(x, y):\n a = x > 0\n b = x == y\n c = x is None\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
assert_eq!(metric.abc.assignments_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_chained_comparison_counts_once() {
check_metrics::<PythonParser>("def f(x):\n return 0 < x < 10\n", "foo.py", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_number_truthy_condition_counts() {
check_metrics::<PythonParser>(
"def f(a):\n if 5:\n pass\n return a and 2\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_boolean_operators_not_counted_directly() {
check_metrics::<PythonParser>(
"def f(a, b, c):\n if a and b or c:\n pass\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_unary_not_counts_as_condition() {
check_metrics::<PythonParser>(
"def f(flag):\n if not flag:\n return 1\n return 0\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_return_unary_not_counts() {
check_metrics::<PythonParser>("def f(flag):\n return not flag\n", "foo.py", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_unary_not_in_argument_list_counts() {
check_metrics::<PythonParser>(
"def f(ready, value):\n log(not ready, value)\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_unary_not_with_comparison_counts_each_once() {
check_metrics::<PythonParser>(
"def f(x):\n if not (x > 0):\n return 1\n return 0\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_unary_not_with_boolean_combinator_counts_each() {
check_metrics::<PythonParser>(
"def f(x, y):\n if not x and y:\n return 1\n return 0\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_control_flow_arms_count_conditions() {
check_metrics::<PythonParser>(
"def f(x):\n if x > 0:\n a = 1\n elif x > -1:\n a = 2\n else:\n a = 3\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_ternary_counts_as_condition() {
check_metrics::<PythonParser>(
"def f(c):\n return 1 if c > 0 else 0\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_ternary_condition_slot_counts_as_a_unary_condition() {
check_metrics::<PythonParser>(
"def f(a, b, c):\n return a if c() else b\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
check_metrics::<PythonParser>(
"def f(a, b, c):\n return a if (c) else b\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
check_metrics::<PythonParser>(
"def f(a, b, c):\n return a if not c else b\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
check_metrics::<PythonParser>(
"def f(a, b, c):\n return (not b) if a else (not c)\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_ternary_branch_operands_are_not_double_counted() {
check_metrics::<PythonParser>(
"def f(a, b, c):\n return (b) if a else (c)\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
check_metrics::<PythonParser>(
"def f(a, b, c):\n return b if a else c\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_ternary_condition_survives_an_interposed_comment() {
check_metrics::<PythonParser>(
"def f(b, c):\n return (b\n # why\n if f() else c)\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
check_metrics::<PythonParser>(
"def f(b, c):\n return (b if\n # why\n f() else c)\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_try_except_finally_count_conditions() {
check_metrics::<PythonParser>(
"def f():\n try:\n pass\n except ValueError:\n pass\n finally:\n pass\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_match_case_counts_conditions() {
check_metrics::<PythonParser>(
"def f(x):\n match x:\n case 1:\n pass\n case _:\n pass\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_match_case_guarded_wildcard_counts() {
check_metrics::<PythonParser>(
"def f(x):\n match x:\n case 1:\n pass\n case _ if x > 0:\n pass\n case _:\n pass\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_complex_function_abc() {
check_metrics::<PythonParser>(
"def f(items, threshold):\n\
\x20 result = []\n\
\x20 for item in items:\n\
\x20 if item > threshold:\n\
\x20 result.append(item)\n\
\x20 return result\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_if_multiple_conditions() {
check_metrics::<PythonParser>(
"def f(a, b, c, d):\n\
\x20 if a or b or c or d: # +4c\n\
\x20 pass\n\
\x20 if a and b and c: # +3c\n\
\x20 pass\n\
\x20 if not a and not b: # +2c (NotOperator x2)\n\
\x20 pass\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_while_conditions() {
check_metrics::<PythonParser>(
"def f(a, b):\n\
\x20 while a or b: # +2c\n\
\x20 break\n\
\x20 while a and not b: # +2c (a + NotOperator)\n\
\x20 break\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_short_circuit_with_boolean_literal_operand() {
check_metrics::<PythonParser>("def f(a):\n return a and True\n", "foo.py", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_await_expression_condition_counts() {
check_metrics::<PythonParser>(
"async def ready(): return True\n\
async def f():\n if await ready(): pass\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_if_call_terminal_condition_counts_once() {
check_metrics::<PythonParser>("def f():\n if foo(): pass\n", "foo.py", |metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "python")]
#[test]
fn python_if_boolean_literal_condition() {
check_metrics::<PythonParser>(
"def f(a):\n\
\x20 if True: pass # +1c\n\
\x20 if False: pass # +1c\n\
\x20 while True: break # +1c\n\
\x20 if a: pass # +1c (Rule 6 — bare identifier as condition)\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_methods_arguments_with_conditions() {
check_metrics::<PythonParser>(
"def f(a, b):\n\
\x20 m(a, b) # +1b\n\
\x20 m(not a, not b) # +1b +2c\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "python")]
#[test]
fn python_return_with_conditions() {
check_metrics::<PythonParser>(
"def m1(z): return not (z >= 0)\n\
def m2(x): return (((not x)))\n\
def m3(x, y): return x and y\n",
"foo.py",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 5);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_empty_unit_zero() {
check_metrics::<RustParser>("", "empty.rs", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "rust")]
#[test]
fn rust_assignments_let_init_plain_and_compound() {
check_metrics::<RustParser>(
"fn f() { let mut x = 0; x = 5; x += 2; x = 7; }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_let_without_initializer_does_not_count() {
check_metrics::<RustParser>(
"fn f() { let a: i32; let _b: i32; a = 5; }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_let_initializers_immutable_and_mutable_count() {
check_metrics::<RustParser>(
"fn f() { let a=1; let b=2; let c=a+b; let mut d=0; d=5; d+=1; }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_calls_are_branches() {
check_metrics::<RustParser>(
"fn f() { g(); 1.to_string(); String::new(); }\nfn g() {}\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_try_operator_is_branch() {
check_metrics::<RustParser>(
"fn f() -> Result<i32, ()> { let r: Result<i32, ()> = Err(()); Ok(r?) }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_comparisons_count_conditions() {
check_metrics::<RustParser>(
"fn f(a: i32, b: i32) -> bool { a < b || a > b || a <= b || a >= b || a == b || a != b }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_generic_brackets_not_conditions() {
check_metrics::<RustParser>(
"fn f() -> Vec<i32> { Vec::<i32>::new() }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_if_let_counts_as_condition() {
check_metrics::<RustParser>(
"fn f(opt: Option<i32>) { if let Some(_v) = opt { } }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_while_let_counts_as_condition() {
check_metrics::<RustParser>(
"fn f(mut it: std::vec::IntoIter<i32>) { while let Some(_y) = it.next() { } }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_match_arms_count_conditions_wildcard_excluded() {
check_metrics::<RustParser>(
"fn f(x: i32) -> i32 { match x { 0 => 1, n if n > 0 => n, _ => -1, } }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_else_counts_as_condition() {
check_metrics::<RustParser>(
"fn f(a: i32, b: i32) -> i32 { if a > b { a } else { b } }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_let_chain2_hidden_rule_drift_marker() {
let src = "fn f(a: bool, b: Option<i32>) {\n\
\x20 if a && let Some(_) = b { }\n\
}\n";
let parser = RustParser::new(
src.as_bytes().to_vec(),
&std::path::PathBuf::from("foo.rs"),
None,
);
assert!(!ast_has_kind_id(&parser, Rust::LetChain2 as u16));
}
#[cfg(feature = "rust")]
#[test]
fn rust_scoped_identifier_condition_counts() {
check_metrics::<RustParser>("fn f() { if crate::FLAG { } }\n", "foo.rs", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "rust")]
#[test]
fn rust_await_expression_condition_counts() {
check_metrics::<RustParser>(
"async fn ready() -> bool { true }\n\
async fn f() { if ready().await { } }\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_complex_function_abc() {
check_metrics::<RustParser>(
"fn f(opt: Option<i32>, xs: Vec<i32>) -> Result<i32, ()> {\n\
\x20 let mut x = 0;\n\
\x20 x = 5;\n\
\x20 x += 2;\n\
\x20 if let Some(_v) = opt { }\n\
\x20 let _ = xs.iter().next();\n\
\x20 let r: Result<i32, ()> = Err(());\n\
\x20 let _v = r?;\n\
\x20 Ok(match x {\n\
\x20 0 => 1,\n\
\x20 n if n > 0 => n,\n\
\x20 _ => -1,\n\
\x20 })\n\
}\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 6);
assert_eq!(metric.abc.branches_sum(), 5);
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_let_chain_bare_identifier_operand_counts() {
check_metrics::<RustParser>(
"fn f(a: bool, y: Option<i32>) {\n\
\x20 if a && let Some(_z) = y { }\n\
}\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_if_multiple_conditions() {
check_metrics::<RustParser>(
"fn f(a: bool, b: bool, c: bool, d: bool) -> i32 {\n\
\x20 if a || b || c || d { return 1; } // +4c\n\
\x20 if a && b && c { return 2; } // +3c\n\
\x20 if !a && !b { return 3; } // +2c\n\
\x20 0\n\
}\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_while_conditions() {
check_metrics::<RustParser>(
"fn f(a: bool, b: bool) {\n\
\x20 while a || b { break; } // +2c\n\
\x20 while a && !b { break; } // +2c\n\
}\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_if_boolean_literal_condition() {
check_metrics::<RustParser>(
"fn f() {\n\
\x20 if true { } // +1c\n\
\x20 if !false { } // +1c\n\
\x20 while true { break; } // +1c\n\
}\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_methods_arguments_with_conditions() {
check_metrics::<RustParser>(
"fn f(a: bool, b: bool) {\n\
\x20 m(a, b); // +1b\n\
\x20 m(!a, !b); // +1b +2c\n\
\x20 m(!a, b, !a); // +1b +2c\n\
}\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_return_with_conditions() {
check_metrics::<RustParser>(
"fn m1(z: i32) -> bool { return !(z >= 0); }\n\
fn m2(x: bool) -> bool { return (((!x))); }\n\
fn m3(x: bool, y: bool) -> bool { return x && y; }\n\
fn m4(y: bool, z: i32) -> bool { return y || (z < 0); }\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_short_circuit_with_boolean_literal_operand() {
check_metrics::<RustParser>(
"fn f(a: bool) -> bool { a && true }\n",
"foo.rs",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_empty_unit_zero() {
check_metrics::<GoParser>("package main\n", "empty.go", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "go")]
#[test]
fn go_assignments_count_plain_compound_short_var_and_incdec() {
check_metrics::<GoParser>(
"package main\nfunc f() { var y = 1; _ = y; x := 0; x = 5; x += 2; x = 7; x++ }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 7);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_var_declarations_count_only_when_initialized() {
check_metrics::<GoParser>(
"package main\nfunc f() int {\n\tvar x = 5\n\tvar y int = 6\n\tvar w int\n\tconst c = 1\n\tz := 7\n\treturn x + y + w + c + z\n}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_grouped_var_block_counts_each_initialized_spec() {
check_metrics::<GoParser>(
"package main\nfunc f() {\n\tvar (\n\t\ta = 1\n\t\tb int\n\t)\n\tvar p, q = 1, 2\n\tr := 0\n\t_ = a + b + p + q + r\n}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_calls_are_branches() {
check_metrics::<GoParser>(
"package main\n\
type R struct{}\n\
func (r R) Inc() {}\n\
func g() {}\n\
func f(s string) { g(); var r R = R{}; r.Inc(); _ = len(s) }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_comparisons_count_conditions() {
check_metrics::<GoParser>(
"package main\nfunc f(a, b int) bool { return a < b || a > b || a <= b || a >= b || a == b || a != b }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_generic_brackets_not_conditions() {
check_metrics::<GoParser>(
"package main\nfunc Min[T int | float64](a, b T) T { return a }\nfunc f() { _ = Min[int](1, 2) }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_switch_arms_count_conditions_default_excluded() {
check_metrics::<GoParser>(
"package main\nfunc f(x int) int { switch x { case 1: return 1; case 2: return 2; case 3: return 3; default: return 0 } }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_type_switch_arms_count_conditions() {
check_metrics::<GoParser>(
"package main\nfunc f(v interface{}) { switch v.(type) { case int: return; case string: return; default: return } }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_select_arms_count_conditions() {
check_metrics::<GoParser>(
"package main\nfunc f(ch chan int) { select { case <-ch: return; case ch <- 1: return; default: return } }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_else_counts_as_condition() {
check_metrics::<GoParser>(
"package main\nfunc f(a, b int) int { if a > b { return a } else { return b } }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_complex_function_abc() {
check_metrics::<GoParser>(
"package main\nfunc f(s string) int {\n\
\x20 var x = 10\n\
\x20 _ = x\n\
\x20 n := 0\n\
\x20 if n < 10 { n = n + 1 } else { n += 2 }\n\
\x20 n++\n\
\x20 _ = len(s)\n\
\x20 switch n {\n\
\x20 case 0: return 0\n\
\x20 case 1: return 1\n\
\x20 default: return n\n\
\x20 }\n\
}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 7);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_if_multiple_conditions() {
check_metrics::<GoParser>(
"package p\n\
func F(a, b, c, d bool) int {\n\
\x20 if a || b || c || d { return 1 } // +4c\n\
\x20 if a && b && c { return 2 } // +3c\n\
\x20 if !a && !b { return 3 } // +2c\n\
\x20 return 0\n\
}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_for_with_conditions() {
check_metrics::<GoParser>(
"package p\n\
func F(a, b bool) {\n\
\x20 for a || b { break } // +2c\n\
\x20 for a && !b { break } // +2c\n\
}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_for_bare_condition_counts() {
check_metrics::<GoParser>(
"package p\n\
func F(ready bool) {\n\
\x20 for true { break } // +1c\n\
\x20 for !ready { break } // +1c\n\
\x20 for i := 0; i < 3; i++ { _ = i } // +1c (the `<`)\n\
}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_three_clause_for_condition_counts() {
check_metrics::<GoParser>(
"package p\n\
func F(a bool) {\n\
\x20 for i := 0; a; i++ { _ = i }\n\
}\n",
"foo.go",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<GoParser>(
"package p\n\
func F(a bool) {\n\
\x20 for i := 0; !a; i++ { _ = i }\n\
}\n",
"foo.go",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<GoParser>(
"package p\n\
func F() {\n\
\x20 for i := 0; ; i++ { break }\n\
}\n",
"foo.go",
|metric| assert_eq!(metric.abc.conditions_sum(), 0),
);
check_metrics::<GoParser>(
"package p\n\
func F(xs []int) {\n\
\x20 for _, v := range xs { _ = v }\n\
}\n",
"foo.go",
|metric| assert_eq!(metric.abc.conditions_sum(), 0),
);
}
#[cfg(feature = "go")]
#[test]
fn go_for_header_slot_skips_comments_and_the_body() {
let cases = [
("for a { break }", 1),
("for /* n */ a { break }", 1),
("for i := 0; a; i++ { _ = i }", 1),
("for /* n */ i := 0; a; i++ { _ = i }", 1),
("for i := 0; /* n */ a; i++ { _ = i }", 1),
("for { break }", 0),
("for /* n */ { break }", 0),
("for _, v := range xs { _ = v }", 0),
("for /* n */ _, v := range xs { _ = v }", 0),
];
let mut ran = 0;
for (body, expected) in cases {
let src = format!("package p\nfunc F(a bool, xs []int) {{\n\t{body}\n}}\n");
assert_eq!(abc_conditions(LANG::Go, &src), expected, "`{body}`");
ran += 1;
}
assert_eq!(ran, cases.len());
assert!(cases.iter().any(|&(_, n)| n == 1));
assert!(cases.iter().any(|&(_, n)| n == 0));
}
#[cfg(feature = "go")]
#[test]
fn go_if_init_statement_condition_counts() {
check_metrics::<GoParser>(
"package p\nfunc F() { if x := g(); x { } }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_if_boolean_literal_condition() {
check_metrics::<GoParser>(
"package p\n\
func F() {\n\
\x20 if true {} // +1c\n\
\x20 if !false {} // +1c\n\
}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_methods_arguments_with_conditions() {
check_metrics::<GoParser>(
"package p\n\
func F(a, b bool) {\n\
\x20 m(a, b) // +1b\n\
\x20 m(!a, !b) // +1b +2c\n\
}\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_return_with_conditions() {
check_metrics::<GoParser>(
"package p\n\
func M1(z int) bool { return !(z >= 0) }\n\
func M2(x bool) bool { return !x }\n\
func M3(x, y bool) bool { return x && y }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "go")]
#[test]
fn go_short_circuit_with_boolean_literal_operand() {
check_metrics::<GoParser>(
"package p\nfunc F(a bool) bool { return a && true }\n",
"foo.go",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_empty_unit_zero() {
check_metrics::<ElixirParser>(":ok\n", "foo.ex", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_defmodule_is_zero_branches() {
check_metrics::<ElixirParser>("defmodule Foo do\nend\n", "foo.ex", |metric| {
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_pattern_match_is_assignment() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f do\n x = 1\n y = x + 1\n y\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_pipeline_each_step_is_branch() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def normalize(s) do\n s |> String.trim() |> String.upcase()\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.branches_sum(), 5);
assert_eq!(metric.abc.assignments_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_comparisons_are_conditions() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(a, b) do\n a == b or a != b or a < b or a > b or a <= b or a >= b\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_strict_equality_is_condition() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(a, b) do\n a === b or a !== b\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_membership_is_a_condition_by_use() {
check_func_space::<ElixirParser, _>(
"defmodule Foo do\n\
def bare(a, b), do: a in b\n\
def negbare(a, b), do: a not in b\n\
def slot(a, b) do\n\
if a in b do\n\
IO.puts(\"x\")\n\
end\n\
end\n\
def cmp(a, b), do: a == b\n\
end\n",
"foo.ex",
|space| {
assert_members_score(
&space.spaces[0],
&[
("bare", 1, 1),
("negbare", 1, 1),
("slot", 2, 2),
("cmp", 1, 1),
],
);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_guard_when_is_condition() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) when x > 0 do\n :pos\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_case_is_condition_and_branch() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) do\n case x do\n 1 -> :one\n _ -> :other\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_cond_is_condition() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x) do\n cond do\n x > 0 -> :pos\n true -> :other\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_for_is_branch_not_condition() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(xs) do\n for x <- xs, do: x * 2\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_mixed_abc() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f do\n x = 1\n if x > 0 do\n side_effect()\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_unary_conditions_in_chain() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(a, b, c) do\n if a && b || c do\n IO.puts(\"x\")\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_keyword_not_negates_like_bang() {
check_func_space::<ElixirParser, _>(
"defmodule Foo do\n def kw(a, b) do\n if a && not b do\n IO.puts(\"x\")\n end\n end\n def bang(a, b) do\n if a && !b do\n IO.puts(\"x\")\n end\n end\nend\n",
"foo.ex",
|space| {
assert_members_score(&space.spaces[0], &[("kw", 3, 3), ("bang", 3, 3)]);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_comparison_operands_add_nothing() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(x, y) do\n if x > 0 && y > 0 do\n IO.puts(\"x\")\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_keyword_and_or_chain_counts_operands() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def f(a, b, c) do\n if a and b or c do\n IO.puts(\"x\")\n end\n end\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_sigil_delimiter_choice_is_abc_invariant() {
for src in ["x = ~s<hi>\n", "x = ~s(hi)\n"] {
check_metrics::<ElixirParser>(src, "foo.ex", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
});
}
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_lt_comparison_still_counts_beside_sigil() {
check_metrics::<ElixirParser>("x = ~s<hi>\ny = a < b\n", "foo.ex", |metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 1);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_operator_identifier_is_not_a_condition() {
check_func_space::<ElixirParser, _>(
"defmodule M do\n\
def asc(l), do: Enum.sort(l, &</2)\n\
def lt(a, b), do: Kernel.<(a, b)\n\
def big(a, b), do: Kernel.max(a, b)\n\
def cmp(a, b), do: a < b\n\
end\n",
"foo.ex",
|space| {
let module = &space.spaces[0];
let scored: Vec<(String, u64)> = module
.spaces
.iter()
.map(|f| {
(
f.name.clone().unwrap_or_default(),
f.metrics.abc.conditions(),
)
})
.collect();
assert_eq!(
scored,
vec![
("asc".to_owned(), 0),
("lt".to_owned(), 0),
("big".to_owned(), 0),
("cmp".to_owned(), 1),
],
);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_empty_unit_zero() {
check_metrics::<CppParser>("", "empty.cpp", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_plain_and_compound_assignments_count() {
check_metrics::<CppParser>(
"void f() { int x = 0; x = 5; x += 2; x = 7; }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_increment_and_decrement_count_as_assignment() {
check_metrics::<CppParser>(
"void f() { int x = 0; x++; --x; ++x; x--; }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 5);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_init_declarators_count_as_assignments() {
check_metrics::<CppParser>(
"void f() { int a=1; int b=2; int c=a+b; int d=0; d=5; d+=1; }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_declaration_without_initializer_does_not_count() {
check_metrics::<CppParser>("void f() { int a; a = 5; }", "foo.cpp", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_init_declarator_brace_paren_init_does_not_count() {
check_metrics::<CppParser>(
"void f() { int x(5); int y{7}; x = 1; }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_calls_are_branches() {
check_metrics::<CppParser>(
"struct S { void m(); }; void g(); void f() { g(); S s; s.m(); auto* p = new int(5); }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.assignments_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_comparisons_count_conditions() {
check_metrics::<CppParser>(
"#include <compare>\n\
bool f(int a, int b) {\n\
return a < b || a > b || a <= b || a >= b || a == b || a != b || (a <=> b) == 0;\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 8);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_short_circuit_ops_not_counted_directly() {
check_metrics::<CppParser>(
"bool f(int a, int b) { return a == b && a > 0 || b < 0; }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_generic_brackets_not_conditions() {
check_metrics::<CppParser>(
"#include <vector>\nstd::vector<int> f() { return std::vector<int>{}; }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_else_and_ternary_count_conditions() {
check_metrics::<CppParser>(
"int f(int a, int b) {\n\
if (a > b) { return a; } else { return b; }\n\
return (b < 0) ? -b : b;\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_ternary_operand_slots_count_as_unary_conditions() {
check_metrics::<CppParser>("void f() { x = a ? !b : !c; }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
});
check_metrics::<CppParser>("void f() { x = (a > 0) ? b : -b; }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
check_metrics::<CppParser>("void f() { x = a ? b ? c : d : e; }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
});
check_metrics::<CppParser>("void f() { x = !a ? b : c; }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_elided_ternary_consequence_still_walks_the_alternative() {
check_metrics::<CppParser>("void f() { x = a ?: !b; }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
});
}
#[cfg(all(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
#[test]
fn c_family_ternary_operand_slots_agree_with_cpp() {
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
const SRC: &str = "void f() { x = a ? !b : !c; }\n";
let conditions = abc_conditions;
let cpp = conditions(LANG::Cpp, SRC);
assert_eq!(cpp, 4, "C++ reference value for `a ? !b : !c`");
assert_eq!(conditions(LANG::C, SRC), cpp, "C must match C++");
assert_eq!(conditions(LANG::Mozcpp, SRC), cpp, "Mozcpp must match C++");
assert_eq!(
conditions(
LANG::Objc,
"@implementation Foo\n\
- (void)bar {\n\
x = a ? !b : !c;\n\
}\n\
@end\n",
),
cpp,
"ObjC must match C++"
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_for_condition_slot_counts_unary_conditions() {
check_metrics::<CppParser>("void f(int a) { for (; a; ) {} }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
});
check_metrics::<CppParser>("void f(int a) { for (; !a; ) {} }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
});
check_metrics::<CppParser>("void f(int a) { for (; (a); ) {} }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
});
check_metrics::<CppParser>(
"void f(int n) { for (int i = 0; i < n; i++) {} }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
check_metrics::<CppParser>("void f() { for (;;) { break; } }", "foo.cpp", |metric| {
assert_eq!(metric.abc.conditions_sum(), 0);
});
}
#[cfg(all(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
#[test]
fn c_family_for_condition_slot_agrees_with_cpp() {
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
const SRC: &str = "void f(int a) { for (; !a; ) {} }\n";
let conditions = abc_conditions;
let cpp = conditions(LANG::Cpp, SRC);
assert_eq!(cpp, 1, "C++ reference value for `for (; !a; )`");
assert_eq!(conditions(LANG::C, SRC), cpp, "C must match C++");
assert_eq!(conditions(LANG::Mozcpp, SRC), cpp, "Mozcpp must match C++");
assert_eq!(
conditions(
LANG::Objc,
"@implementation Foo\n\
- (void)bar {\n\
for (; !a; ) {}\n\
}\n\
@end\n",
),
cpp,
"ObjC must match C++"
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_switch_cases_count_default_excluded() {
check_metrics::<CppParser>(
"void f(int x) {\n\
switch (x) {\n\
case 1: break;\n\
case 2: break;\n\
default: break;\n\
}\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_try_catch_count_conditions() {
check_metrics::<CppParser>(
"void f() { try { } catch (int) { } catch (...) { } }",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_complex_function_abc() {
check_metrics::<CppParser>(
"int f(int a, int b) {\n\
int x = 0;\n\
x = 5;\n\
x += 2;\n\
x++;\n\
if (a == b && a > 0) {\n\
x = (a > b) ? a : b;\n\
} else if (a < b || !x) {\n\
x = b;\n\
}\n\
switch (x) {\n\
case 1: break;\n\
case 2: break;\n\
default: break;\n\
}\n\
auto* p = new int(5);\n\
return f(a, b);\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 7);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_if_multiple_conditions() {
check_metrics::<CppParser>(
"void f(bool a, bool b, bool c, bool d) {\n\
\x20 if (a || b || c || d) {} // +4c\n\
\x20 if (a && b && c) {} // +3c\n\
\x20 if (!a && !b) {} // +2c\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_while_and_do_while_conditions() {
check_metrics::<CppParser>(
"void f(bool a, bool b) {\n\
\x20 while (a || b) {} // +2c\n\
\x20 do {} while (a && !b); // +2c\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_if_constexpr_condition_counts() {
check_metrics::<CppParser>(
"template <int N> void f() {\n\
\x20 if constexpr (true) { } // +1c\n\
\x20 if (false) { } // +1c\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_cast_expression_in_logical_chain_counts() {
check_metrics::<CppParser>(
"void f(void* ptr, bool ready) { if ((bool)ptr && ready) { } }\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_qualified_identifier_condition_counts() {
check_metrics::<CppParser>(
"namespace n { extern bool flag; }\n\
void f() { if (n::flag) { } }\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_if_boolean_literal_condition() {
check_metrics::<CppParser>(
"void f() {\n\
\x20 if (true) {} // +1c\n\
\x20 if (!false) {} // +1c\n\
\x20 while (true) {} // +1c\n\
\x20 do {} while (false); // +1c\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_methods_arguments_with_conditions() {
check_metrics::<CppParser>(
"void f(bool a, bool b) {\n\
\x20 m(a, b); // +1b\n\
\x20 m(!a, !b); // +1b +2c\n\
}\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_return_with_conditions() {
check_metrics::<CppParser>(
"bool m1(int z) { return !(z >= 0); }\n\
bool m2(bool x) { return (((!x))); }\n\
bool m3(bool x, bool y) { return x && y; }\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "cpp")]
#[test]
fn cpp_short_circuit_with_boolean_literal_operand() {
check_metrics::<CppParser>(
"bool f(bool a) { return a && true; }\n",
"foo.cpp",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_empty_unit_zero() {
check_metrics::<JavascriptParser>("", "empty.js", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_plain_and_compound_assignments_count() {
check_metrics::<JavascriptParser>(
"function f() { let x = 0; x = 5; x += 2; x = 7; }",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_const_initializer_not_assignment() {
check_metrics::<JavascriptParser>(
"function f() { const PI = 3.14; let x = 1; var y = 2; x = 9; }",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_asi_const_does_not_suppress_later_assignments() {
check_metrics::<JavascriptParser>(
"function f() {
const a = 1
x = 2
return x
}",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_nested_arrow_const_does_not_leak() {
check_metrics::<JavascriptParser>(
"function h() {
const f = () => { y = 1 }
z = 2
}",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_non_declarator_equals_still_count() {
check_metrics::<JavascriptParser>(
"class K { f = 1; g(p = 2, {q = 3} = {}) { let d = 4 } }",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 5);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_const_initializer_value_assignments_still_count() {
check_metrics::<JavascriptParser>(
"function m(o, a, b) { const x = (o.p = 1); const y = a || (b = 2); }",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_const_declarator_shapes_stay_suppressed() {
check_metrics::<JavascriptParser>(
"function f(o, xs) {
const a = 1, b = 2
const {c = 5, d: {e = 6} = {}} = o
const [g = 7, ...[h = 8]] = xs
let i = 3
var j = 4
for (const x of xs) { k(x) }
}",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_increment_and_decrement_count_as_assignment() {
check_metrics::<JavascriptParser>(
"function f() { let x = 0; x++; --x; }",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_calls_are_branches() {
check_metrics::<JavascriptParser>(
"function f() { g(1); new Foo(2); }",
"foo.js",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_comparisons_count_conditions() {
check_metrics::<JavascriptParser>(
"function f(a, b) { return a == b && a === b && a != b && a !== b && a < b && a > b && a <= b && a >= b; }",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 8);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_jsx_elements_are_not_conditions() {
check_metrics::<JavascriptParser>(
"function f(a, b) {
if (a < b) { return <div className=\"x\"><span>hi</span></div>; }
return <br />;
}",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_number_truthy_condition_counts() {
check_metrics::<JavascriptParser>(
"function f(x) { while (5) {} return x && 5; }",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_number_truthy_condition_counts() {
check_metrics::<TypescriptParser>(
"function f(x: number) { while (5) {} return x && 5; }",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_nullish_coalescing_counts_condition() {
check_metrics::<JavascriptParser>(
"function f(a, b) { return a ?? b; }",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_else_ternary_case_default_try_catch() {
check_metrics::<JavascriptParser>(
"function f(a) { if (a > 0) {} else {} let x = a ? 1 : 2; switch (x) { case 1: break; default: break; } try { } catch (e) { } }",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 7);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_ternary_operand_slots_count_as_unary_conditions() {
check_metrics::<JavascriptParser>(
"function f() { x = a ? !b : !c; }",
"foo.js",
|metric| assert_eq!(metric.abc.conditions_sum(), 4),
);
check_metrics::<JavascriptParser>(
"function f() { x = (a > 0) ? b : -b; }",
"foo.js",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
check_metrics::<JavascriptParser>(
"function f() { x = a ? b ? c : d : e; }",
"foo.js",
|metric| assert_eq!(metric.abc.conditions_sum(), 4),
);
check_metrics::<JavascriptParser>("function f() { x = !a ? b : c; }", "foo.js", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_ternary_operand_slots_count_as_unary_conditions() {
check_metrics::<TypescriptParser>(
"function f() { x = a ? !b : !c; }",
"foo.ts",
|metric| assert_eq!(metric.abc.conditions_sum(), 4),
);
check_metrics::<TypescriptParser>(
"function f() { x = (a > 0) ? b : -b; }",
"foo.ts",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_for_condition_slot_counts_unary_conditions() {
check_metrics::<JavascriptParser>("function f(a) { for (; a; ) {} }", "foo.js", |metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
});
check_metrics::<JavascriptParser>(
"function f(a) { for (; !a; ) {} }",
"foo.js",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<JavascriptParser>(
"function f(a) { for (; (a); ) {} }",
"foo.js",
|metric| assert_eq!(metric.abc.conditions_sum(), 1),
);
check_metrics::<JavascriptParser>(
"function f(n) { for (let i = 0; i < n; i++) {} }",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
check_metrics::<JavascriptParser>(
"function f() { for (;;) { break; } }",
"foo.js",
|metric| assert_eq!(metric.abc.conditions_sum(), 0),
);
}
#[cfg(all(feature = "javascript", feature = "mozjs", feature = "typescript"))]
#[test]
fn js_family_for_condition_slot_agrees_with_javascript() {
#[cfg(any(feature = "javascript", feature = "mozjs", feature = "typescript"))]
const BARE: &str = "function f(a) { for (; a; ) {} }\n";
#[cfg(any(feature = "javascript", feature = "mozjs", feature = "typescript"))]
const EMPTY: &str = "function f() { for (;;) { break; } }\n";
let conditions = abc_conditions;
let bare = conditions(LANG::Javascript, BARE);
assert_eq!(bare, 1, "JavaScript reference value for `for (; a; )`");
assert_eq!(
conditions(LANG::Javascript, EMPTY),
0,
"JavaScript `for (;;)`"
);
for lang in [LANG::Mozjs, LANG::Typescript, LANG::Tsx] {
assert_eq!(conditions(lang, BARE), bare, "{lang:?} bare for-condition");
assert_eq!(conditions(lang, EMPTY), 0, "{lang:?} empty for-condition");
}
}
#[cfg(all(feature = "javascript", feature = "mozjs", feature = "typescript"))]
#[test]
fn js_family_ternary_operand_slots_agree_with_javascript() {
#[cfg(any(feature = "javascript", feature = "mozjs", feature = "typescript"))]
const SRC: &str = "function f(a, b, c) { x = a ? !b : !c; }\n";
let javascript = abc_conditions(LANG::Javascript, SRC);
assert_eq!(
javascript, 4,
"JavaScript reference value for `a ? !b : !c`"
);
for lang in [LANG::Mozjs, LANG::Typescript, LANG::Tsx] {
assert_eq!(
abc_conditions(lang, SRC),
javascript,
"{lang:?} ternary operand slots"
);
}
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_instanceof_counts_condition() {
check_metrics::<JavascriptParser>(
"function f(x) { return x instanceof Foo; }",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_complex_function_abc() {
check_metrics::<JavascriptParser>(
"function f(a, b) {\n\
let x = 0;\n\
x = 5;\n\
x += 2;\n\
x++;\n\
if (a == b && a > 0) {\n\
x = (a > b) ? a : b;\n\
} else if (a < b || !x) {\n\
x = b;\n\
}\n\
switch (x) {\n\
case 1: break;\n\
default: break;\n\
}\n\
let p = new Bar();\n\
return f(a, b);\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 7);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 8);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_asi_const_does_not_suppress_later_assignments() {
check_metrics::<MozjsParser>(
"function f() {
const a = 1
x = 2
y = 3
}",
"foo.jsm",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_const_declarator_shapes_stay_suppressed() {
check_metrics::<MozjsParser>(
"function f(o, xs) {
const a = 1, b = 2
const {c = 5, d: {e = 6} = {}} = o
const [g = 7, ...[h = 8]] = xs
let i = 3
var j = 4
for (const x of xs) { k(x) }
}",
"foo.jsm",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_complex_function_abc() {
check_metrics::<MozjsParser>(
"function f(a, b) {\n\
let x = 0;\n\
x = 5;\n\
x += 2;\n\
x++;\n\
if (a == b && a > 0) {\n\
x = (a > b) ? a : b;\n\
} else if (a < b || !x) {\n\
x = b;\n\
}\n\
switch (x) {\n\
case 1: break;\n\
default: break;\n\
}\n\
let p = new Bar();\n\
return f(a, b);\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 7);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 8);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_await_expression_condition_counts() {
check_metrics::<JavascriptParser>(
"async function ready() { return true; }\n\
async function f() { if (await ready()) { } }\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_member_expression_condition_counts() {
check_metrics::<JavascriptParser>(
"function f(o) {\n\
\x20 if (o.x) {} // +1c\n\
\x20 return o.x && o.y; // +2c (walker on &&)\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_if_boolean_literal_condition() {
check_metrics::<JavascriptParser>(
"function f() {\n\
\x20 if (true) {} // +1c\n\
\x20 if (!false) {} // +1c\n\
\x20 while (true) {} // +1c\n\
\x20 do {} while (false); // +1c\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_methods_arguments_with_conditions() {
check_metrics::<JavascriptParser>(
"function f(a, b) {\n\
\x20 m(a, b); // +1b\n\
\x20 m(!a, !b); // +1b +2c\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_return_with_conditions() {
check_metrics::<JavascriptParser>(
"function m1(z) { return !(z >= 0); }\n\
function m2(x) { return (((!x))); }\n\
function m3(x, y) { return x && y; }\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_if_boolean_literal_condition() {
check_metrics::<TypescriptParser>(
"function f() {\n\
\x20 if (true) {}\n\
\x20 if (!false) {}\n\
\x20 while (true) {}\n\
\x20 do {} while (false);\n\
}\n",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_methods_arguments_with_conditions() {
check_metrics::<TypescriptParser>(
"function f(a: boolean, b: boolean) {\n\
\x20 m(a, b);\n\
\x20 m(!a, !b);\n\
}\n",
"foo.ts",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_return_with_conditions() {
check_metrics::<TypescriptParser>(
"function m1(z: number): boolean { return !(z >= 0); }\n\
function m2(x: boolean): boolean { return (((!x))); }\n\
function m3(x: boolean, y: boolean): boolean { return x && y; }\n",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_if_boolean_literal_condition() {
check_metrics::<TsxParser>(
"function f() {\n\
\x20 if (true) {}\n\
\x20 if (!false) {}\n\
\x20 while (true) {}\n\
\x20 do {} while (false);\n\
}\n",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_methods_arguments_with_conditions() {
check_metrics::<TsxParser>(
"function f(a: boolean, b: boolean) {\n\
\x20 m(a, b);\n\
\x20 m(!a, !b);\n\
}\n",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_return_with_conditions() {
check_metrics::<TsxParser>(
"function m1(z: number): boolean { return !(z >= 0); }\n\
function m2(x: boolean): boolean { return (((!x))); }\n\
function m3(x: boolean, y: boolean): boolean { return x && y; }\n",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_jsx_elements_are_not_conditions() {
check_metrics::<MozjsParser>(
"function f(a, b) {
if (a < b) { return <div className=\"x\"><span>hi</span></div>; }
return <br />;
}",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
assert_eq!(metric.abc.assignments_sum(), 1);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_if_boolean_literal_condition() {
check_metrics::<MozjsParser>(
"function f() {\n\
\x20 if (true) {}\n\
\x20 if (!false) {}\n\
\x20 while (true) {}\n\
\x20 do {} while (false);\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_methods_arguments_with_conditions() {
check_metrics::<MozjsParser>(
"function f(a, b) {\n\
\x20 m(a, b);\n\
\x20 m(!a, !b);\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_return_with_conditions() {
check_metrics::<MozjsParser>(
"function m1(z) { return !(z >= 0); }\n\
function m2(x) { return (((!x))); }\n\
function m3(x, y) { return x && y; }\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_if_multiple_conditions() {
check_metrics::<JavascriptParser>(
"function f(a, b, c, d) {\n\
\x20 if (a || b || c || d) {} // +4c\n\
\x20 if (a && b && c) {} // +3c\n\
\x20 if (!a && !b) {} // +2c\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_while_and_do_while_conditions() {
check_metrics::<JavascriptParser>(
"function f(a, b) {\n\
\x20 while (a || b) {} // +2c\n\
\x20 do {} while (a && !b); // +2c\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "javascript")]
#[test]
fn javascript_short_circuit_with_boolean_literal_operand() {
check_metrics::<JavascriptParser>(
"function f(a) { return a && true; }\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_if_multiple_conditions() {
check_metrics::<TypescriptParser>(
"function f(a: boolean, b: boolean, c: boolean, d: boolean) {\n\
\x20 if (a || b || c || d) {} // +4c\n\
\x20 if (a && b && c) {} // +3c\n\
\x20 if (!a && !b) {} // +2c\n\
}\n",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_while_and_do_while_conditions() {
check_metrics::<TypescriptParser>(
"function f(a: boolean, b: boolean) {\n\
\x20 while (a || b) {} // +2c\n\
\x20 do {} while (a && !b); // +2c\n\
}\n",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn typescript_short_circuit_with_boolean_literal_operand() {
check_metrics::<TypescriptParser>(
"function f(a: boolean): boolean { return a && true; }\n",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_if_multiple_conditions() {
check_metrics::<TsxParser>(
"function f(a: boolean, b: boolean, c: boolean, d: boolean) {\n\
\x20 if (a || b || c || d) {} // +4c\n\
\x20 if (a && b && c) {} // +3c\n\
\x20 if (!a && !b) {} // +2c\n\
}\n",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_while_and_do_while_conditions() {
check_metrics::<TsxParser>(
"function f(a: boolean, b: boolean) {\n\
\x20 while (a || b) {} // +2c\n\
\x20 do {} while (a && !b); // +2c\n\
}\n",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "typescript")]
#[test]
fn tsx_short_circuit_with_boolean_literal_operand() {
check_metrics::<TsxParser>(
"function f(a: boolean): boolean { return a && true; }\n",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_if_multiple_conditions() {
check_metrics::<MozjsParser>(
"function f(a, b, c, d) {\n\
\x20 if (a || b || c || d) {} // +4c\n\
\x20 if (a && b && c) {} // +3c\n\
\x20 if (!a && !b) {} // +2c\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_while_and_do_while_conditions() {
check_metrics::<MozjsParser>(
"function f(a, b) {\n\
\x20 while (a || b) {} // +2c\n\
\x20 do {} while (a && !b); // +2c\n\
}\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "mozjs")]
#[test]
fn mozjs_short_circuit_with_boolean_literal_operand() {
check_metrics::<MozjsParser>(
"function f(a) { return a && true; }\n",
"foo.js",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_empty_unit_zero() {
check_metrics::<PerlParser>("", "empty.pl", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "perl")]
#[test]
fn perl_plain_and_compound_assignments_count() {
check_metrics::<PerlParser>(
"sub f { my $x = 0; $x = 5; $x += 2; $x .= \"a\"; $x **= 3; }",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 5);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_calls_are_branches() {
check_metrics::<PerlParser>(
"sub f { foo(); bar 1, 2; my $a = shift; }",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_method_invocation_counts_as_branch() {
check_metrics::<PerlParser>(
"sub f { my $obj = shift; $obj->run($x); $obj->ping; }",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_readline_angle_brackets_are_not_conditions() {
check_func_space::<PerlParser, _>(
"sub g {\n\
my ($a, $b, $fh) = @_;\n\
my $l = <FH>;\n\
my $m = <$fh>;\n\
my $n = <STDIN>;\n\
if ($a < $b) { return 1; }\n\
if ($a > $b) { return 2; }\n\
return 0;\n\
}",
"foo.pl",
|space| {
assert_deepest_conditions_match_cyclomatic(&space, 2);
assert_eq!(space.metrics.abc.assignments_sum(), 4);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_numeric_and_string_comparisons_count_conditions() {
check_metrics::<PerlParser>(
"sub f {\n\
my $r;\n\
$r = $a == $b;\n\
$r = $a != $b;\n\
$r = $a < $b;\n\
$r = $a > $b;\n\
$r = $a <= $b;\n\
$r = $a >= $b;\n\
$r = $a <=> $b;\n\
$r = $a eq $b;\n\
$r = $a ne $b;\n\
$r = $a lt $b;\n\
$r = $a gt $b;\n\
$r = $a le $b;\n\
$r = $a ge $b;\n\
$r = $a cmp $b;\n\
}",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 14);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 14);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_short_circuit_not_counted_directly_ternary_counts() {
check_metrics::<PerlParser>(
"sub f {\n\
my $r;\n\
$r = $a && $b;\n\
$r = $a || $b;\n\
$r = $a // $b;\n\
$r = $a and $b;\n\
$r = $a or $b;\n\
$r = $a xor $b;\n\
$r = $a ? 1 : 2;\n\
}",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 7);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 10);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_ternary_operand_slots_count_as_unary_conditions() {
check_metrics::<PerlParser>("sub f { my $x = $a ? !$b : !$c; }", "foo.pl", |metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
});
check_metrics::<PerlParser>(
"sub f { my $x = ($a > 0) ? $b : -$b; }",
"foo.pl",
|metric| assert_eq!(metric.abc.conditions_sum(), 2),
);
check_metrics::<PerlParser>("sub f { my $x = !$a ? $b : $c; }", "foo.pl", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
});
check_metrics::<PerlParser>(
"sub f { my $x = $a ? ($b ? $c : $d) : $e; }",
"foo.pl",
|metric| assert_eq!(metric.abc.conditions_sum(), 4),
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_elsif_and_else_count_conditions() {
check_metrics::<PerlParser>(
"sub f {\n\
my $x = 0;\n\
if ($a == $b) {\n\
$x = 1;\n\
} elsif ($a < $b) {\n\
$x = 2;\n\
} else {\n\
$x = 3;\n\
}\n\
}",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_regex_match_operators_count_conditions() {
check_metrics::<PerlParser>(
"sub f { my $s = shift; my $m = $s =~ /foo/; my $n = $s !~ /bar/; }",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_complex_function_abc() {
check_metrics::<PerlParser>(
"sub run {\n\
my $total = 0;\n\
for (my $i = 0; $i < 10; $i++) {\n\
if ($i % 2 == 0) {\n\
do_work($i);\n\
} else {\n\
$total += $i;\n\
}\n\
}\n\
print \"done\\n\";\n\
return $total;\n\
}",
"foo.pl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_if_multiple_conditions() {
check_metrics::<PerlParser>(
"sub f {\n\
my ($a, $b, $c, $d) = @_;\n\
if ($a || $b || $c || $d) { return 1; } # +4c\n\
if ($a && $b && $c) { return 2; } # +3c\n\
if (!$a && !$b) { return 3; } # +2c\n\
return 0;\n\
}",
"foo.pl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_while_and_until_conditions() {
check_metrics::<PerlParser>(
"sub f {\n\
my ($a, $b) = @_;\n\
while ($a || $b) { last; } # +2c\n\
until ($a && !$b) { last; } # +2c\n\
}",
"foo.pl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_for_header_condition_slot_counts_unary_conditions() {
let cases = [
("for (my $i = 0; $ok; $i++) { }", 1),
("for (my $i = 0; !$ok; $i++) { }", 1),
("for (my $i = 0; ($ok); $i++) { }", 1),
("for (my $i = 0; $i < $n; $i++) { }", 1),
("for (my $i = 0; $ok && $j; $i++) { }", 2),
("for (;;) { last; }", 0),
("for my $x (@l) { }", 0),
];
let mut ran = 0;
for (body, expected) in cases {
let src = format!("sub f {{ {body} }}\n");
assert_eq!(abc_conditions(LANG::Perl, &src), expected, "`{body}`");
ran += 1;
}
assert_eq!(ran, cases.len());
assert!(cases.iter().any(|&(_, n)| n == 0));
assert!(cases.iter().any(|&(_, n)| n == 2));
for condition in ["$ok", "!$ok", "($ok)", "$ok && $j"] {
let in_for = format!("sub f {{ for (my $i = 0; {condition}; $i++) {{ }} }}\n");
let in_if = format!("sub f {{ if ({condition}) {{ }} }}\n");
assert_eq!(
abc_conditions(LANG::Perl, &in_for),
abc_conditions(LANG::Perl, &in_if),
"`{condition}` must score alike in a `for` header and an `if`",
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_short_circuit_counts_scalar_variable_operands() {
check_metrics::<PerlParser>(
"sub f { my ($a) = @_; return $a && $b; }\n",
"foo.pl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_array_in_binary_operand_descends_to_scalar_context_value() {
check_metrics::<PerlParser>(
"sub f { my ($a, $x, $y) = @_;\n\
\x20 my $r = $a || ($x, $y); # +2c: $a + last-named $y\n\
\x20 my $s = $a || ($x); # +2c: $a + only-named $x\n\
\x20 $r + $s;\n\
}\n",
"foo.pl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_if_scalar_variable_condition() {
check_metrics::<PerlParser>(
"sub f { my ($a) = @_; if ($a) { return 1; } }\n",
"foo.pl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_methods_arguments_with_conditions() {
check_metrics::<PerlParser>(
"sub f { my ($a, $b) = @_; call($a, $b); call(!$a, !$b); }\n",
"foo.pl",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "perl")]
#[test]
fn perl_return_with_conditions() {
check_metrics::<PerlParser>(
"sub m1 { my ($z) = @_; return !($z); }\n\
sub m2 { my ($x) = @_; return (((!$x))); }\n\
sub m3 { my ($x, $y) = @_; return $x && $y; }\n",
"foo.pl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_empty_unit_zero() {
check_metrics::<LuaParser>("", "empty.lua", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "lua")]
#[test]
fn lua_assignments_count_locals_and_plain() {
check_metrics::<LuaParser>(
"function f()\n\
local x = 0\n\
x = 1\n\
local a, b = 1, 2\n\
a, b = b, a\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_calls_are_branches() {
check_metrics::<LuaParser>(
"function r(x)\n\
print(x)\n\
obj.m(x)\n\
obj:m(x)\n\
return f(g(1))\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 5);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_comparisons_count_logical_ops_do_not() {
check_metrics::<LuaParser>(
"function f(a, b)\n\
local r\n\
r = a == b\n\
r = a ~= b\n\
r = a < b\n\
r = a > b\n\
r = a <= b\n\
r = a >= b\n\
r = a and b\n\
r = a or b\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 8);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 10);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_variable_attributes_are_not_conditions() {
check_metrics::<LuaParser>(
"local function f(a, b)\n\
local x <const> = 1\n\
local y <close> = nil\n\
if a < b then return x end\n\
return y\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_elseif_and_else_count_conditions() {
check_metrics::<LuaParser>(
"function f(x)\n\
if x > 0 then\n\
return 1\n\
elseif x < 0 then\n\
return -1\n\
else\n\
return 0\n\
end\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_complex_function_abc() {
check_metrics::<LuaParser>(
"function run(n)\n\
local total = 0\n\
for i = 1, n do\n\
if i % 2 == 0 then\n\
do_work(i)\n\
else\n\
total = total + i\n\
end\n\
end\n\
print(\"done\")\n\
return total\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 2);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_if_multiple_conditions() {
check_metrics::<LuaParser>(
"function f(a, b, c, d)\n\
if a or b or c or d then return 1 end -- +4c\n\
if a and b and c then return 2 end -- +3c\n\
if not a and not b then return 3 end -- +2c\n\
return 0\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_while_conditions() {
check_metrics::<LuaParser>(
"function f(a, b)\n\
while a or b do break end -- +2c\n\
repeat break until a and not b -- +2c\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_short_circuit_with_boolean_literal_operand() {
check_metrics::<LuaParser>("function f(a) return a and true end", "foo.lua", |metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "lua")]
#[test]
fn lua_number_truthy_condition_counts() {
check_metrics::<LuaParser>(
"function f(a)\n\
\x20 if 1 then return 1 end\n\
\x20 return a and 2\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_if_boolean_literal_condition() {
check_metrics::<LuaParser>(
"function f()\n\
if true then end -- +1c\n\
if not false then end -- +1c\n\
while true do break end -- +1c\n\
repeat break until false -- +1c\n\
end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_methods_arguments_with_conditions() {
check_metrics::<LuaParser>(
"function f(a, b) m(a, b); m(not a, not b) end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "lua")]
#[test]
fn lua_return_with_conditions() {
check_metrics::<LuaParser>(
"function m1(z) return not (z >= 0) end\n\
function m2(x) return (((not x))) end\n\
function m3(x, y) return x and y end",
"foo.lua",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_empty_unit_zero() {
check_metrics::<TclParser>("", "empty.tcl", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_set_command_counts_assignment() {
check_metrics::<TclParser>(
"proc f {} {\n\
set x 1\n\
set y 2\n\
set x [expr {$x + $y}]\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_incr_append_lappend_count_assignment() {
check_metrics::<TclParser>(
"proc f {} {\n\
set x 0\n\
incr x\n\
append s \"hi\"\n\
lappend lst 1\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_qualified_mutator_commands_count_assignment() {
check_metrics::<TclParser>(
"proc f {} {\n\
::incr x\n\
::append s \"hi\"\n\
::lappend lst 1\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_namespaced_mutator_command_stays_a_branch() {
check_metrics::<TclParser>(
"proc f {} {\n\
ns::incr x\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 1);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_computed_command_name_is_not_an_assignment() {
check_metrics::<TclParser>(
"proc f {cmd x} {\n\
$cmd $x\n\
incr x\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 0);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_generic_commands_are_branches() {
check_metrics::<TclParser>(
"proc f {} {\n\
puts \"hello\"\n\
do_work 1 2\n\
return 0\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.conditions_sum(), 0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_comparisons_count_logical_ops_do_not() {
check_metrics::<TclParser>(
"proc f {a b} {\n\
set r [expr {$a == $b}]\n\
set r [expr {$a != $b}]\n\
set r [expr {$a < $b}]\n\
set r [expr {$a > $b}]\n\
set r [expr {$a <= $b}]\n\
set r [expr {$a >= $b}]\n\
set r [expr {$a eq $b}]\n\
set r [expr {$a ne $b}]\n\
set r [expr {$a && $b}]\n\
set r [expr {$a || $b}]\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 10);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 12);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_ternary_counts_condition() {
check_metrics::<TclParser>(
"proc f {a b c} {\n\
set r [expr {$a ? $b : $c}]\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_parenthesised_ternary_condition_matches_the_bare_form() {
let conditions = |source: &str| {
crate::test_support::metrics_verbatim(
crate::LANG::Tcl,
source.as_bytes(),
crate::MetricsOptions::default(),
)
.abc
.conditions_sum()
};
let bare = conditions("proc f {a b c} {\n set r [expr {$a ? !$b : !$c}]\n}");
assert_eq!(bare, 4, "the bare form is the documented reference value");
assert_eq!(
conditions("proc f {a b c} {\n set r [expr {($a) ? !$b : !$c}]\n}"),
bare,
"parenthesising the condition must not change the count"
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_parenthesised_negated_operands_match_the_bare_form() {
let conditions = |source: &str| {
crate::test_support::metrics_verbatim(
crate::LANG::Tcl,
source.as_bytes(),
crate::MetricsOptions::default(),
)
.abc
.conditions_sum()
};
for (bare, parenthesised) in [
(
"proc f {a} {\n if {!$a} { puts x }\n}",
"proc f {a} {\n if {!($a)} { puts x }\n}",
),
(
"proc f {a b} {\n if {$a && !$b} { puts x }\n}",
"proc f {a b} {\n if {$a && !($b)} { puts x }\n}",
),
(
"proc f {a b c} {\n set r [expr {$a ? !$b : !$c}]\n}",
"proc f {a b c} {\n set r [expr {$a ? !($b) : !$c}]\n}",
),
] {
let want = conditions(bare);
assert!(want > 0, "the bare form must count something: {bare}");
assert_eq!(
conditions(parenthesised),
want,
"parenthesising the negated operand changed the count\n bare: {bare}\n paren: {parenthesised}"
);
}
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_parenthesised_negated_operands_match_the_bare_form() {
let conditions = |source: &str| {
crate::test_support::metrics_verbatim(
crate::LANG::Irules,
source.as_bytes(),
crate::MetricsOptions::default(),
)
.abc
.conditions_sum()
};
let want = conditions("when X {\n if { !$a } { log local0. hi }\n}\n");
assert_eq!(want, 1);
assert_eq!(
conditions("when X {\n if { !($a) } { log local0. hi }\n}\n"),
want
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_bare_truthy_and_negated_predicates_count_one_condition() {
let conditions = |source: &str| {
crate::test_support::metrics_verbatim(
crate::LANG::Tcl,
source.as_bytes(),
crate::MetricsOptions::default(),
)
.abc
.conditions_sum()
};
assert_eq!(conditions("proc f {a} {\n if {$a} { puts x }\n}"), 1);
assert_eq!(conditions("proc f {a} {\n if {!$a} { puts x }\n}"), 1);
assert_eq!(conditions("proc f {a} {\n while {$a} { puts x }\n}"), 1);
assert_eq!(conditions("proc f {a} {\n while {!$a} { puts x }\n}"), 1);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_bare_truthy_elseif_predicate_counts_one_condition() {
let conditions = |source: &str| {
crate::test_support::metrics_verbatim(
crate::LANG::Tcl,
source.as_bytes(),
crate::MetricsOptions::default(),
)
.abc
.conditions_sum()
};
assert_eq!(
conditions("proc f {a b} {\n if {$a} { puts x } elseif {$b} { puts y }\n}"),
3
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_elseif_and_else_count_conditions() {
check_metrics::<TclParser>(
"proc f {x} {\n\
if {$x > 0} {\n\
return 1\n\
} elseif {$x < 0} {\n\
return -1\n\
} else {\n\
return 0\n\
}\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 3);
assert_eq!(metric.abc.conditions_sum(), 4);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_if_multiple_conditions() {
check_metrics::<TclParser>(
"proc f {a b c d} {\n\
if {[expr {$a || $b || $c || $d}]} { return 1 } \n\
if {[expr {$a && $b && $c}]} { return 2 } \n\
return 0\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 9);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_while_conditions() {
check_metrics::<TclParser>(
"proc f {a b} {\n\
while {[expr {$a || $b}]} { break } \n\
while {[expr {$a && $b}]} { break } \n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 6);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_short_circuit_with_boolean_literal_operand() {
check_metrics::<TclParser>(
"proc f {a} { return [expr {$a && 1}] }\n",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "tcl")]
#[test]
fn tcl_complex_function_abc() {
check_metrics::<TclParser>(
"proc run {n} {\n\
set total 0\n\
for {set i 0} {$i < $n} {incr i} {\n\
if {$i % 2 == 0} {\n\
do_work $i\n\
} else {\n\
incr total $i\n\
}\n\
}\n\
puts \"done\"\n\
return $total\n\
}",
"foo.tcl",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4);
assert_eq!(metric.abc.branches_sum(), 5);
assert_eq!(metric.abc.conditions_sum(), 2);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_set_assignment() {
check_metrics::<IrulesParser>("when X {\n set x 1\n}\n", "foo.irule", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.branches_sum(), 0);
assert_eq!(metric.abc.conditions_sum(), 0);
});
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_mutator_commands() {
check_metrics::<IrulesParser>(
"when X {\n incr x\n append s \"y\"\n lappend l 1\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_qualified_mutator_commands() {
check_metrics::<IrulesParser>(
"when X {\n ::incr x\n ::append s \"y\"\n ::lappend l 1\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.branches_sum(), 0);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_namespaced_mutator_command_stays_a_branch() {
check_metrics::<IrulesParser>("when X {\n ns::incr x\n}\n", "foo.irule", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 1);
});
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_branch_commands() {
check_metrics::<IrulesParser>(
"when X {\n log local0. hi\n pool p1\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0);
assert_eq!(metric.abc.branches_sum(), 2);
assert_eq!(metric.abc.conditions_sum(), 0);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_comparison_condition() {
check_metrics::<IrulesParser>(
"when X {\n if { $a == 1 } { log local0. hi }\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_string_op_condition() {
check_metrics::<IrulesParser>(
"when X {\n if { $a contains \"x\" } { log local0. hi }\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_elseif_else_conditions() {
check_metrics::<IrulesParser>(
"when X {\n if { $a } { set r 1 } elseif { $b } { set r 2 } else { set r 3 }\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3);
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_ternary_condition() {
check_metrics::<IrulesParser>(
"when X {\n set y [expr { $a > 0 ? 1 : 0 }]\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_negated_operands_in_chain() {
check_metrics::<IrulesParser>(
"when X {\n if { !$a && !$b } { log local0. hi }\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_bare_truthy_counts_one_condition() {
check_metrics::<IrulesParser>(
"when X {\n if { $a } { log local0. hi }\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_negated_bare_truthy_counts_one_condition() {
check_metrics::<IrulesParser>(
"when X {\n if { !$a } { log local0. hi }\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.branches_sum(), 1);
assert_eq!(metric.abc.conditions_sum(), 1);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_ternary_routes_its_operand_slots() {
check_metrics::<IrulesParser>(
"when X {\n set y [expr { $a ? !$b : !$c }]\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 4);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_comparison_ternary_is_unchanged_by_slot_routing() {
check_metrics::<IrulesParser>(
"when X {\n set y [expr { $a > 0 ? 1 : 0 }]\n}\n",
"foo.irule",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 2);
},
);
}
#[cfg(feature = "irules")]
#[test]
fn irules_abc_parenthesised_ternary_condition_matches_the_bare_form() {
let conditions = |source: &str| {
crate::test_support::metrics_verbatim(
crate::LANG::Irules,
source.as_bytes(),
crate::MetricsOptions::default(),
)
.abc
.conditions_sum()
};
let bare = conditions("when X {\n set y [expr { $a ? !$b : !$c }]\n}\n");
assert_eq!(bare, 4, "the bare form is the documented reference value");
assert_eq!(
conditions("when X {\n set y [expr { ($a) ? !$b : !$c }]\n}\n"),
bare,
"parenthesising the condition must not change the count"
);
}
#[cfg(feature = "rust")]
#[test]
fn rust_match_guard_scores_one_condition_however_spelled() {
let src = "fn is_even(n: i32) -> bool {
n == 0
}
fn tok(x: i32) -> i32 {
match x { n if n > 5 => 1, _ => 0 }
}
fn call(x: i32) -> i32 {
match x { n if is_even(n) => 1, _ => 0 }
}
fn bare(x: i32, b: bool) -> i32 {
match x { _ if b => 1, _ => 0 }
}
fn paren(x: i32, b: bool) -> i32 {
match x { n if (b) => n, _ => 0 }
}
fn none(x: i32) -> i32 {
match x { 1 => 1, _ => 0 }
}";
assert_fixture_spells::<RustParser>(
src,
"foo.rs",
&[
(Rust::If as u16, 4, "match guards"),
(Rust::GT as u16, 1, "`tok`'s `>`"),
(Rust::CallExpression as u16, 1, "the `is_even` call"),
(
Rust::ParenthesizedExpression as u16,
1,
"`paren`'s parenthesised guard",
),
(Rust::MatchPattern as u16, 10, "match arms"),
],
);
check_func_space::<RustParser, _>(src, "foo.rs", |space| {
assert_members_score(
&space,
&[
("is_even", 1, 1),
("tok", 2, 3),
("call", 2, 3),
("bare", 2, 3),
("paren", 2, 3),
("none", 1, 2),
],
);
});
}
#[cfg(feature = "java")]
#[test]
fn java_pattern_switch_guard_scores_one_condition_however_spelled() {
let src = "class T {
static boolean isEven(int i) { return i == 0; }
int tok(Object o) { return switch (o) { case Integer i when i > 5 -> 1; default -> 0; }; }
int call(Object o) { return switch (o) { case Integer i when isEven(i) -> 1; default -> 0; }; }
int bare(Object o, boolean b) { return switch (o) { case Integer i when b -> 1; default -> 0; }; }
int paren(Object o, boolean b) { return switch (o) { case Integer i when (b) -> 1; default -> 0; }; }
int none(Object o) { return switch (o) { case Integer i -> 1; default -> 0; }; }
}";
assert_fixture_spells::<JavaParser>(
src,
"foo.java",
&[
(Java::Guard as u16, 4, "`when` guards"),
(Java::GT as u16, 1, "`tok`'s `>`"),
(Java::MethodInvocation as u16, 1, "the `isEven` call"),
(
Java::ParenthesizedExpression as u16,
6,
"the switch subjects and `paren`'s guard",
),
],
);
check_func_space::<JavaParser, _>(src, "foo.java", |space| {
assert_members_score(
&space.spaces[0],
&[
("isEven", 1, 1),
("tok", 2, 3),
("call", 2, 3),
("bare", 2, 3),
("paren", 2, 3),
("none", 1, 2),
],
);
});
}
#[cfg(feature = "python")]
#[test]
fn python_case_guard_scores_one_condition_however_spelled() {
let src = "def is_even(n):
return n == 0
def tok(x):
match x:
case n if n > 5:
return 1
case _:
return 0
def call(x):
match x:
case n if is_even(x):
return 1
case _:
return 0
def bare(x, b):
match x:
case _ if b:
return 1
case _:
return 0
def paren(x, b):
match x:
case n if (b):
return n
case _:
return 0
def none(x):
match x:
case 1:
return 1
case _:
return 0
";
assert_fixture_spells::<PythonParser>(
src,
"foo.py",
&[
(Python::IfClause as u16, 4, "`case` guards"),
(
Python::ComparisonOperator as u16,
2,
"`tok`'s `>` and `is_even`'s `==`",
),
(Python::Call as u16, 1, "the `is_even` call"),
(
Python::ParenthesizedExpression as u16,
1,
"`paren`'s parenthesised guard",
),
],
);
check_func_space::<PythonParser, _>(src, "foo.py", |space| {
assert_members_score(
&space,
&[
("is_even", 1, 1),
("tok", 2, 3),
("call", 2, 3),
("bare", 2, 3),
("paren", 2, 3),
("none", 1, 2),
],
);
});
}
#[cfg(feature = "python")]
#[test]
fn python_comprehension_if_clause_is_not_a_case_guard() {
let src = "def m(xs):
return [x for x in xs if x]
";
assert_fixture_spells::<PythonParser>(
src,
"foo.py",
&[(Python::IfClause as u16, 1, "the comprehension filter")],
);
check_func_space::<PythonParser, _>(src, "foo.py", |space| {
assert_members_score(&space, &[("m", 0, 3)]);
});
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_in_clause_guard_scores_one_condition_however_spelled() {
let src = "def is_even(x)
x == 0
end
def tok(x)
case x
in [n] if n > 5 then 1
in _ then 0
end
end
def call(x)
case x
in [n] if n.even? then 1
in _ then 0
end
end
def bare(x, b)
case x
in [n] if b then 1
in _ then 0
end
end
def unguard(x, b)
case x
in [n] unless b then 1
in _ then 0
end
end
def paren(x, b)
case x
in [n] if (b) then 1
in _ then 0
end
end
def none(x)
case x
in [n] then 1
in _ then 0
end
end
";
assert_fixture_spells::<RubyParser>(
src,
"foo.rb",
&[
(Ruby::IfGuard as u16, 4, "`if` guards"),
(Ruby::UnlessGuard as u16, 1, "the `unless` guard"),
(Ruby::GT as u16, 1, "`tok`'s `>`"),
(
Ruby::ParenthesizedStatements as u16,
1,
"`paren`'s parenthesised guard",
),
],
);
let parser = RubyParser::new(
src.as_bytes().to_vec(),
&std::path::PathBuf::from("foo.rb"),
None,
);
assert!(
!ast_has_kind_id(&parser, Ruby::Guard as u16),
"`_guard` stopped being hidden — the defensive arms now fire"
);
check_func_space::<RubyParser, _>(src, "foo.rb", |space| {
assert_members_score(
&space,
&[
("is_even", 1, 1),
("tok", 2, 3),
("call", 2, 3),
("bare", 2, 3),
("unguard", 2, 3),
("paren", 2, 3),
("none", 1, 2),
],
);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_guard_is_a_decision_however_spelled() {
let src = "defmodule T do
def is_even(x) do
x == 0
end
def tok(x) do
case x do
n when n > 5 -> 1
_ -> 0
end
end
def call(x) do
case x do
n when is_integer(n) -> 1
_ -> 0
end
end
def bare(x, b) do
case x do
_n when b -> 1
_ -> 0
end
end
def none(x) do
case x do
1 -> 1
_ -> 0
end
end
end
";
assert_fixture_spells::<ElixirParser>(
src,
"foo.ex",
&[
(Elixir::When as u16, 3, "`when` guards"),
(Elixir::GT as u16, 1, "`tok`'s `>`"),
],
);
check_func_space::<ElixirParser, _>(src, "foo.ex", |space| {
assert_members_score(
&space.spaces[0],
&[
("is_even", 1, 1),
("tok", 2, 3),
("call", 2, 3),
("bare", 2, 3),
("none", 1, 2),
],
);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_guard_scores_one_however_spelled() {
let src = "defmodule T do
def tok(x) do
case x do
n when n > 5 -> 1
_ -> 0
end
end
def eq(x) do
case x do
n when n == 5 -> 1
_ -> 0
end
end
def call(x) do
case x do
n when is_integer(n) -> 1
_ -> 0
end
end
def bare(x, b) do
case x do
_n when b -> 1
_ -> 0
end
end
def paren(x, b) do
case x do
_n when (b) -> 1
_ -> 0
end
end
def negated(x, b) do
case x do
_n when not b -> 1
_ -> 0
end
end
def membership(x) do
case x do
n when n in [1, 2] -> 1
_ -> 0
end
end
def nonmembership(x) do
case x do
n when n not in [1, 2] -> 1
_ -> 0
end
end
end
";
assert_fixture_spells::<ElixirParser>(
src,
"foo.ex",
&[
(Elixir::When as u16, 8, "one `when` guard per member"),
(Elixir::GT as u16, 1, "`tok`'s `>`"),
(Elixir::EQEQ as u16, 1, "`eq`'s `==`"),
(Elixir::Block as u16, 1, "`paren`'s parenthesised guard"),
(Elixir::Not as u16, 1, "`negated`'s `not`"),
(Elixir::In as u16, 1, "`membership`'s `in`"),
(Elixir::Notin as u16, 1, "`nonmembership`'s `not in`"),
],
);
check_func_space::<ElixirParser, _>(src, "foo.ex", |space| {
assert_every_member_scores(
&space.spaces[0],
8,
2,
"one for the `case` arm and one for the guard, however the guard is spelled",
);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_repeated_guard_matches_the_or_chain() {
let src = "defmodule T do
def repeated(n) when is_integer(n) when is_float(n) when is_atom(n) do
n
end
def ored(n) when is_integer(n) or is_float(n) or is_atom(n) do
n
end
def single(n) when is_integer(n) do
n
end
def clause(x) do
case x do
n when is_integer(n) when is_float(n) -> 1
_ -> 0
end
end
def clause_or(x) do
case x do
n when is_integer(n) or is_float(n) -> 1
_ -> 0
end
end
end
";
assert_fixture_spells::<ElixirParser>(
src,
"foo.ex",
&[
(
Elixir::When as u16,
8,
"three repeated, one `or`-spelled, one single, two \
repeated and one `or`-spelled in a clause",
),
(Elixir::Or as u16, 3, "the two `or` spellings' operators"),
],
);
check_func_space::<ElixirParser, _>(src, "foo.ex", |space| {
let members: Vec<(u64, u64)> = space.spaces[0]
.spaces
.iter()
.map(|m| {
(
m.metrics.abc.conditions(),
m.metrics.cyclomatic.cyclomatic(),
)
})
.collect();
assert_eq!(
members,
vec![(3, 4), (3, 4), (1, 2), (3, 4), (3, 4)],
"a repeated guard scores one alternative per `when`; the \
single guard is the control one alternative lower"
);
assert_eq!(
members[0], members[1],
"`when a when b when c` is the or-chain `when a or b or c`"
);
assert_eq!(
members[3], members[4],
"and the same holds on a `stab_clause` anchor"
);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_typespec_when_is_not_a_guard() {
let src = "defmodule T do
@spec plain(a) :: a when a: integer
def plain(x) do
x
end
@spec guarded(a) :: a when a: integer
def guarded(x) when is_integer(x) do
x
end
end
";
assert_fixture_spells::<ElixirParser>(
src,
"foo.ex",
&[(
Elixir::When as u16,
3,
"two typespec `when`s and one head guard",
)],
);
check_func_space::<ElixirParser, _>(src, "foo.ex", |space| {
let module = &space.spaces[0];
let members: Vec<(u64, u64)> = module
.spaces
.iter()
.map(|m| {
(
m.metrics.abc.conditions(),
m.metrics.cyclomatic.cyclomatic(),
)
})
.collect();
assert_eq!(
members,
vec![(0, 1), (1, 2)],
"`plain` scores nothing; `guarded` scores its head guard \
in both metrics"
);
assert_eq!(
module.metrics.abc.conditions(),
0,
"a typespec `when` is type syntax, not a guard"
);
});
}
#[cfg(feature = "elixir")]
#[test]
fn elixir_defguard_head_is_a_guard() {
let src = "defmodule T do
defguard is_int(x) when is_integer(x)
defguardp is_at(x) when is_atom(x)
def plain(x) do
x
end
end
";
assert_fixture_spells::<ElixirParser>(
src,
"foo.ex",
&[(
Elixir::When as u16,
2,
"one per `defguard` / `defguardp` head",
)],
);
check_func_space::<ElixirParser, _>(src, "foo.ex", |space| {
let module = &space.spaces[0];
assert_eq!(
(
module.metrics.abc.conditions(),
module.metrics.cyclomatic.cyclomatic()
),
(2, 3),
"each of `defguard` and `defguardp` carries one guard, on \
both axes (cyclomatic counts from its base of 1)"
);
let members: Vec<(u64, u64)> = module
.spaces
.iter()
.map(|m| {
(
m.metrics.abc.conditions(),
m.metrics.cyclomatic.cyclomatic(),
)
})
.collect();
assert_eq!(
members,
vec![(0, 1)],
"`defguard` opens no function space, so `plain` is the \
module's only member and scores nothing"
);
});
}
#[cfg(feature = "csharp")]
#[test]
fn csharp_is_tests_score_outside_a_boolean_slot() {
let src = "class A {
bool outIs(object x) => x is int;
bool outPat(object x) => x is null;
bool outCtl(int x) => x == 1;
int inIs(object x) { if (x is int) { return 1; } return 0; }
int inAnd(object x, bool b) { if (x is int && b) { return 1; } return 0; }
}";
assert_csharp_fixture_spells(
src,
&[
(Csharp::IsExpression as u16, 3, "the bare `is` type tests"),
(
Csharp::IsPatternExpression as u16,
1,
"`outPat`'s null pattern",
),
(Csharp::EQEQ as u16, 1, "`outCtl`'s comparison"),
],
);
check_func_space::<CsharpParser, _>(src, "foo.cs", |space| {
assert_members_score(
&space.spaces[0],
&[
("outIs", 1, 1),
("outPat", 1, 1),
("outCtl", 1, 1),
("inIs", 1, 2),
("inAnd", 2, 3),
],
);
});
}
#[cfg(feature = "java")]
#[test]
fn java_instanceof_scores_outside_a_boolean_slot() {
let src = "class A {
boolean outOf(Object x) { return x instanceof String; }
boolean outCtl(int x) { return x == 1; }
int inOf(Object x) { if (x instanceof String) { return 1; } return 0; }
int inAnd(Object x, boolean b) { if (x instanceof String && b) { return 1; } return 0; }
}";
assert_fixture_spells::<JavaParser>(
src,
"foo.java",
&[
(
Java::InstanceofExpression as u16,
3,
"the `instanceof` tests",
),
(Java::EQEQ as u16, 1, "`outCtl`'s comparison"),
],
);
check_func_space::<JavaParser, _>(src, "foo.java", |space| {
assert_members_score(
&space.spaces[0],
&[
("outOf", 1, 1),
("outCtl", 1, 1),
("inOf", 1, 2),
("inAnd", 2, 3),
],
);
});
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_relational_productions_score_outside_a_boolean_slot() {
let src = "class A {
def outIs(x) { def b = x instanceof String; return b }
def outIn(x, l) { def b = x in l; return b }
def outShip(a, c) { def r = a <=> c; return r }
def outCtl(x) { def b = x == 1; return b }
def inIs(x) { if (x instanceof String) { return 1 }; return 0 }
def inIn(x, l) { if (x in l) { return 1 }; return 0 }
def forIn(l) { for (q in l) { }; return 0 }
}";
assert_fixture_spells::<GroovyParser>(
src,
"foo.groovy",
&[
(
Groovy::InstanceofExpression as u16,
2,
"the `instanceof` tests",
),
(Groovy::MembershipExpression as u16, 2, "the `in` tests"),
(Groovy::SpaceshipExpression as u16, 1, "`outShip`'s `<=>`"),
(
Groovy::ForInStatement as u16,
1,
"`forIn`'s loop header, which spells the same `in`",
),
(Groovy::EQEQ as u16, 1, "`outCtl`'s comparison"),
],
);
check_func_space::<GroovyParser, _>(src, "foo.groovy", |space| {
assert_members_score(
&space.spaces[0],
&[
("outIs", 1, 1),
("outIn", 1, 1),
("outShip", 1, 1),
("outCtl", 1, 1),
("inIs", 1, 2),
("inIn", 1, 2),
("forIn", 0, 2),
],
);
});
}
#[cfg(feature = "kotlin")]
#[test]
fn kotlin_is_and_in_score_outside_a_boolean_slot() {
let src = "class K {
fun outIs(a: Any): Boolean { val b = a is String; return b }
fun outIn(a: Int): Boolean { val b = a in 1..2; return b }
fun outCtl(a: Int): Boolean { val b = a == 1; return b }
fun inIs(a: Any): Int { if (a is String) { return 1 }; return 0 }
fun forIn(l: List<Int>): Int { for (q in l) { }; return 0 }
}";
assert_kotlin_class_members(
src,
&[
(Kotlin::IsExpression as u16, 2, "the `is` tests"),
(Kotlin::InExpression as u16, 1, "`outIn`'s `in` test"),
(
Kotlin::ForStatement as u16,
1,
"`forIn`'s loop header, which spells the same `in`",
),
(Kotlin::EQEQ as u16, 1, "`outCtl`'s comparison"),
],
&[
("outIs", 1, 1),
("outIn", 1, 1),
("outCtl", 1, 1),
("inIs", 1, 2),
("forIn", 0, 2),
],
);
}
#[cfg(feature = "ruby")]
#[test]
fn ruby_test_pattern_scores_outside_a_boolean_slot() {
let src = "def out_in(a)
b = a in Integer
b
end
def out_ctl(a)
b = a == 1
b
end
def in_in(a)
if a in Integer
return 1
end
0
end
def for_in(l)
for q in l do end
0
end
";
assert_fixture_spells::<RubyParser>(
src,
"foo.rb",
&[
(Ruby::TestPattern as u16, 2, "the one-line `in` tests"),
(
Ruby::For as u16,
1,
"`for_in`'s loop header, which spells the same `in`",
),
(Ruby::EQEQ as u16, 1, "`out_ctl`'s comparison"),
],
);
check_func_space::<RubyParser, _>(src, "foo.rb", |space| {
assert_members_score(
&space,
&[
("out_in", 1, 1),
("out_ctl", 1, 1),
("in_in", 1, 2),
("for_in", 0, 2),
],
);
});
}
#[cfg(feature = "python")]
#[test]
fn python_walrus_is_a_unary_condition() {
let src = "def in_slot(g):
if (n := g()):
return 1
return 0
def chain(g, b):
return b and (n := g())
def ctrl(g):
if g():
return 1
return 0
";
assert_fixture_spells::<PythonParser>(
src,
"foo.py",
&[(Python::NamedExpression as u16, 2, "the walrus bindings")],
);
check_func_space::<PythonParser, _>(src, "foo.py", |space| {
assert_members_score(
&space,
&[
("in_slot", 1, 2),
("chain", 2, 2),
("ctrl", 1, 2),
],
);
for (name, assignments) in [("in_slot", 1u64), ("chain", 1), ("ctrl", 0)] {
assert_eq!(
child_space(&space, name).metrics.abc.assignments(),
assignments,
"{name}: the walrus also pays on the A axis, on purpose"
);
}
});
}
#[cfg(feature = "rust")]
#[test]
fn rust_macro_invocation_is_a_unary_condition() {
let src = "fn in_slot(x: Option<u8>) -> u8 { if matches!(x, Some(_)) { 1 } else { 0 } }
fn cfg_slot() -> u8 { if cfg!(unix) { 1 } else { 0 } }
fn chain(x: Option<u8>, b: bool) -> bool { b && matches!(x, Some(_)) }
fn ctrl(x: Option<u8>) -> u8 { if x.is_some() { 1 } else { 0 } }
";
assert_fixture_spells::<RustParser>(
src,
"foo.rs",
&[(
Rust::MacroInvocation as u16,
3,
"the `matches!` / `cfg!` predicates",
)],
);
check_func_space::<RustParser, _>(src, "foo.rs", |space| {
assert_members_score(
&space,
&[
("in_slot", 2, 2),
("cfg_slot", 2, 2),
("chain", 2, 2),
("ctrl", 2, 2),
],
);
});
}
}
#[cfg(test)]
#[cfg(any(
feature = "c",
feature = "cpp",
feature = "csharp",
feature = "groovy",
feature = "java",
feature = "javascript",
feature = "mozcpp",
feature = "mozjs",
feature = "objc",
feature = "perl",
feature = "php",
feature = "typescript"
))]
mod ternary_comment_invariance {
use crate::test_support::metrics_verbatim;
use crate::{LANG, MetricsOptions};
fn conditions(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.abc
.conditions_sum()
}
fn cases(lang: LANG) -> Option<[(String, String); 2]> {
let (template, paren, negated, comment) = match lang {
LANG::Cpp | LANG::C | LANG::Objc | LANG::Mozcpp => {
("int f(){ int x = a ? {} : c; }", "(b)", "!b", "/*n*/ ")
}
LANG::Java => (
"class K{ void f(){ int x = a ? {} : c; } }",
"(b)",
"!b",
"/*n*/ ",
),
LANG::Csharp => (
"class K{ void f(){ var x = a ? {} : c; } }",
"(b)",
"!b",
"/*n*/ ",
),
LANG::Groovy => ("def f(){ def x = a ? {} : c }", "(b)", "!b", "/*n*/ "),
LANG::Javascript | LANG::Typescript | LANG::Tsx | LANG::Mozjs => {
("function f(){ var x = a ? {} : c; }", "(b)", "!b", "/*n*/ ")
}
LANG::Php => (
"<?php function f(){ $x = $a ? {} : $c; }",
"($b)",
"!$b",
"/*n*/ ",
),
LANG::Perl => ("sub f { my $x = $a ? {} : $c; }", "($b)", "!$b", "# n\n "),
_ => return None,
};
let build = |operand: &str, with_comment: bool| {
let slot = if with_comment {
format!("{comment}{operand}")
} else {
operand.to_owned()
};
template.replace("{}", &slot)
};
Some([
(build(paren, false), build(paren, true)),
(build(negated, false), build(negated, true)),
])
}
#[test]
fn a_comment_before_a_branch_operand_changes_nothing() {
let mut checked = 0;
for lang in LANG::into_enum_iter() {
if !lang.is_enabled() {
continue;
}
let Some(pairs) = cases(lang) else { continue };
checked += 1;
for (base, commented) in pairs {
assert_eq!(
conditions(lang, &commented),
conditions(lang, &base),
"{lang:?}: a comment changed the ABC conditions of a ternary\n \
without: {base}\n with: {commented}"
);
}
}
assert!(
checked > 0,
"no ternary language enabled; this test asserted nothing"
);
}
#[test]
fn the_baseline_values_are_two_and_three() {
let mut checked = 0;
for lang in LANG::into_enum_iter() {
if !lang.is_enabled() {
continue;
}
let Some([(paren, _), (negated, _)]) = cases(lang) else {
continue;
};
assert_eq!(
conditions(lang, &paren),
2,
"{lang:?}: parenthesised branch"
);
assert_eq!(conditions(lang, &negated), 3, "{lang:?}: negated branch");
checked += 1;
}
assert!(
checked > 0,
"no ternary language enabled; this test asserted nothing"
);
}
}
#[cfg(test)]
#[cfg(any(
feature = "elixir",
feature = "lua",
feature = "perl",
feature = "ruby"
))]
mod keyword_negation_parity {
use crate::test_support::metrics_verbatim;
use crate::{LANG, MetricsOptions};
fn conditions(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.abc
.conditions_sum()
}
#[cfg(any(feature = "elixir", feature = "perl", feature = "ruby"))]
fn pairs(lang: LANG) -> Option<Vec<(String, String)>> {
let build =
|t: &str| -> (String, String) { (t.replace("{NOT}", "!"), t.replace("{NOT}", "not ")) };
let templates: &[&str] = match lang {
LANG::Ruby => &[
"def f(b)\n if {NOT}b\n 1\n end\nend\n",
"def f(a, b, c)\n x = a ? ({NOT}b) : ({NOT}c)\nend\n",
"def f(a, b, c)\n x = a ? b : ({NOT}c)\nend\n",
],
LANG::Perl => &[
"sub f { if ({NOT}$b) { 1; } }",
"sub f { my $x = $a ? ({NOT}$b) : ({NOT}$c); }",
"sub f { my $x = $a ? $b : ({NOT}$c); }",
],
LANG::Elixir => &["def f(b) do\n if {NOT}b do\n 1\n end\nend\n"],
_ => return None,
};
Some(templates.iter().map(|t| build(t)).collect())
}
#[test]
#[cfg(any(feature = "elixir", feature = "perl", feature = "ruby"))]
fn the_not_keyword_scores_like_bang() {
let mut checked = 0;
for lang in LANG::into_enum_iter() {
if !lang.is_enabled() {
continue;
}
let Some(pairs) = pairs(lang) else { continue };
checked += 1;
for (bang, keyword) in pairs {
assert_eq!(
conditions(lang, &keyword),
conditions(lang, &bang),
"{lang:?}: `not` and `!` scored differently\n bang: {bang}\n keyword: {keyword}"
);
}
}
assert!(
checked > 0,
"no language enabled; this test asserted nothing"
);
}
#[test]
#[cfg(any(feature = "perl", feature = "ruby"))]
fn the_baseline_values_are_one_and_four() {
let mut checked = 0;
for (lang, guard, ternary) in [
(
LANG::Ruby,
"def f(b)\n if not b\n 1\n end\nend\n",
"def f(a, b, c)\n x = a ? (not b) : (not c)\nend\n",
),
(
LANG::Perl,
"sub f { if (not $b) { 1; } }",
"sub f { my $x = $a ? (not $b) : (not $c); }",
),
] {
if !lang.is_enabled() {
continue;
}
assert_eq!(conditions(lang, guard), 1, "{lang:?}: `if not b`");
assert_eq!(conditions(lang, ternary), 4, "{lang:?}: `not` ternary");
checked += 1;
}
assert!(
checked > 0,
"no language enabled; this test asserted nothing"
);
}
#[test]
#[cfg(feature = "lua")]
fn lua_counts_its_only_negation_keyword() {
assert!(
LANG::Lua.is_enabled(),
"compiled under `feature = \"lua\"` but `LANG::Lua` reports disabled; \
this test asserted nothing"
);
assert_eq!(
conditions(
LANG::Lua,
"function f(b)\n if not b then return 1 end\nend\n"
),
1
);
}
}
#[cfg(test)]
#[cfg(any(
feature = "ruby",
feature = "elixir",
feature = "perl",
feature = "python",
feature = "lua",
feature = "javascript",
feature = "php",
feature = "groovy",
feature = "c",
feature = "cpp",
feature = "mozcpp",
feature = "objc"
))]
mod numeric_bool_operands {
use crate::test_support::metrics_verbatim;
use crate::{LANG, MetricsOptions};
type Slot = (&'static str, u64, u64);
type Case = ([Slot; 2], &'static str, &'static [&'static str], usize);
fn conditions(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.abc
.conditions_sum()
}
fn cyclomatic_sum(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.cyclomatic
.cyclomatic_sum()
}
fn cases(lang: LANG) -> Option<Case> {
Some(match lang {
LANG::Ruby => (
[
("def f(a)\n a && {}\nend\n", 2, 3),
("def f\n if {}\n 1\n end\nend\n", 1, 3),
],
"b",
&["1", "1.0", "1r", "2i", "1ri"],
5,
),
LANG::Elixir => (
[
("def f(a) do\n a && {}\nend\n", 2, 3),
("def f() do\n if {} do\n 1\n end\nend\n", 1, 3),
],
"b",
&["1", "1.0", "?a"],
3,
),
LANG::Perl => (
[
("sub f {\n my $x = $a && {};\n}\n", 2, 3),
("sub f {\n if ({}) { 1; }\n}\n", 1, 3),
],
"$b",
&["1", "1.0", "1.5e10", "0xff"],
4,
),
LANG::Python => (
[
("def f(a):\n return a and {}\n", 2, 3),
("def f():\n if {}:\n return 1\n", 1, 3),
],
"b",
&["1", "1.0", "0xff", "1j"],
4,
),
LANG::Lua => (
[
("function f(a)\n return a and {}\nend\n", 2, 3),
("function f()\n if {} then return 1 end\nend\n", 1, 3),
],
"b",
&["1", "1.0", "0xff"],
3,
),
LANG::Javascript => (
[
("function f(a) {\n return a && {};\n}\n", 2, 3),
("function f() {\n if ({}) { return 1; }\n}\n", 1, 3),
],
"b",
&["1", "1.0", "0xff"],
3,
),
LANG::Php => (
[
("<?php\nfunction f($a) {\n return $a && {};\n}\n", 2, 3),
("<?php\nfunction f() {\n if ({}) { return 1; }\n}\n", 1, 3),
],
"$b",
&["1", "1.0"],
2,
),
LANG::Groovy => (
[
("def f(a) {\n return a && {}\n}\n", 2, 3),
("def f() {\n if ({}) { return 1 }\n}\n", 1, 3),
],
"b",
&["1"],
1,
),
LANG::C | LANG::Cpp | LANG::Mozcpp | LANG::Objc => (
[
("int f(int a) {\n return a && {};\n}\n", 2, 3),
("int f() {\n if ({}) { return 1; }\n}\n", 1, 3),
],
"b",
&["1", "'c'"],
2,
),
_ => return None,
})
}
fn for_each_case(check: impl Fn(LANG, Case)) {
let mut checked = 0;
for lang in LANG::into_enum_iter() {
if !lang.is_enabled() {
continue;
}
let Some(case @ (slots, _, numerics, expected_kinds)) = cases(lang) else {
continue;
};
assert!(
!numerics.is_empty(),
"{lang:?}: the numeric-operand list is empty; this language asserted nothing"
);
assert_eq!(
numerics.len(),
expected_kinds,
"{lang:?}: the numeric-operand list no longer covers one spelling \
per grammar numeric kind"
);
for (template, _, _) in slots {
assert!(
template.contains("{}"),
"{lang:?}: template lost its `{{}}` operand slot: {template}"
);
}
check(lang, case);
checked += 1;
}
assert!(
checked > 0,
"no truthy-valued language enabled; this test asserted nothing"
);
}
#[test]
fn a_numeric_operand_scores_like_an_identifier_operand() {
for_each_case(|lang, (slots, identifier, numerics, _)| {
for (template, _, _) in slots {
let baseline = conditions(lang, &template.replace("{}", identifier));
for numeric in numerics {
let source = template.replace("{}", numeric);
let scored = conditions(lang, &source);
assert_eq!(
scored, baseline,
"{lang:?}: `{numeric}` scored {scored} unary conditions against \
`{identifier}`'s {baseline}\n source: {source}"
);
}
}
});
}
#[test]
fn every_operand_scores_its_recorded_values() {
for_each_case(|lang, (slots, identifier, numerics, _)| {
for (template, expected_conditions, expected_cyclomatic) in slots {
for operand in std::iter::once(identifier).chain(numerics.iter().copied()) {
let source = template.replace("{}", operand);
assert_eq!(
conditions(lang, &source),
expected_conditions,
"{lang:?}: `{operand}` conditions\n source: {source}"
);
assert_eq!(
cyclomatic_sum(lang, &source),
expected_cyclomatic,
"{lang:?}: `{operand}` cyclomatic_sum\n source: {source}"
);
}
}
});
}
}
#[cfg(test)]
#[cfg(any(
feature = "groovy",
feature = "perl",
feature = "ruby",
feature = "objc"
))]
mod own_production_bool_constructs {
use crate::test_support::metrics_verbatim;
use crate::{LANG, MetricsOptions};
type Slot = (&'static str, u64, u64);
type Case = ([Slot; 2], &'static str, &'static [&'static str], usize);
fn conditions(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.abc
.conditions_sum()
}
fn cyclomatic_sum(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.cyclomatic
.cyclomatic_sum()
}
fn cases(lang: LANG) -> Option<Case> {
Some(match lang {
LANG::Groovy => (
[
("def f(a, b, l, s) {\n return {} && b\n}\n", 2, 3),
("def f(a, b, l, s) {\n if ({}) { return 1 }\n}\n", 1, 3),
],
"b",
&[
"a in l",
"a !in l",
"a === b",
"a !== b",
"s =~ /p/",
"s ==~ /p/",
"!(a in l)",
"l[0]",
"a.@b",
],
9,
),
LANG::Perl => (
[
("sub f {\n my $x = {} && $b;\n}\n", 2, 3),
("sub f {\n if ({}) { 1; }\n}\n", 1, 3),
],
"$a",
&["/^#/", "m{^#}", "!/^#/"],
3,
),
LANG::Ruby => (
[
("def f(a)\n ({}) && b\nend\n", 2, 3),
("def f(a)\n if {}\n 1\n end\nend\n", 1, 3),
],
"a",
&["a in Integer", "!(a in Integer)"],
2,
),
LANG::Objc => (
[
("int f(int a) {\n return {} && b;\n}\n", 2, 3),
("int f() {\n if ({}) { return 1; }\n}\n", 1, 3),
],
"a",
&[
"@available(iOS 13.0, *)",
"__builtin_available(macOS 10.15, *)",
"!@available(iOS 13.0, *)",
],
3,
),
_ => return None,
})
}
fn for_each_case(check: impl Fn(LANG, Case)) {
let mut checked = 0;
for lang in LANG::into_enum_iter() {
if !lang.is_enabled() {
continue;
}
let Some(case @ (slots, _, constructs, expected_constructs)) = cases(lang) else {
continue;
};
assert!(
!constructs.is_empty(),
"{lang:?}: the construct list is empty; this language asserted nothing"
);
assert_eq!(
constructs.len(),
expected_constructs,
"{lang:?}: the construct list no longer covers every own-production \
boolean spelling this language was fixed for"
);
for (template, _, _) in slots {
assert!(
template.contains("{}"),
"{lang:?}: template lost its `{{}}` slot: {template}"
);
}
check(lang, case);
checked += 1;
}
assert!(
checked > 0,
"no language with an own-production boolean construct enabled; \
this test asserted nothing"
);
}
#[test]
fn an_own_production_construct_scores_like_an_identifier() {
for_each_case(|lang, (slots, identifier, constructs, _)| {
for (template, _, _) in slots {
let baseline = conditions(lang, &template.replace("{}", identifier));
for construct in constructs {
let source = template.replace("{}", construct);
let scored = conditions(lang, &source);
assert_eq!(
scored, baseline,
"{lang:?}: `{construct}` scored {scored} conditions against \
`{identifier}`'s {baseline}\n source: {source}"
);
}
}
});
}
#[test]
#[cfg(feature = "groovy")]
fn groovy_safe_navigation_closes_the_two_below_gap() {
for (template, control_conditions, control_cyclomatic) in [
("def f(a, b, l) {\n return {} && b\n}\n", 2, 3),
("def f(a, b, l) {\n if ({}) { return 1 }\n}\n", 1, 3),
] {
let control = template.replace("{}", "b");
assert_eq!(conditions(LANG::Groovy, &control), control_conditions);
assert_eq!(cyclomatic_sum(LANG::Groovy, &control), control_cyclomatic);
for spelling in ["a?.b", "a??.b", "l?[0]"] {
let source = template.replace("{}", spelling);
assert_eq!(
conditions(LANG::Groovy, &source),
control_conditions,
"`{spelling}` conditions\n source: {source}"
);
assert_eq!(
cyclomatic_sum(LANG::Groovy, &source),
control_cyclomatic + 1,
"`{spelling}` cyclomatic_sum: the navigation operator's own \
decision has moved\n source: {source}"
);
}
}
}
#[test]
#[cfg(feature = "groovy")]
fn groovy_negation_operand_survives_an_interposed_comment() {
let template = "def f(a) {\n if ({}) { return 1 }\n}\n";
assert_eq!(conditions(LANG::Groovy, &template.replace("{}", "!a")), 1);
assert_eq!(
conditions(LANG::Groovy, &template.replace("{}", "! /*c*/ a")),
1,
"the `!` operand is being read positionally again; a comment displaces it"
);
assert_eq!(conditions(LANG::Groovy, &template.replace("{}", "(a)")), 1);
assert_eq!(
conditions(LANG::Groovy, &template.replace("{}", "( /*c*/ a)")),
0,
"`parenthesized_expression` now survives an interposed comment — if the \
grammar gained a field for its inner expression, read it and delete this"
);
}
#[test]
#[cfg(feature = "groovy")]
fn groovy_arithmetic_unary_is_not_a_condition() {
let template = "def f(a) {\n if ({}) { return 1 }\n}\n";
assert_eq!(conditions(LANG::Groovy, &template.replace("{}", "a")), 1);
for spelling in ["~a", "-a", "+a"] {
assert_eq!(
conditions(LANG::Groovy, &template.replace("{}", spelling)),
0,
"`{spelling}` is arithmetic, not a boolean operand"
);
}
}
#[test]
#[cfg(feature = "perl")]
fn the_two_perl_match_spellings_are_distinct_kinds() {
use crate::{ParserTrait, Perl, PerlParser};
use big_code_analysis_ast::test_support::ast_has_kind_id;
assert_ne!(
Perl::PatternMatcher as u16,
Perl::PatternMatcherM as u16,
"the two spellings share one kind id; one terminal-set entry is dead"
);
for (src, present, absent) in [
(
"sub f { if (/^#/) { 1; } }",
("pattern_matcher", Perl::PatternMatcher as u16),
("pattern_matcher_m", Perl::PatternMatcherM as u16),
),
(
"sub f { if (m{^#}) { 1; } }",
("pattern_matcher_m", Perl::PatternMatcherM as u16),
("pattern_matcher", Perl::PatternMatcher as u16),
),
] {
let parser = PerlParser::new(
src.as_bytes().to_vec(),
&std::path::PathBuf::from("f.pl"),
None,
);
let (present_name, present_id) = present;
let (absent_name, absent_id) = absent;
assert!(
ast_has_kind_id(&parser, present_id),
"`{src}` no longer emits `{present_name}`"
);
assert!(
!ast_has_kind_id(&parser, absent_id),
"`{src}` now also emits `{absent_name}`; the two spellings have \
collapsed and one `perl_bool_terminal_kinds!()` entry is dead"
);
}
}
#[test]
#[cfg(all(
feature = "objc",
any(feature = "c", feature = "cpp", feature = "mozcpp")
))]
fn the_c_family_grammars_do_not_emit_available_expression() {
use crate::ParserTrait;
const SOURCE: &str = "int f(int a) {\n if (@available(iOS 13.0, *)) { return 1; }\n}\n";
fn emits<P: ParserTrait>(path: &str) -> bool {
let parser = P::new(
SOURCE.as_bytes().to_vec(),
&std::path::PathBuf::from(path),
None,
);
parser
.root()
.preorder()
.any(|node| node.kind() == "available_expression")
}
assert!(
emits::<crate::ObjcParser>("f.m"),
"the fixture no longer parses to an `available_expression`; every \
Objective-C row above is now measuring some other node"
);
let c_family = [
#[cfg(feature = "c")]
("C", emits::<crate::CParser>("f.c")),
#[cfg(feature = "cpp")]
("C++", emits::<crate::CppParser>("f.cpp")),
#[cfg(feature = "mozcpp")]
("Mozcpp", emits::<crate::MozcppParser>("f.cpp")),
];
for (language, emitted) in c_family {
assert!(
!emitted,
"{language} now emits `available_expression`, so the shared \
`cpp_bool_terminal_kinds!()` entry is no longer inert there \
and its ABC conditions have moved"
);
}
}
#[test]
fn every_construct_scores_its_recorded_values() {
for_each_case(|lang, (slots, identifier, constructs, _)| {
for (template, expected_conditions, expected_cyclomatic) in slots {
for spelling in std::iter::once(identifier).chain(constructs.iter().copied()) {
let source = template.replace("{}", spelling);
assert_eq!(
conditions(lang, &source),
expected_conditions,
"{lang:?}: `{spelling}` conditions\n source: {source}"
);
assert_eq!(
cyclomatic_sum(lang, &source),
expected_cyclomatic,
"{lang:?}: `{spelling}` cyclomatic_sum\n source: {source}"
);
}
}
});
}
}
#[cfg(test)]
#[cfg(feature = "perl")]
mod perl_statement_modifier_parity {
use crate::test_support::{assert_perl_fixture_spells, metrics_verbatim};
use crate::{LANG, MetricsOptions, Perl};
fn conditions(source: &str) -> u64 {
metrics_verbatim(LANG::Perl, source.as_bytes(), MetricsOptions::default())
.abc
.conditions_sum()
}
const CONDITION_MODIFIERS: [(&str, u16, &str); 4] = [
("if", Perl::IfSimpleStatement as u16, "if_simple_statement"),
(
"unless",
Perl::UnlessSimpleStatement as u16,
"unless_simple_statement",
),
(
"while",
Perl::WhileSimpleStatement as u16,
"while_simple_statement",
),
(
"until",
Perl::UntilSimpleStatement as u16,
"until_simple_statement",
),
];
#[test]
fn a_modifier_scores_like_its_block_form() {
assert!(
LANG::Perl.is_enabled(),
"compiled under `feature = \"perl\"` but `LANG::Perl` reports disabled; \
this test asserted nothing"
);
for (keyword, kind, name) in CONDITION_MODIFIERS {
let modifier = format!("sub f {{ my $x = shift; return 1 {keyword} $x; }}");
let block = format!("sub f {{ my $x = shift; {keyword} ($x) {{ return 1; }} }}");
assert_perl_fixture_spells(&modifier, &[(kind, 1, name)]);
assert_eq!(
conditions(&modifier),
conditions(&block),
"`{keyword}` modifier and block form disagree\n modifier: {modifier}\n block: {block}"
);
assert_eq!(
conditions(&modifier),
1,
"`{keyword}` modifier: the bare scalar predicate is one condition"
);
}
}
#[test]
fn a_modifier_condition_keeps_its_substructure() {
for (predicate, expected) in [("!$x", 1), ("($x)", 1), ("$x && $x", 2), ("$x > 2", 1)] {
let modifier = format!("sub f {{ my $x = shift; return 1 if {predicate}; }}");
let block = format!("sub f {{ my $x = shift; if ({predicate}) {{ return 1; }} }}");
assert_perl_fixture_spells(
&modifier,
&[(Perl::IfSimpleStatement as u16, 1, "if_simple_statement")],
);
assert_eq!(
conditions(&modifier),
expected,
"`if {predicate}` modifier\n source: {modifier}"
);
assert_eq!(
conditions(&block),
expected,
"`if ({predicate})` block form\n source: {block}"
);
}
}
#[test]
fn the_for_modifier_is_not_a_condition() {
for keyword in ["for", "foreach"] {
let source = format!("sub f {{ my $x = shift; print 5 {keyword} @$x; }}");
assert_perl_fixture_spells(
&source,
&[(Perl::ForSimpleStatement as u16, 1, "for_simple_statement")],
);
let abc =
metrics_verbatim(LANG::Perl, source.as_bytes(), MetricsOptions::default()).abc;
assert_eq!(abc.conditions_sum(), 0, "`{keyword}` modifier: {source}");
assert_eq!(
abc.assignments_sum(),
1,
"`{keyword}`: fixture stopped scoring"
);
assert_eq!(
abc.branches_sum(),
2,
"`{keyword}`: fixture stopped scoring"
);
}
assert_eq!(
conditions("sub f { my $x = shift; return 1 if $x; }"),
1,
"control: the `if` modifier still counts, so the zero above is a \
policy decision and not a dead arm"
);
}
}
#[cfg(test)]
#[cfg(any(
feature = "javascript",
feature = "mozjs",
feature = "typescript",
feature = "python",
feature = "lua",
feature = "php",
feature = "groovy",
feature = "perl",
feature = "ruby",
feature = "elixir"
))]
mod literal_bool_operands {
use crate::test_support::{assert_fixture_spells, metrics_verbatim};
use crate::{LANG, MetricsOptions};
type Slot = (&'static str, u64, u64);
type Literal = (&'static str, u16);
type Case = ([Slot; 2], &'static str, &'static [Literal], usize);
macro_rules! js_literals {
($Lang:ident, $string:ident) => {
&[
("\"s\"", crate::$Lang::$string as u16),
("`t`", crate::$Lang::TemplateString as u16),
("/re/", crate::$Lang::Regex as u16),
("null", crate::$Lang::Null as u16),
("undefined", crate::$Lang::Undefined as u16),
("{}", crate::$Lang::Object as u16),
("[]", crate::$Lang::Array as u16),
]
};
}
const JS_SLOTS: [Slot; 2] = [
("function f(a) {\n return a && {};\n}\n", 2, 3),
("function f() {\n if ({}) { return 1; }\n}\n", 1, 3),
];
fn conditions(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.abc
.conditions_sum()
}
fn cyclomatic_sum(lang: LANG, source: &str) -> u64 {
metrics_verbatim(lang, source.as_bytes(), MetricsOptions::default())
.cyclomatic
.cyclomatic_sum()
}
fn assert_spells(lang: LANG, source: &str, kinds: &[(u16, usize, &str)]) {
match lang {
#[cfg(feature = "javascript")]
LANG::Javascript => {
assert_fixture_spells::<crate::JavascriptParser>(source, "f.js", kinds);
}
#[cfg(feature = "mozjs")]
LANG::Mozjs => assert_fixture_spells::<crate::MozjsParser>(source, "f.jsm", kinds),
#[cfg(feature = "typescript")]
LANG::Typescript => {
assert_fixture_spells::<crate::TypescriptParser>(source, "f.ts", kinds);
}
#[cfg(feature = "typescript")]
LANG::Tsx => assert_fixture_spells::<crate::TsxParser>(source, "f.tsx", kinds),
#[cfg(feature = "python")]
LANG::Python => assert_fixture_spells::<crate::PythonParser>(source, "f.py", kinds),
#[cfg(feature = "lua")]
LANG::Lua => assert_fixture_spells::<crate::LuaParser>(source, "f.lua", kinds),
#[cfg(feature = "php")]
LANG::Php => assert_fixture_spells::<crate::PhpParser>(source, "f.php", kinds),
#[cfg(feature = "groovy")]
LANG::Groovy => {
assert_fixture_spells::<crate::GroovyParser>(source, "f.groovy", kinds);
}
#[cfg(feature = "perl")]
LANG::Perl => assert_fixture_spells::<crate::PerlParser>(source, "f.pl", kinds),
#[cfg(feature = "ruby")]
LANG::Ruby => assert_fixture_spells::<crate::RubyParser>(source, "f.rb", kinds),
#[cfg(feature = "elixir")]
LANG::Elixir => assert_fixture_spells::<crate::ElixirParser>(source, "f.ex", kinds),
other => panic!("{other:?} has a case row but no parser arm"),
}
}
fn cases(lang: LANG) -> Option<Case> {
Some(match lang {
LANG::Javascript => (JS_SLOTS, "b", js_literals!(Javascript, String2), 7),
LANG::Mozjs => (JS_SLOTS, "b", js_literals!(Mozjs, String2), 7),
LANG::Typescript => (JS_SLOTS, "b", js_literals!(Typescript, String), 7),
LANG::Tsx => (JS_SLOTS, "b", js_literals!(Tsx, String2), 7),
LANG::Python => (
[
("def f(a):\n return a and {}\n", 2, 3),
("def f():\n if {}:\n return 1\n", 1, 3),
],
"b",
&[
("'s'", crate::Python::String as u16),
("'a' 'b'", crate::Python::ConcatenatedString as u16),
("None", crate::Python::None as u16),
("[]", crate::Python::List as u16),
("{1}", crate::Python::Set as u16),
("()", crate::Python::Tuple as u16),
("{}", crate::Python::Dictionary as u16),
("...", crate::Python::Ellipsis as u16),
],
8,
),
LANG::Lua => (
[
("function f(a)\n return a and {}\nend\n", 2, 3),
("function f()\n if {} then return 1 end\nend\n", 1, 3),
],
"b",
&[
("\"s\"", crate::Lua::String as u16),
("{}", crate::Lua::TableConstructor as u16),
],
2,
),
LANG::Php => (
[
("<?php\nfunction f($a) {\n return $a && {};\n}\n", 2, 3),
("<?php\nfunction f() {\n if ({}) { return 1; }\n}\n", 1, 3),
],
"$b",
&[
("'s'", crate::Php::String as u16),
("\"x$b\"", crate::Php::EncapsedString as u16),
("<<<EOT\ns\nEOT", crate::Php::Heredoc as u16),
("<<<'EOT'\ns\nEOT", crate::Php::Nowdoc as u16),
("[]", crate::Php::ArrayCreationExpression as u16),
("null", crate::Php::Null as u16),
("`ls`", crate::Php::ShellCommandExpression as u16),
("(bool)$b", crate::Php::CastExpression as u16),
],
8,
),
LANG::Groovy => (
[
("def f(a) {\n return a && {}\n}\n", 2, 3),
("def f() {\n if ({}) { return 1 }\n}\n", 1, 3),
],
"b",
&[
("\"s\"", crate::Groovy::StringLiteral as u16),
("null", crate::Groovy::NullLiteral as u16),
("[]", crate::Groovy::ListLiteral as u16),
("[:]", crate::Groovy::MapLiteral as u16),
],
4,
),
LANG::Perl => (
[
(
"sub f {\n my ($a, $b) = @_;\n if ($a && {}) { print 1; }\n}\n",
2,
4,
),
(
"sub f {\n my ($a, $b) = @_;\n if ({}) { print 1; }\n}\n",
1,
3,
),
],
"$b",
&[
("'s'", crate::Perl::StringSingleQuoted as u16),
("q(s)", crate::Perl::StringQQuoted as u16),
("\"s\"", crate::Perl::StringDoubleQuoted as u16),
("qq(s)", crate::Perl::StringQqQuoted as u16),
("qx(ls)", crate::Perl::CommandQxQuoted as u16),
("`ls`", crate::Perl::BacktickQuoted as u16),
("qw(a b)", crate::Perl::WordListQw as u16),
("[1, 2]", crate::Perl::ArrayRef as u16),
("{ a => 1 }", crate::Perl::HashRef as u16),
("qr/re/", crate::Perl::RegexPatternQr as u16),
("__FILE__", crate::Perl::SpecialLiteral as u16),
],
11,
),
LANG::Ruby => (
[
("def f(a, b)\n if a && {} then 1 else 0 end\nend\n", 3, 4),
("def f(a, b)\n if {} then 1 else 0 end\nend\n", 2, 3),
],
"b",
&[
("\"s\"", crate::Ruby::String as u16),
("\"a\" \"b\"", crate::Ruby::ChainedString as u16),
("`ls`", crate::Ruby::Subshell as u16),
("[1, 2]", crate::Ruby::Array as u16),
("{ a: 1 }", crate::Ruby::Hash as u16),
("%w[a b]", crate::Ruby::StringArray as u16),
("%i[a b]", crate::Ruby::SymbolArray as u16),
("/re/", crate::Ruby::Regex as u16),
("?a", crate::Ruby::Character as u16),
(":sym", crate::Ruby::SimpleSymbol as u16),
(":\"sym\"", crate::Ruby::DelimitedSymbol as u16),
],
11,
),
LANG::Elixir => (
[
(
"defmodule M do\n def f(a, b) do\n if a && {} do\n a\n end\n end\nend\n",
3,
5,
),
(
"defmodule M do\n def f(a, b) do\n if a && !{} do\n a\n end\n end\nend\n",
3,
5,
),
],
"b",
&[
("\"s\"", crate::Elixir::String as u16),
("'c'", crate::Elixir::Charlist as u16),
("~r/re/", crate::Elixir::Sigil as u16),
(":\"q a\"", crate::Elixir::QuotedAtom as u16),
("[1, 2]", crate::Elixir::List as u16),
("{1, 2}", crate::Elixir::Tuple as u16),
("%{a: 1}", crate::Elixir::Map as u16),
("<<1>>", crate::Elixir::Bitstring as u16),
],
8,
),
_ => return None,
})
}
fn for_each_case(check: impl Fn(LANG, Case)) {
let mut checked = 0;
for lang in LANG::into_enum_iter() {
if !lang.is_enabled() {
continue;
}
let Some(case @ (slots, _, literals, expected_kinds)) = cases(lang) else {
continue;
};
assert!(
!literals.is_empty(),
"{lang:?}: the literal-operand list is empty; this language asserted nothing"
);
assert_eq!(
literals.len(),
expected_kinds,
"{lang:?}: the literal-operand list no longer covers one spelling \
per grammar literal kind"
);
for (template, _, _) in slots {
assert!(
template.contains("{}"),
"{lang:?}: template lost its `{{}}` operand slot: {template}"
);
}
check(lang, case);
checked += 1;
}
assert!(
checked > 0,
"no truthy-valued language enabled; this test asserted nothing"
);
}
#[test]
fn a_literal_operand_scores_like_an_identifier_operand() {
for_each_case(|lang, (slots, identifier, literals, _)| {
for (template, _, _) in slots {
let baseline = conditions(lang, &template.replace("{}", identifier));
for (literal, _) in literals {
let source = template.replace("{}", literal);
let scored = conditions(lang, &source);
assert_eq!(
scored, baseline,
"{lang:?}: `{literal}` scored {scored} unary conditions against \
`{identifier}`'s {baseline}\n source: {source}"
);
}
}
});
}
#[test]
fn every_literal_operand_scores_its_recorded_values() {
for_each_case(|lang, (slots, identifier, literals, _)| {
for (template, expected_conditions, expected_cyclomatic) in slots {
let operands = std::iter::once(identifier)
.chain(literals.iter().map(|(spelling, _)| *spelling));
for operand in operands {
let source = template.replace("{}", operand);
assert_eq!(
conditions(lang, &source),
expected_conditions,
"{lang:?}: `{operand}` conditions\n source: {source}"
);
assert_eq!(
cyclomatic_sum(lang, &source),
expected_cyclomatic,
"{lang:?}: `{operand}` cyclomatic_sum\n source: {source}"
);
}
}
});
}
#[cfg(any(feature = "perl", feature = "ruby"))]
#[test]
fn a_heredoc_operand_scores_like_an_identifier_operand() {
#[cfg(feature = "perl")]
{
let heredoc = "sub f {\n my ($a, $b) = @_;\n if ($a && <<\"EOT\") { print 1; }\nhello\nEOT\n}\n";
assert_spells(
LANG::Perl,
heredoc,
&[(crate::Perl::HeredocInitializer as u16, 1, "<<\"EOT\"")],
);
assert_eq!(
conditions(LANG::Perl, heredoc),
conditions(
LANG::Perl,
"sub f {\n my ($a, $b) = @_;\n if ($a && $b) { print 1; }\n}\n"
),
"Perl heredoc operand"
);
}
#[cfg(feature = "ruby")]
{
let heredoc =
"def f(a, b)\n if a && <<~TXT then 1 else 0 end\n hello\n TXT\nend\n";
assert_spells(
LANG::Ruby,
heredoc,
&[(crate::Ruby::HeredocBeginning as u16, 1, "<<~TXT")],
);
assert_eq!(
conditions(LANG::Ruby, heredoc),
conditions(
LANG::Ruby,
"def f(a, b)\n if a && b then 1 else 0 end\nend\n"
),
"Ruby heredoc operand"
);
}
}
#[test]
fn every_literal_spelling_parses_to_the_kind_its_row_names() {
for_each_case(|lang, (slots, _, literals, _)| {
for (template, _, _) in slots {
for (literal, kind) in literals {
let source = template.replace("{}", literal);
assert_spells(lang, &source, &[(*kind, 1, literal)]);
}
}
});
}
}
#[cfg(all(test, any(feature = "php", feature = "groovy")))]
mod hidden_literal_supertypes {
use crate::test_support::ast_has_kind_id;
use crate::*;
#[cfg(feature = "php")]
#[test]
fn php_hidden_string_supertype_is_unreachable() {
let src = "<?php\nfunction f($a) {\n return $a && 's' && \"x$a\" && <<<EOT\ns\nEOT;\n}\n";
let parser = PhpParser::new(src.as_bytes().to_vec(), std::path::Path::new("f.php"), None);
assert!(
ast_has_kind_id(&parser, Php::String as u16),
"control: the fixture must carry the reachable `string` kind"
);
assert!(
!ast_has_kind_id(&parser, Php::String3 as u16),
"`_string` is no longer hidden; the defensive arm in \
`php_bool_terminal_kinds!()` is now live and needs a fixture"
);
}
#[cfg(feature = "groovy")]
#[test]
fn groovy_hidden_slashy_string_is_unreachable() {
let src = "def f(a) {\n return a && /re/ && \"s\"\n}\n";
let parser = GroovyParser::new(
src.as_bytes().to_vec(),
std::path::Path::new("f.groovy"),
None,
);
assert!(
ast_has_kind_id(&parser, Groovy::StringLiteral as u16),
"control: a slashy string must still parse to `string_literal`"
);
assert!(
!ast_has_kind_id(&parser, Groovy::SlashyString as u16),
"`_slashy_string` is no longer hidden; `groovy_bool_terminal_kinds!()` \
omits it on the strength of it being unreachable"
);
}
}