Skip to main content
Glama

In Memoria

structural.rs40.7 kB
//! Structural pattern detection and analysis #[cfg(feature = "napi-bindings")] use napi_derive::napi; use crate::patterns::types::{Pattern, PatternExample, StructuralPattern, PatternExtractor}; use crate::types::{ParseError, SemanticConcept, LineRange}; use std::collections::{HashMap, HashSet}; use walkdir::WalkDir; use std::fs; use std::path::Path; /// Analyzer for detecting architectural and structural patterns #[cfg_attr(feature = "napi-bindings", napi)] pub struct StructuralPatternAnalyzer { patterns: HashMap<String, StructuralPattern>, architecture_signatures: HashMap<String, ArchitectureSignature>, } #[derive(Debug, Clone)] struct ArchitectureSignature { pattern_name: String, required_components: Vec<String>, directory_structure: Vec<String>, file_patterns: Vec<String>, confidence_threshold: f64, } #[derive(Debug, Clone)] struct DirectoryAnalysis { path: String, subdirectories: Vec<String>, file_types: HashMap<String, usize>, depth: usize, } #[cfg_attr(feature = "napi-bindings", napi)] impl StructuralPatternAnalyzer { #[cfg_attr(feature = "napi-bindings", napi(constructor))] pub fn new() -> Self { let mut analyzer = StructuralPatternAnalyzer { patterns: HashMap::new(), architecture_signatures: HashMap::new(), }; analyzer.initialize_signatures(); analyzer } /// Initialize common architectural pattern signatures fn initialize_signatures(&mut self) { // MVC Pattern self.architecture_signatures.insert("MVC".to_string(), ArchitectureSignature { pattern_name: "Model-View-Controller".to_string(), required_components: vec!["model".to_string(), "view".to_string(), "controller".to_string()], directory_structure: vec!["models/".to_string(), "views/".to_string(), "controllers/".to_string()], file_patterns: vec!["*Controller.*".to_string(), "*Model.*".to_string(), "*View.*".to_string()], confidence_threshold: 0.7, }); // Clean Architecture self.architecture_signatures.insert("Clean".to_string(), ArchitectureSignature { pattern_name: "Clean Architecture".to_string(), required_components: vec!["domain".to_string(), "application".to_string(), "infrastructure".to_string(), "presentation".to_string()], directory_structure: vec!["domain/".to_string(), "application/".to_string(), "infrastructure/".to_string(), "presentation/".to_string()], file_patterns: vec!["*Service.*".to_string(), "*Repository.*".to_string(), "*UseCase.*".to_string()], confidence_threshold: 0.8, }); // Layered Architecture self.architecture_signatures.insert("Layered".to_string(), ArchitectureSignature { pattern_name: "Layered Architecture".to_string(), required_components: vec!["api".to_string(), "service".to_string(), "data".to_string()], directory_structure: vec!["api/".to_string(), "service/".to_string(), "data/".to_string()], file_patterns: vec!["*Api.*".to_string(), "*Service.*".to_string(), "*Repository.*".to_string()], confidence_threshold: 0.6, }); // Microservices self.architecture_signatures.insert("Microservices".to_string(), ArchitectureSignature { pattern_name: "Microservices Architecture".to_string(), required_components: vec!["service".to_string(), "gateway".to_string()], directory_structure: vec!["services/".to_string(), "gateway/".to_string()], file_patterns: vec!["*Service.*".to_string(), "docker*".to_string(), "*Gateway.*".to_string()], confidence_threshold: 0.7, }); // Modular Monolith self.architecture_signatures.insert("Modular".to_string(), ArchitectureSignature { pattern_name: "Modular Architecture".to_string(), required_components: vec!["modules".to_string(), "shared".to_string()], directory_structure: vec!["modules/".to_string(), "shared/".to_string()], file_patterns: vec!["mod.*".to_string(), "index.*".to_string()], confidence_threshold: 0.5, }); // Event-Driven Architecture self.architecture_signatures.insert("EventDriven".to_string(), ArchitectureSignature { pattern_name: "Event-Driven Architecture".to_string(), required_components: vec!["events".to_string(), "handlers".to_string(), "publishers".to_string()], directory_structure: vec!["events/".to_string(), "handlers/".to_string()], file_patterns: vec!["*Event.*".to_string(), "*Handler.*".to_string(), "*Publisher.*".to_string()], confidence_threshold: 0.7, }); } /// Analyze structural patterns from codebase organization pub fn analyze_codebase_structure(&mut self, path: &str) -> Result<Vec<Pattern>, ParseError> { let directory_analysis = self.analyze_directory_structure(path)?; let file_analysis = self.analyze_file_patterns(path)?; let mut detected_patterns = Vec::new(); // Check each architectural signature for (pattern_key, signature) in &self.architecture_signatures { let confidence = self.calculate_structure_confidence( &directory_analysis, &file_analysis, signature, ); if confidence >= signature.confidence_threshold { let examples = self.collect_structure_examples(path, signature)?; let example_count = examples.len() as u32; let pattern = Pattern { id: format!("structural_{}", pattern_key), pattern_type: "structural".to_string(), description: format!( "{} detected with {:.1}% confidence", signature.pattern_name, confidence * 100.0 ), frequency: example_count, confidence, examples, contexts: vec!["architecture".to_string()], }; detected_patterns.push(pattern); // Store in internal patterns let structural_pattern = StructuralPattern { pattern_type: signature.pattern_name.clone(), frequency: example_count, characteristics: signature.required_components.clone(), confidence, }; self.patterns.insert(pattern_key.clone(), structural_pattern); } } Ok(detected_patterns) } /// Analyze concepts for structural relationships pub fn analyze_concept_structures(&mut self, concepts: &[SemanticConcept]) -> Result<Vec<Pattern>, ParseError> { let mut detected_patterns = Vec::new(); // Analyze file organization patterns let file_organization = self.analyze_file_organization(concepts); // Analyze dependency patterns let dependency_patterns = self.analyze_dependency_patterns(concepts); // Analyze naming structure patterns let naming_structure = self.analyze_naming_structure_patterns(concepts); detected_patterns.extend(file_organization); detected_patterns.extend(dependency_patterns); detected_patterns.extend(naming_structure); Ok(detected_patterns) } /// Detect violations of structural patterns pub fn detect_structural_violations(&self, concepts: &[SemanticConcept]) -> Vec<String> { let mut violations = Vec::new(); // Check for common structural anti-patterns violations.extend(self.detect_god_object_violations(concepts)); violations.extend(self.detect_circular_dependency_violations(concepts)); violations.extend(self.detect_layer_violations(concepts)); violations.extend(self.detect_coupling_violations(concepts)); violations } /// Generate structural recommendations pub fn generate_structural_recommendations(&self, concepts: &[SemanticConcept]) -> Vec<String> { let mut recommendations = Vec::new(); // File organization recommendations let file_metrics = self.calculate_file_metrics(concepts); if file_metrics.avg_concepts_per_file > 20.0 { recommendations.push("Consider breaking down large files into smaller, more focused modules".to_string()); } if file_metrics.max_concepts_per_file > 50 { recommendations.push(format!( "One file contains {} concepts - consider splitting this monolithic file", file_metrics.max_concepts_per_file )); } if file_metrics.total_files < 5 && concepts.len() > 100 { recommendations.push(format!( "Only {} files for {} concepts - consider better separation of concerns", file_metrics.total_files, concepts.len() )); } // Coupling recommendations let coupling_metrics = self.calculate_coupling_metrics(concepts); if coupling_metrics.high_coupling_count > 5 { recommendations.push("Reduce tight coupling between components using dependency injection or interfaces".to_string()); } // Layer separation recommendations if self.has_layer_violations(concepts) { recommendations.push("Establish clear layer boundaries and enforce dependency directions".to_string()); } // Modularity recommendations let modularity_score = self.calculate_modularity_score(concepts); if modularity_score < 0.6 { recommendations.push("Consider refactoring into more modular components with clear responsibilities".to_string()); } if recommendations.is_empty() { recommendations.push("Structural patterns look good! Consider documenting architectural decisions".to_string()); } recommendations } /// Analyze directory structure recursively fn analyze_directory_structure(&self, path: &str) -> Result<Vec<DirectoryAnalysis>, ParseError> { let mut analyses = Vec::new(); for entry in WalkDir::new(path).max_depth(5).into_iter().filter_map(|e| e.ok()) { if entry.file_type().is_dir() { let dir_path = entry.path(); let mut analysis = DirectoryAnalysis { path: dir_path.to_string_lossy().to_string(), subdirectories: Vec::new(), file_types: HashMap::new(), depth: entry.depth(), }; // Analyze immediate children if let Ok(entries) = fs::read_dir(dir_path) { for child_entry in entries.filter_map(|e| e.ok()) { if child_entry.file_type().ok().is_some_and(|ft| ft.is_dir()) { if let Some(name) = child_entry.file_name().to_str() { analysis.subdirectories.push(name.to_string()); } } else if let Some(extension) = child_entry.path().extension().and_then(|s| s.to_str()) { *analysis.file_types.entry(extension.to_string()).or_insert(0) += 1; } } } analyses.push(analysis); } } Ok(analyses) } /// Analyze file patterns in the codebase fn analyze_file_patterns(&self, path: &str) -> Result<HashMap<String, Vec<String>>, ParseError> { let mut file_patterns: HashMap<String, Vec<String>> = HashMap::new(); for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) { if entry.file_type().is_file() { let file_path = entry.path(); if let Some(file_name) = file_path.file_name().and_then(|n| n.to_str()) { // Categorize files by patterns if file_name.contains("Controller") { file_patterns.entry("controller".to_string()).or_default().push(file_name.to_string()); } if file_name.contains("Model") { file_patterns.entry("model".to_string()).or_default().push(file_name.to_string()); } if file_name.contains("View") { file_patterns.entry("view".to_string()).or_default().push(file_name.to_string()); } if file_name.contains("Service") { file_patterns.entry("service".to_string()).or_default().push(file_name.to_string()); } if file_name.contains("Repository") { file_patterns.entry("repository".to_string()).or_default().push(file_name.to_string()); } if file_name.contains("Handler") { file_patterns.entry("handler".to_string()).or_default().push(file_name.to_string()); } } } } Ok(file_patterns) } /// Calculate confidence score for a structural pattern fn calculate_structure_confidence( &self, directory_analysis: &[DirectoryAnalysis], file_patterns: &HashMap<String, Vec<String>>, signature: &ArchitectureSignature, ) -> f64 { let mut score = 0.0; let mut max_score = 0.0; // Check directory structure matches max_score += 0.4; let mut dir_matches = 0; let mut depth_penalty = 0.0; for required_dir in &signature.directory_structure { for analysis in directory_analysis { if analysis.path.contains(required_dir) || analysis.subdirectories.iter().any(|d| d.contains(&required_dir.replace("/", ""))) { dir_matches += 1; // Apply depth-based scoring - deeper structures get slight penalty for complexity if analysis.depth > 4 { depth_penalty += 0.1; } break; } } } if !signature.directory_structure.is_empty() { let base_score = dir_matches as f64 / signature.directory_structure.len() as f64; let depth_adjusted_score = (base_score - (depth_penalty / signature.directory_structure.len() as f64)).max(0.0); score += 0.4 * depth_adjusted_score; } // Check component existence max_score += 0.3; let mut component_matches = 0; for component in &signature.required_components { if file_patterns.contains_key(component) { component_matches += 1; } } if !signature.required_components.is_empty() { score += 0.3 * (component_matches as f64 / signature.required_components.len() as f64); } // Check file patterns max_score += 0.3; let mut pattern_matches = 0; for pattern in &signature.file_patterns { let pattern_key = pattern.replace("*", "").replace(".", "").to_lowercase(); if file_patterns.keys().any(|k| k.contains(&pattern_key)) { pattern_matches += 1; } } if !signature.file_patterns.is_empty() { score += 0.3 * (pattern_matches as f64 / signature.file_patterns.len() as f64); } if max_score > 0.0 { score / max_score } else { 0.0 } } /// Collect examples of structural patterns fn collect_structure_examples(&self, path: &str, signature: &ArchitectureSignature) -> Result<Vec<PatternExample>, ParseError> { let mut examples = Vec::new(); for entry in WalkDir::new(path).max_depth(3).into_iter().filter_map(|e| e.ok()) { if entry.file_type().is_dir() { let dir_name = entry.file_name().to_string_lossy().to_string(); // Check if directory name matches required components for component in &signature.required_components { if dir_name.to_lowercase().contains(&component.to_lowercase()) { examples.push(PatternExample { code: format!("Directory: {}", dir_name), file_path: entry.path().to_string_lossy().to_string(), line_range: LineRange { start: 1, end: 1 }, }); break; } } } } // Limit examples to avoid overwhelming output examples.truncate(10); Ok(examples) } /// Analyze file organization patterns from concepts fn analyze_file_organization(&self, concepts: &[SemanticConcept]) -> Vec<Pattern> { let mut patterns = Vec::new(); let mut file_concept_map: HashMap<String, Vec<&SemanticConcept>> = HashMap::new(); // Group concepts by file for concept in concepts { file_concept_map.entry(concept.file_path.clone()).or_default().push(concept); } // Check for single responsibility principle violations let large_files: Vec<_> = file_concept_map.iter() .filter(|(_, concepts)| concepts.len() > 10) .collect(); if !large_files.is_empty() { patterns.push(Pattern { id: "structural_large_files".to_string(), pattern_type: "structural".to_string(), description: format!("Files with too many concepts detected ({} files)", large_files.len()), frequency: large_files.len() as u32, confidence: 0.8, examples: large_files.into_iter().take(5).map(|(file_path, concepts)| { PatternExample { code: format!("File contains {} concepts", concepts.len()), file_path: file_path.clone(), line_range: LineRange { start: 1, end: 1 }, } }).collect(), contexts: vec!["organization".to_string()], }); } patterns } /// Analyze dependency patterns from concepts fn analyze_dependency_patterns(&self, concepts: &[SemanticConcept]) -> Vec<Pattern> { let mut patterns = Vec::new(); let mut dependencies: HashMap<String, HashSet<String>> = HashMap::new(); // Build dependency graph from relationships for concept in concepts { let concept_file = Path::new(&concept.file_path) .file_stem() .and_then(|s| s.to_str()) .unwrap_or(&concept.file_path); for (relationship_type, target) in &concept.relationships { if relationship_type.contains("import") || relationship_type.contains("depends") { dependencies.entry(concept_file.to_string()).or_default().insert(target.clone()); } } } // Detect circular dependencies let cycles = self.detect_cycles(&dependencies); if !cycles.is_empty() { patterns.push(Pattern { id: "structural_circular_dependencies".to_string(), pattern_type: "structural".to_string(), description: format!("Circular dependencies detected ({} cycles)", cycles.len()), frequency: cycles.len() as u32, confidence: 0.9, examples: cycles.into_iter().take(3).map(|cycle| { PatternExample { code: format!("Circular dependency: {}", cycle.join(" -> ")), file_path: "multiple_files".to_string(), line_range: LineRange { start: 1, end: 1 }, } }).collect(), contexts: vec!["dependency".to_string()], }); } patterns } /// Analyze naming structure patterns fn analyze_naming_structure_patterns(&self, concepts: &[SemanticConcept]) -> Vec<Pattern> { let mut patterns = Vec::new(); let mut namespace_patterns: HashMap<String, u32> = HashMap::new(); // Analyze namespace/module patterns for concept in concepts { if let Some(namespace) = self.extract_namespace_from_path(&concept.file_path) { *namespace_patterns.entry(namespace).or_insert(0) += 1; } } if !namespace_patterns.is_empty() { let most_common = namespace_patterns.iter() .max_by_key(|(_, count)| *count) .map(|(ns, count)| (ns.clone(), *count)); if let Some((namespace, count)) = most_common { patterns.push(Pattern { id: "structural_namespace_organization".to_string(), pattern_type: "structural".to_string(), description: format!("Consistent namespace organization detected ({})", namespace), frequency: count, confidence: 0.7, examples: vec![PatternExample { code: format!("Namespace pattern: {}", namespace), file_path: "multiple_files".to_string(), line_range: LineRange { start: 1, end: 1 }, }], contexts: vec!["organization".to_string()], }); } } patterns } /// Detect God Object violations fn detect_god_object_violations(&self, concepts: &[SemanticConcept]) -> Vec<String> { let mut violations = Vec::new(); let mut class_method_counts: HashMap<String, u32> = HashMap::new(); for concept in concepts { if concept.concept_type == "class" || concept.concept_type == "struct" { // Count methods in this class let method_count = concepts.iter() .filter(|c| c.concept_type == "method" || c.concept_type == "function") .filter(|c| c.file_path == concept.file_path) .filter(|c| c.line_range.start >= concept.line_range.start && c.line_range.end <= concept.line_range.end) .count() as u32; class_method_counts.insert(concept.name.clone(), method_count); if method_count > 20 { violations.push(format!( "Potential God Object: '{}' has {} methods ({}:{})", concept.name, method_count, concept.file_path, concept.line_range.start )); } } } violations } /// Detect circular dependency violations fn detect_circular_dependency_violations(&self, concepts: &[SemanticConcept]) -> Vec<String> { let mut violations = Vec::new(); let mut dependencies: HashMap<String, HashSet<String>> = HashMap::new(); // Build dependency graph for concept in concepts { for (rel_type, target) in &concept.relationships { if rel_type.contains("depends") || rel_type.contains("import") { dependencies.entry(concept.name.clone()).or_default().insert(target.clone()); } } } // Simple cycle detection let cycles = self.detect_cycles(&dependencies); for cycle in cycles { violations.push(format!( "Circular dependency detected: {}", cycle.join(" -> ") )); } violations } /// Detect layer violations fn detect_layer_violations(&self, concepts: &[SemanticConcept]) -> Vec<String> { let mut violations = Vec::new(); // Define layer hierarchy (lower numbers = higher layers) let layer_hierarchy = [ ("presentation", 0), ("api", 0), ("application", 1), ("domain", 2), ("infrastructure", 3), ("data", 3), ].iter().cloned().collect::<HashMap<&str, u32>>(); for concept in concepts { let concept_layer = self.determine_layer(&concept.file_path, &layer_hierarchy); for (rel_type, target) in &concept.relationships { if rel_type.contains("depends") || rel_type.contains("import") { // Find target concept to determine its layer if let Some(target_concept) = concepts.iter().find(|c| c.name == *target) { let target_layer = self.determine_layer(&target_concept.file_path, &layer_hierarchy); // Check for violations (higher layer depending on lower layer) if let (Some(concept_level), Some(target_level)) = (concept_layer, target_layer) { if concept_level < target_level { violations.push(format!( "Layer violation: {} depends on {} (higher layer depending on lower layer)", concept.name, target )); } } } } } } violations } /// Detect coupling violations fn detect_coupling_violations(&self, concepts: &[SemanticConcept]) -> Vec<String> { let mut violations = Vec::new(); let mut coupling_counts: HashMap<String, u32> = HashMap::new(); for concept in concepts { let coupling_count = concept.relationships.len() as u32; coupling_counts.insert(concept.name.clone(), coupling_count); if coupling_count > 10 { violations.push(format!( "High coupling detected: '{}' has {} dependencies ({}:{})", concept.name, coupling_count, concept.file_path, concept.line_range.start )); } } violations } /// Helper methods fn extract_namespace_from_path(&self, path: &str) -> Option<String> { let path_obj = Path::new(path); path_obj.parent()?.file_name()?.to_str().map(String::from) } fn detect_cycles(&self, dependencies: &HashMap<String, HashSet<String>>) -> Vec<Vec<String>> { // Simple DFS-based cycle detection let mut cycles = Vec::new(); let mut visited = HashSet::new(); let mut path = Vec::new(); for node in dependencies.keys() { if !visited.contains(node) { Self::dfs_cycle_detection(node, dependencies, &mut visited, &mut path, &mut cycles); } } cycles } fn dfs_cycle_detection( node: &str, dependencies: &HashMap<String, HashSet<String>>, visited: &mut HashSet<String>, path: &mut Vec<String>, cycles: &mut Vec<Vec<String>>, ) { if path.contains(&node.to_string()) { // Found a cycle if let Some(cycle_start) = path.iter().position(|n| n == node) { let cycle = path[cycle_start..].to_vec(); cycles.push(cycle); } return; } if visited.contains(node) { return; } visited.insert(node.to_string()); path.push(node.to_string()); if let Some(deps) = dependencies.get(node) { for dep in deps { Self::dfs_cycle_detection(dep, dependencies, visited, path, cycles); } } path.pop(); } fn determine_layer(&self, file_path: &str, layer_hierarchy: &HashMap<&str, u32>) -> Option<u32> { for (layer_name, level) in layer_hierarchy { if file_path.to_lowercase().contains(layer_name) { return Some(*level); } } None } fn calculate_file_metrics(&self, concepts: &[SemanticConcept]) -> FileMetrics { let mut file_concept_counts: HashMap<String, u32> = HashMap::new(); for concept in concepts { *file_concept_counts.entry(concept.file_path.clone()).or_insert(0) += 1; } let total_concepts = concepts.len() as f64; let file_count = file_concept_counts.len() as f64; let avg_concepts_per_file = if file_count > 0.0 { total_concepts / file_count } else { 0.0 }; FileMetrics { avg_concepts_per_file, max_concepts_per_file: file_concept_counts.values().max().copied().unwrap_or(0), total_files: file_count as u32, } } fn calculate_coupling_metrics(&self, concepts: &[SemanticConcept]) -> CouplingMetrics { let mut high_coupling_count = 0; let mut total_coupling = 0; for concept in concepts { let coupling = concept.relationships.len(); total_coupling += coupling; if coupling > 8 { high_coupling_count += 1; } } CouplingMetrics { high_coupling_count, avg_coupling: if !concepts.is_empty() { total_coupling as f64 / concepts.len() as f64 } else { 0.0 }, } } fn has_layer_violations(&self, concepts: &[SemanticConcept]) -> bool { !self.detect_layer_violations(concepts).is_empty() } fn calculate_modularity_score(&self, concepts: &[SemanticConcept]) -> f64 { // Enhanced modularity score based on file organization, coupling, and distribution let file_metrics = self.calculate_file_metrics(concepts); let coupling_metrics = self.calculate_coupling_metrics(concepts); // Base file organization score let file_score: f64 = if file_metrics.avg_concepts_per_file <= 15.0 { 0.4 } else { 0.1 }; // Penalize files that are too large (monolithic) let max_file_penalty: f64 = if file_metrics.max_concepts_per_file > 50 { 0.2 } else { 0.0 }; // Reward reasonable file distribution let distribution_bonus: f64 = if file_metrics.total_files >= 3 && (concepts.len() as f64 / file_metrics.total_files as f64) < 25.0 { 0.2 } else { 0.0 }; let coupling_score: f64 = if coupling_metrics.avg_coupling <= 5.0 { 0.4 } else { 0.1 }; (file_score + distribution_bonus + coupling_score - max_file_penalty).clamp(0.0_f64, 1.0_f64) } } #[derive(Debug)] struct FileMetrics { avg_concepts_per_file: f64, max_concepts_per_file: u32, total_files: u32, } #[derive(Debug)] struct CouplingMetrics { high_coupling_count: u32, avg_coupling: f64, } impl PatternExtractor for StructuralPatternAnalyzer { fn extract_patterns(&self, path: &str) -> Result<Vec<Pattern>, ParseError> { let mut analyzer = self.clone(); analyzer.analyze_codebase_structure(path) } } impl Clone for StructuralPatternAnalyzer { fn clone(&self) -> Self { StructuralPatternAnalyzer { patterns: self.patterns.clone(), architecture_signatures: self.architecture_signatures.clone(), } } } impl Default for StructuralPatternAnalyzer { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; use std::collections::HashMap; use tempfile::TempDir; use std::fs; fn create_test_concept(name: &str, concept_type: &str, file_path: &str, start: u32, end: u32) -> SemanticConcept { SemanticConcept { id: format!("test_{}", name), name: name.to_string(), concept_type: concept_type.to_string(), confidence: 0.8, file_path: file_path.to_string(), line_range: LineRange { start, end }, relationships: HashMap::new(), metadata: HashMap::new(), } } #[test] fn test_structural_pattern_analyzer_creation() { let analyzer = StructuralPatternAnalyzer::new(); assert!(!analyzer.architecture_signatures.is_empty()); } #[test] fn test_mvc_pattern_detection() { let temp_dir = TempDir::new().unwrap(); let base_path = temp_dir.path(); // Create MVC directory structure fs::create_dir_all(base_path.join("models")).unwrap(); fs::create_dir_all(base_path.join("views")).unwrap(); fs::create_dir_all(base_path.join("controllers")).unwrap(); // Create some files fs::write(base_path.join("models/UserModel.js"), "// User model").unwrap(); fs::write(base_path.join("views/UserView.js"), "// User view").unwrap(); fs::write(base_path.join("controllers/UserController.js"), "// User controller").unwrap(); let mut analyzer = StructuralPatternAnalyzer::new(); let patterns = analyzer.analyze_codebase_structure(base_path.to_str().unwrap()).unwrap(); let mvc_pattern = patterns.iter().find(|p| p.id.contains("MVC")); assert!(mvc_pattern.is_some()); if let Some(pattern) = mvc_pattern { assert!(pattern.confidence >= 0.7); assert_eq!(pattern.pattern_type, "structural"); } } #[test] fn test_god_object_detection() { let mut concepts = Vec::new(); // Create a class with many methods (God Object) let god_class = create_test_concept("GodClass", "class", "GodClass.js", 1, 100); concepts.push(god_class); // Add many methods to the same file for i in 0..25 { let method = create_test_concept( &format!("method{}", i), "method", "GodClass.js", i * 3 + 2, i * 3 + 4, ); concepts.push(method); } let analyzer = StructuralPatternAnalyzer::new(); let violations = analyzer.detect_god_object_violations(&concepts); assert!(!violations.is_empty()); assert!(violations[0].contains("God Object")); } #[test] fn test_circular_dependency_detection() { let mut concepts = Vec::new(); // Create concepts with circular dependencies let mut concept_a = create_test_concept("ModuleA", "class", "ModuleA.js", 1, 10); concept_a.relationships.insert("depends_on".to_string(), "ModuleB".to_string()); concepts.push(concept_a); let mut concept_b = create_test_concept("ModuleB", "class", "ModuleB.js", 1, 10); concept_b.relationships.insert("depends_on".to_string(), "ModuleA".to_string()); concepts.push(concept_b); let analyzer = StructuralPatternAnalyzer::new(); let violations = analyzer.detect_circular_dependency_violations(&concepts); assert!(!violations.is_empty()); assert!(violations[0].contains("Circular dependency")); } #[test] fn test_high_coupling_detection() { let mut concept = create_test_concept("HighlyCoupled", "class", "test.js", 1, 10); // Add many dependencies for i in 0..15 { concept.relationships.insert(format!("depends_{}", i), format!("Dependency{}", i)); } let concepts = vec![concept]; let analyzer = StructuralPatternAnalyzer::new(); let violations = analyzer.detect_coupling_violations(&concepts); assert!(!violations.is_empty()); assert!(violations[0].contains("High coupling")); } #[test] fn test_file_organization_analysis() { let concepts = vec![ // Many concepts in one file (violation) create_test_concept("Concept1", "class", "large_file.js", 1, 10), create_test_concept("Concept2", "class", "large_file.js", 11, 20), create_test_concept("Concept3", "function", "large_file.js", 21, 25), ]; // Add more concepts to trigger the pattern let mut all_concepts = concepts; for i in 4..15 { all_concepts.push(create_test_concept( &format!("Concept{}", i), "function", "large_file.js", i * 5, i * 5 + 3, )); } let analyzer = StructuralPatternAnalyzer::new(); let patterns = analyzer.analyze_file_organization(&all_concepts); let large_file_pattern = patterns.iter().find(|p| p.id.contains("large_files")); assert!(large_file_pattern.is_some()); } #[test] fn test_namespace_pattern_analysis() { let concepts = vec![ create_test_concept("Service1", "class", "services/UserService.js", 1, 10), create_test_concept("Service2", "class", "services/OrderService.js", 1, 10), create_test_concept("Service3", "class", "services/PaymentService.js", 1, 10), ]; let analyzer = StructuralPatternAnalyzer::new(); let patterns = analyzer.analyze_naming_structure_patterns(&concepts); let namespace_pattern = patterns.iter().find(|p| p.id.contains("namespace")); assert!(namespace_pattern.is_some()); if let Some(pattern) = namespace_pattern { assert!(pattern.description.contains("services")); } } #[test] fn test_modularity_score_calculation() { let good_concepts = vec![ create_test_concept("Service1", "class", "service1.js", 1, 10), create_test_concept("Service2", "class", "service2.js", 1, 10), ]; let bad_concepts = vec![ create_test_concept("GodClass", "class", "god.js", 1, 10), ]; // Add many concepts to the god class file let mut all_bad_concepts = bad_concepts; for i in 1..25 { all_bad_concepts.push(create_test_concept( &format!("Method{}", i), "method", "god.js", i * 2, i * 2 + 1, )); } let analyzer = StructuralPatternAnalyzer::new(); let good_score = analyzer.calculate_modularity_score(&good_concepts); let bad_score = analyzer.calculate_modularity_score(&all_bad_concepts); assert!(good_score > bad_score); assert!(good_score >= 0.8); assert!(bad_score <= 0.5); } #[test] fn test_layer_violation_detection() { let mut presentation_concept = create_test_concept("UI", "class", "presentation/UI.js", 1, 10); presentation_concept.relationships.insert("depends_on".to_string(), "DatabaseLayer".to_string()); let data_concept = create_test_concept("DatabaseLayer", "class", "data/Database.js", 1, 10); let concepts = vec![presentation_concept, data_concept]; let analyzer = StructuralPatternAnalyzer::new(); let violations = analyzer.detect_layer_violations(&concepts); assert!(!violations.is_empty()); assert!(violations[0].contains("Layer violation")); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pi22by7/In-Memoria'

If you have feedback or need assistance with the MCP directory API, please join our Discord server