abcop 0.17.0

Must-have ABC complexity gate for AI development. Ruby, Rust, Python, Go, JS/TS, C/C++, PHP, Java, C#, Swift, Zig, Dart, Solidity, ObjC
//! End-to-end assertions over the C# backend: AbcSize vectors,
//! UsedOnce candidacy and NeverUsed reporting.

use super::{build, never_used_offenses, used_once_offenses};
use crate::abc::parse_vector;
use crate::paths::{Lang, parse_file_lang};

fn parse(src: &'static str) -> crate::csharp::CSharpFile<'static> {
    build(
        src.as_bytes(),
        parse_file_lang(src.as_bytes(), Lang::CSharp).expect("csharp parses"),
    )
}

fn scores(src: &'static str) -> Vec<(String, u32, u32, u32)> {
    super::abc::all_scores(&parse(src))
        .into_iter()
        .map(|o| {
            let (a, b, c) = parse_vector(&o.vector);
            (o.name, a, b, c)
        })
        .collect()
}

fn used(src: &'static str) -> Vec<String> {
    let fm = parse(src);
    if std::env::var("ABCOP_DBG").is_ok() {
        for (i, sc) in fm.scopes.iter().enumerate() {
            for (n, e) in &sc.entries {
                eprintln!(
                    "[dbg] scope{i} {n} w={} r={:?} kind={:?}",
                    e.writes.len(),
                    e.reads,
                    e.intro_kind
                );
            }
        }
        for o in super::used_once_offenses(&fm) {
            eprintln!("[dbg] offense {}:{} {}", o.line, o.column, o.name);
        }
    }
    let mut v: Vec<_> = used_once_offenses(&fm)
        .into_iter()
        .map(|o| o.name)
        .collect();
    v.sort();
    v
}

fn dead(src: &'static str) -> Vec<String> {
    let mut v: Vec<_> = never_used_offenses(&parse(src))
        .into_iter()
        .map(|o| o.name)
        .collect();
    v.sort();
    v
}

#[test]
fn assignment_and_binary_count_as_a_and_b() {
    assert_eq!(
        scores("class K {\n  int Simple(int a) {\n    var x = a + 1;\n    return x;\n  }\n}"),
        vec![("Simple".to_string(), 1, 1, 0)]
    );
}

#[test]
fn branches_loops_sections_tally_c() {
    // A: total declarator, foreach head it, += = 3 (no invocations here)
    // B: none -- member reads are free
    // C: foreach, if, ==, ||, >, elseif-if, <, switch section x2 = 9
    let src = "class K {\n  int Run(System.Collections.Generic.List<string> items, int limit) {\n\
               \x20   var total = 0;\n\
               \x20   foreach (var it in items) {\n\
               \x20     if (it == null || it.Length > limit) { total += it.Length; }\n\
               \x20     else if (total < 0) { break; }\n\
               \x20   }\n\
               \x20   switch (total) {\n\
               \x20     case 0: break;\n\
               \x20     default: break;\n\
               \x20   }\n\
               \x20   return total;\n\
               \x20 }\n}";
    assert_eq!(scores(src), vec![("Run".to_string(), 3, 0, 9)]);
}

#[test]
fn constructors_are_units_and_lambdas_roll_in() {
    let got = scores(
        "class K {\n  int seed;\n  K(int seed) {\n\
               \x20   this.seed = seed;\n\
               \x20   System.Func<int,int> s = v => v * 2;\n\
               \x20   Use(s);\n\
               \x20 }\n}",
    );
    assert_eq!(got.len(), 1);
    // A: the local `s` declarator (`this.seed` is a field target)
    // B: * inside the lambda and the Use(...) invocation
    assert_eq!(got[0], ("K".to_string(), 1, 2, 0));
}

#[test]
fn member_names_are_not_variable_reads() {
    let src = "class K {\n  int count;\n  void M(K other) {\n\
               \x20   other.count = 1;\n\
               \x20   System.Console.WriteLine(other.count);\n\
               \x20 }\n}";
    assert_eq!(
        dead(src),
        Vec::<String>::new(),
        "field writes are not local bindings"
    );
}

#[test]
fn used_once_flags_single_pure_straightline_binding() {
    assert_eq!(
        used("class K {\n  int Ok(int f) {\n    var x = 42;\n    return x + f;\n  }\n}"),
        vec!["x"]
    );
}

#[test]
fn used_once_rejections() {
    let src = "class K {\n  int Rej(bool f) {\n\
               \x20   var a = Id();\n\
               \x20   var b = 1; b = 2;\n\
               \x20   var c = 1; c += 1;\n\
               \x20   if (f) { var e = 1; }\n\
               \x20   return a;\n\
               \x20 }\n}";
    assert_eq!(used(src), Vec::<String>::new());
}

#[test]
fn used_once_immediate_call_chain_yes_loop_no() {
    assert_eq!(
        used("class K {\n  int Ok() {\n    var a = Id();\n    return a;\n  }\n}"),
        vec!["a"]
    );
    assert_eq!(
        used(
            "class K {\n  void Loop(System.Collections.Generic.List<int> items) {\n\
             \x20   var a = Id();\n\
             \x20   foreach (var i in items) { System.Console.WriteLine(a); }\n\
             \x20 }\n}"
        ),
        Vec::<String>::new()
    );
}

#[test]
fn never_used_dead_call_keeps_initializer() {
    let f = never_used_offenses(&parse(
        "class K {\n  int Dd() {\n    var gone = Id();\n    return 0;\n  }\n}",
    ));
    assert_eq!(f.len(), 1);
    assert_eq!(f[0].name, "gone");
    assert!(f[0].keep_init);
}

#[test]
fn never_used_reports_dead_writes_once() {
    let src = "class K {\n  int Dd() {\n    var unused = 1;\n    return 0;\n  }\n}";
    assert_eq!(dead(src), vec!["unused"]);
}

#[test]
fn protocol_bindings_are_exempt() {
    let src = "class K {\n  void Proto(System.Collections.Generic.List<int> items) {\n\
               \x20   foreach (var i in items) { System.Console.WriteLine(i); }\n\
               \x20   try { throw new System.Exception(\"x\"); }\n\
               \x20   catch (System.Exception e) { }\n\
               \x20 }\n}";
    assert_eq!(dead(src), vec!["e"]);
}