use depyler_core::hir::{BinOp, HirExpr, HirStmt};
pub fn calculate_cyclomatic(body: &[HirStmt]) -> u32 {
let mut complexity = 1;
for stmt in body {
complexity += cyclomatic_stmt(stmt);
}
complexity
}
fn cyclomatic_stmt(stmt: &HirStmt) -> u32 {
match stmt {
HirStmt::If {
then_body,
else_body,
..
} => {
let mut complexity = 1; complexity += cyclomatic_body(then_body);
if let Some(else_stmts) = else_body {
complexity += cyclomatic_body(else_stmts);
}
complexity
}
HirStmt::While { body, .. } => {
1 + cyclomatic_body(body) }
HirStmt::For { body, .. } => {
1 + cyclomatic_body(body) }
HirStmt::Expr(expr) => cyclomatic_expr(expr),
_ => 0,
}
}
fn cyclomatic_body(body: &[HirStmt]) -> u32 {
body.iter().map(cyclomatic_stmt).sum()
}
fn cyclomatic_expr(expr: &HirExpr) -> u32 {
match expr {
HirExpr::Binary {
op: BinOp::And | BinOp::Or,
left,
right,
} => 1 + cyclomatic_expr(left) + cyclomatic_expr(right),
_ => 0,
}
}
pub fn calculate_cognitive(body: &[HirStmt]) -> u32 {
cognitive_body(body, 0).0
}
fn cognitive_body(body: &[HirStmt], nesting: u32) -> (u32, u32) {
let mut total_complexity = 0;
let mut max_nesting = nesting;
for stmt in body {
let (stmt_complexity, stmt_nesting) = cognitive_stmt(stmt, nesting);
total_complexity += stmt_complexity;
max_nesting = max_nesting.max(stmt_nesting);
}
(total_complexity, max_nesting)
}
fn cognitive_stmt(stmt: &HirStmt, nesting: u32) -> (u32, u32) {
match stmt {
HirStmt::If {
condition,
then_body,
else_body,
} => {
let mut complexity = 1 + nesting; let mut max_nesting = nesting;
complexity += cognitive_condition(condition);
let (then_complexity, then_nesting) = cognitive_body(then_body, nesting + 1);
complexity += then_complexity;
max_nesting = max_nesting.max(then_nesting);
if let Some(else_stmts) = else_body {
complexity += 1; let (else_complexity, else_nesting) = cognitive_body(else_stmts, nesting + 1);
complexity += else_complexity;
max_nesting = max_nesting.max(else_nesting);
}
(complexity, max_nesting)
}
HirStmt::While { condition, body } => {
let mut complexity = 1 + nesting;
complexity += cognitive_condition(condition);
let (body_complexity, body_nesting) = cognitive_body(body, nesting + 1);
complexity += body_complexity;
(complexity, body_nesting)
}
HirStmt::For { body, .. } => {
let complexity = 1 + nesting;
let (body_complexity, body_nesting) = cognitive_body(body, nesting + 1);
(complexity + body_complexity, body_nesting)
}
_ => (0, nesting),
}
}
fn cognitive_condition(expr: &HirExpr) -> u32 {
match expr {
HirExpr::Binary {
op: BinOp::And | BinOp::Or,
left,
right,
} => 1 + cognitive_condition(left) + cognitive_condition(right),
HirExpr::Unary { operand, .. } => cognitive_condition(operand),
HirExpr::Attribute { value, .. } => cognitive_condition(value),
_ => 0,
}
}
pub fn calculate_max_nesting(body: &[HirStmt]) -> usize {
cognitive_body(body, 0).1 as usize
}
pub fn count_statements(body: &[HirStmt]) -> usize {
let mut count = 0;
for stmt in body {
count += 1;
count += match stmt {
HirStmt::If {
then_body,
else_body,
..
} => {
count_statements(then_body) + else_body.as_ref().map_or(0, |b| count_statements(b))
}
HirStmt::While { body, .. } | HirStmt::For { body, .. } => count_statements(body),
_ => 0,
};
}
count
}
#[cfg(test)]
mod tests {
use super::*;
use depyler_core::hir::{AssignTarget, HirExpr, HirStmt, Literal};
#[test]
fn test_cyclomatic_simple_function() {
let body = vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))];
assert_eq!(calculate_cyclomatic(&body), 1);
}
#[test]
fn test_cyclomatic_if_statement() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
else_body: None,
}];
assert_eq!(calculate_cyclomatic(&body), 2);
}
#[test]
fn test_cyclomatic_if_else_statement() {
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(
2,
))))]),
}];
assert_eq!(calculate_cyclomatic(&body), 2);
}
#[test]
fn test_cyclomatic_while_loop() {
let body = vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Return(None)],
}];
assert_eq!(calculate_cyclomatic(&body), 2);
}
#[test]
fn test_cyclomatic_for_loop() {
let body = vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Literal(Literal::Int(0)),
body: vec![HirStmt::Return(None)],
}];
assert_eq!(calculate_cyclomatic(&body), 2);
}
#[test]
fn test_cyclomatic_logical_operators() {
let condition = HirExpr::Binary {
op: BinOp::And,
left: Box::new(HirExpr::Literal(Literal::Bool(true))),
right: Box::new(HirExpr::Literal(Literal::Bool(false))),
};
let body = vec![HirStmt::Expr(condition)];
assert_eq!(calculate_cyclomatic(&body), 2); }
#[test]
fn test_cognitive_simple_function() {
let body = vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))];
assert_eq!(calculate_cognitive(&body), 0);
}
#[test]
fn test_cognitive_nested_if() {
let nested_if = HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(2))))],
else_body: None,
};
let body = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![nested_if],
else_body: None,
}];
assert_eq!(calculate_cognitive(&body), 3);
}
#[test]
fn test_cognitive_logical_operators() {
let complex_condition = HirExpr::Binary {
op: BinOp::And,
left: Box::new(HirExpr::Binary {
op: BinOp::Or,
left: Box::new(HirExpr::Literal(Literal::Bool(true))),
right: Box::new(HirExpr::Literal(Literal::Bool(false))),
}),
right: Box::new(HirExpr::Literal(Literal::Bool(true))),
};
let body = vec![HirStmt::If {
condition: complex_condition,
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
else_body: None,
}];
assert_eq!(calculate_cognitive(&body), 3);
}
#[test]
fn test_count_statements() {
let body = vec![
HirStmt::Assign {
target: depyler_core::hir::AssignTarget::Symbol("x".to_string()),
value: HirExpr::Literal(Literal::Int(1)),
type_annotation: None,
},
HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![
HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1)))),
HirStmt::Return(None),
],
else_body: Some(vec![HirStmt::Return(None)]),
},
];
assert_eq!(count_statements(&body), 5);
}
#[test]
fn test_max_nesting() {
let deeply_nested = vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Literal(Literal::Int(0)),
body: vec![HirStmt::Return(None)],
}],
}],
else_body: None,
}];
assert_eq!(calculate_max_nesting(&deeply_nested), 3);
}
}