debtmap 0.16.6

Code complexity and technical debt analyzer
Documentation
use crate::extraction_patterns::{
    AccumulationOp, AnalysisContext, ComplexityImpact, ExtractablePattern, ExtractionSuggestion,
    MatchedPattern, Parameter, PatternMatcher,
};
use syn::File;

pub struct JavaScriptPatternMatcher;

impl Default for JavaScriptPatternMatcher {
    fn default() -> Self {
        Self::new()
    }
}

impl JavaScriptPatternMatcher {
    pub fn new() -> Self {
        Self
    }

    #[allow(dead_code)]
    fn detect_array_methods(&self) -> Vec<ExtractablePattern> {
        // Would analyze JS AST for array method chains
        Vec::new()
    }

    #[allow(dead_code)]
    fn detect_promise_chains(&self) -> Vec<ExtractablePattern> {
        // Would analyze JS AST for promise chains that could be extracted
        Vec::new()
    }

    #[allow(dead_code)]
    fn detect_callback_patterns(&self) -> Vec<ExtractablePattern> {
        // Would analyze JS AST for callback patterns
        Vec::new()
    }
}

impl PatternMatcher for JavaScriptPatternMatcher {
    fn match_patterns(&self, _ast: &File, _context: &AnalysisContext) -> Vec<MatchedPattern> {
        // JavaScript pattern matching would use a JS parser
        // For now, return empty vec as placeholder
        Vec::new()
    }

    fn score_confidence(&self, pattern: &MatchedPattern, context: &AnalysisContext) -> f32 {
        use crate::extraction_patterns::confidence::ConfidenceScorer;
        ConfidenceScorer::score_pattern(pattern, context)
    }

    fn generate_extraction(&self, pattern: &MatchedPattern) -> ExtractionSuggestion {
        use crate::extraction_patterns::naming::FunctionNameInferrer;

        let suggested_name = FunctionNameInferrer::infer_name(&pattern.pattern, "javascript");

        let (start_line, end_line) = match &pattern.pattern {
            ExtractablePattern::AccumulationLoop {
                start_line,
                end_line,
                ..
            }
            | ExtractablePattern::GuardChainSequence {
                start_line,
                end_line,
                ..
            }
            | ExtractablePattern::TransformationPipeline {
                start_line,
                end_line,
                ..
            }
            | ExtractablePattern::SimilarBranches {
                start_line,
                end_line,
                ..
            }
            | ExtractablePattern::NestedExtraction {
                start_line,
                end_line,
                ..
            } => (*start_line, *end_line),
        };

        let parameters = self.extract_parameters(&pattern.pattern);
        let return_type = self.infer_return_type(&pattern.pattern);
        let complexity_reduction =
            self.calculate_complexity_reduction(&pattern.pattern, &pattern.context);
        let example = self.generate_example(&pattern.pattern, &suggested_name);

        ExtractionSuggestion {
            pattern_type: pattern.pattern.clone(),
            start_line,
            end_line,
            suggested_name,
            confidence: pattern.confidence,
            parameters,
            return_type,
            complexity_reduction,
            example_transformation: example,
        }
    }
}

impl JavaScriptPatternMatcher {
    fn extract_parameters(&self, pattern: &ExtractablePattern) -> Vec<Parameter> {
        match pattern {
            ExtractablePattern::AccumulationLoop {
                iterator_binding, ..
            } => {
                vec![Parameter {
                    name: format!("{}Array", iterator_binding),
                    type_hint: "Array".to_string(),
                    is_mutable: false,
                }]
            }
            ExtractablePattern::GuardChainSequence { .. } => {
                vec![Parameter {
                    name: "value".to_string(),
                    type_hint: "any".to_string(),
                    is_mutable: false,
                }]
            }
            ExtractablePattern::TransformationPipeline { input_binding, .. } => {
                vec![Parameter {
                    name: input_binding.clone(),
                    type_hint: "any".to_string(),
                    is_mutable: false,
                }]
            }
            _ => Vec::new(),
        }
    }

    fn infer_return_type(&self, pattern: &ExtractablePattern) -> String {
        match pattern {
            ExtractablePattern::AccumulationLoop { operation, .. } => {
                Self::classify_accumulation_return_type(operation)
            }
            ExtractablePattern::GuardChainSequence { .. } => "boolean".to_string(),
            ExtractablePattern::TransformationPipeline { output_type, .. } => output_type.clone(),
            _ => "void".to_string(),
        }
    }

    /// Pure function to classify return type based on accumulation operation
    fn classify_accumulation_return_type(operation: &AccumulationOp) -> String {
        match operation {
            AccumulationOp::Sum | AccumulationOp::Product => "number".to_string(),
            AccumulationOp::Concatenation => "string".to_string(),
            AccumulationOp::Collection => "Array".to_string(),
            AccumulationOp::Custom(_) => "any".to_string(),
        }
    }

    fn calculate_complexity_reduction(
        &self,
        pattern: &ExtractablePattern,
        context: &AnalysisContext,
    ) -> ComplexityImpact {
        let extracted_complexity = match pattern {
            ExtractablePattern::AccumulationLoop {
                filter, transform, ..
            } => {
                let base = 2;
                let filter_complexity = if filter.is_some() { 1 } else { 0 };
                let transform_complexity = if transform.is_some() { 1 } else { 0 };
                base + filter_complexity + transform_complexity
            }
            ExtractablePattern::GuardChainSequence { checks, .. } => checks.len() as u32,
            ExtractablePattern::TransformationPipeline { stages, .. } => stages.len() as u32 * 2,
            _ => 3,
        };

        ComplexityImpact {
            current_cyclomatic: context.complexity_before,
            predicted_cyclomatic: context
                .complexity_before
                .saturating_sub(extracted_complexity),
            current_cognitive: context.complexity_before * 2,
            predicted_cognitive: (context.complexity_before * 2)
                .saturating_sub(extracted_complexity * 2),
            extracted_function_complexity: extracted_complexity,
        }
    }

    fn generate_example(&self, pattern: &ExtractablePattern, function_name: &str) -> String {
        match pattern {
            ExtractablePattern::AccumulationLoop {
                iterator_binding, ..
            } => {
                format!(
                    "// Before:\n\
                     let result = 0;\n\
                     for (const {} of items) {{\n\
                         result += {};\n\
                     }}\n\n\
                     // After:\n\
                     const result = {}(items);",
                    iterator_binding, iterator_binding, function_name
                )
            }
            ExtractablePattern::GuardChainSequence { .. } => {
                format!(
                    "// Before:\n\
                     if (!condition1) {{ throw new Error('Invalid'); }}\n\
                     if (!condition2) {{ throw new Error('Invalid'); }}\n\n\
                     // After:\n\
                     {}(value);",
                    function_name
                )
            }
            ExtractablePattern::TransformationPipeline { .. } => {
                format!(
                    "// Before:\n\
                     const doubled = items.map(x => x * 2);\n\
                     const filtered = doubled.filter(x => x > 10);\n\
                     const result = filtered.sort();\n\n\
                     // After:\n\
                     const result = {}(items);",
                    function_name
                )
            }
            _ => "// Example transformation".to_string(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::extraction_patterns::{AccumulationOp, ExtractablePattern, ReturnType};

    #[test]
    fn test_classify_accumulation_return_type() {
        // Test numeric operations return "number"
        assert_eq!(
            JavaScriptPatternMatcher::classify_accumulation_return_type(&AccumulationOp::Sum),
            "number"
        );
        assert_eq!(
            JavaScriptPatternMatcher::classify_accumulation_return_type(&AccumulationOp::Product),
            "number"
        );

        // Test string operation returns "string"
        assert_eq!(
            JavaScriptPatternMatcher::classify_accumulation_return_type(
                &AccumulationOp::Concatenation
            ),
            "string"
        );

        // Test collection operation returns "Array"
        assert_eq!(
            JavaScriptPatternMatcher::classify_accumulation_return_type(
                &AccumulationOp::Collection
            ),
            "Array"
        );

        // Test custom operation returns "any"
        assert_eq!(
            JavaScriptPatternMatcher::classify_accumulation_return_type(&AccumulationOp::Custom(
                "custom".to_string()
            )),
            "any"
        );
    }

    #[test]
    fn test_infer_return_type_guard_chain() {
        let matcher = JavaScriptPatternMatcher;
        let pattern = ExtractablePattern::GuardChainSequence {
            checks: vec![],
            early_return: ReturnType {
                type_name: "default".to_string(),
                is_early_return: true,
            },
            start_line: 1,
            end_line: 10,
        };
        assert_eq!(matcher.infer_return_type(&pattern), "boolean");
    }

    #[test]
    fn test_infer_return_type_transformation_pipeline() {
        let matcher = JavaScriptPatternMatcher;
        let pattern = ExtractablePattern::TransformationPipeline {
            input_binding: "input".to_string(),
            stages: vec![],
            output_type: "CustomType".to_string(),
            start_line: 1,
            end_line: 10,
        };
        assert_eq!(matcher.infer_return_type(&pattern), "CustomType");
    }

    #[test]
    fn test_infer_return_type_accumulation_loops() {
        let matcher = JavaScriptPatternMatcher;

        // Test Sum operation
        let sum_pattern = ExtractablePattern::AccumulationLoop {
            iterator_binding: "items".to_string(),
            accumulator: "sum".to_string(),
            operation: AccumulationOp::Sum,
            filter: None,
            transform: None,
            start_line: 1,
            end_line: 10,
        };
        assert_eq!(matcher.infer_return_type(&sum_pattern), "number");

        // Test Collection operation
        let collection_pattern = ExtractablePattern::AccumulationLoop {
            iterator_binding: "items".to_string(),
            accumulator: "result".to_string(),
            operation: AccumulationOp::Collection,
            filter: None,
            transform: None,
            start_line: 1,
            end_line: 10,
        };
        assert_eq!(matcher.infer_return_type(&collection_pattern), "Array");
    }

    #[test]
    fn test_infer_return_type_default_cases() {
        let matcher = JavaScriptPatternMatcher;

        // Test default case with an unhandled pattern - using SimilarBranches as it exists
        let pattern = ExtractablePattern::SimilarBranches {
            condition_var: "test".to_string(),
            common_operations: vec![],
            branch_specific: vec![],
            start_line: 1,
            end_line: 10,
        };
        assert_eq!(matcher.infer_return_type(&pattern), "void");
    }
}