#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::Result;
use std::path::Path;
use crate::models::error::TemplateError;
use crate::services::accurate_complexity_analyzer::AccurateComplexityAnalyzer;
use crate::services::complexity::{ComplexityMetrics, FileComplexityMetrics, FunctionComplexity};
use crate::services::context::FileContext;
use crate::services::file_classifier::FileClassifier;
use crate::services::enhanced_ast_visitor::EnhancedAstVisitor;
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_rust_file_with_complexity(
path: &Path,
) -> Result<FileComplexityMetrics, TemplateError> {
analyze_rust_file_with_complexity_and_classifier(path, None).await
}
#[allow(clippy::cast_possible_truncation)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_rust_file_with_complexity_and_classifier(
path: &Path,
_classifier: Option<&FileClassifier>,
) -> Result<FileComplexityMetrics, TemplateError> {
let analyzer = AccurateComplexityAnalyzer::new();
let accurate_result = analyzer
.analyze_file(path)
.await
.map_err(|e| TemplateError::InvalidUtf8(e.to_string()))?;
let mut function_metrics = Vec::new();
let mut total_cyclomatic = 0u32;
let mut total_cognitive = 0u32;
let mut max_nesting = 0u32;
for (i, func) in accurate_result.functions.iter().enumerate() {
total_cyclomatic += func.cyclomatic_complexity;
total_cognitive += func.cognitive_complexity;
let line_start = func.line_start;
let line_end = if i + 1 < accurate_result.functions.len() {
accurate_result.functions[i + 1]
.line_start
.saturating_sub(1)
} else {
line_start + 50 };
function_metrics.push(FunctionComplexity {
name: func.name.clone(),
line_start,
line_end,
metrics: ComplexityMetrics {
cyclomatic: func.cyclomatic_complexity as u16,
cognitive: func.cognitive_complexity as u16,
nesting_max: ((func.cognitive_complexity / 3).min(255)) as u8,
lines: (line_end.saturating_sub(line_start).max(1)) as u16,
halstead: None,
},
});
max_nesting = max_nesting.max(func.cognitive_complexity / 3);
}
let avg_cyclomatic = if function_metrics.is_empty() {
1
} else {
total_cyclomatic / function_metrics.len() as u32
};
let avg_cognitive = if function_metrics.is_empty() {
0
} else {
total_cognitive / function_metrics.len() as u32
};
Ok(FileComplexityMetrics {
path: path.display().to_string(),
total_complexity: ComplexityMetrics {
cyclomatic: avg_cyclomatic as u16,
cognitive: avg_cognitive as u16,
nesting_max: max_nesting.min(255) as u8,
lines: function_metrics
.last()
.map(|f| f.line_end as u16)
.unwrap_or(0),
halstead: None,
},
functions: function_metrics,
classes: Vec::new(), })
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_rust_file(path: &Path) -> Result<FileContext, TemplateError> {
analyze_rust_file_with_classifier(path, None).await
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_rust_file_with_classifier(
path: &Path,
_classifier: Option<&FileClassifier>,
) -> Result<FileContext, TemplateError> {
let content = tokio::fs::read_to_string(path)
.await
.map_err(TemplateError::Io)?;
let syntax_tree =
syn::parse_file(&content).map_err(|e| TemplateError::InvalidUtf8(e.to_string()))?;
let visitor = EnhancedAstVisitor::new(path);
let items = visitor.extract_items(&syntax_tree);
Ok(FileContext {
path: path.display().to_string(),
language: "rust".to_string(),
items,
complexity_metrics: None,
})
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}