mod.rs•22.8 kB
//! Pattern learning and analysis modules
//!
//! This module provides comprehensive pattern recognition and learning capabilities
//! across multiple domains: naming conventions, structural patterns, implementation
//! patterns, and approach prediction.
// Core types and traits
pub mod types;
// Specialized pattern analyzers
pub mod naming;
pub mod structural;
pub mod implementation;
pub mod prediction;
pub mod learning;
// Re-export main types and analyzers
pub use types::*;
pub use naming::NamingPatternAnalyzer;
pub use structural::StructuralPatternAnalyzer;
pub use implementation::ImplementationPatternAnalyzer;
pub use prediction::ApproachPredictor;
pub use learning::PatternLearningEngine;
// Legacy compatibility - re-export the main pattern learning functionality
// through the new modular engine
#[cfg(feature = "napi-bindings")]
use napi_derive::napi;
/// Legacy PatternLearner for backwards compatibility
#[derive(Default)]
#[cfg_attr(feature = "napi-bindings", napi)]
pub struct PatternLearner {
    engine: PatternLearningEngine,
}
#[cfg_attr(feature = "napi-bindings", napi)]
impl PatternLearner {
    #[cfg_attr(feature = "napi-bindings", napi(constructor))]
    pub fn new() -> Self {
        PatternLearner {
            engine: PatternLearningEngine::new(),
        }
    }
    /// Learn patterns from an entire codebase
    /// 
    /// # Safety
    /// This function is marked unsafe for NAPI compatibility. It performs file system operations
    /// and pattern analysis that are inherently safe but marked unsafe for JavaScript interop.
    #[cfg_attr(feature = "napi-bindings", napi)]
    pub async unsafe fn learn_from_codebase(&mut self, path: String) -> Result<Vec<Pattern>, crate::types::ParseError> {
        self.engine.learn_from_codebase(path).await
    }
    /// Extract patterns from a specific path
    /// 
    /// # Safety
    /// This function is marked unsafe for NAPI compatibility. It performs file system operations
    /// and pattern analysis that are inherently safe but marked unsafe for JavaScript interop.
    #[cfg_attr(feature = "napi-bindings", napi)]
    pub async unsafe fn extract_patterns(&self, path: String) -> Result<Vec<Pattern>, crate::types::ParseError> {
        // Use the learning engine to extract patterns
        let naming_analyzer = NamingPatternAnalyzer::new();
        let structural_analyzer = StructuralPatternAnalyzer::new();
        let implementation_analyzer = ImplementationPatternAnalyzer::new();
        
        let mut all_patterns = Vec::new();
        
        // Extract patterns from each analyzer
        all_patterns.extend(naming_analyzer.extract_patterns(&path)?);
        all_patterns.extend(structural_analyzer.extract_patterns(&path)?);
        all_patterns.extend(implementation_analyzer.extract_patterns(&path)?);
        
        Ok(all_patterns)
    }
    /// Analyze file changes to identify patterns (original signature)
    /// 
    /// # Safety
    /// This function is marked unsafe due to NAPI bindings requirements.
    /// It should only be called from properly initialized JavaScript contexts.
    #[cfg_attr(feature = "napi-bindings", napi)]
    pub async unsafe fn analyze_file_change(
        &self,
        change_data: String,
    ) -> Result<PatternAnalysisResult, crate::types::ParseError> {
        self.analyze_file_change_internal(change_data).await
    }
    /// Internal implementation for analyze_file_change (from original)
    pub async fn analyze_file_change_internal(
        &self,
        change_data: String,
    ) -> Result<PatternAnalysisResult, crate::types::ParseError> {
        // Parse the change data (would be JSON in real implementation)
        let detected = self.detect_patterns_in_change(&change_data)?;
        let violations = self.detect_pattern_violations(&change_data)?;
        let recommendations = self.generate_recommendations(&detected, &violations)?;
        Ok(PatternAnalysisResult {
            detected,
            violations,
            recommendations,
            learned: None, // Would contain newly learned patterns
        })
    }
    /// Find patterns relevant to a given problem description (original signature)
    /// 
    /// # Safety
    /// This function is marked unsafe due to NAPI bindings requirements.
    /// It should only be called from properly initialized JavaScript contexts.
    #[cfg_attr(feature = "napi-bindings", napi)]
    pub async unsafe fn find_relevant_patterns(
        &self,
        problem_description: String,
        current_file: Option<String>,
        selected_code: Option<String>,
    ) -> Result<Vec<Pattern>, crate::types::ParseError> {
        self.find_relevant_patterns_internal(problem_description, current_file, selected_code)
            .await
    }
    /// Internal implementation for find_relevant_patterns (from original)
    pub async fn find_relevant_patterns_internal(
        &self,
        problem_description: String,
        current_file: Option<String>,
        selected_code: Option<String>,
    ) -> Result<Vec<Pattern>, crate::types::ParseError> {
        let mut relevant_patterns = Vec::new();
        // Analyze problem description for keywords
        let keywords = self.extract_keywords(&problem_description);
        // Find patterns matching the context using engine's learned patterns
        let learned_patterns = self.engine.get_learned_patterns();
        for pattern in learned_patterns {
            let relevance_score =
                self.calculate_pattern_relevance(&pattern, &keywords, ¤t_file, &selected_code);
            if relevance_score > 0.5 {
                relevant_patterns.push(pattern);
            }
        }
        // Sort by relevance and confidence
        relevant_patterns.sort_by(|a, b| {
            let score_a = a.confidence * a.frequency as f64;
            let score_b = b.confidence * b.frequency as f64;
            score_b
                .partial_cmp(&score_a)
                .unwrap_or(std::cmp::Ordering::Equal)
        });
        Ok(relevant_patterns.into_iter().take(5).collect())
    }
    /// Predict coding approach based on problem description and context (original signature)
    /// 
    /// # Safety
    /// This function is marked unsafe due to NAPI bindings requirements.
    /// It should only be called from properly initialized JavaScript contexts.
    #[cfg_attr(feature = "napi-bindings", napi)]
    pub async unsafe fn predict_approach(
        &self,
        problem_description: String,
        context: std::collections::HashMap<String, String>,
    ) -> Result<ApproachPrediction, crate::types::ParseError> {
        self.predict_approach_internal(problem_description, context)
            .await
    }
    /// Internal implementation for predict_approach (from original)
    pub async fn predict_approach_internal(
        &self,
        problem_description: String,
        context: std::collections::HashMap<String, String>,
    ) -> Result<ApproachPrediction, crate::types::ParseError> {
        let keywords = self.extract_keywords(&problem_description);
        let relevant_patterns = self.find_patterns_by_keywords(&keywords);
        // Analyze problem complexity
        let complexity = self.estimate_problem_complexity(&problem_description, &context);
        // Generate approach based on learned patterns
        let approach = self.generate_approach(&relevant_patterns, &complexity);
        let prediction = ApproachPrediction {
            approach: approach.description,
            confidence: approach.confidence,
            reasoning: approach.reasoning,
            patterns: relevant_patterns
                .into_iter()
                .map(|p| p.pattern_type)
                .collect(),
            complexity: complexity.to_string(),
        };
        Ok(prediction)
    }
    /// Learn from analysis data
    /// 
    /// # Safety
    /// This function is marked unsafe for NAPI compatibility. It performs data parsing and
    /// learning operations that are inherently safe but marked unsafe for JavaScript interop.
    #[cfg_attr(feature = "napi-bindings", napi)]
    pub async unsafe fn learn_from_analysis(&mut self, analysis_data: String) -> Result<bool, crate::types::ParseError> {
        self.engine.learn_from_analysis(analysis_data).await
    }
    /// Update pattern learner from change data (from original implementation)
    /// 
    /// # Safety
    /// This function is marked unsafe for NAPI compatibility. It performs data parsing and
    /// pattern update operations that are inherently safe but marked unsafe for JavaScript interop.
    #[cfg_attr(feature = "napi-bindings", napi)]
    pub async unsafe fn update_from_change(&mut self, change_data: String) -> Result<bool, crate::types::ParseError> {
        self.engine.update_from_change(change_data).await
    }
    // Helper methods from original implementation
    fn detect_patterns_in_change(&self, _change_data: &str) -> Result<Vec<String>, crate::types::ParseError> {
        // Detect which patterns are present in the change
        Ok(vec!["naming_camelCase_function".to_string()])
    }
    fn detect_pattern_violations(&self, _change_data: &str) -> Result<Vec<String>, crate::types::ParseError> {
        // Detect violations of established patterns
        Ok(vec![])
    }
    fn generate_recommendations(
        &self,
        _detected: &[String],
        _violations: &[String],
    ) -> Result<Vec<String>, crate::types::ParseError> {
        // Generate recommendations based on detected patterns and violations
        Ok(vec![
            "Consider using consistent naming convention".to_string()
        ])
    }
    fn extract_keywords(&self, text: &str) -> Vec<String> {
        // Extract relevant keywords from text
        text.split_whitespace()
            .filter(|word| word.len() > 3)
            .map(|word| word.to_lowercase())
            .collect()
    }
    fn calculate_pattern_relevance(
        &self,
        pattern: &Pattern,
        keywords: &[String],
        _current_file: &Option<String>,
        _selected_code: &Option<String>,
    ) -> f64 {
        // Calculate how relevant a pattern is to the current context
        let mut relevance = 0.0;
        // Check keyword matches
        for keyword in keywords {
            if pattern.description.to_lowercase().contains(keyword) {
                relevance += 0.2;
            }
            if pattern.pattern_type.to_lowercase().contains(keyword) {
                relevance += 0.3;
            }
        }
        // Factor in pattern confidence and frequency
        relevance += pattern.confidence * 0.3;
        relevance += (pattern.frequency as f64 / 100.0) * 0.2;
        relevance.min(1.0)
    }
    fn find_patterns_by_keywords(&self, keywords: &[String]) -> Vec<Pattern> {
        let mut matching_patterns = Vec::new();
        let learned_patterns = self.engine.get_learned_patterns();
        for pattern in learned_patterns {
            for keyword in keywords {
                if pattern.description.to_lowercase().contains(keyword)
                    || pattern.pattern_type.to_lowercase().contains(keyword)
                {
                    matching_patterns.push(pattern);
                    break;
                }
            }
        }
        matching_patterns
    }
    fn estimate_problem_complexity(
        &self,
        problem_description: &str,
        _context: &std::collections::HashMap<String, String>,
    ) -> ProblemComplexity {
        let word_count = problem_description.split_whitespace().count();
        if word_count < 10 {
            ProblemComplexity::Low
        } else if word_count < 30 {
            ProblemComplexity::Medium
        } else {
            ProblemComplexity::High
        }
    }
    fn generate_approach(
        &self,
        relevant_patterns: &[Pattern],
        complexity: &ProblemComplexity,
    ) -> GeneratedApproach {
        let confidence = if relevant_patterns.is_empty() {
            0.3
        } else {
            relevant_patterns.iter().map(|p| p.confidence).sum::<f64>()
                / relevant_patterns.len() as f64
        };
        let description = match complexity {
            ProblemComplexity::Low => {
                "Use simple, direct implementation following established patterns"
            }
            ProblemComplexity::Medium => {
                "Break down into smaller components, apply relevant design patterns"
            }
            ProblemComplexity::High => {
                "Design comprehensive solution with multiple layers and patterns"
            }
        };
        let reasoning = format!(
            "Based on {} relevant patterns and {} complexity assessment",
            relevant_patterns.len(),
            complexity
        );
        GeneratedApproach {
            description: description.to_string(),
            confidence,
            reasoning,
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::LineRange;
    #[test]
    fn test_pattern_learner_creation() {
        let learner = PatternLearner::new();
        assert!(learner.engine.get_learning_metrics().total_patterns_learned == 0);
    }
    #[test]
    fn test_pattern_creation() {
        let pattern = Pattern {
            id: "test_pattern".to_string(),
            pattern_type: "naming".to_string(),
            description: "Test pattern".to_string(),
            frequency: 5,
            confidence: 0.8,
            examples: vec![],
            contexts: vec!["test".to_string()],
        };
        assert_eq!(pattern.id, "test_pattern");
        assert_eq!(pattern.pattern_type, "naming");
        assert_eq!(pattern.frequency, 5);
        assert_eq!(pattern.confidence, 0.8);
    }
    #[test]
    fn test_pattern_analysis_result() {
        let result = PatternAnalysisResult {
            detected: vec!["pattern1".to_string()],
            violations: vec!["violation1".to_string()],
            recommendations: vec!["Use consistent naming".to_string()],
            learned: None,
        };
        assert_eq!(result.detected.len(), 1);
        assert_eq!(result.violations.len(), 1);
        assert_eq!(result.recommendations.len(), 1);
        assert!(result.learned.is_none());
    }
    #[tokio::test]
    async fn test_extract_patterns_internal() {
        let learner = PatternLearner::new();
        let result = unsafe { learner.extract_patterns("/test/path".to_string()).await };
        
        assert!(result.is_ok());
        let patterns = result.unwrap();
        // Should have some patterns from the analyzers
        assert!(!patterns.is_empty());
    }
    #[tokio::test]
    async fn test_analyze_file_change_internal() {
        let learner = PatternLearner::new();
        let change_data = r#"{
            "type": "modify",
            "file": "test.ts",
            "oldPath": "test.ts",
            "newPath": "test.ts"
        }"#.to_string();
        
        let result = learner.analyze_file_change_internal(change_data).await;
        
        assert!(result.is_ok());
        let analysis = result.unwrap();
        assert!(analysis.detected.len() >= 1);
        assert!(analysis.recommendations.len() >= 1);
    }
    #[tokio::test]
    async fn test_find_relevant_patterns_internal() {
        let mut learner = PatternLearner::new();
        
        // Add a test pattern to the engine first
        let pattern = Pattern {
            id: "test_function".to_string(),
            pattern_type: "function".to_string(),
            description: "Function pattern for testing".to_string(),
            frequency: 10,
            confidence: 0.9,
            examples: vec![PatternExample {
                code: "function test() {}".to_string(),
                file_path: "test.ts".to_string(),
                line_range: LineRange { start: 1, end: 1 },
            }],
            contexts: vec!["typescript".to_string()],
        };
        learner.engine.insert_pattern("test_function".to_string(), pattern);
        
        let result = learner.find_relevant_patterns_internal(
            "I need to create a function".to_string(),
            Some("test.ts".to_string()),
            None,
        ).await;
        
        assert!(result.is_ok());
        let patterns = result.unwrap();
        assert!(patterns.len() > 0);
        assert_eq!(patterns[0].pattern_type, "function");
    }
    #[tokio::test]
    async fn test_predict_approach_internal() {
        let mut learner = PatternLearner::new();
        
        // Add test patterns
        let pattern = Pattern {
            id: "api_pattern".to_string(),
            pattern_type: "api".to_string(),
            description: "REST API pattern".to_string(),
            frequency: 15,
            confidence: 0.85,
            examples: vec![PatternExample {
                code: "app.get('/api', handler)".to_string(),
                file_path: "server.js".to_string(),
                line_range: LineRange { start: 10, end: 10 },
            }],
            contexts: vec!["express".to_string()],
        };
        learner.engine.insert_pattern("api_pattern".to_string(), pattern);
        
        let mut context = std::collections::HashMap::new();
        context.insert("framework".to_string(), "express".to_string());
        context.insert("language".to_string(), "javascript".to_string());
        
        let result = learner.predict_approach_internal(
            "Build a REST API endpoint".to_string(),
            context,
        ).await;
        
        assert!(result.is_ok());
        let prediction = result.unwrap();
        assert!(prediction.confidence > 0.0);
        assert!(prediction.patterns.len() > 0);
        assert!(!prediction.approach.is_empty());
    }
    #[tokio::test]
    async fn test_learn_from_analysis() {
        let mut learner = PatternLearner::new();
        let analysis_data = r#"{
            "patterns": {
                "detected": ["service_pattern", "dependency_injection"],
                "learned": []
            },
            "concepts": [
                {
                    "name": "UserService",
                    "type": "class",
                    "patterns": ["service", "dependency_injection"]
                }
            ]
        }"#.to_string();
        
        let result = unsafe { learner.learn_from_analysis(analysis_data).await };
        assert!(result.is_ok());
        let updated = result.unwrap();
        assert!(updated); // Should return true since patterns were updated
    }
    #[tokio::test]
    async fn test_update_from_change() {
        let mut learner = PatternLearner::new();
        
        let change_data = r#"{
            "type": "modify",
            "path": "test.ts",
            "content": "function newName() {}",
            "language": "typescript"
        }"#.to_string();
        
        let result = unsafe { learner.update_from_change(change_data).await };
        assert!(result.is_ok());
        assert!(result.unwrap());
    }
    #[test]
    fn test_extract_keywords() {
        let learner = PatternLearner::new();
        let keywords = learner.extract_keywords("Build a REST API endpoint using Express");
        
        assert!(keywords.contains(&"build".to_string()));
        assert!(keywords.contains(&"rest".to_string()));
        assert!(keywords.contains(&"endpoint".to_string()));
        assert!(keywords.contains(&"using".to_string()));
        assert!(keywords.contains(&"express".to_string()));
    }
    #[test]
    fn test_problem_complexity_estimation() {
        let learner = PatternLearner::new();
        let context = std::collections::HashMap::new();
        
        let low = learner.estimate_problem_complexity("Simple task", &context);
        assert_eq!(low, ProblemComplexity::Low);
        
        let medium = learner.estimate_problem_complexity("Build a REST API with authentication and user management", &context);
        assert_eq!(medium, ProblemComplexity::Medium);
        
        let high = learner.estimate_problem_complexity(
            "Design and implement a comprehensive microservices architecture with distributed caching, message queuing, service discovery, and fault tolerance", 
            &context
        );
        assert_eq!(high, ProblemComplexity::High);
    }
    #[test]
    fn test_pattern_relevance_calculation() {
        let learner = PatternLearner::new();
        let pattern = Pattern {
            id: "test".to_string(),
            pattern_type: "function".to_string(),
            description: "Function pattern for JavaScript development".to_string(),
            frequency: 10,
            confidence: 0.8,
            examples: vec![],
            contexts: vec!["javascript".to_string()],
        };
        
        let keywords = vec!["function".to_string(), "javascript".to_string()];
        let relevance = learner.calculate_pattern_relevance(&pattern, &keywords, &None, &None);
        
        assert!(relevance > 0.5);
    }
    #[test]
    fn test_module_exports() {
        // Test that all main types are accessible
        let _naming = NamingPatternAnalyzer::new();
        let _structural = StructuralPatternAnalyzer::new();
        let _implementation = ImplementationPatternAnalyzer::new();
        let _predictor = ApproachPredictor::new();
        let _engine = PatternLearningEngine::new();
        
        // Test legacy compatibility
        let _legacy = PatternLearner::new();
    }
    #[test]
    fn test_approach_prediction_types() {
        let prediction = ApproachPrediction {
            approach: "Use modular architecture".to_string(),
            confidence: 0.85,
            reasoning: "Based on complexity analysis".to_string(),
            patterns: vec!["modular".to_string()],
            complexity: "medium".to_string(),
        };
        assert_eq!(prediction.approach, "Use modular architecture");
        assert_eq!(prediction.confidence, 0.85);
        assert!(!prediction.reasoning.is_empty());
        assert!(!prediction.patterns.is_empty());
        assert_eq!(prediction.complexity, "medium");
    }
    #[test]
    fn test_pattern_example_creation() {
        let example = PatternExample {
            code: "function calculateTotal() { return 42; }".to_string(),
            file_path: "utils.ts".to_string(),
            line_range: LineRange { start: 15, end: 15 },
        };
        assert!(example.code.contains("function"));
        assert!(example.code.contains("calculateTotal"));
        assert_eq!(example.file_path, "utils.ts");
        assert_eq!(example.line_range.start, 15);
        assert_eq!(example.line_range.end, 15);
    }
}