debtmap 0.16.3

Code complexity and technical debt analyzer
Documentation
use crate::core::{FileMetrics, FunctionMetrics};
use crate::refactoring::{
    ComplexityLevel, DetectedPattern, EffortEstimate, FunctionRole, FunctionalPattern, Priority,
    PureFunctionSpec, RefactoringDetector, RefactoringOpportunity, TestabilityLevel,
};

pub struct HighComplexityDetector;

impl RefactoringDetector for HighComplexityDetector {
    fn detect_opportunities(
        &self,
        function: &FunctionMetrics,
        _file: &FileMetrics,
        role: &FunctionRole,
        _patterns: &[DetectedPattern],
    ) -> Vec<RefactoringOpportunity> {
        let tolerance = match role {
            FunctionRole::PureLogic {
                complexity_tolerance,
                ..
            } => *complexity_tolerance,
            FunctionRole::IOOrchestrator {
                complexity_tolerance,
                ..
            } => *complexity_tolerance,
            _ => 5,
        };

        if function.cyclomatic > tolerance {
            let excess_complexity = function.cyclomatic - tolerance;
            let functions_to_extract = ((excess_complexity as f32) / 3.0).ceil() as u32 + 1;

            vec![RefactoringOpportunity::ExtractPureFunctions {
                source_function: function.name.clone(),
                complexity_level: if function.cyclomatic > 15 {
                    ComplexityLevel::Severe
                } else if function.cyclomatic > 10 {
                    ComplexityLevel::High
                } else {
                    ComplexityLevel::Moderate
                },
                extraction_strategy:
                    crate::refactoring::ExtractionStrategy::DirectFunctionalTransformation {
                        patterns_to_apply: vec![
                            FunctionalPattern::MapOverLoop,
                            FunctionalPattern::FilterPredicate,
                        ],
                        functions_to_extract,
                    },
                suggested_functions: generate_suggested_functions(
                    &function.name,
                    functions_to_extract,
                ),
                functional_patterns: vec![
                    FunctionalPattern::MapOverLoop,
                    FunctionalPattern::FilterPredicate,
                    FunctionalPattern::ComposeFunctions,
                ],
                benefits: vec![
                    format!(
                        "Reduces complexity from {} to ~{}",
                        function.cyclomatic, tolerance
                    ),
                    "Pure functions are easily unit tested".to_string(),
                    "Improves code readability and maintainability".to_string(),
                ],
                effort_estimate: if functions_to_extract > 3 {
                    EffortEstimate::Medium
                } else {
                    EffortEstimate::Low
                },
                example: None,
            }]
        } else {
            vec![]
        }
    }

    fn priority(&self) -> Priority {
        Priority::High
    }
}

fn create_validation_spec(base_name: &str) -> PureFunctionSpec {
    PureFunctionSpec {
        name: format!("{}_validate", base_name),
        inputs: vec!["input: &Input".to_string()],
        output: "Result<ValidInput, Error>".to_string(),
        purpose: "Validate input data".to_string(),
        no_side_effects: true,
        testability: TestabilityLevel::Easy,
    }
}

fn create_step_spec(base_name: &str, step_num: u32) -> PureFunctionSpec {
    PureFunctionSpec {
        name: format!("{}_step_{}", base_name, step_num),
        inputs: vec!["data: &Data".to_string()],
        output: "StepResult".to_string(),
        purpose: format!("Processing step {}", step_num),
        no_side_effects: true,
        testability: TestabilityLevel::Easy,
    }
}

fn generate_suggested_functions(base_name: &str, count: u32) -> Vec<PureFunctionSpec> {
    let mut functions = Vec::new();

    if count > 0 {
        functions.push(create_validation_spec(base_name));
    }

    if count > 1 {
        functions.push(PureFunctionSpec {
            name: format!("{}_transform", base_name),
            inputs: vec!["data: ValidInput".to_string()],
            output: "TransformedData".to_string(),
            purpose: "Transform validated data".to_string(),
            no_side_effects: true,
            testability: TestabilityLevel::Easy,
        });
    }

    if count > 2 {
        functions.push(PureFunctionSpec {
            name: format!("{}_process", base_name),
            inputs: vec!["data: TransformedData".to_string()],
            output: "ProcessedResult".to_string(),
            purpose: "Apply business logic".to_string(),
            no_side_effects: true,
            testability: TestabilityLevel::Moderate,
        });
    }

    if count > 3 {
        for i in 4..=count {
            functions.push(create_step_spec(base_name, i));
        }
    }

    functions
}

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

    #[test]
    fn test_create_validation_spec() {
        let spec = create_validation_spec("my_function");
        assert_eq!(spec.name, "my_function_validate");
        assert_eq!(spec.inputs, vec!["input: &Input".to_string()]);
        assert_eq!(spec.output, "Result<ValidInput, Error>");
        assert_eq!(spec.purpose, "Validate input data");
        assert!(spec.no_side_effects);
        assert!(matches!(spec.testability, TestabilityLevel::Easy));
    }

    #[test]
    fn test_create_step_spec() {
        let spec = create_step_spec("process", 5);
        assert_eq!(spec.name, "process_step_5");
        assert_eq!(spec.inputs, vec!["data: &Data".to_string()]);
        assert_eq!(spec.output, "StepResult");
        assert_eq!(spec.purpose, "Processing step 5");
        assert!(spec.no_side_effects);
        assert!(matches!(spec.testability, TestabilityLevel::Easy));
    }

    #[test]
    fn test_generate_suggested_functions_zero_count() {
        let functions = generate_suggested_functions("base", 0);
        assert!(functions.is_empty());
    }

    #[test]
    fn test_generate_suggested_functions_one_count() {
        let functions = generate_suggested_functions("handler", 1);
        assert_eq!(functions.len(), 1);
        assert_eq!(functions[0].name, "handler_validate");
    }

    #[test]
    fn test_generate_suggested_functions_three_count() {
        let functions = generate_suggested_functions("processor", 3);
        assert_eq!(functions.len(), 3);
        assert_eq!(functions[0].name, "processor_validate");
        assert_eq!(functions[1].name, "processor_transform");
        assert_eq!(functions[2].name, "processor_process");
    }

    #[test]
    fn test_generate_suggested_functions_many_count() {
        let functions = generate_suggested_functions("complex", 6);
        assert_eq!(functions.len(), 6);
        assert_eq!(functions[0].name, "complex_validate");
        assert_eq!(functions[1].name, "complex_transform");
        assert_eq!(functions[2].name, "complex_process");
        assert_eq!(functions[3].name, "complex_step_4");
        assert_eq!(functions[4].name, "complex_step_5");
        assert_eq!(functions[5].name, "complex_step_6");
    }
}