use super::super::shared::{count_keyword, is_comment_line};
pub(super) fn calculate_cognitive_complexity(lines: &[&str], lang: &str) -> usize {
let mut complexity = 0usize;
let mut nesting_depth = 0usize;
let mut in_logical_sequence = false;
for line in lines {
let trimmed = line.trim();
if is_comment_line(trimmed, lang) {
continue;
}
let opens = count_structure_opens(trimmed, lang);
let closes = count_structure_closes(trimmed, lang);
let control_structures = count_control_structures(trimmed, lang);
for _ in 0..control_structures {
complexity += 1 + nesting_depth;
}
let (new_in_sequence, seq_complexity) =
count_logical_sequences(trimmed, in_logical_sequence);
complexity += seq_complexity;
in_logical_sequence = new_in_sequence;
if lang == "rust" || lang == "rs" {
complexity += count_labeled_jumps(trimmed);
}
nesting_depth = nesting_depth.saturating_add(opens);
nesting_depth = nesting_depth.saturating_sub(closes);
}
complexity
}
fn count_control_structures(line: &str, lang: &str) -> usize {
let mut count = 0;
match lang {
"rust" | "rs" => {
if line.contains("if ") && !line.contains("else if ") {
count += line.matches("if ").count();
}
if line.contains("else if ") {
count += line.matches("else if ").count();
}
count += count_keyword(line, "match ");
count += count_keyword(line, "for ");
count += count_keyword(line, "while ");
count += count_keyword(line, "loop ");
}
"python" | "py" => {
count += count_keyword(line, "if ");
count += count_keyword(line, "elif ");
count += count_keyword(line, "for ");
count += count_keyword(line, "while ");
count += count_keyword(line, "except ");
count += count_keyword(line, "except:");
}
"javascript" | "js" | "typescript" | "ts" | "jsx" | "tsx" => {
let else_if_count = count_keyword(line, "else if ") + count_keyword(line, "else if(");
count += else_if_count;
let total_if = count_keyword(line, "if ") + count_keyword(line, "if(");
count += total_if.saturating_sub(else_if_count);
count += count_keyword(line, "switch ");
count += count_keyword(line, "switch(");
count += count_keyword(line, "for ");
count += count_keyword(line, "for(");
count += count_keyword(line, "while ");
count += count_keyword(line, "while(");
count += count_keyword(line, "catch ");
count += count_keyword(line, "catch(");
}
"go" => {
let else_if_count = count_keyword(line, "else if ");
count += else_if_count;
let total_if = count_keyword(line, "if ");
count += total_if.saturating_sub(else_if_count);
count += count_keyword(line, "switch ");
count += count_keyword(line, "select ");
count += count_keyword(line, "for ");
}
"c" | "c++" | "cpp" | "java" | "c#" | "csharp" => {
let else_if_count = count_keyword(line, "else if ") + count_keyword(line, "else if(");
count += else_if_count;
let total_if = count_keyword(line, "if ") + count_keyword(line, "if(");
count += total_if.saturating_sub(else_if_count);
count += count_keyword(line, "switch ");
count += count_keyword(line, "switch(");
count += count_keyword(line, "for ");
count += count_keyword(line, "for(");
count += count_keyword(line, "while ");
count += count_keyword(line, "while(");
count += count_keyword(line, "catch ");
count += count_keyword(line, "catch(");
}
_ => {}
}
count
}
fn count_structure_opens(line: &str, lang: &str) -> usize {
match lang {
"python" | "py" => {
let mut count = 0;
if line.contains("if ") || line.contains("elif ") {
count += 1;
}
if line.contains("for ") || line.contains("while ") {
count += 1;
}
if line.contains("try:") || line.contains("except ") || line.contains("except:") {
count += 1;
}
if line.contains("with ") {
count += 1;
}
count
}
_ => line.chars().filter(|&c| c == '{').count(),
}
}
fn count_structure_closes(line: &str, lang: &str) -> usize {
match lang {
"python" | "py" => {
0
}
_ => line.chars().filter(|&c| c == '}').count(),
}
}
fn count_logical_sequences(line: &str, was_in_sequence: bool) -> (bool, usize) {
let has_and = line.contains("&&") || line.contains(" and ");
let has_or = line.contains("||") || line.contains(" or ");
if has_and || has_or {
let cost = if was_in_sequence { 0 } else { 1 };
(true, cost)
} else {
(false, 0)
}
}
fn count_labeled_jumps(line: &str) -> usize {
let mut count = 0;
if line.contains("break '") {
count += 1;
}
if line.contains("continue '") {
count += 1;
}
count
}