debtmap 0.19.1

Code complexity and technical debt analyzer
Documentation
use crate::core::{DebtItem, DebtType, FunctionMetrics, Priority};
use std::path::Path;

pub fn detect_complexity_debt(
    path: &Path,
    threshold: u32,
    functions: &[FunctionMetrics],
) -> Vec<DebtItem> {
    functions
        .iter()
        .filter(|function| !function.is_test)
        .flat_map(|function| debt_for_function(path, threshold, function))
        .collect()
}

fn debt_for_function(path: &Path, threshold: u32, function: &FunctionMetrics) -> Vec<DebtItem> {
    let mut items = Vec::new();

    if function.cyclomatic > threshold || function.cognitive > threshold {
        items.push(complexity_debt(path, function, threshold));
    }

    if function.nesting > 4 {
        items.push(nesting_debt(path, function));
    }

    if function.length > 50 {
        items.push(length_debt(path, function));
    }

    items.extend(advisory_debt_items(path, function));

    items
}

fn complexity_debt(path: &Path, function: &FunctionMetrics, threshold: u32) -> DebtItem {
    let max_complexity = function.cyclomatic.max(function.cognitive);

    DebtItem {
        id: format!("go-high-complexity-{}-{}", path.display(), function.line),
        debt_type: DebtType::Complexity {
            cyclomatic: function.cyclomatic,
            cognitive: function.cognitive,
        },
        priority: complexity_priority(max_complexity, threshold),
        file: path.to_path_buf(),
        line: function.line,
        column: None,
        message: format!(
            "Function '{}' has high complexity (cyclomatic: {}, cognitive: {})",
            function.name, function.cyclomatic, function.cognitive
        ),
        context: Some("Consider breaking this function into smaller functions.".to_string()),
    }
}

fn nesting_debt(path: &Path, function: &FunctionMetrics) -> DebtItem {
    DebtItem {
        id: format!("go-deep-nesting-{}-{}", path.display(), function.line),
        debt_type: DebtType::NestedLoops {
            depth: function.nesting,
            complexity_estimate: format!("O(n^{})", function.nesting),
        },
        priority: if function.nesting > 6 {
            Priority::High
        } else {
            Priority::Medium
        },
        file: path.to_path_buf(),
        line: function.line,
        column: None,
        message: format!(
            "Function '{}' has deep nesting ({} levels)",
            function.name, function.nesting
        ),
        context: Some("Consider guard clauses or extracting nested logic.".to_string()),
    }
}

fn length_debt(path: &Path, function: &FunctionMetrics) -> DebtItem {
    DebtItem {
        id: format!("go-long-function-{}-{}", path.display(), function.line),
        debt_type: DebtType::CodeSmell {
            smell_type: Some("long_function".to_string()),
        },
        priority: if function.length > 100 {
            Priority::High
        } else {
            Priority::Medium
        },
        file: path.to_path_buf(),
        line: function.line,
        column: None,
        message: format!(
            "Function '{}' is too long ({} lines)",
            function.name, function.length
        ),
        context: Some("Consider splitting this function into smaller units.".to_string()),
    }
}

fn advisory_debt_items(path: &Path, function: &FunctionMetrics) -> Vec<DebtItem> {
    function
        .detected_patterns
        .clone()
        .unwrap_or_default()
        .into_iter()
        .filter_map(|pattern| advisory_debt(path, function, &pattern))
        .collect()
}

fn advisory_debt(path: &Path, function: &FunctionMetrics, pattern: &str) -> Option<DebtItem> {
    let definition = advisory_definition(pattern)?;

    Some(DebtItem {
        id: format!("go-{}-{}-{}", pattern, path.display(), function.line),
        debt_type: DebtType::CodeSmell {
            smell_type: Some(pattern.to_string()),
        },
        priority: definition.priority,
        file: path.to_path_buf(),
        line: function.line,
        column: None,
        message: format!("Function '{}' {}", function.name, definition.message),
        context: Some(definition.context.to_string()),
    })
}

struct AdvisoryDefinition {
    priority: Priority,
    message: &'static str,
    context: &'static str,
}

fn advisory_definition(pattern: &str) -> Option<AdvisoryDefinition> {
    let definition = match pattern {
        "repetitive-error-handling" => AdvisoryDefinition {
            priority: Priority::Low,
            message: "has repetitive error handling",
            context: "Consider extracting a helper if the repeated handling obscures core logic.",
        },
        "swallowed-error" => AdvisoryDefinition {
            priority: Priority::Medium,
            message: "appears to discard an error value",
            context: "Handle, return, or explicitly document why the error can be ignored.",
        },
        "panic-in-production" => AdvisoryDefinition {
            priority: Priority::High,
            message: "calls panic outside test or main code",
            context: "Prefer returning an error unless process termination is intentional.",
        },
        "recover-without-handling" => AdvisoryDefinition {
            priority: Priority::Medium,
            message: "uses recover without visible handling",
            context: "Recover paths should log, convert, or otherwise handle the panic value.",
        },
        "goroutine-without-synchronization" => AdvisoryDefinition {
            priority: Priority::Medium,
            message: "starts a goroutine with no local lifecycle signal",
            context: "Check for cancellation, synchronization, or ownership of the goroutine lifetime.",
        },
        "defer-in-loop" => AdvisoryDefinition {
            priority: Priority::Medium,
            message: "defers work inside a loop",
            context: "Deferred calls in loops can delay cleanup and retain resources longer than expected.",
        },
        "channel-operation" => AdvisoryDefinition {
            priority: Priority::Low,
            message: "uses channel operations",
            context: "Review blocking behavior, buffering, and cancellation for this concurrency path.",
        },
        "pointer-receiver-mutation" => AdvisoryDefinition {
            priority: Priority::Low,
            message: "mutates receiver state",
            context: "State mutation is expected in some methods; keep ownership and invariants explicit.",
        },
        "collection-mutation" => AdvisoryDefinition {
            priority: Priority::Low,
            message: "mutates a map or slice element",
            context: "Review shared ownership and aliasing before passing mutable collections across boundaries.",
        },
        "package-global-mutation" => AdvisoryDefinition {
            priority: Priority::Medium,
            message: "mutates package-level state",
            context: "Package-level mutation can hide dependencies and complicate concurrent use.",
        },
        _ => return None,
    };

    Some(definition)
}

fn complexity_priority(complexity: u32, threshold: u32) -> Priority {
    if complexity > threshold * 2 {
        Priority::Critical
    } else if complexity > threshold + 5 {
        Priority::High
    } else {
        Priority::Medium
    }
}