kimun 0.20.0

Code metrics tool — health score, complexity, duplication, hotspots, ownership
use super::*;

fn rust_markers() -> &'static ComplexityMarkers {
    super::super::markers::markers_for("Rust").unwrap()
}

fn python_markers() -> &'static ComplexityMarkers {
    super::super::markers::markers_for("Python").unwrap()
}

fn c_markers() -> &'static ComplexityMarkers {
    super::super::markers::markers_for("C").unwrap()
}

fn make_lines(code: &str) -> (Vec<String>, Vec<LineKind>) {
    let lines: Vec<String> = code.lines().map(String::from).collect();
    let kinds = vec![LineKind::Code; lines.len()];
    (lines, kinds)
}

#[test]
fn simple_function_no_branches() {
    let (lines, kinds) = make_lines("fn main() {\n    let x = 1;\n}\n");
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions.len(), 1);
    assert_eq!(result.functions[0].complexity, 1);
    assert_eq!(result.functions[0].level, CyclomaticLevel::Simple);
}

#[test]
fn function_with_if() {
    let (lines, kinds) = make_lines("fn foo() {\n    if x > 0 {\n        bar();\n    }\n}\n");
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions[0].complexity, 2);
}

#[test]
fn function_with_if_and_and() {
    let (lines, kinds) =
        make_lines("fn foo() {\n    if x > 0 && y > 0 {\n        bar();\n    }\n}\n");
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions[0].complexity, 3);
}

#[test]
fn nested_if_and_for() {
    let (lines, kinds) = make_lines(
        "fn foo() {\n    if x > 0 {\n        for i in items {\n            bar();\n        }\n    }\n}\n",
    );
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions[0].complexity, 3);
}

#[test]
fn word_boundary_notify_not_if() {
    let (lines, kinds) = make_lines("fn foo() {\n    notify();\n    ifdef();\n    life();\n}\n");
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions[0].complexity, 1);
}

#[test]
fn else_if_counts_as_one() {
    let (lines, kinds) = make_lines(
        "fn foo() {\n    if x > 0 {\n        a();\n    } else if y > 0 {\n        b();\n    }\n}\n",
    );
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions[0].complexity, 3);
}

#[test]
fn two_rust_functions() {
    let (lines, kinds) =
        make_lines("fn foo() {\n    if x > 0 {\n        a();\n    }\n}\nfn bar() {\n    b();\n}\n");
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions.len(), 2);
    assert_eq!(result.functions[0].complexity, 2);
    assert_eq!(result.functions[0].name, "foo");
    assert_eq!(result.functions[1].complexity, 1);
    assert_eq!(result.functions[1].name, "bar");
}

#[test]
fn python_function_by_indent() {
    let (lines, kinds) =
        make_lines("def foo():\n    if x > 0:\n        bar()\n\ndef baz():\n    pass\n");
    let mut kinds = kinds;
    kinds[3] = LineKind::Blank;
    let result = analyze(&lines, &kinds, python_markers()).unwrap();
    assert_eq!(result.functions.len(), 2);
    assert_eq!(result.functions[0].name, "foo");
    assert_eq!(result.functions[0].complexity, 2);
    assert_eq!(result.functions[1].name, "baz");
    assert_eq!(result.functions[1].complexity, 1);
}

#[test]
fn c_family_function_detection() {
    let (lines, kinds) = make_lines(
        "int main(int argc, char *argv[]) {\n    if (argc > 1) {\n        printf(\"hi\");\n    }\n    return 0;\n}\n",
    );
    let result = analyze(&lines, &kinds, c_markers()).unwrap();
    assert_eq!(result.functions.len(), 1);
    assert_eq!(result.functions[0].name, "main");
    assert_eq!(result.functions[0].complexity, 2);
}

#[test]
fn file_with_no_functions_uses_implicit() {
    let (lines, kinds) = make_lines("let x = 1;\nif true { foo(); }\n");
    let markers = super::super::markers::markers_for("Haskell").unwrap();
    let result = analyze(&lines, &kinds, markers).unwrap();
    assert_eq!(result.functions.len(), 1);
    assert_eq!(result.functions[0].name, "<file>");
}

#[test]
fn threshold_boundaries() {
    assert_eq!(CyclomaticLevel::from_complexity(1), CyclomaticLevel::Simple);
    assert_eq!(CyclomaticLevel::from_complexity(5), CyclomaticLevel::Simple);
    assert_eq!(
        CyclomaticLevel::from_complexity(6),
        CyclomaticLevel::Moderate
    );
    assert_eq!(
        CyclomaticLevel::from_complexity(10),
        CyclomaticLevel::Moderate
    );
    assert_eq!(
        CyclomaticLevel::from_complexity(11),
        CyclomaticLevel::Complex
    );
    assert_eq!(
        CyclomaticLevel::from_complexity(20),
        CyclomaticLevel::Complex
    );
    assert_eq!(
        CyclomaticLevel::from_complexity(21),
        CyclomaticLevel::HighlyComplex
    );
    assert_eq!(
        CyclomaticLevel::from_complexity(50),
        CyclomaticLevel::HighlyComplex
    );
    assert_eq!(
        CyclomaticLevel::from_complexity(51),
        CyclomaticLevel::Extreme
    );
}

#[test]
fn empty_input_returns_none() {
    let markers = rust_markers();
    assert!(analyze(&[], &[], markers).is_none());
}

#[test]
fn all_comments_returns_none() {
    let lines = vec!["// comment".to_string()];
    let kinds = vec![LineKind::Comment];
    assert!(analyze(&lines, &kinds, rust_markers()).is_none());
}

#[test]
fn operator_counting() {
    assert_eq!(count_operator("x && y && z", "&&"), 2);
    assert_eq!(count_operator("x || y", "||"), 1);
    assert_eq!(count_operator("no operators", "&&"), 0);
}

#[test]
fn keyword_word_boundary() {
    assert_eq!(count_keyword("if x > 0", "if"), 1);
    assert_eq!(count_keyword("notify()", "if"), 0);
    assert_eq!(count_keyword("elif x", "if"), 0);
    assert_eq!(count_keyword("if_something", "if"), 0);
}

#[test]
fn level_display() {
    assert_eq!(CyclomaticLevel::Simple.as_str(), "simple");
    assert_eq!(CyclomaticLevel::Moderate.as_str(), "moderate");
    assert_eq!(CyclomaticLevel::Complex.as_str(), "complex");
    assert_eq!(CyclomaticLevel::HighlyComplex.as_str(), "highly complex");
    assert_eq!(CyclomaticLevel::Extreme.as_str(), "extreme");
}

#[test]
fn level_serde() {
    assert_eq!(
        serde_json::to_string(&CyclomaticLevel::HighlyComplex).unwrap(),
        "\"highly_complex\""
    );
}

#[test]
fn keywords_in_strings_not_counted() {
    let (lines, kinds) = make_lines(
        "fn foo() {\n    let kw = [\"if\", \"for\", \"while\", \"match\"];\n    let s = \"if x && y || z\";\n}\n",
    );
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions[0].complexity, 1);
}

#[test]
fn braces_in_char_literals_not_counted() {
    let (lines, kinds) = make_lines(
        "fn foo() {\n    if c == '{' {\n        bar();\n    }\n}\nfn bar() {\n    baz();\n}\n",
    );
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.functions.len(), 2);
    assert_eq!(result.functions[0].name, "foo");
    assert_eq!(result.functions[0].complexity, 2);
    assert_eq!(result.functions[1].name, "bar");
    assert_eq!(result.functions[1].complexity, 1);
}

#[test]
fn aggregation_stats() {
    let (lines, kinds) =
        make_lines("fn foo() {\n    if x > 0 {\n        a();\n    }\n}\nfn bar() {\n    b();\n}\n");
    let result = analyze(&lines, &kinds, rust_markers()).unwrap();
    assert_eq!(result.total_complexity, 3);
    assert_eq!(result.max_complexity, 2);
    assert!((result.avg_complexity - 1.5).abs() < 0.01);
}