use super::pure::Pattern;
use crate::effects::{effect_fail, effect_pure, AnalysisEffect};
use crate::env::{AnalysisEnv, RealEnv};
use crate::errors::AnalysisError;
use crate::extraction::{DetectedPattern, UnifiedFileExtractor};
use std::path::PathBuf;
use stillwater::effect::prelude::*;
fn convert_extracted_patterns(patterns: &[DetectedPattern]) -> Vec<Pattern> {
patterns
.iter()
.map(|p| match p {
DetectedPattern::GodObject { name, field_count } => Pattern::GodObject {
name: name.clone(),
field_count: *field_count,
},
DetectedPattern::LongFunction { name, lines } => Pattern::LongFunction {
name: name.clone(),
lines: *lines,
},
DetectedPattern::ManyParameters { name, param_count } => Pattern::ManyParameters {
name: name.clone(),
param_count: *param_count,
},
DetectedPattern::DeepNesting {
function_name,
depth,
} => Pattern::DeepNesting {
function_name: function_name.clone(),
depth: *depth,
},
})
.collect()
}
pub fn calculate_cyclomatic_effect(path: PathBuf) -> AnalysisEffect<u32> {
from_fn(move |env: &RealEnv| {
let content = env.file_system().read_to_string(&path).map_err(|e| {
AnalysisError::io_with_path(format!("Failed to read file: {}", e), path.clone())
})?;
let extracted = UnifiedFileExtractor::extract(&path, &content).map_err(|e| {
AnalysisError::parse(format!("Failed to parse {}: {}", path.display(), e))
})?;
let total_complexity: u32 = extracted.functions.iter().map(|f| f.cyclomatic).sum();
Ok(total_complexity)
})
.boxed()
}
pub fn calculate_cognitive_effect(path: PathBuf) -> AnalysisEffect<u32> {
from_fn(move |env: &RealEnv| {
let content = env.file_system().read_to_string(&path).map_err(|e| {
AnalysisError::io_with_path(format!("Failed to read file: {}", e), path.clone())
})?;
let extracted = UnifiedFileExtractor::extract(&path, &content).map_err(|e| {
AnalysisError::parse(format!("Failed to parse {}: {}", path.display(), e))
})?;
let total_cognitive: u32 = extracted.functions.iter().map(|f| f.cognitive).sum();
Ok(total_cognitive)
})
.boxed()
}
pub fn detect_patterns_effect(path: PathBuf) -> AnalysisEffect<Vec<Pattern>> {
from_fn(move |env: &RealEnv| {
let content = env.file_system().read_to_string(&path).map_err(|e| {
AnalysisError::io_with_path(format!("Failed to read file: {}", e), path.clone())
})?;
let extracted = UnifiedFileExtractor::extract(&path, &content).map_err(|e| {
AnalysisError::parse(format!("Failed to parse {}: {}", path.display(), e))
})?;
let patterns = convert_extracted_patterns(&extracted.detected_patterns);
Ok(patterns)
})
.boxed()
}
#[derive(Debug, Clone)]
pub struct ComplexityResult {
pub cyclomatic: u32,
pub cognitive: u32,
pub patterns: Vec<Pattern>,
}
pub fn analyze_complexity_effect(path: PathBuf) -> AnalysisEffect<ComplexityResult> {
from_fn(move |env: &RealEnv| {
let content = env.file_system().read_to_string(&path).map_err(|e| {
AnalysisError::io_with_path(format!("Failed to read file: {}", e), path.clone())
})?;
let extracted = UnifiedFileExtractor::extract(&path, &content).map_err(|e| {
AnalysisError::parse(format!("Failed to parse {}: {}", path.display(), e))
})?;
let cyclomatic: u32 = extracted.functions.iter().map(|f| f.cyclomatic).sum();
let cognitive: u32 = extracted.functions.iter().map(|f| f.cognitive).sum();
let patterns = convert_extracted_patterns(&extracted.detected_patterns);
Ok(ComplexityResult {
cyclomatic,
cognitive,
patterns,
})
})
.boxed()
}
pub fn calculate_cyclomatic_from_string(content: String) -> AnalysisEffect<u32> {
use std::path::Path;
let path = Path::new("<string>");
match UnifiedFileExtractor::extract(path, &content) {
Ok(extracted) => {
let total: u32 = extracted.functions.iter().map(|f| f.cyclomatic).sum();
effect_pure(total)
}
Err(e) => effect_fail(AnalysisError::parse(format!("Parse error: {}", e))),
}
}
pub fn calculate_cognitive_from_string(content: String) -> AnalysisEffect<u32> {
use std::path::Path;
let path = Path::new("<string>");
match UnifiedFileExtractor::extract(path, &content) {
Ok(extracted) => {
let total: u32 = extracted.functions.iter().map(|f| f.cognitive).sum();
effect_pure(total)
}
Err(e) => effect_fail(AnalysisError::parse(format!("Parse error: {}", e))),
}
}
pub fn detect_patterns_from_string(content: String) -> AnalysisEffect<Vec<Pattern>> {
use std::path::Path;
let path = Path::new("<string>");
match UnifiedFileExtractor::extract(path, &content) {
Ok(extracted) => effect_pure(convert_extracted_patterns(&extracted.detected_patterns)),
Err(e) => effect_fail(AnalysisError::parse(format!("Parse error: {}", e))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::DebtmapConfig;
use crate::effects::run_effect;
#[test]
fn test_cyclomatic_from_string() {
let code = "fn foo() { if true { } }".to_string();
let effect = calculate_cyclomatic_from_string(code);
let result = run_effect(effect, DebtmapConfig::default());
assert!(result.is_ok());
assert_eq!(result.unwrap(), 2); }
#[test]
fn test_cognitive_from_string() {
let code = "fn foo() { if true { if false { } } }".to_string();
let effect = calculate_cognitive_from_string(code);
let result = run_effect(effect, DebtmapConfig::default());
assert!(result.is_ok());
assert_eq!(result.unwrap(), 3);
}
#[test]
fn test_patterns_from_string() {
let code = r#"
struct Big { a: i32, b: i32, c: i32, d: i32, e: i32, f: i32 }
"#
.to_string();
let effect = detect_patterns_from_string(code);
let result = run_effect(effect, DebtmapConfig::default());
assert!(result.is_ok());
let patterns = result.unwrap();
assert!(!patterns.is_empty());
assert!(matches!(&patterns[0], Pattern::GodObject { .. }));
}
#[test]
fn test_parse_error_handling() {
let invalid_code = "fn foo( {".to_string(); let effect = calculate_cyclomatic_from_string(invalid_code);
let result = run_effect(effect, DebtmapConfig::default());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Parse error"));
}
}