Skip to main content
Glama

In Memoria

complexity.rs9.41 kB
//! Complexity analysis and metrics calculation #[cfg(feature = "napi-bindings")] use napi_derive::napi; use crate::types::{SemanticConcept, ComplexityMetrics}; use std::collections::HashMap; /// Analyzer for calculating code complexity metrics #[cfg_attr(feature = "napi-bindings", napi)] pub struct ComplexityAnalyzer; #[cfg_attr(feature = "napi-bindings", napi)] impl ComplexityAnalyzer { #[cfg_attr(feature = "napi-bindings", napi(constructor))] pub fn new() -> Self { ComplexityAnalyzer } /// Calculate complexity metrics from a set of semantic concepts pub fn calculate_complexity(concepts: &Vec<SemanticConcept>) -> ComplexityMetrics { let mut function_count = 0; let mut class_count = 0; let mut file_count = HashMap::new(); let mut total_lines = 0; let mut max_depth = 0; for concept in concepts { match concept.concept_type.as_str() { "function" | "method" | "procedure" => function_count += 1, "class" | "interface" | "struct" | "enum" => class_count += 1, _ => {} } // Count unique files file_count.insert(&concept.file_path, true); // Track line ranges for total lines calculation let concept_lines = concept.line_range.end - concept.line_range.start + 1; total_lines += concept_lines; // Calculate depth from relationships (simplified metric) let relationship_depth = concept.relationships.len() as u32; if relationship_depth > max_depth { max_depth = relationship_depth; } } let file_count = file_count.len() as u32; let avg_functions_per_file = if file_count > 0 { function_count as f64 / file_count as f64 } else { 0.0 }; let avg_lines_per_concept = if !concepts.is_empty() { total_lines as f64 / concepts.len() as f64 } else { 0.0 }; ComplexityMetrics { cyclomatic_complexity: Self::estimate_cyclomatic_complexity(concepts), cognitive_complexity: Self::estimate_cognitive_complexity(concepts), function_count, class_count, file_count, avg_functions_per_file, avg_lines_per_concept, max_nesting_depth: max_depth, } } /// Estimate cyclomatic complexity based on concept analysis fn estimate_cyclomatic_complexity(concepts: &Vec<SemanticConcept>) -> f64 { let mut total_complexity = 0.0; let mut function_count = 0; for concept in concepts { if concept.concept_type == "function" || concept.concept_type == "method" { function_count += 1; // Base complexity of 1 for each function let mut complexity = 1.0; // Add complexity based on metadata patterns if let Some(body) = concept.metadata.get("body") { complexity += Self::count_decision_points(body); } // Factor in confidence - lower confidence might indicate more complex code complexity *= 2.0 - concept.confidence; total_complexity += complexity; } } if function_count > 0 { total_complexity / function_count as f64 } else { 1.0 } } /// Estimate cognitive complexity based on nesting and control flow fn estimate_cognitive_complexity(concepts: &Vec<SemanticConcept>) -> f64 { let mut total_cognitive = 0.0; let mut function_count = 0; for concept in concepts { if concept.concept_type == "function" || concept.concept_type == "method" { function_count += 1; let mut cognitive = 0.0; // Base cognitive load cognitive += 1.0; // Add load based on relationships (dependencies increase cognitive load) cognitive += concept.relationships.len() as f64 * 0.5; // Add load based on line span (longer functions are harder to understand) let line_span = concept.line_range.end - concept.line_range.start; if line_span > 20 { cognitive += (line_span as f64 / 20.0) * 0.3; } total_cognitive += cognitive; } } if function_count > 0 { total_cognitive / function_count as f64 } else { 1.0 } } /// Count decision points in code (simplified heuristic) fn count_decision_points(body: &str) -> f64 { let mut count = 0.0; // Look for common control flow keywords for keyword in &["if", "while", "for", "switch", "case", "catch", "&&", "||"] { count += body.matches(keyword).count() as f64; } // Ternary operators count += body.matches('?').count() as f64; count } } impl Default for ComplexityAnalyzer { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; use crate::types::LineRange; use std::collections::HashMap; 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_complexity_analyzer_creation() { let _analyzer = ComplexityAnalyzer::new(); assert!(true); // Constructor should work } #[test] fn test_calculate_basic_complexity() { let concepts = vec![ create_test_concept("test_function", "function", "test.rs", 1, 10), create_test_concept("TestClass", "class", "test.rs", 15, 30), ]; let metrics = ComplexityAnalyzer::calculate_complexity(&concepts); assert_eq!(metrics.function_count, 1); assert_eq!(metrics.class_count, 1); assert_eq!(metrics.file_count, 1); assert!(metrics.cyclomatic_complexity > 0.0); assert!(metrics.cognitive_complexity > 0.0); } #[test] fn test_empty_concepts() { let concepts = vec![]; let metrics = ComplexityAnalyzer::calculate_complexity(&concepts); assert_eq!(metrics.function_count, 0); assert_eq!(metrics.class_count, 0); assert_eq!(metrics.file_count, 0); assert_eq!(metrics.avg_functions_per_file, 0.0); assert_eq!(metrics.avg_lines_per_concept, 0.0); } #[test] fn test_multiple_files() { let concepts = vec![ create_test_concept("func1", "function", "file1.rs", 1, 10), create_test_concept("func2", "function", "file2.rs", 1, 15), create_test_concept("Class1", "class", "file1.rs", 20, 40), ]; let metrics = ComplexityAnalyzer::calculate_complexity(&concepts); assert_eq!(metrics.function_count, 2); assert_eq!(metrics.class_count, 1); assert_eq!(metrics.file_count, 2); assert_eq!(metrics.avg_functions_per_file, 1.0); } #[test] fn test_complex_function_with_metadata() { let mut concept = create_test_concept("complex_func", "function", "test.rs", 1, 50); concept.metadata.insert("body".to_string(), "if (x > 0) { while (y < 10) { if (z) return; } }".to_string()); let concepts = vec![concept]; let metrics = ComplexityAnalyzer::calculate_complexity(&concepts); // Should have higher complexity due to control flow assert!(metrics.cyclomatic_complexity > 1.0); assert!(metrics.cognitive_complexity > 1.0); } #[test] fn test_count_decision_points() { let body1 = "if (x > 0) return x;"; assert_eq!(ComplexityAnalyzer::count_decision_points(body1), 1.0); let body2 = "if (x > 0 && y < 5) { while (z) { for (i = 0; i < 10; i++) {} } }"; assert!(ComplexityAnalyzer::count_decision_points(body2) >= 4.0); let body3 = "return x > 0 ? x : -x;"; assert!(ComplexityAnalyzer::count_decision_points(body3) >= 1.0); } #[test] fn test_relationships_impact_complexity() { let mut concept = create_test_concept("connected_func", "function", "test.rs", 1, 20); concept.relationships.insert("calls".to_string(), "other_func".to_string()); concept.relationships.insert("uses".to_string(), "SomeClass".to_string()); let concepts = vec![concept]; let metrics = ComplexityAnalyzer::calculate_complexity(&concepts); // Relationships should increase cognitive complexity assert!(metrics.cognitive_complexity > 1.0); assert_eq!(metrics.max_nesting_depth, 2); } }

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