use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ComplexityMetrics {
pub cyclomatic_complexity: u32,
pub branches: u32,
pub loops: u32,
pub logical_operators: u32,
pub max_nesting_depth: u32,
pub exception_handlers: u32,
pub early_returns: u32,
}
impl ComplexityMetrics {
pub fn new() -> Self {
Self {
cyclomatic_complexity: 1,
..Default::default()
}
}
pub fn calculate_cyclomatic(&mut self) {
self.cyclomatic_complexity =
1 + self.branches + self.loops + self.logical_operators + self.exception_handlers;
}
pub fn grade(&self) -> char {
match self.cyclomatic_complexity {
1..=5 => 'A',
6..=10 => 'B',
11..=20 => 'C',
21..=50 => 'D',
_ => 'F',
}
}
pub fn exceeds_threshold(&self, threshold: u32) -> bool {
self.cyclomatic_complexity > threshold
}
pub fn has_high_nesting(&self) -> bool {
self.max_nesting_depth > 4
}
pub fn merge_nested(&mut self, nested: &ComplexityMetrics) {
self.branches += nested.branches;
self.loops += nested.loops;
self.logical_operators += nested.logical_operators;
self.exception_handlers += nested.exception_handlers;
self.early_returns += nested.early_returns;
}
pub fn with_branches(mut self, count: u32) -> Self {
self.branches = count;
self
}
pub fn with_loops(mut self, count: u32) -> Self {
self.loops = count;
self
}
pub fn with_logical_operators(mut self, count: u32) -> Self {
self.logical_operators = count;
self
}
pub fn with_nesting_depth(mut self, depth: u32) -> Self {
self.max_nesting_depth = depth;
self
}
pub fn with_exception_handlers(mut self, count: u32) -> Self {
self.exception_handlers = count;
self
}
pub fn with_early_returns(mut self, count: u32) -> Self {
self.early_returns = count;
self
}
pub fn finalize(mut self) -> Self {
self.calculate_cyclomatic();
self
}
}
#[derive(Debug, Default)]
pub struct ComplexityBuilder {
metrics: ComplexityMetrics,
current_nesting: u32,
}
impl ComplexityBuilder {
pub fn new() -> Self {
Self {
metrics: ComplexityMetrics::new(),
current_nesting: 0,
}
}
pub fn add_branch(&mut self) {
self.metrics.branches += 1;
}
pub fn add_loop(&mut self) {
self.metrics.loops += 1;
}
pub fn add_logical_operator(&mut self) {
self.metrics.logical_operators += 1;
}
pub fn add_exception_handler(&mut self) {
self.metrics.exception_handlers += 1;
}
pub fn add_early_return(&mut self) {
self.metrics.early_returns += 1;
}
pub fn enter_scope(&mut self) {
self.current_nesting += 1;
if self.current_nesting > self.metrics.max_nesting_depth {
self.metrics.max_nesting_depth = self.current_nesting;
}
}
pub fn exit_scope(&mut self) {
self.current_nesting = self.current_nesting.saturating_sub(1);
}
pub fn current_depth(&self) -> u32 {
self.current_nesting
}
pub fn build(mut self) -> ComplexityMetrics {
self.metrics.calculate_cyclomatic();
self.metrics
}
pub fn current(&self) -> &ComplexityMetrics {
&self.metrics
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_has_base_complexity() {
let metrics = ComplexityMetrics::new();
assert_eq!(metrics.cyclomatic_complexity, 1);
}
#[test]
fn test_grade_simple() {
let metrics = ComplexityMetrics {
cyclomatic_complexity: 3,
..Default::default()
};
assert_eq!(metrics.grade(), 'A');
}
#[test]
fn test_grade_moderate() {
let metrics = ComplexityMetrics {
cyclomatic_complexity: 8,
..Default::default()
};
assert_eq!(metrics.grade(), 'B');
}
#[test]
fn test_grade_complex() {
let metrics = ComplexityMetrics {
cyclomatic_complexity: 15,
..Default::default()
};
assert_eq!(metrics.grade(), 'C');
}
#[test]
fn test_grade_very_complex() {
let metrics = ComplexityMetrics {
cyclomatic_complexity: 35,
..Default::default()
};
assert_eq!(metrics.grade(), 'D');
}
#[test]
fn test_grade_untestable() {
let metrics = ComplexityMetrics {
cyclomatic_complexity: 60,
..Default::default()
};
assert_eq!(metrics.grade(), 'F');
}
#[test]
fn test_calculate_cyclomatic() {
let mut metrics = ComplexityMetrics::new()
.with_branches(3)
.with_loops(2)
.with_logical_operators(1);
metrics.calculate_cyclomatic();
assert_eq!(metrics.cyclomatic_complexity, 7);
}
#[test]
fn test_builder_basic() {
let mut builder = ComplexityBuilder::new();
builder.add_branch();
builder.add_branch();
builder.add_loop();
let metrics = builder.build();
assert_eq!(metrics.cyclomatic_complexity, 4);
}
#[test]
fn test_builder_nesting() {
let mut builder = ComplexityBuilder::new();
builder.enter_scope();
builder.add_branch();
builder.enter_scope();
builder.add_loop();
builder.enter_scope();
builder.exit_scope();
builder.exit_scope();
builder.exit_scope();
let metrics = builder.build();
assert_eq!(metrics.max_nesting_depth, 3);
}
#[test]
fn test_exceeds_threshold() {
let metrics = ComplexityMetrics {
cyclomatic_complexity: 15,
..Default::default()
};
assert!(metrics.exceeds_threshold(10));
assert!(!metrics.exceeds_threshold(20));
}
#[test]
fn test_has_high_nesting() {
let low_nesting = ComplexityMetrics {
max_nesting_depth: 3,
..Default::default()
};
assert!(!low_nesting.has_high_nesting());
let high_nesting = ComplexityMetrics {
max_nesting_depth: 5,
..Default::default()
};
assert!(high_nesting.has_high_nesting());
}
}