use crate::complexity::{
calculate_cognitive_for_block,
visitor_detector::{PatternInfo, PatternType},
};
pub fn calculate_cyclomatic_with_visitor(
block: &syn::Block,
_func: &syn::ItemFn,
_file_ast: Option<&syn::File>,
) -> u32 {
use crate::complexity::cyclomatic::calculate_cyclomatic;
calculate_cyclomatic(block)
}
pub fn calculate_cognitive_with_visitor(
block: &syn::Block,
func: &syn::ItemFn,
file_ast: Option<&syn::File>,
) -> u32 {
try_detect_visitor_pattern(func, file_ast)
.map(|pattern_info| apply_cognitive_pattern_scaling(block, &pattern_info))
.unwrap_or_else(|| calculate_cognitive_syn(block))
}
fn apply_cognitive_pattern_scaling(block: &syn::Block, pattern_info: &PatternInfo) -> u32 {
let base_cognitive = calculate_cognitive_syn(block);
match pattern_info.pattern_type {
PatternType::Visitor => ((base_cognitive as f32).log2().ceil()).max(1.0) as u32,
PatternType::ExhaustiveMatch => ((base_cognitive as f32).sqrt().ceil()).max(2.0) as u32,
PatternType::SimpleMapping => ((base_cognitive as f32) * 0.2).max(1.0) as u32,
_ => base_cognitive,
}
}
pub fn calculate_cognitive_syn(block: &syn::Block) -> u32 {
calculate_cognitive_for_block(block)
}
fn try_detect_visitor_pattern(
func: &syn::ItemFn,
file_ast: Option<&syn::File>,
) -> Option<PatternInfo> {
use crate::complexity::visitor_detector::detect_visitor_pattern;
file_ast.and_then(|ast| detect_visitor_pattern(ast, func))
}
pub fn calculate_nesting(block: &syn::Block) -> u32 {
crate::complexity::pure::calculate_max_nesting_depth(block)
}
pub fn count_lines(block: &syn::Block) -> usize {
use syn::spanned::Spanned;
let span = block.span();
let start_line = span.start().line;
let end_line = span.end().line;
if end_line >= start_line {
end_line - start_line + 1
} else {
1
}
}
pub fn count_function_lines(item_fn: &syn::ItemFn) -> usize {
use syn::spanned::Spanned;
let span = item_fn.span();
let start_line = span.start().line;
let end_line = span.end().line;
if end_line >= start_line {
end_line - start_line + 1
} else {
1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_count_lines_simple_block() {
let code = r#"
fn test() {
let x = 1;
let y = 2;
}
"#;
let file: syn::File = syn::parse_str(code).unwrap();
if let syn::Item::Fn(item_fn) = &file.items[0] {
let lines = count_lines(&item_fn.block);
assert!(lines > 0);
}
}
#[test]
fn test_calculate_nesting_simple() {
let code = r#"
{
let x = 1;
}
"#;
let block: syn::Block = syn::parse_str(code).unwrap();
let nesting = calculate_nesting(&block);
assert_eq!(nesting, 0);
}
#[test]
fn test_calculate_nesting_with_if() {
let code = r#"
{
if true {
let x = 1;
}
}
"#;
let block: syn::Block = syn::parse_str(code).unwrap();
let nesting = calculate_nesting(&block);
assert_eq!(nesting, 1);
}
#[test]
fn test_calculate_nesting_nested() {
let code = r#"
{
if true {
for i in 0..10 {
let x = 1;
}
}
}
"#;
let block: syn::Block = syn::parse_str(code).unwrap();
let nesting = calculate_nesting(&block);
assert_eq!(nesting, 2);
}
#[test]
fn test_else_if_chain_flat_nesting() {
let code = r#"
{
if a {
x
} else if b {
y
} else if c {
z
} else {
w
}
}
"#;
let block: syn::Block = syn::parse_str(code).unwrap();
assert_eq!(
calculate_nesting(&block),
1,
"else-if chain should have nesting 1"
);
}
#[test]
fn test_nested_if_inside_then() {
let code = r#"
{
if a {
if b {
x
}
}
}
"#;
let block: syn::Block = syn::parse_str(code).unwrap();
assert_eq!(
calculate_nesting(&block),
2,
"if inside then should have nesting 2"
);
}
#[test]
fn test_match_with_else_if_chain() {
let code = r#"
{
match x {
A => {
if a {
} else if b {
} else if c {
}
}
_ => {}
}
}
"#;
let block: syn::Block = syn::parse_str(code).unwrap();
assert_eq!(
calculate_nesting(&block),
2,
"match + else-if chain should have nesting 2"
);
}
#[test]
fn test_long_else_if_chain_nesting() {
let code = r#"
{
if a {
} else if b {
} else if c {
} else if d {
} else if e {
} else if f {
} else if g {
} else if h {
}
}
"#;
let block: syn::Block = syn::parse_str(code).unwrap();
assert_eq!(
calculate_nesting(&block),
1,
"long else-if chain should still have nesting 1"
);
}
}