#[derive(Debug, Clone)]
pub struct ComplexityBlock {
pub id: u64,
pub z: f32, }
#[derive(Debug, Clone)]
pub struct ComplexityResult {
pub cyclomatic_complexity: u32,
pub block_count: usize,
pub total_branches: u32,
pub avg_branches_per_block: f32,
pub rating: ComplexityRating,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComplexityRating {
Low,
Medium,
High,
VeryHigh,
}
impl ComplexityRating {
pub fn as_str(&self) -> &'static str {
match self {
ComplexityRating::Low => "Low (1-10)",
ComplexityRating::Medium => "Medium (11-20)",
ComplexityRating::High => "High (21-50)",
ComplexityRating::VeryHigh => "Very High (51+)",
}
}
pub fn from_complexity(complexity: u32) -> Self {
match complexity {
0..=10 => ComplexityRating::Low,
11..=20 => ComplexityRating::Medium,
21..=50 => ComplexityRating::High,
_ => ComplexityRating::VeryHigh,
}
}
}
pub fn calculate_cyclomatic_complexity(blocks: &[ComplexityBlock]) -> ComplexityResult {
let block_count = blocks.len();
let total_branches: u32 = blocks.iter().map(|b| b.z as u32).sum();
let cyclomatic_complexity = 1 + total_branches;
let avg_branches = if block_count > 0 {
total_branches as f32 / block_count as f32
} else {
0.0
};
let rating = ComplexityRating::from_complexity(cyclomatic_complexity);
ComplexityResult {
cyclomatic_complexity,
block_count,
total_branches,
avg_branches_per_block: avg_branches,
rating,
}
}
pub fn calculate_per_block_complexity(blocks: &[ComplexityBlock]) -> Vec<BlockComplexity> {
blocks
.iter()
.map(|b| {
BlockComplexity {
block_id: b.id,
branch_count: b.z as u32,
local_complexity: 1 + b.z as u32,
}
})
.collect()
}
#[derive(Debug, Clone)]
pub struct BlockComplexity {
pub block_id: u64,
pub branch_count: u32,
pub local_complexity: u32,
}
pub fn find_most_complex_blocks(blocks: &[ComplexityBlock], n: usize) -> Vec<BlockComplexity> {
let mut complexities: Vec<BlockComplexity> = calculate_per_block_complexity(blocks);
complexities.sort_by_key(|block| std::cmp::Reverse(block.local_complexity));
complexities.into_iter().take(n).collect()
}
pub fn is_complexity_acceptable(complexity: u32, threshold: u32) -> bool {
complexity <= threshold
}
pub fn get_complexity_trend(complexity: u32) -> &'static str {
match complexity {
0..=5 => "Very simple - easy to understand and test",
6..=10 => "Simple - well-structured code",
11..=15 => "Moderate - consider adding comments",
16..=20 => "Complex - consider refactoring",
21..=30 => "Very complex - should be refactored",
_ => "Extremely complex - immediate refactoring recommended",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cyclomatic_complexity_simple() {
let blocks = vec![
ComplexityBlock { id: 0, z: 0.0 },
ComplexityBlock { id: 1, z: 0.0 },
ComplexityBlock { id: 2, z: 0.0 },
];
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(
result.cyclomatic_complexity, 1,
"Simple function should have complexity 1"
);
assert_eq!(result.block_count, 3);
assert_eq!(result.total_branches, 0);
assert_eq!(result.rating, ComplexityRating::Low);
}
#[test]
fn test_cyclomatic_complexity_single_if() {
let blocks = vec![
ComplexityBlock { id: 0, z: 0.0 }, ComplexityBlock { id: 1, z: 1.0 }, ComplexityBlock { id: 2, z: 0.0 }, ComplexityBlock { id: 3, z: 0.0 }, ];
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(
result.cyclomatic_complexity, 2,
"Single if should have complexity 2"
);
assert_eq!(result.total_branches, 1);
}
#[test]
fn test_cyclomatic_complexity_nested_if() {
let blocks = vec![
ComplexityBlock { id: 0, z: 0.0 }, ComplexityBlock { id: 1, z: 1.0 }, ComplexityBlock { id: 2, z: 1.0 }, ComplexityBlock { id: 3, z: 0.0 }, ComplexityBlock { id: 4, z: 0.0 }, ];
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(
result.cyclomatic_complexity, 3,
"Nested if should have complexity 3"
);
assert_eq!(result.total_branches, 2);
}
#[test]
fn test_cyclomatic_complexity_multiple_branches() {
let blocks = vec![
ComplexityBlock { id: 0, z: 0.0 }, ComplexityBlock { id: 1, z: 1.0 }, ComplexityBlock { id: 2, z: 1.0 }, ComplexityBlock { id: 3, z: 1.0 }, ComplexityBlock { id: 4, z: 0.0 }, ];
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(
result.cyclomatic_complexity, 4,
"Three ifs should have complexity 4"
);
}
#[test]
fn test_complexity_rating_low() {
let blocks: Vec<ComplexityBlock> =
(0..10).map(|i| ComplexityBlock { id: i, z: 0.0 }).collect();
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(result.rating, ComplexityRating::Low);
}
#[test]
fn test_complexity_rating_medium() {
let blocks: Vec<ComplexityBlock> =
(0..15).map(|i| ComplexityBlock { id: i, z: 1.0 }).collect();
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(result.rating, ComplexityRating::Medium);
}
#[test]
fn test_complexity_rating_high() {
let blocks: Vec<ComplexityBlock> =
(0..30).map(|i| ComplexityBlock { id: i, z: 1.0 }).collect();
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(result.rating, ComplexityRating::High);
}
#[test]
fn test_complexity_rating_very_high() {
let blocks: Vec<ComplexityBlock> =
(0..60).map(|i| ComplexityBlock { id: i, z: 1.0 }).collect();
let result = calculate_cyclomatic_complexity(&blocks);
assert_eq!(result.rating, ComplexityRating::VeryHigh);
}
#[test]
fn test_per_block_complexity() {
let blocks = vec![
ComplexityBlock { id: 0, z: 0.0 },
ComplexityBlock { id: 1, z: 2.0 }, ComplexityBlock { id: 2, z: 0.0 },
];
let complexities = calculate_per_block_complexity(&blocks);
assert_eq!(complexities.len(), 3);
assert_eq!(complexities[1].local_complexity, 3); }
#[test]
fn test_find_most_complex_blocks() {
let blocks = vec![
ComplexityBlock { id: 0, z: 0.0 },
ComplexityBlock { id: 1, z: 3.0 }, ComplexityBlock { id: 2, z: 1.0 },
ComplexityBlock { id: 3, z: 2.0 }, ComplexityBlock { id: 4, z: 0.0 },
];
let top = find_most_complex_blocks(&blocks, 2);
assert_eq!(top.len(), 2);
assert_eq!(top[0].block_id, 1); assert_eq!(top[1].block_id, 3); }
#[test]
fn test_is_complexity_acceptable() {
assert!(
is_complexity_acceptable(5, 10),
"5 should be acceptable with threshold 10"
);
assert!(
!is_complexity_acceptable(15, 10),
"15 should not be acceptable with threshold 10"
);
assert!(
is_complexity_acceptable(10, 10),
"10 should be acceptable with threshold 10"
);
}
#[test]
fn test_get_complexity_trend() {
assert!(get_complexity_trend(3).contains("simple"));
assert!(get_complexity_trend(25).contains("complex"));
assert!(get_complexity_trend(60).contains("refactoring"));
}
#[test]
fn test_avg_branches_per_block() {
let blocks = vec![
ComplexityBlock { id: 0, z: 0.0 },
ComplexityBlock { id: 1, z: 2.0 },
ComplexityBlock { id: 2, z: 4.0 },
];
let result = calculate_cyclomatic_complexity(&blocks);
assert!((result.avg_branches_per_block - 2.0).abs() < 0.001);
}
}