debtmap 0.16.4

Code complexity and technical debt analyzer
Documentation
use crate::core::{ComplexityMetrics, FunctionMetrics};
use std::path::PathBuf;

pub fn calculate_average_complexity(metrics: &[FunctionMetrics]) -> f64 {
    if metrics.is_empty() {
        return 0.0;
    }

    let total: u32 = metrics.iter().map(|m| m.cyclomatic).sum();
    total as f64 / metrics.len() as f64
}

pub fn find_max_complexity(metrics: &[FunctionMetrics]) -> u32 {
    metrics.iter().map(|m| m.cyclomatic).max().unwrap_or(0)
}

pub fn count_high_complexity(metrics: &[FunctionMetrics], threshold: u32) -> usize {
    metrics.iter().filter(|m| m.is_complex(threshold)).count()
}

pub fn combine_metrics(left: ComplexityMetrics, right: ComplexityMetrics) -> ComplexityMetrics {
    let functions = [left.functions, right.functions].concat();
    let (cyclomatic, cognitive) = functions.iter().fold((0, 0), |(cyc, cog), f| {
        (cyc + f.cyclomatic, cog + f.cognitive)
    });

    ComplexityMetrics {
        functions,
        cyclomatic_complexity: cyclomatic,
        cognitive_complexity: cognitive,
    }
}

pub fn filter_metrics<F>(metrics: ComplexityMetrics, predicate: F) -> ComplexityMetrics
where
    F: Fn(&FunctionMetrics) -> bool,
{
    let functions: Vec<_> = metrics.functions.into_iter().filter(predicate).collect();
    let (cyclomatic, cognitive) = functions.iter().fold((0, 0), |(cyc, cog), f| {
        (cyc + f.cyclomatic, cog + f.cognitive)
    });

    ComplexityMetrics {
        functions,
        cyclomatic_complexity: cyclomatic,
        cognitive_complexity: cognitive,
    }
}

pub fn sort_by_complexity(mut metrics: Vec<FunctionMetrics>) -> Vec<FunctionMetrics> {
    metrics.sort_by(|a, b| b.cyclomatic.cmp(&a.cyclomatic));
    metrics
}

pub fn group_by_file(
    metrics: Vec<FunctionMetrics>,
) -> std::collections::HashMap<PathBuf, Vec<FunctionMetrics>> {
    use std::collections::HashMap;

    metrics.into_iter().fold(HashMap::new(), |mut acc, metric| {
        acc.entry(metric.file.clone()).or_default().push(metric);
        acc
    })
}

pub fn calculate_nesting_penalty(nesting: u32) -> u32 {
    match nesting {
        0..=2 => 0,
        3..=4 => 1,
        5..=6 => 2,
        _ => 3,
    }
}

pub fn calculate_length_penalty(length: usize) -> u32 {
    match length {
        0..=20 => 0,
        21..=50 => 1,
        51..=100 => 2,
        _ => 3,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::FunctionMetrics;

    fn create_test_function(name: &str, cyclomatic: u32, cognitive: u32) -> FunctionMetrics {
        FunctionMetrics {
            name: name.to_string(),
            file: PathBuf::from("test.rs"),
            line: 1,
            cyclomatic,
            cognitive,
            nesting: 0,
            length: 10,
            is_test: false,
            visibility: None,
            is_trait_method: false,
            in_test_module: false,
            entropy_score: None,
            is_pure: None,
            purity_confidence: None,
            purity_reason: None,
            call_dependencies: None,
            detected_patterns: None,
            upstream_callers: None,
            downstream_callees: None,
            mapping_pattern_result: None,
            adjusted_complexity: None,
            composition_metrics: None,
            language_specific: None,
            purity_level: None,
            error_swallowing_count: None,
            error_swallowing_patterns: None,
            entropy_analysis: None,
        }
    }

    #[test]
    fn test_filter_metrics_keeps_matching_functions() {
        let metrics = ComplexityMetrics {
            functions: vec![
                create_test_function("simple", 1, 1),
                create_test_function("complex", 10, 15),
                create_test_function("medium", 5, 7),
            ],
            cyclomatic_complexity: 16,
            cognitive_complexity: 23,
        };

        let filtered = filter_metrics(metrics, |f| f.cyclomatic > 4);

        assert_eq!(filtered.functions.len(), 2);
        assert_eq!(filtered.functions[0].name, "complex");
        assert_eq!(filtered.functions[1].name, "medium");
        assert_eq!(filtered.cyclomatic_complexity, 15);
        assert_eq!(filtered.cognitive_complexity, 22);
    }

    #[test]
    fn test_filter_metrics_empty_result() {
        let metrics = ComplexityMetrics {
            functions: vec![
                create_test_function("simple1", 1, 1),
                create_test_function("simple2", 2, 2),
            ],
            cyclomatic_complexity: 3,
            cognitive_complexity: 3,
        };

        let filtered = filter_metrics(metrics, |f| f.cyclomatic > 10);

        assert_eq!(filtered.functions.len(), 0);
        assert_eq!(filtered.cyclomatic_complexity, 0);
        assert_eq!(filtered.cognitive_complexity, 0);
    }

    #[test]
    fn test_filter_metrics_all_match() {
        let metrics = ComplexityMetrics {
            functions: vec![
                create_test_function("func1", 10, 12),
                create_test_function("func2", 15, 18),
            ],
            cyclomatic_complexity: 25,
            cognitive_complexity: 30,
        };

        let filtered = filter_metrics(metrics.clone(), |_| true);

        assert_eq!(filtered.functions.len(), 2);
        assert_eq!(filtered.cyclomatic_complexity, 25);
        assert_eq!(filtered.cognitive_complexity, 30);
    }

    #[test]
    fn test_filter_metrics_by_cognitive_complexity() {
        let metrics = ComplexityMetrics {
            functions: vec![
                create_test_function("low_cognitive", 5, 3),
                create_test_function("high_cognitive", 3, 20),
                create_test_function("medium_cognitive", 4, 10),
            ],
            cyclomatic_complexity: 12,
            cognitive_complexity: 33,
        };

        let filtered = filter_metrics(metrics, |f| f.cognitive > 9);

        assert_eq!(filtered.functions.len(), 2);
        assert!(filtered
            .functions
            .iter()
            .any(|f| f.name == "high_cognitive"));
        assert!(filtered
            .functions
            .iter()
            .any(|f| f.name == "medium_cognitive"));
    }

    #[test]
    fn test_filter_metrics_complex_predicate() {
        let metrics = ComplexityMetrics {
            functions: vec![
                create_test_function("func1", 5, 10),
                create_test_function("func2", 10, 5),
                create_test_function("func3", 8, 8),
            ],
            cyclomatic_complexity: 23,
            cognitive_complexity: 23,
        };

        let filtered = filter_metrics(metrics, |f| f.cyclomatic > 7 && f.cognitive < 9);

        assert_eq!(filtered.functions.len(), 2);
        assert!(filtered.functions.iter().any(|f| f.name == "func2"));
        assert!(filtered.functions.iter().any(|f| f.name == "func3"));
    }
}