#![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 serde::Serialize;
use serde::ser::{SerializeStruct, Serializer};
use std::fmt;
use crate::checker::Checker;
use crate::macros::{
csharp_invocation_expr_kinds, csharp_paren_expr_kinds, csharp_prefix_unary_expr_kinds,
implement_metric_trait,
};
use crate::node::Node;
use crate::*;
#[derive(Debug, Clone)]
pub struct Stats {
assignments: f64,
assignments_sum: f64,
assignments_min: f64,
assignments_max: f64,
branches: f64,
branches_sum: f64,
branches_min: f64,
branches_max: f64,
conditions: f64,
conditions_sum: f64,
conditions_min: f64,
conditions_max: f64,
space_count: usize,
declaration: Vec<DeclKind>,
}
#[derive(Debug, Clone)]
enum DeclKind {
Var,
Const,
}
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,
declaration: Vec::new(),
}
}
}
impl Serialize for Stats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut st = serializer.serialize_struct("abc", 13)?;
st.serialize_field("assignments", &self.assignments_sum())?;
st.serialize_field("branches", &self.branches_sum())?;
st.serialize_field("conditions", &self.conditions_sum())?;
st.serialize_field("magnitude", &self.magnitude_sum())?;
st.serialize_field("assignments_average", &self.assignments_average())?;
st.serialize_field("branches_average", &self.branches_average())?;
st.serialize_field("conditions_average", &self.conditions_average())?;
st.serialize_field("assignments_min", &self.assignments_min())?;
st.serialize_field("assignments_max", &self.assignments_max())?;
st.serialize_field("branches_min", &self.branches_min())?;
st.serialize_field("branches_max", &self.branches_max())?;
st.serialize_field("conditions_min", &self.conditions_min())?;
st.serialize_field("conditions_max", &self.conditions_max())?;
st.end()
}
}
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) -> f64 {
self.assignments
}
#[must_use]
pub fn assignments_sum(&self) -> f64 {
self.assignments_sum
}
#[must_use]
pub fn assignments_average(&self) -> f64 {
self.assignments_sum() / self.space_count as f64
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn assignments_min(&self) -> f64 {
if self.assignments_min == f64::MAX {
0.0
} else {
self.assignments_min
}
}
#[must_use]
pub fn assignments_max(&self) -> f64 {
self.assignments_max
}
#[must_use]
pub fn branches(&self) -> f64 {
self.branches
}
#[must_use]
pub fn branches_sum(&self) -> f64 {
self.branches_sum
}
#[must_use]
pub fn branches_average(&self) -> f64 {
self.branches_sum() / self.space_count as f64
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn branches_min(&self) -> f64 {
if self.branches_min == f64::MAX {
0.0
} else {
self.branches_min
}
}
#[must_use]
pub fn branches_max(&self) -> f64 {
self.branches_max
}
#[must_use]
pub fn conditions(&self) -> f64 {
self.conditions
}
#[must_use]
pub fn conditions_sum(&self) -> f64 {
self.conditions_sum
}
#[must_use]
pub fn conditions_average(&self) -> f64 {
self.conditions_sum() / self.space_count as f64
}
#[allow(clippy::float_cmp)]
#[must_use]
pub fn conditions_min(&self) -> f64 {
if self.conditions_min == f64::MAX {
0.0
} else {
self.conditions_min
}
}
#[must_use]
pub fn conditions_max(&self) -> f64 {
self.conditions_max
}
#[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 trait Abc
where
Self: Checker,
{
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats);
}
fn java_inspect_container(container_node: &Node, conditions: &mut f64) {
use Java::*;
let mut node = *container_node;
let mut node_kind = node.kind_id().into();
let Some(parent) = node.parent() else { return };
let mut has_boolean_content = match parent.kind_id().into() {
BinaryExpression | IfStatement | WhileStatement | DoStatement | ForStatement => true,
TernaryExpression => node
.previous_sibling()
.is_none_or(|prev_node| !matches!(prev_node.kind_id().into(), QMARK | COLON)),
_ => false,
};
loop {
let is_parenthesised_exp = matches!(node_kind, ParenthesizedExpression);
let is_not_operator = matches!(node_kind, UnaryExpression)
&& node
.child(0)
.is_some_and(|c| matches!(c.kind_id().into(), BANG));
if !is_parenthesised_exp && !is_not_operator {
break;
}
if !has_boolean_content && is_not_operator {
has_boolean_content = true;
}
let Some(child) = node.child(1) else { break };
node = child;
node_kind = node.kind_id().into();
if matches!(node_kind, MethodInvocation | Identifier | True | False) {
if has_boolean_content {
*conditions += 1.;
}
break;
}
}
}
fn csharp_inspect_container(container_node: &Node, conditions: &mut f64) {
use Csharp::*;
let mut node = *container_node;
let mut node_kind = node.kind_id().into();
let Some(parent) = node.parent() else { return };
let mut has_boolean_content = match parent.kind_id().into() {
BinaryExpression | IfStatement | WhileStatement | DoStatement | ForStatement => true,
ConditionalExpression => node
.previous_sibling()
.is_none_or(|prev| !matches!(prev.kind_id().into(), QMARK | COLON)),
_ => false,
};
loop {
let is_parens = matches!(node_kind, csharp_paren_expr_kinds!());
let is_not = matches!(node_kind, csharp_prefix_unary_expr_kinds!())
&& node
.child(0)
.is_some_and(|c| matches!(c.kind_id().into(), BANG));
if !is_parens && !is_not {
break;
}
if !has_boolean_content && is_not {
has_boolean_content = true;
}
let Some(child) = node.child(1) else { break };
node = child;
node_kind = node.kind_id().into();
if matches!(
node_kind,
crate::Csharp::InvocationExpression
| crate::Csharp::InvocationExpression2
| crate::Csharp::InvocationExpression3
| Identifier
| True
| False
) {
if has_boolean_content {
*conditions += 1.;
}
break;
}
}
}
fn csharp_count_unary_conditions(list_node: &Node, conditions: &mut f64) {
use Csharp::*;
let list_kind = list_node.kind_id().into();
let mut cursor = list_node.cursor();
if cursor.goto_first_child() {
loop {
let node = cursor.node();
let node_kind = node.kind_id().into();
if matches!(
node_kind,
crate::Csharp::InvocationExpression
| crate::Csharp::InvocationExpression2
| crate::Csharp::InvocationExpression3
| Identifier
| True
| False
) && matches!(list_kind, BinaryExpression)
{
*conditions += 1.;
} else {
csharp_inspect_container(&node, conditions);
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
fn java_count_unary_conditions(list_node: &Node, conditions: &mut f64) {
use Java::*;
let list_kind = list_node.kind_id().into();
let mut cursor = list_node.cursor();
if cursor.goto_first_child() {
loop {
let node = cursor.node();
let node_kind = node.kind_id().into();
if matches!(node_kind, MethodInvocation | Identifier | True | False)
&& matches!(list_kind, BinaryExpression)
{
*conditions += 1.;
} else {
java_inspect_container(&node, conditions);
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
fn groovy_inspect_container(container_node: &Node, conditions: &mut f64) {
use Groovy::*;
let mut node = *container_node;
let mut node_kind = node.kind_id().into();
let Some(parent) = node.parent() else { return };
let mut has_boolean_content = match parent.kind_id().into() {
BinaryExpression | IfStatement | WhileStatement | DoWhileStatement | ForStatement => true,
TernaryExpression => node
.previous_sibling()
.is_none_or(|prev_node| !matches!(prev_node.kind_id().into(), QMARK | COLON)),
_ => false,
};
loop {
let is_parenthesised_exp = matches!(node_kind, ParenthesizedExpression);
let is_not_operator = matches!(node_kind, UnaryExpression)
&& node
.child(0)
.is_some_and(|c| matches!(c.kind_id().into(), BANG));
if !is_parenthesised_exp && !is_not_operator {
break;
}
if !has_boolean_content && is_not_operator {
has_boolean_content = true;
}
let Some(child) = node.child(1) else { break };
node = child;
node_kind = node.kind_id().into();
if matches!(
node_kind,
MethodInvocation | CommandChain | Identifier | True | False
) {
if has_boolean_content {
*conditions += 1.;
}
break;
}
}
}
fn groovy_count_unary_conditions(list_node: &Node, conditions: &mut f64) {
use Groovy::*;
let list_kind = list_node.kind_id().into();
let mut cursor = list_node.cursor();
if cursor.goto_first_child() {
loop {
let node = cursor.node();
let node_kind = node.kind_id().into();
if matches!(
node_kind,
MethodInvocation | CommandChain | Identifier | True | False
) && matches!(list_kind, BinaryExpression)
{
*conditions += 1.;
} else {
groovy_inspect_container(&node, conditions);
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
implement_metric_trait!(Abc, PreprocCode, CcommentCode);
macro_rules! ts_abc_compute {
($lang:ident) => {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use $lang::*;
match node.kind_id().into() {
PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ | STARSTAREQ | AMPEQ | PIPEEQ
| CARETEQ | LTLTEQ | GTGTEQ | GTGTGTEQ | AMPAMPEQ | PIPEPIPEEQ | QMARKQMARKEQ
| PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
LexicalDeclaration | VariableDeclaration => {
stats.declaration.push(DeclKind::Var);
}
Const => {
if let Some(DeclKind::Var) = stats.declaration.last() {
stats.declaration.push(DeclKind::Const);
}
}
SEMI => {
if let Some(DeclKind::Const | DeclKind::Var) = stats.declaration.last() {
stats.declaration.clear();
}
}
EQ if !matches!(stats.declaration.last(), Some(DeclKind::Const)) => {
stats.assignments += 1.;
}
CallExpression | NewExpression => {
stats.branches += 1.;
}
EQEQ | EQEQEQ | BANGEQ | BANGEQEQ | LTEQ | GTEQ | QMARK | QMARKQMARK
| Instanceof | Else | Case | Default | Try | Catch => {
stats.conditions += 1.;
}
GT | LT
if node.parent().is_some_and(|p| {
!matches!(p.kind_id().into(), TypeArguments | TypeParameters)
}) =>
{
stats.conditions += 1.;
}
_ => {}
}
}
};
}
impl Abc for TypescriptCode {
ts_abc_compute!(Typescript);
}
impl Abc for TsxCode {
ts_abc_compute!(Tsx);
}
macro_rules! js_abc_compute {
($lang:ident) => {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use $lang::*;
match node.kind_id().into() {
PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ | STARSTAREQ | AMPEQ | PIPEEQ
| CARETEQ | LTLTEQ | GTGTEQ | GTGTGTEQ | AMPAMPEQ | PIPEPIPEEQ | QMARKQMARKEQ
| PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
LexicalDeclaration | VariableDeclaration => {
stats.declaration.push(DeclKind::Var);
}
Const => {
if let Some(DeclKind::Var) = stats.declaration.last() {
stats.declaration.push(DeclKind::Const);
}
}
SEMI => {
if let Some(DeclKind::Const | DeclKind::Var) = stats.declaration.last() {
stats.declaration.clear();
}
}
EQ if !matches!(stats.declaration.last(), Some(DeclKind::Const)) => {
stats.assignments += 1.;
}
CallExpression | NewExpression => {
stats.branches += 1.;
}
EQEQ | EQEQEQ | BANGEQ | BANGEQEQ | LTEQ | GTEQ | LT | GT | QMARK | QMARKQMARK
| Instanceof | Else | Case | Default | Try | Catch => {
stats.conditions += 1.;
}
_ => {}
}
}
};
}
impl Abc for JavascriptCode {
js_abc_compute!(Javascript);
}
impl Abc for MozjsCode {
js_abc_compute!(Mozjs);
}
impl Abc for KotlinCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Kotlin::*;
match node.kind_id().into() {
PropertyDeclaration | ClassParameter => {
stats.declaration.push(DeclKind::Var);
}
Val => {
if let Some(DeclKind::Var) = stats.declaration.last() {
stats.declaration.push(DeclKind::Const);
}
}
SEMI => {
if let Some(DeclKind::Const | DeclKind::Var) = stats.declaration.last() {
stats.declaration.clear();
}
}
PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ | PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
EQ if stats
.declaration
.last()
.is_none_or(|decl| matches!(decl, DeclKind::Var)) =>
{
stats.assignments += 1.;
}
CallExpression => {
stats.branches += 1.;
}
LTEQ | GTEQ | EQEQ | EQEQEQ | BANGEQ | BANGEQEQ | WhenEntry | CatchBlock
| QMARKCOLON | AsQMARK => {
stats.conditions += 1.;
}
Else if node.parent().is_some_and(|p| p.kind_id() == IfExpression) => {
stats.conditions += 1.;
}
LT | GT
if node.parent().is_some_and(|p| {
!matches!(p.kind_id().into(), TypeArguments | TypeParameters)
}) =>
{
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for PhpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Php::*;
match node.kind_id().into() {
AssignmentExpression
| AugmentedAssignmentExpression
| ReferenceAssignmentExpression
| PLUSPLUS
| DASHDASH => {
stats.assignments += 1.;
}
FunctionCallExpression
| MemberCallExpression
| ScopedCallExpression
| NullsafeMemberCallExpression
| ObjectCreationExpression => {
stats.branches += 1.;
}
EQEQ
| EQEQEQ
| BANGEQ
| BANGEQEQ
| LT
| GT
| LTEQ
| GTEQ
| LTEQGT
| LTGT
| Instanceof
| ConditionalExpression
| ElseClause
| ElseClause2
| ElseIfClause
| ElseIfClause2
| CaseStatement
| DefaultStatement
| MatchConditionalExpression
| MatchDefaultExpression
| CatchClause => {
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for RubyCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Ruby::*;
match node.kind_id().into() {
Assignment | Assignment2 | OperatorAssignment | OperatorAssignment2 => {
stats.assignments += 1.;
}
Call | Call2 | Call3 | Call4 | Super | Yield | Yield2 => {
stats.branches += 1.;
}
EQEQ | BANGEQ | EQEQEQ | LT | GT | LTEQ | GTEQ | LTEQGT | EQTILDE | BANGTILDE
| Else | Elsif | When | QMARK | Rescue | RescueModifier | RescueModifier2
| RescueModifier3 => {
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for PythonCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Python::*;
match node.kind_id().into() {
Assignment if node.first_child(|id| id == EQ).is_some() => {
stats.assignments += 1.;
}
AugmentedAssignment | NamedExpression => {
stats.assignments += 1.;
}
Call => {
stats.branches += 1.;
}
ComparisonOperator
| BooleanOperator
| ConditionalExpression
| ElifClause
| ElseClause
| ExceptClause
| FinallyClause
| NotOperator => {
stats.conditions += 1.;
}
CaseClause if super::npa::python_case_clause_counts(node, UNDERSCORE as u16) => {
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for RustCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Rust::*;
match node.kind_id().into() {
AssignmentExpression | CompoundAssignmentExpr => {
stats.assignments += 1.;
}
CallExpression | TryExpression => {
stats.branches += 1.;
}
LTEQ | GTEQ | EQEQ | BANGEQ | LetCondition | Else => {
stats.conditions += 1.;
}
LT | GT
if node
.parent()
.is_some_and(|p| matches!(p.kind_id().into(), BinaryExpression)) =>
{
stats.conditions += 1.;
}
MatchArm | MatchArm2 => {
let is_bare_wildcard = node.child_by_field_name("pattern").is_some_and(|pat| {
super::npa::pattern_is_bare_underscore(&pat, UNDERSCORE as u16)
});
if !is_bare_wildcard {
stats.conditions += 1.;
}
}
_ => {}
}
}
}
impl Abc for GoCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Go as G;
match node.kind_id().into() {
G::AssignmentStatement | G::ShortVarDeclaration | G::IncStatement | G::DecStatement => {
stats.assignments += 1.;
}
G::CallExpression => {
stats.branches += 1.;
}
G::EQEQ
| G::BANGEQ
| G::LTEQ
| G::GTEQ
| G::Else
| G::ExpressionCase
| G::TypeCase
| G::CommunicationCase => {
stats.conditions += 1.;
}
G::LT | G::GT
if node
.parent()
.is_some_and(|p| matches!(p.kind_id().into(), G::BinaryExpression)) =>
{
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for CppCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Cpp::*;
match node.kind_id().into() {
AssignmentExpression | AssignmentExpression2 | UpdateExpression => {
stats.assignments += 1.;
}
CallExpression | CallExpression2 | NewExpression => {
stats.branches += 1.;
}
LTEQ | GTEQ | EQEQ | BANGEQ | LTEQGT | AMPAMP | PIPEPIPE | Else | Case | QMARK
| Try | Try2 | Catch => {
stats.conditions += 1.;
}
LT | GT
if node.parent().is_some_and(|p| {
matches!(p.kind_id().into(), BinaryExpression | BinaryExpression2)
}) =>
{
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for BashCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
match node.kind_id().into() {
Bash::VariableAssignment | Bash::VariableAssignment2 => {
stats.assignments += 1.;
}
Bash::Command => {
stats.branches += 1.;
}
Bash::EQEQ
| Bash::BANGEQ
| Bash::LT
| Bash::GT
| Bash::LTEQ
| Bash::GTEQ
| Bash::EQTILDE
| Bash::TestOperator => {
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for JavaCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Java::*;
match node.kind_id().into() {
STAREQ | SLASHEQ | PERCENTEQ | DASHEQ | PLUSEQ | LTLTEQ | GTGTEQ | AMPEQ | PIPEEQ
| CARETEQ | GTGTGTEQ | PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
FieldDeclaration | LocalVariableDeclaration => {
stats.declaration.push(DeclKind::Var);
}
Final => {
if let Some(DeclKind::Var) = stats.declaration.last() {
stats.declaration.push(DeclKind::Const);
}
}
SEMI => {
if let Some(DeclKind::Const | DeclKind::Var) = stats.declaration.last() {
stats.declaration.clear();
}
}
EQ if stats
.declaration
.last()
.is_none_or(|decl| matches!(decl, DeclKind::Var)) =>
{
stats.assignments += 1.;
}
MethodInvocation | New => {
stats.branches += 1.;
}
GTEQ | LTEQ | EQEQ | BANGEQ | Else | Case | Default | QMARK | Try | Catch => {
stats.conditions += 1.;
}
GT | LT => {
if let Some(parent) = node.parent()
&& !matches!(parent.kind_id().into(), TypeArguments)
{
stats.conditions += 1.;
}
}
AMPAMP | PIPEPIPE => {
if let Some(parent) = node.parent() {
java_count_unary_conditions(&parent, &mut stats.conditions);
}
}
ArgumentList => {
java_count_unary_conditions(node, &mut stats.conditions);
}
VariableDeclarator | AssignmentExpression => {
if let Some(right_operand) = node.child(2)
&& matches!(
right_operand.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
java_inspect_container(&right_operand, &mut stats.conditions);
}
}
IfStatement | WhileStatement => {
if let Some(condition) = node.child(1)
&& matches!(condition.kind_id().into(), ParenthesizedExpression)
{
java_inspect_container(&condition, &mut stats.conditions);
}
}
DoStatement => {
if let Some(condition) = node.child(3)
&& matches!(condition.kind_id().into(), ParenthesizedExpression)
{
java_inspect_container(&condition, &mut stats.conditions);
}
}
ForStatement => {
if let Some(condition) = node.child(3) {
match condition.kind_id().into() {
SEMI => {
if let Some(cond) = node.child(4) {
match cond.kind_id().into() {
MethodInvocation | Identifier | True | False | SEMI
| RPAREN => {
stats.conditions += 1.;
}
ParenthesizedExpression | UnaryExpression => {
java_inspect_container(&cond, &mut stats.conditions);
}
_ => {}
}
}
}
MethodInvocation | Identifier | True | False => {
stats.conditions += 1.;
}
ParenthesizedExpression | UnaryExpression => {
java_inspect_container(&condition, &mut stats.conditions);
}
_ => {}
}
}
}
ReturnStatement => {
if let Some(value) = node.child(1)
&& matches!(
value.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
java_inspect_container(&value, &mut stats.conditions);
}
}
LambdaExpression => {
if let Some(value) = node.child(2)
&& matches!(
value.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
java_inspect_container(&value, &mut stats.conditions);
}
}
TernaryExpression => {
if let Some(condition) = node.child(0) {
match condition.kind_id().into() {
MethodInvocation | Identifier | True | False => {
stats.conditions += 1.;
}
ParenthesizedExpression | UnaryExpression => {
java_inspect_container(&condition, &mut stats.conditions);
}
_ => {}
}
}
if let Some(expression) = node.child(2)
&& matches!(
expression.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
java_inspect_container(&expression, &mut stats.conditions);
}
if let Some(expression) = node.child(4)
&& matches!(
expression.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
java_inspect_container(&expression, &mut stats.conditions);
}
}
_ => {}
}
}
}
impl Abc for GroovyCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Groovy::*;
match node.kind_id().into() {
STAREQ | SLASHEQ | PERCENTEQ | DASHEQ | PLUSEQ | LTLTEQ | GTGTEQ | AMPEQ | PIPEEQ
| CARETEQ | GTGTGTEQ | PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
FieldDeclaration | LocalVariableDeclaration => {
stats.declaration.push(DeclKind::Var);
}
Final => {
if let Some(DeclKind::Var) = stats.declaration.last() {
stats.declaration.push(DeclKind::Const);
}
}
SEMI => {
if let Some(DeclKind::Const | DeclKind::Var) = stats.declaration.last() {
stats.declaration.clear();
}
}
EQ if stats
.declaration
.last()
.is_none_or(|decl| matches!(decl, DeclKind::Var)) =>
{
stats.assignments += 1.;
}
MethodInvocation | CommandChain | New => {
stats.branches += 1.;
}
GTEQ | LTEQ | EQEQ | BANGEQ | Else | Case | Default | QMARK | Try | Catch => {
stats.conditions += 1.;
}
GT | LT => {
if let Some(parent) = node.parent()
&& !matches!(parent.kind_id().into(), TypeArguments)
{
stats.conditions += 1.;
}
}
AMPAMP | PIPEPIPE => {
if let Some(parent) = node.parent() {
groovy_count_unary_conditions(&parent, &mut stats.conditions);
}
}
ArgumentList => {
groovy_count_unary_conditions(node, &mut stats.conditions);
}
VariableDeclarator | AssignmentExpression => {
if let Some(right_operand) = node.child(2)
&& matches!(
right_operand.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
groovy_inspect_container(&right_operand, &mut stats.conditions);
}
}
IfStatement | WhileStatement => {
if let Some(condition) = node.child(2) {
groovy_count_condition(&condition, &mut stats.conditions);
}
}
DoWhileStatement => {
if let Some(condition) = node.child(4) {
groovy_count_condition(&condition, &mut stats.conditions);
}
}
ForStatement => {
if let Some(condition) = node.child(3) {
if matches!(condition.kind_id().into(), SEMI) {
if let Some(cond) = node.child(4) {
if matches!(cond.kind_id().into(), SEMI | RPAREN) {
stats.conditions += 1.;
} else {
groovy_count_condition(&cond, &mut stats.conditions);
}
}
} else {
groovy_count_condition(&condition, &mut stats.conditions);
}
}
}
ReturnStatement => {
if let Some(value) = node.child(1)
&& matches!(
value.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
groovy_inspect_container(&value, &mut stats.conditions);
}
}
TernaryExpression => {
if let Some(condition) = node.child(0) {
groovy_count_condition(&condition, &mut stats.conditions);
}
for branch_idx in [2, 4] {
if let Some(expression) = node.child(branch_idx)
&& matches!(
expression.kind_id().into(),
ParenthesizedExpression | UnaryExpression
)
{
groovy_inspect_container(&expression, &mut stats.conditions);
}
}
}
_ => {}
}
}
}
fn groovy_count_condition(condition: &Node, conditions: &mut f64) {
use Groovy::*;
match condition.kind_id().into() {
MethodInvocation | CommandChain | Identifier | True | False => {
*conditions += 1.;
}
ParenthesizedExpression | UnaryExpression => {
groovy_inspect_container(condition, conditions);
}
_ => {}
}
}
impl Abc for CsharpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Csharp::*;
match node.kind_id().into() {
STAREQ | SLASHEQ | PERCENTEQ | DASHEQ | PLUSEQ | LTLTEQ | GTGTEQ | GTGTGTEQ | AMPEQ
| PIPEEQ | CARETEQ | QMARKQMARKEQ | PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
FieldDeclaration | LocalDeclarationStatement => {
stats.declaration.push(DeclKind::Var);
}
Const => {
if let Some(DeclKind::Var) = stats.declaration.last() {
stats.declaration.push(DeclKind::Const);
}
}
SEMI => {
if let Some(DeclKind::Const | DeclKind::Var) = stats.declaration.last() {
stats.declaration.clear();
}
}
EQ if !matches!(stats.declaration.last(), Some(DeclKind::Const)) => {
stats.assignments += 1.;
}
crate::Csharp::InvocationExpression
| crate::Csharp::InvocationExpression2
| crate::Csharp::InvocationExpression3
| ObjectCreationExpression => {
stats.branches += 1.;
}
GTEQ | LTEQ | EQEQ | BANGEQ | Else | Case | Default | QMARK | Try | Catch => {
stats.conditions += 1.;
}
GT | LT => {
if let Some(parent) = node.parent()
&& !matches!(
parent.kind_id().into(),
TypeArgumentList | TypeParameterList | FunctionPointerType
)
{
stats.conditions += 1.;
}
}
AMPAMP | PIPEPIPE => {
if let Some(parent) = node.parent() {
csharp_count_unary_conditions(&parent, &mut stats.conditions);
}
}
ArgumentList => {
csharp_count_unary_conditions(node, &mut stats.conditions);
}
crate::Csharp::VariableDeclarator
| crate::Csharp::VariableDeclarator2
| AssignmentExpression => {
inspect_csharp_child(node, 2, &mut stats.conditions);
}
IfStatement | WhileStatement => {
if let Some(condition) = node.child(1)
&& matches!(condition.kind_id().into(), csharp_paren_expr_kinds!())
{
csharp_inspect_container(&condition, &mut stats.conditions);
}
}
DoStatement => {
if let Some(condition) = node.child(3)
&& matches!(condition.kind_id().into(), csharp_paren_expr_kinds!())
{
csharp_inspect_container(&condition, &mut stats.conditions);
}
}
ReturnStatement => {
inspect_csharp_child(node, 1, &mut stats.conditions);
}
LambdaExpression => {
inspect_csharp_child(node, 2, &mut stats.conditions);
}
ConditionalExpression => {
if let Some(condition) = node.child(0) {
match condition.kind_id().into() {
crate::Csharp::InvocationExpression
| crate::Csharp::InvocationExpression2
| crate::Csharp::InvocationExpression3
| Identifier
| True
| False => {
stats.conditions += 1.;
}
crate::Csharp::ParenthesizedExpression
| crate::Csharp::ParenthesizedExpression2
| crate::Csharp::ParenthesizedExpression3
| crate::Csharp::PrefixUnaryExpression
| crate::Csharp::PrefixUnaryExpression2 => {
csharp_inspect_container(&condition, &mut stats.conditions);
}
_ => {}
}
}
inspect_csharp_child(node, 2, &mut stats.conditions);
inspect_csharp_child(node, 4, &mut stats.conditions);
}
ForStatement => {
if let Some(condition) = node.child_by_field_name("condition") {
let kind = condition.kind_id().into();
if matches!(kind, csharp_invocation_expr_kinds!())
|| matches!(kind, Identifier | BooleanLiteral)
{
stats.conditions += 1.;
} else if matches!(kind, csharp_paren_expr_kinds!())
|| matches!(kind, csharp_prefix_unary_expr_kinds!())
{
csharp_inspect_container(&condition, &mut stats.conditions);
}
}
}
_ => {}
}
}
}
impl Abc for ElixirCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
use Elixir as E;
match node.kind_id().into() {
E::BinaryOperator | E::BinaryOperator2 | E::BinaryOperator3
if node
.child(1)
.is_some_and(|c| c.kind_id() == E::EQ as u16) =>
{
stats.assignments += 1.;
}
E::PIPEGT => {
stats.branches += 1.;
}
E::Call => {
let keyword = super::cognitive::elixir_call_keyword(node, code);
let is_definition_or_directive = matches!(
keyword,
Some(
"def" | "defp" | "defmacro" | "defmacrop"
| "defmodule" | "defstruct" | "defprotocol" | "defimpl"
| "alias" | "import" | "require" | "use"
)
);
if !is_definition_or_directive {
stats.branches += 1.;
}
if matches!(keyword, Some("if" | "unless" | "case" | "cond" | "with")) {
stats.conditions += 1.;
}
}
E::EQEQ | E::EQEQEQ | E::BANGEQ | E::BANGEQEQ
| E::LT | E::GT | E::LTEQ | E::GTEQ
| E::When => {
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for PerlCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Perl as P;
match node.kind_id().into() {
P::EQ
| P::PLUSEQ
| P::DASHEQ
| P::STAREQ
| P::SLASHEQ
| P::PERCENTEQ
| P::STARSTAREQ
| P::DOTEQ
| P::XEQ
| P::AMPEQ
| P::PIPEEQ
| P::CARETEQ
| P::LTLTEQ
| P::GTGTEQ
| P::AMPAMPEQ
| P::PIPEPIPEEQ
| P::SLASHSLASHEQ
| P::AMPDOTEQ
| P::PIPEDOTEQ
| P::CARETDOTEQ => {
stats.assignments += 1.;
}
P::CallExpressionWithSpacedArgs
| P::CallExpressionWithSub
| P::CallExpressionWithArgsWithBrackets
| P::CallExpressionWithVariable
| P::CallExpressionRecursive
| P::MethodInvocation => {
stats.branches += 1.;
}
P::CallExpressionWithBareword
if !node.parent().is_some_and(|p| {
matches!(
p.kind_id().into(),
P::CallExpressionWithSpacedArgs
| P::CallExpressionWithSub
| P::CallExpressionWithArgsWithBrackets
| P::CallExpressionWithVariable
| P::CallExpressionRecursive
)
}) =>
{
stats.branches += 1.;
}
P::EQEQ | P::BANGEQ | P::LT | P::GT | P::LTEQ | P::GTEQ | P::LTEQGT
| P::Eq | P::Ne | P::Lt | P::Gt | P::Le | P::Ge | P::Cmp
| P::EQTILDE | P::BANGTILDE
| P::AMPAMP | P::PIPEPIPE | P::SLASHSLASH
| P::And | P::Or | P::Xor
| P::TernaryExpression
| P::ElsifClause
| P::ElseClause => {
stats.conditions += 1.;
}
_ => {}
}
}
}
impl Abc for LuaCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
match node.kind_id().into() {
Lua::AssignmentStatement | Lua::AssignmentStatement2 => {
stats.assignments += 1.;
}
Lua::FunctionCall => {
stats.branches += 1.;
}
Lua::EQEQ
| Lua::TILDEEQ
| Lua::LT
| Lua::GT
| Lua::LTEQ
| Lua::GTEQ
| Lua::And
| Lua::Or
| Lua::ElseifStatement
| Lua::ElseStatement => {
stats.conditions += 1.;
}
_ => {}
}
}
}
const TCL_ASSIGNMENT_COMMANDS: &[&[u8]] = &[b"incr", b"append", b"lappend"];
impl Abc for TclCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
match node.kind_id().into() {
Tcl::Set => {
stats.assignments += 1.;
}
Tcl::Command => {
if tcl_command_is_assignment(node, code) {
stats.assignments += 1.;
} else {
stats.branches += 1.;
}
}
Tcl::EQEQ
| Tcl::BANGEQ
| Tcl::LT
| Tcl::GT
| Tcl::LTEQ
| Tcl::GTEQ
| Tcl::Eq
| Tcl::Ne
| Tcl::In
| Tcl::Ni
| Tcl::AMPAMP
| Tcl::PIPEPIPE
| Tcl::TernaryExpr
| Tcl::Elseif
| Tcl::Else => {
stats.conditions += 1.;
}
_ => {}
}
}
}
fn tcl_command_is_assignment(node: &Node, code: &[u8]) -> bool {
let Some(first) = node.child(0) else {
return false;
};
let start = first.start_byte();
let end = first.end_byte();
if end > code.len() || start >= end {
return false;
}
let word = &code[start..end];
TCL_ASSIGNMENT_COMMANDS.contains(&word)
}
fn inspect_csharp_child(node: &Node, idx: usize, conditions: &mut f64) {
if let Some(child) = node.child(idx)
&& matches!(
child.kind_id().into(),
crate::Csharp::ParenthesizedExpression
| crate::Csharp::ParenthesizedExpression2
| crate::Csharp::ParenthesizedExpression3
| crate::Csharp::PrefixUnaryExpression
| crate::Csharp::PrefixUnaryExpression2
)
{
csharp_inspect_container(&child, conditions);
}
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::doc_markdown,
clippy::needless_raw_string_hashes,
clippy::too_many_lines
)]
mod tests {
use crate::tools::check_metrics;
use super::*;
#[test]
fn abc_empty_file_min_is_zero() {
let stats = Stats::default();
assert_eq!(stats.assignments_min(), 0.0);
assert_eq!(stats.branches_min(), 0.0);
assert_eq!(stats.conditions_min(), 0.0);
}
#[test]
fn java_eq_arm_increments_when_declaration_stack_is_empty() {
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.0);
},
);
}
#[test]
fn java_eq_arm_skips_when_declaration_stack_top_is_const() {
check_metrics::<JavaParser>(
"class A {
final int X = 1;
final int Y = 2;
void m() { final int Z = 3; }
}",
"foo.java",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
},
);
}
#[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.0,
"branches": 0.0,
"conditions": 0.0,
"magnitude": 8.0,
"assignments_average": 2.6666666666666665,
"branches_average": 0.0,
"conditions_average": 0.0,
"assignments_min": 0.0,
"assignments_max": 4.0,
"branches_min": 0.0,
"branches_max": 0.0,
"conditions_min": 0.0,
"conditions_max": 0.0
}"###
);
},
);
}
#[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.0,
"branches": 3.0,
"conditions": 12.0,
"magnitude": 18.681541692269406,
"assignments_average": 14.0,
"branches_average": 3.0,
"conditions_average": 12.0,
"assignments_min": 14.0,
"assignments_max": 14.0,
"branches_min": 3.0,
"branches_max": 3.0,
"conditions_min": 12.0,
"conditions_max": 12.0
}"###
);
},
);
}
#[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.0,
"branches": 6.0,
"conditions": 16.0,
"magnitude": 22.090722034374522,
"assignments_average": 14.0,
"branches_average": 6.0,
"conditions_average": 16.0,
"assignments_min": 14.0,
"assignments_max": 14.0,
"branches_min": 6.0,
"branches_max": 6.0,
"conditions_min": 16.0,
"conditions_max": 16.0
}"###
);
},
);
}
#[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.0,
"branches": 14.0,
"conditions": 10.0,
"magnitude": 17.204650534085253,
"assignments_average": 0.0,
"branches_average": 14.0,
"conditions_average": 10.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 14.0,
"branches_max": 14.0,
"conditions_min": 10.0,
"conditions_max": 10.0
}"###
);
},
);
}
#[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.0,
"branches": 8.0,
"conditions": 22.0,
"magnitude": 23.40939982143925,
"assignments_average": 0.0,
"branches_average": 8.0,
"conditions_average": 22.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 8.0,
"branches_max": 8.0,
"conditions_min": 22.0,
"conditions_max": 22.0
}"###
);
},
);
}
#[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.0,
"branches": 5.0,
"conditions": 30.0,
"magnitude": 30.4138126514911,
"assignments_average": 0.0,
"branches_average": 5.0,
"conditions_average": 30.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 5.0,
"branches_max": 5.0,
"conditions_min": 30.0,
"conditions_max": 30.0
}"###
);
},
);
}
#[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.0,
"branches": 2.0,
"conditions": 12.0,
"magnitude": 12.165525060596439,
"assignments_average": 0.0,
"branches_average": 2.0,
"conditions_average": 12.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 2.0,
"branches_max": 2.0,
"conditions_min": 12.0,
"conditions_max": 12.0
}"###
);
},
);
}
#[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.0,
"branches": 0.0,
"conditions": 9.0,
"magnitude": 9.0,
"assignments_average": 0.0,
"branches_average": 0.0,
"conditions_average": 1.2857142857142858,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 0.0,
"branches_max": 0.0,
"conditions_min": 0.0,
"conditions_max": 3.0
}"###
);
},
);
}
#[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.0,
"branches": 1.0,
"conditions": 0.0,
"magnitude": 1.0,
"assignments_average": 0.0,
"branches_average": 0.14285714285714285,
"conditions_average": 0.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 0.0,
"branches_max": 1.0,
"conditions_min": 0.0,
"conditions_max": 0.0
}"###
);
},
);
}
#[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.0,
"branches": 1.0,
"conditions": 9.0,
"magnitude": 12.767145334803704,
"assignments_average": 9.0,
"branches_average": 1.0,
"conditions_average": 9.0,
"assignments_min": 9.0,
"assignments_max": 9.0,
"branches_min": 1.0,
"branches_max": 1.0,
"conditions_min": 9.0,
"conditions_max": 9.0
}"###
);
},
);
}
#[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.0,
"branches": 2.0,
"conditions": 8.0,
"magnitude": 12.96148139681572,
"assignments_average": 10.0,
"branches_average": 2.0,
"conditions_average": 8.0,
"assignments_min": 10.0,
"assignments_max": 10.0,
"branches_min": 2.0,
"branches_max": 2.0,
"conditions_min": 8.0,
"conditions_max": 8.0
}"###
);
},
);
}
#[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 ( ; ; ) {} // +1c (one implicit unary condition set to true)
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r###"
{
"assignments": 8.0,
"branches": 0.0,
"conditions": 6.0,
"magnitude": 10.0,
"assignments_average": 1.6,
"branches_average": 0.0,
"conditions_average": 1.2,
"assignments_min": 0.0,
"assignments_max": 8.0,
"branches_min": 0.0,
"branches_max": 0.0,
"conditions_min": 0.0,
"conditions_max": 4.0
}"###
);
},
);
}
#[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.0,
"branches": 4.0,
"conditions": 24.0,
"magnitude": 24.839484696748443,
"assignments_average": 5.0,
"branches_average": 4.0,
"conditions_average": 24.0,
"assignments_min": 5.0,
"assignments_max": 5.0,
"branches_min": 4.0,
"branches_max": 4.0,
"conditions_min": 24.0,
"conditions_max": 24.0
}"###
);
},
);
}
#[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.0,
"branches": 0.0,
"conditions": 0.0,
"magnitude": 3.0,
"assignments_average": 1.5,
"branches_average": 0.0,
"conditions_average": 0.0,
"assignments_min": 0.0,
"assignments_max": 3.0,
"branches_min": 0.0,
"branches_max": 0.0,
"conditions_min": 0.0,
"conditions_max": 0.0
}"###
);
},
);
}
#[test]
fn bash_commands_only() {
check_metrics::<BashParser>(
"f() {
echo a
ls
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r###"
{
"assignments": 0.0,
"branches": 2.0,
"conditions": 0.0,
"magnitude": 2.0,
"assignments_average": 0.0,
"branches_average": 1.0,
"conditions_average": 0.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 0.0,
"branches_max": 2.0,
"conditions_min": 0.0,
"conditions_max": 0.0
}"###
);
},
);
}
#[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| {
insta::assert_json_snapshot!(
metric.abc,
@r###"
{
"assignments": 0.0,
"branches": 4.0,
"conditions": 4.0,
"magnitude": 5.656854249492381,
"assignments_average": 0.0,
"branches_average": 2.0,
"conditions_average": 2.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 0.0,
"branches_max": 4.0,
"conditions_min": 0.0,
"conditions_max": 4.0
}"###
);
},
);
}
#[test]
fn bash_magnitude() {
check_metrics::<BashParser>(
"f() {
a=1
b=2
if [[ \"$a\" == \"$b\" ]]; then
echo eq
fi
}",
"foo.sh",
|metric| {
insta::assert_json_snapshot!(
metric.abc,
@r###"
{
"assignments": 2.0,
"branches": 1.0,
"conditions": 1.0,
"magnitude": 2.449489742783178,
"assignments_average": 1.0,
"branches_average": 0.5,
"conditions_average": 0.5,
"assignments_min": 0.0,
"assignments_max": 2.0,
"branches_min": 0.0,
"branches_max": 1.0,
"conditions_min": 0.0,
"conditions_max": 1.0
}"###
);
},
);
}
#[test]
fn java_malformed_parenthesized_no_panic() {
check_metrics::<JavaParser>("class A { void m() { if (( }) }", "foo.java", |metric| {
assert_eq!(metric.abc.assignments(), 0.0);
assert_eq!(metric.abc.branches(), 0.0);
assert_eq!(metric.abc.conditions(), 0.0);
assert_eq!(metric.abc.magnitude(), 0.0);
});
}
#[test]
fn groovy_no_abc() {
check_metrics::<GroovyParser>(
"// just a comment, no executable code",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
},
);
}
#[test]
fn groovy_single_assignment() {
check_metrics::<GroovyParser>("int x = 1", "foo.groovy", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
});
}
#[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.0);
},
);
}
#[test]
fn groovy_branches() {
check_metrics::<GroovyParser>(
"void f() {
doStuff()
helper.invoke()
new Worker()
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.branches_sum(), 3.0);
},
);
}
#[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.0);
},
);
}
#[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.0);
},
);
}
#[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.0);
},
);
}
#[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.0);
},
);
}
#[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.0);
},
);
}
#[test]
fn groovy_malformed_parenthesized_no_panic() {
check_metrics::<GroovyParser>("def x = (((", "foo.groovy", |metric| {
assert_eq!(metric.abc.assignments_sum(), 1.0);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 3.0);
},
);
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 2.0);
},
);
}
#[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.0);
assert_eq!(metric.abc.conditions_sum(), 3.0);
},
);
}
#[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.0);
},
);
}
#[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.0);
assert_eq!(metric.abc.conditions_sum(), 1.0);
},
);
}
#[test]
fn groovy_eq_arm_increments_when_no_declaration() {
check_metrics::<GroovyParser>(
"void f(int x) {
x = 42
}",
"foo.groovy",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
},
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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.0,
"branches": 0.0,
"conditions": 1.0,
"magnitude": 1.0,
"assignments_average": 0.0,
"branches_average": 0.0,
"conditions_average": 0.3333333333333333,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 0.0,
"branches_max": 0.0,
"conditions_min": 0.0,
"conditions_max": 1.0
}
"###
);
},
);
}
#[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.0,
"branches": 1.0,
"conditions": 1.0,
"magnitude": 1.4142135623730951,
"assignments_average": 0.0,
"branches_average": 0.25,
"conditions_average": 0.25,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 0.0,
"branches_max": 1.0,
"conditions_min": 0.0,
"conditions_max": 1.0
}
"###
);
},
);
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
},
);
}
#[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.0,
"branches": 0.0,
"conditions": 0.0,
"magnitude": 0.0,
"assignments_average": 0.0,
"branches_average": 0.0,
"conditions_average": 0.0,
"assignments_min": 0.0,
"assignments_max": 0.0,
"branches_min": 0.0,
"branches_max": 0.0,
"conditions_min": 0.0,
"conditions_max": 0.0
}
"###
);
},
);
}
#[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),
);
}
#[test]
fn csharp_malformed_parenthesized_no_panic() {
check_metrics::<CsharpParser>("class A { void M() { if (( }) }", "foo.cs", |metric| {
assert_eq!(metric.abc.assignments(), 0.0);
assert_eq!(metric.abc.branches(), 0.0);
});
}
#[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.0,
"function-pointer-type angle brackets must not count"
);
},
);
}
#[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),
);
}
#[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.0);
assert_eq!(metric.abc.conditions_max(), 0.0);
},
);
}
#[test]
fn php_zero_abc() {
check_metrics::<PhpParser>("<?php\n", "foo.php", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[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),
);
}
#[test]
fn kotlin_empty_class() {
check_metrics::<KotlinParser>("class C {}", "foo.kt", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 6.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 3.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[test]
fn typescript_assignments_basic() {
check_metrics::<TypescriptParser>(
"class C {
m(): void {
let x = 0; // const-sentinel suppressed since `let`, but x is Var → +1
x = 1; // +1
x += 2; // +1
x++; // +1
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 4.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn typescript_const_excluded_from_assignments() {
check_metrics::<TypescriptParser>(
"class C {
m(): void {
const a = 1; // suppressed (Const sentinel)
const b = 2; // suppressed
let c = 3; // +1 (Var sentinel)
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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: // +1
return 0;
}
}
}",
"foo.ts",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn tsx_conditions_switch_case() {
check_metrics::<TsxParser>(
"class C {
m(x: number): number {
switch (x) {
case 1: return 1;
case 2: return 2;
default: return 0;
}
}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.abc.conditions_sum(), 3.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn ruby_zero_abc() {
check_metrics::<RubyParser>("\n", "foo.rb", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
assert_eq!(metric.abc.conditions_sum(), 2.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn python_empty_module_zero() {
check_metrics::<PythonParser>("", "empty.py", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.conditions_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 3.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[test]
fn python_boolean_operators_count_conditions() {
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(), 2.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.conditions_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
assert_eq!(metric.abc.conditions_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn rust_empty_unit_zero() {
check_metrics::<RustParser>("", "empty.rs", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[test]
fn rust_assignments_count_outside_let() {
check_metrics::<RustParser>(
"fn f() { let mut x = 0; x = 5; x += 2; x = 7; }",
"foo.rs",
|metric| {
assert_eq!(metric.abc.assignments_sum(), 3.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 2.0);
assert_eq!(metric.abc.branches_sum(), 5.0);
assert_eq!(metric.abc.conditions_sum(), 4.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn go_empty_unit_zero() {
check_metrics::<GoParser>("package main\n", "empty.go", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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(), 6.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 6.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
assert_eq!(metric.abc.conditions_sum(), 4.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn elixir_empty_unit_zero() {
check_metrics::<ElixirParser>(":ok\n", "foo.ex", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.assignments_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 2.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 2.0);
assert_eq!(metric.abc.conditions_sum(), 2.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn cpp_empty_unit_zero() {
check_metrics::<CppParser>("", "empty.cpp", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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(), 3.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 4.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 14.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn cpp_short_circuit_ops_count_conditions() {
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(), 5.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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(), 5.0);
assert_eq!(metric.abc.branches_sum(), 2.0);
assert_eq!(metric.abc.conditions_sum(), 10.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn javascript_empty_unit_zero() {
check_metrics::<JavascriptParser>("", "empty.js", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 2.0);
assert_eq!(metric.abc.conditions_sum(), 8.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 2.0);
assert_eq!(metric.abc.conditions_sum(), 8.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn perl_empty_unit_zero() {
check_metrics::<PerlParser>("", "empty.pl", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 3.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 3.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 14.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn perl_short_circuit_and_ternary_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 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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 7.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 4.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 1.0);
assert_eq!(metric.abc.conditions_sum(), 2.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 2.0);
assert_eq!(metric.abc.conditions_sum(), 3.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn lua_empty_unit_zero() {
check_metrics::<LuaParser>("", "empty.lua", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 5.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn lua_comparisons_and_boolean_ops_count_conditions() {
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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 8.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 4.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 2.0);
assert_eq!(metric.abc.conditions_sum(), 2.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn tcl_empty_unit_zero() {
check_metrics::<TclParser>("", "empty.tcl", |metric| {
assert_eq!(metric.abc.assignments_sum(), 0.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
});
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 3.0);
assert_eq!(metric.abc.conditions_sum(), 0.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[test]
fn tcl_comparisons_and_boolean_ops_count_conditions() {
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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 10.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 0.0);
assert_eq!(metric.abc.conditions_sum(), 1.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 3.0);
assert_eq!(metric.abc.conditions_sum(), 4.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
#[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.0);
assert_eq!(metric.abc.branches_sum(), 5.0);
assert_eq!(metric.abc.conditions_sum(), 2.0);
insta::assert_json_snapshot!(metric.abc);
},
);
}
}