impl CComplexityAnalyzer {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self {
cyclomatic_complexity: 0,
cognitive_complexity: 0,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn analyze_complexity(&mut self, source: &str) -> Result<(u32, u32), String> {
self.cyclomatic_complexity = 1;
self.cognitive_complexity = 0;
let mut nesting_depth = 0;
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.starts_with("#") {
continue;
}
let is_if_stmt = trimmed.starts_with("if ") || trimmed.starts_with("if(");
let is_else_if = trimmed.starts_with("else if") || trimmed.contains("} else if");
let _is_else = trimmed.starts_with("else") && !is_else_if;
let is_switch = trimmed.starts_with("switch ");
let is_case = trimmed.starts_with("case ") || trimmed.starts_with("default:");
let is_loop = trimmed.starts_with("while ")
|| trimmed.starts_with("for ")
|| trimmed.starts_with("do ");
let is_goto = trimmed.starts_with("goto ");
if is_if_stmt || is_else_if || is_switch || is_case || is_loop || is_goto {
self.cyclomatic_complexity += 1;
}
if is_if_stmt || is_else_if || is_switch || is_loop {
self.cognitive_complexity += 1 + nesting_depth;
nesting_depth += 1;
} else if trimmed.contains("{") && !trimmed.contains("}") {
nesting_depth += 1;
}
if trimmed.contains("}") {
nesting_depth = nesting_depth.saturating_sub(1);
}
}
Ok((self.cyclomatic_complexity, self.cognitive_complexity))
}
}
#[cfg(feature = "c-ast")]
#[allow(clippy::cast_possible_truncation)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_c_file(
path: &Path,
) -> Result<crate::services::context::FileContext, crate::models::error::TemplateError> {
use crate::models::error::TemplateError;
use crate::services::complexity::ComplexityMetrics;
use crate::services::context::FileContext;
let content = tokio::fs::read_to_string(path)
.await
.map_err(TemplateError::Io)?;
let visitor = CAstVisitor::new(path);
let items = visitor
.analyze_c_source(&content)
.map_err(TemplateError::InvalidUtf8)?;
let mut analyzer = CComplexityAnalyzer::new();
let (cyclomatic, cognitive) = analyzer
.analyze_complexity(&content)
.map_err(TemplateError::InvalidUtf8)?;
let func_metrics = ComplexityMetrics::new(
(cyclomatic & 0xFFFF) as u16, (cognitive & 0xFFFF) as u16, 0, std::cmp::min(items.len(), 65535) as u16, );
let file_complexity = crate::services::complexity::FileComplexityMetrics {
path: path.display().to_string(),
total_complexity: func_metrics,
functions: vec![crate::services::complexity::FunctionComplexity {
name: path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string(),
line_start: 1,
line_end: std::cmp::min(items.len(), 65535) as u32,
metrics: func_metrics,
}],
classes: vec![],
};
let complexity_metrics = Some(file_complexity);
Ok(FileContext {
path: path.display().to_string(),
language: "c".to_string(),
items,
complexity_metrics,
})
}