devscout-rs 0.9.0

Fast code index for C# and TypeScript codebases -- map, find, refs, impact from the CLI.
use super::*;

fn hits(src: &str) -> Vec<(String, usize)> {
    extract_rs_file(src)
        .fragment
        .sites
        .into_iter()
        .filter(|s| s.mac)
        .inspect(|s| assert_eq!(s.role, RsRole::Call))
        .map(|s| (s.path, s.line))
        .collect()
}

fn h(path: &str, line: usize) -> (String, usize) {
    (path.into(), line)
}

#[test]
fn calls_in_macro_arguments_are_call_sites() {
    let src = "fn t() {\n    assert_eq!(a(1), 2);\n    assert!(b());\n    let v = vec![c(d(1))];\n    assert!(matches!(e(), Some(_)));\n    let s = format!(\"{} {}\", f(), m::g::<u8>(1));\n    println!(\"{}\", crate::k::h(2));\n}\n";
    assert_eq!(
        hits(src),
        vec![
            h("a", 2),
            h("b", 3),
            h("c", 4),
            h("d", 4),
            h("e", 5),
            h("Some", 5),
            h("f", 6),
            h("m::g", 6),
            h("crate::k::h", 7),
        ]
    );
}

#[test]
fn a_contextual_keyword_is_an_ordinary_path_segment() {
    let src = "fn t() {\n    assert!(Shelf::default().is_empty());\n    x!(union::raw(1));\n}\n";
    assert_eq!(hits(src), vec![h("Shelf::default", 2), h("union::raw", 3)]);
}

#[test]
fn a_turbofish_inside_or_closing_a_path_is_passed_over() {
    let src = "fn t() {\n    x!(Vec::<Lamp>::new(), p::q::<Vec<u8>>(1), r::<u8, u16>(2));\n}\n";
    assert_eq!(hits(src), vec![h("Vec::new", 2), h("p::q", 2), h("r", 2)]);
}

#[test]
fn methods_fragments_strings_definitions_and_macro_names_are_no_calls() {
    let src = "macro_rules! m { ($f:ident) => { $f(1); inner(2) } }\nfn t(x: u8) {\n    assert!(x.len() > 0, \"{}\", \"f(1)\");\n    y!(fn z(a: u8) {});\n    outer!(nested!(w(3)));\n}\n#[cfg_attr(test, derive(Debug))]\nstruct S;\n";
    assert_eq!(hits(src), vec![h("w", 5)]);
}

#[test]
fn macro_hits_are_shadowed_like_other_calls() {
    let src = "fn t(p: u8) {\n    let c = |q| q;\n    fn nested() {}\n    assert!(c(p(1)) + nested());\n    assert!(self::c(2));\n}\n";
    assert_eq!(hits(src), vec![h("self::c", 5)]);
}

#[test]
fn hits_keep_source_order_across_nested_trees() {
    let src = "fn t() {\n    assert_eq!(f(g(1)), h(2));\n}\n";
    assert_eq!(hits(src), vec![h("f", 2), h("g", 2), h("h", 2)]);
}

#[test]
fn a_deep_token_tree_is_scanned_without_exhausting_a_worker_stack() {
    let depth = 5000;
    let src = format!(
        "fn t() {{ m!({}f(1){}); }}\n",
        "(".repeat(depth),
        ")".repeat(depth)
    );
    let handle = std::thread::Builder::new()
        .stack_size(256 * 1024)
        .spawn(move || {
            extract_rs_file(&src)
                .fragment
                .sites
                .iter()
                .filter(|s| s.mac)
                .count()
        })
        .unwrap();
    assert_eq!(handle.join().unwrap(), 1);
}

#[test]
fn a_name_the_tree_may_bind_shadows_its_calls_but_a_type_written_in_it_does_not() {
    let src = "fn t() {\n    assert!(0 < v.iter().filter(|f| f(1) > 0).count());\n    x!(Vec::<Lamp>::new(), a as Lamp, Lamp::new());\n}\nfn u() {\n    Lamp::new();\n    y!(Lamp; Lamp::new());\n}\n";
    assert_eq!(hits(src), vec![h("Vec::new", 3), h("Lamp::new", 3)]);
    let calls: Vec<String> = extract_rs_file(src)
        .fragment
        .sites
        .into_iter()
        .filter(|s| s.line > 4)
        .map(|s| s.path)
        .collect();
    assert!(
        calls.is_empty(),
        "an item a statement macro may declare shadows its whole block: {calls:?}"
    );
}

#[test]
fn a_struct_field_name_is_no_binding_but_a_closure_parameter_beside_it_is() {
    let src = "fn t() {\n    let v = vec![Fact { identity: identity(1), f: |a, b: u8| b(2) }];\n    assert_eq!(n, Counts { import: 1 });\n    import(3);\n}\n";
    let calls: Vec<(String, usize)> = extract_rs_file(src)
        .fragment
        .sites
        .into_iter()
        .filter(|s| s.role == RsRole::Call)
        .map(|s| (s.path, s.line))
        .collect();
    assert_eq!(calls, vec![h("identity", 2), h("import", 4)]);
}

#[test]
#[ignore = "manual scale probe for many macro invocations in one function"]
fn many_invocations_do_not_copy_function_wide_names() {
    use std::fmt::Write;
    use std::time::Instant;

    for n in [5_000, 20_000] {
        let mut src = String::from("fn f(x: i32) -> i32 { x }\nfn t() {\n");
        for i in 0..n {
            writeln!(src, "let a{i} = 0;").unwrap();
        }
        for i in 0..n {
            writeln!(src, "assert!(f(b{i}) > a{i});").unwrap();
        }
        src.push_str("}\n");
        let start = Instant::now();
        let sites = extract_rs_file(&src).fragment.sites;
        eprintln!("{n} macro invocations: {:?}", start.elapsed());
        assert_eq!(sites.iter().filter(|s| s.path == "f").count(), n);
    }
}