#![allow(unused)]
#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::service_registry::ServiceRegistry;
use anyhow::Result;
use serde::Serialize;
use std::path::Path;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ComplexityAnalysisRequest {
pub path: std::path::PathBuf,
pub language: Option<String>,
pub include_tests: bool,
pub max_complexity_threshold: Option<u32>,
pub output_format: ComplexityOutputFormat,
}
#[derive(Debug, Clone)]
pub enum ComplexityOutputFormat {
Json,
Summary,
Detailed,
}
#[derive(Debug, Clone, Serialize)]
pub struct ComplexityAnalysisResult {
pub total_files: usize,
pub violations: Vec<ComplexityViolation>,
pub average_complexity: f64,
pub max_complexity: u32,
pub summary: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct ComplexityViolation {
pub file_path: String,
pub function_name: String,
pub line_number: usize,
pub complexity: u32,
pub complexity_type: String,
}
#[derive(Clone)]
pub struct ComplexityFacade {
registry: Arc<ServiceRegistry>,
}
impl ComplexityFacade {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(registry: Arc<ServiceRegistry>) -> Self {
Self { registry }
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn analyze_project(
&self,
request: ComplexityAnalysisRequest,
) -> Result<ComplexityAnalysisResult> {
use crate::cli::analysis_utilities::analyze_project_files;
use crate::services::complexity::{aggregate_results_with_thresholds, Violation};
const MAX_CYCLOMATIC: u16 = 20;
const MAX_COGNITIVE: u16 = 15;
let file_metrics = analyze_project_files(
&request.path,
request.language.as_deref(),
&[],
MAX_CYCLOMATIC,
MAX_COGNITIVE,
)
.await?;
let total_files = file_metrics.len();
let report = aggregate_results_with_thresholds(
file_metrics,
Some(MAX_CYCLOMATIC),
Some(MAX_COGNITIVE),
);
let violations: Vec<ComplexityViolation> = report
.violations
.iter()
.map(|v| {
let (file, function, value, line, kind) = match v {
Violation::Error {
file,
function,
value,
line,
rule,
..
}
| Violation::Warning {
file,
function,
value,
line,
rule,
..
} => (file, function, *value, *line, rule),
};
ComplexityViolation {
file_path: file.clone(),
function_name: function.clone().unwrap_or_else(|| "<file>".to_string()),
line_number: line as usize,
complexity: value as u32,
complexity_type: kind.clone(),
}
})
.collect();
let max_complexity = violations.iter().map(|v| v.complexity).max().unwrap_or(0);
let average_complexity = f64::from(report.summary.median_cyclomatic);
Ok(ComplexityAnalysisResult {
total_files,
summary: format!(
"Analyzed {} file(s) in {} with {} violation(s)",
total_files,
request.path.display(),
violations.len()
),
violations,
average_complexity,
max_complexity,
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_file<P: AsRef<Path>>(
&self,
path: P,
language: Option<&str>,
) -> Result<ComplexityAnalysisResult> {
let request = ComplexityAnalysisRequest {
path: path.as_ref().to_path_buf(),
language: language.map(std::string::ToString::to_string),
include_tests: true,
max_complexity_threshold: Some(20),
output_format: ComplexityOutputFormat::Detailed,
};
self.analyze_project(request).await
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_language_thresholds(&self, language: &str) -> ComplexityThresholds {
match language {
"rust" => ComplexityThresholds {
warning: 15,
error: 25,
max_acceptable: 20,
},
"typescript" | "javascript" => ComplexityThresholds {
warning: 10,
error: 20,
max_acceptable: 15,
},
"python" => ComplexityThresholds {
warning: 12,
error: 20,
max_acceptable: 15,
},
_ => ComplexityThresholds {
warning: 10,
error: 20,
max_acceptable: 15,
},
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn validate_complexity(
&self,
result: &ComplexityAnalysisResult,
language: &str,
) -> ValidationResult {
let thresholds = self.get_language_thresholds(language);
let warnings = result
.violations
.iter()
.filter(|v| v.complexity >= thresholds.warning && v.complexity < thresholds.error)
.count();
let errors = result
.violations
.iter()
.filter(|v| v.complexity >= thresholds.error)
.count();
ValidationResult {
passed: errors == 0,
warnings,
errors,
max_complexity: result.max_complexity,
threshold_exceeded: result.max_complexity > thresholds.max_acceptable,
}
}
}
#[derive(Debug, Clone)]
pub struct ComplexityThresholds {
pub warning: u32,
pub error: u32,
pub max_acceptable: u32,
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub passed: bool,
pub warnings: usize,
pub errors: usize,
pub max_complexity: u32,
pub threshold_exceeded: bool,
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::services::service_registry::ServiceRegistry;
#[tokio::test]
async fn test_complexity_facade_creation() {
let registry = Arc::new(ServiceRegistry::new());
let facade = ComplexityFacade::new(registry);
let thresholds = facade.get_language_thresholds("rust");
assert_eq!(thresholds.warning, 15);
assert_eq!(thresholds.error, 25);
}
#[tokio::test]
async fn test_complexity_validation() {
let registry = Arc::new(ServiceRegistry::new());
let facade = ComplexityFacade::new(registry);
let result = ComplexityAnalysisResult {
total_files: 1,
violations: vec![ComplexityViolation {
file_path: "test.rs".to_string(),
function_name: "test_fn".to_string(),
line_number: 1,
complexity: 30,
complexity_type: "cyclomatic".to_string(),
}],
average_complexity: 30.0,
max_complexity: 30,
summary: "Test".to_string(),
};
let validation = facade.validate_complexity(&result, "rust");
assert!(!validation.passed);
assert_eq!(validation.errors, 1);
assert!(validation.threshold_exceeded);
}
}