devscout-rs 0.9.0

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

fn sites(src: &str) -> Vec<RsSite> {
    extract_rs_file(src).fragment.sites
}

fn brief(src: &str) -> Vec<(RsRole, String, usize)> {
    sites(src)
        .into_iter()
        .map(|s| (s.role, s.path, s.line))
        .collect()
}

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

use RsRole::{Call, Impl, Lit, Pat, Type, Value};

#[test]
fn callee_forms_are_call_sites() {
    let src = "fn a() {\n    f();\n    m::f();\n    super::f();\n    crate::m::f();\n    Lamp::new();\n    g::<u8>();\n    ::ext::h();\n}\n";
    assert_eq!(
        brief(src),
        vec![
            s(Call, "f", 2),
            s(Call, "m::f", 3),
            s(Call, "super::f", 4),
            s(Call, "crate::m::f", 5),
            s(Call, "Lamp::new", 6),
            s(Call, "g", 7),
            s(Call, "::ext::h", 8),
        ]
    );
}

#[test]
fn self_rooted_paths_carry_their_impl_or_trait() {
    let src = "struct Lamp;\nimpl Lamp {\n    fn a() { Self::b(); Self {}; }\n    fn b() {}\n}\ntrait Shine {\n    fn c() { Self::d(); }\n    fn d();\n}\nfn e() { Self::f(); }\n";
    let got: Vec<(String, Option<usize>, Option<String>)> = sites(src)
        .into_iter()
        .map(|s| (s.path, s.self_impl, s.self_trait))
        .collect();
    assert_eq!(
        got,
        vec![
            ("Lamp".into(), None, None),
            ("Self::b".into(), Some(0), None),
            ("Self::d".into(), None, Some("Shine".into())),
        ],
        "`Self` alone and a `Self` path outside any impl or trait give no site"
    );
}

#[test]
fn type_positions_literals_and_impls_have_their_roles() {
    let src = "struct W { f: Field }\nstruct T(Tup);\ntype A = Aliased;\nfn g<P: Bound>(x: Param) -> Ret where P: Clause {\n    let y: Ann = x as Cast;\n    let v: Vec<Arg> = Vec::new();\n    let d: &dyn Obj = &y;\n    Lit { a: 1 };\n}\nimpl Tr for W {}\nimpl W {}\n";
    assert_eq!(
        brief(src),
        vec![
            s(Type, "Field", 1),
            s(Type, "Tup", 2),
            s(Type, "Aliased", 3),
            s(Type, "Bound", 4),
            s(Type, "Param", 4),
            s(Type, "Ret", 4),
            s(Type, "Clause", 4),
            s(Type, "Ann", 5),
            s(Type, "Cast", 5),
            s(Type, "Vec", 6),
            s(Type, "Arg", 6),
            s(Call, "Vec::new", 6),
            s(Type, "Obj", 7),
            s(Lit, "Lit", 8),
            s(Impl, "Tr", 10),
            s(Type, "W", 10),
            s(Type, "W", 11),
        ]
    );
}

#[test]
fn values_and_pattern_qualifiers() {
    let src = "fn g(e: u8) {\n    let h = Unit;\n    let k = m::CONST;\n    match e { E::V(x) => {}, E::W { y, .. } => {}, S => {} }\n    let z = Tuple(1);\n}\n";
    assert_eq!(
        brief(src),
        vec![
            s(Value, "Unit", 2),
            s(Value, "m::CONST", 3),
            s(Pat, "E::V", 4),
            s(Pat, "E::W", 4),
            s(Call, "Tuple", 5),
        ],
        "a parameter, a binding and an uppercase binding-shaped pattern give no site"
    );
}

#[test]
fn locals_closures_nested_fns_and_generics_shadow() {
    let src = "fn f() {}\nfn g<T: Tr>(p: u8) {\n    let x = 0;\n    let c = |q| q + 1;\n    c(1);\n    p(2);\n    if let Some(w) = None { w(3); }\n    for z in it { z(4); }\n    match 1 { v => v(5) }\n    fn f() {}\n    f();\n    T::make();\n    self::f();\n    x.f();\n}\n";
    let got: Vec<String> = sites(src).into_iter().map(|s| s.path).collect();
    assert_eq!(
        got,
        vec!["Tr", "Some", "None", "it", "self::f"],
        "a local, a closure, a nested fn, a generic and a method name never give a site"
    );
}

#[test]
fn a_path_split_over_lines_records_each_segment_line() {
    let src = "fn a() {\n    crate::m\n        ::f();\n}\n";
    let site = &sites(src)[0];
    assert_eq!((site.line, site.lines.clone()), (3, vec![2, 2, 3]));
}

#[test]
fn scope_and_block_are_recorded() {
    let src = "mod m {\n    fn a() {\n        use x::y;\n        y();\n        { z(); }\n    }\n}\nfn b() { w(); }\n";
    let got: Vec<(String, String, Option<usize>)> = sites(src)
        .into_iter()
        .map(|s| (s.path, s.scope, s.block))
        .collect();
    assert_eq!(
        got,
        vec![
            ("y".into(), "m".into(), Some(0)),
            ("z".into(), "m".into(), Some(0)),
            ("w".into(), "".into(), None),
        ]
    );
}

#[test]
fn nothing_is_read_from_skipped_regions() {
    let src = "use a::b;\n#[derive(Clone)]\n#[cfg(feature = \"f\")]\nstruct S;\nmacro_rules! m { () => { f() } }\nfn g() { let s = \"f()\"; x.method(); x.field; }\nfn broken( { h(); }\n";
    let got: Vec<String> = sites(src).into_iter().map(|s| s.path).collect();
    assert!(
        !got.iter()
            .any(|p| matches!(p.as_str(), "a::b" | "Clone" | "f" | "method" | "field")),
        "{got:?}"
    );
}

#[test]
fn qualified_paths_bind_nothing_but_their_types_are_type_sites() {
    let src = "fn a() { <Lamp as Shine>::glow(); }\n";
    assert_eq!(brief(src), vec![s(Type, "Lamp", 1), s(Type, "Shine", 1)]);
}

#[test]
fn deep_nesting_is_walked_without_exhausting_a_worker_stack() {
    let depth = 3000;
    let src = format!(
        "fn a() {{ {}f(); {} }}\n",
        "{ let c = || ".repeat(depth),
        "; }".repeat(depth)
    );
    let handle = std::thread::Builder::new()
        .stack_size(256 * 1024)
        .spawn(move || extract_rs_file(&src).fragment.sites.len())
        .unwrap();
    assert_eq!(handle.join().unwrap(), 1);
}

#[test]
fn a_trait_inside_a_function_body_gives_its_self_paths_no_owner() {
    let src = "trait Shine {\n    fn d();\n}\nfn outer() {\n    trait Shine {\n        fn c() { Self::d(); }\n        fn d();\n    }\n}\n";
    assert!(
        sites(src)
            .iter()
            .all(|s| s.self_trait.is_none() && s.path != "Self::d"),
        "a body-local trait is not the module-level trait of the same path"
    );
}