devscout-rs 0.9.0

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

const LAMP: &str = "pub struct Lamp;\npub trait Shine { fn beam(); }\npub type Alias = Lamp;\n";

#[test]
fn each_callee_form_gives_one_uses_member_edge_with_the_model_fields() {
    let files = [
        ("src/lib.rs", "mod m;\nmod lamp;\nmod glow;\nfn f() {}\nfn t() {\n    f();\n    m::g();\n    crate::m::g();\n    lamp::Lamp::glow();\n    lamp::Shine::beam();\n    m::h::<u8>();\n}\n"),
        ("src/m.rs", "pub fn g() {}\npub fn h<T>() {}\npub fn back() { super::f(); }\n"),
        ("src/lamp.rs", LAMP),
        ("src/glow.rs", "impl crate::lamp::Lamp {\n    pub fn glow() { Self::dim(); }\n    pub fn dim() {}\n}\n"),
    ];
    let graph = graph_for(&files);
    let json: Vec<String> = graph
        .edges
        .iter()
        .map(|e| serde_json::to_string(e).unwrap())
        .collect();
    assert_eq!(
        json,
        vec![
            r#"{"kind":"uses-member","from_file":"src/lib.rs","from_line":6,"to":"src/lib.rs#f","to_file":"src/lib.rs","member":"f"}"#,
            r#"{"kind":"uses-member","from_file":"src/lib.rs","from_line":7,"to":"src/m.rs#g","to_file":"src/m.rs","member":"g"}"#,
            r#"{"kind":"uses-member","from_file":"src/lib.rs","from_line":8,"to":"src/m.rs#g","to_file":"src/m.rs","member":"g"}"#,
            r#"{"kind":"uses-type","from_file":"src/lib.rs","from_line":9,"to":"src/lamp.rs#Lamp","to_file":"src/lamp.rs"}"#,
            r#"{"kind":"uses-member","from_file":"src/lib.rs","from_line":9,"to":"src/glow.rs#Lamp::glow","to_file":"src/glow.rs","member":"glow"}"#,
            r#"{"kind":"uses-type","from_file":"src/lib.rs","from_line":10,"to":"src/lamp.rs#Shine","to_file":"src/lamp.rs"}"#,
            r#"{"kind":"uses-member","from_file":"src/lib.rs","from_line":10,"to":"src/lamp.rs#Shine::beam","to_file":"src/lamp.rs","member":"beam"}"#,
            r#"{"kind":"uses-member","from_file":"src/lib.rs","from_line":11,"to":"src/m.rs#h","to_file":"src/m.rs","member":"h"}"#,
            r#"{"kind":"uses-member","from_file":"src/m.rs","from_line":3,"to":"src/lib.rs#f","to_file":"src/lib.rs","member":"f"}"#,
            r#"{"kind":"uses-type","from_file":"src/lamp.rs","from_line":3,"to":"src/lamp.rs#Lamp","to_file":"src/lamp.rs"}"#,
            r#"{"kind":"uses-type","from_file":"src/glow.rs","from_line":1,"to":"src/lamp.rs#Lamp","to_file":"src/lamp.rs"}"#,
            r#"{"kind":"uses-member","from_file":"src/glow.rs","from_line":2,"to":"src/glow.rs#Lamp::dim","to_file":"src/glow.rs","member":"dim"}"#,
        ]
    );
    assert_eq!(graph.stats.edges_by_kind.uses_member, 8);
    assert_eq!(graph.stats.edges_by_kind.uses_type, 4);
}

#[test]
fn no_edge_from_methods_shadows_generics_aliases_or_std() {
    let src = "pub fn glow() {}\npub struct Lamp;\nimpl Lamp { pub fn make() {} }\npub type Alias = Lamp;\nfn t<T: Default>(glow: u8, x: Lamp) {\n    x.glow();\n    glow;\n    let c = |glow: u8| glow;\n    T::default();\n    Alias::make();\n    std::mem::drop(1);\n    Vec::<u8>::new();\n}\nfn u() {\n    fn glow() {}\n    glow();\n}\n";
    let got = edges_for(&[("src/lib.rs", src)]);
    assert!(!got.iter().any(|e| e.starts_with("uses-member")), "{got:?}");
    assert!(
        got.contains(&"uses-type src/lib.rs:10 -> src/lib.rs#Alias".to_string()),
        "an alias qualifier gives uses-type and binds nothing after it: {got:?}"
    );
}

#[test]
fn type_positions_literals_constructors_and_impls_give_their_edges() {
    let src = "pub struct Lamp;\npub struct Wick(u8);\npub struct Shade { a: u8 }\npub trait Shine {}\npub enum Mode { On }\npub type Alias = Lamp;\npub struct Holder { f: Lamp }\nfn t(p: Lamp) -> Shade {\n    let a: Alias = Lamp;\n    let w = Wick(1);\n    let m = Mode::On;\n    let g: Vec<Shade> = Vec::new();\n    let s = Shade { a: 1 };\n    s\n}\nimpl Lamp {}\nimpl Shine for Lamp {}\nimpl std::fmt::Display for Lamp { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) } }\nfn b<T: Shine>(x: &dyn Shine) where T: Shine {}\n";
    let got = edges_for(&[("src/lib.rs", src)]);
    let want = [
        "uses-type src/lib.rs:6 -> src/lib.rs#Lamp",
        "uses-type src/lib.rs:7 -> src/lib.rs#Lamp",
        "uses-type src/lib.rs:8 -> src/lib.rs#Lamp",
        "uses-type src/lib.rs:8 -> src/lib.rs#Shade",
        "uses-type src/lib.rs:9 -> src/lib.rs#Alias",
        "uses-type src/lib.rs:9 -> src/lib.rs#Lamp",
        "uses-type src/lib.rs:10 -> src/lib.rs#Wick",
        "uses-type src/lib.rs:11 -> src/lib.rs#Mode",
        "uses-type src/lib.rs:12 -> src/lib.rs#Shade",
        "uses-type src/lib.rs:13 -> src/lib.rs#Shade",
        "uses-type src/lib.rs:16 -> src/lib.rs#Lamp",
        "implements src/lib.rs:17 -> src/lib.rs#Shine",
        "uses-type src/lib.rs:17 -> src/lib.rs#Lamp",
        "uses-type src/lib.rs:18 -> src/lib.rs#Lamp",
        "uses-type src/lib.rs:19 -> src/lib.rs#Shine",
        "uses-type src/lib.rs:19 -> src/lib.rs#Shine",
        "uses-type src/lib.rs:19 -> src/lib.rs#Shine",
    ];
    assert_eq!(got, want);
    let graph = graph_for(&[("src/lib.rs", src)]);
    let implements: Vec<String> = graph
        .edges
        .iter()
        .filter(|e| matches!(e, Edge::Implements { .. }))
        .map(|e| serde_json::to_string(e).unwrap())
        .collect();
    assert_eq!(
        implements,
        vec![
            r#"{"kind":"implements","from_file":"src/lib.rs","from_line":17,"to":"src/lib.rs#Shine","to_file":"src/lib.rs"}"#
        ]
    );
    assert_eq!(graph.stats.edges_by_kind.implements, 1);
}

#[test]
fn calls_in_macro_arguments_give_the_edges_the_same_call_would() {
    let src = "mod m { pub fn g(x: u8) -> u8 { x } }\npub struct Lamp;\nimpl Lamp { pub fn new() -> u8 { 1 } }\nfn f(x: u8) -> u8 { x }\nfn t() {\n    assert_eq!(f(1), m::g(2));\n    assert!(f(3) > 0);\n    let v = vec![f(m::g(4))];\n    assert!(matches!(f(5), 5));\n    let s = format!(\"{} f(9)\", Lamp::new());\n}\n";
    assert_eq!(
        edges_for(&[("src/lib.rs", src)]),
        vec![
            "uses-type src/lib.rs:3 -> src/lib.rs#Lamp",
            "uses-member src/lib.rs:6 -> src/lib.rs#f",
            "uses-member src/lib.rs:6 -> src/lib.rs#m::g",
            "uses-member src/lib.rs:7 -> src/lib.rs#f",
            "uses-member src/lib.rs:8 -> src/lib.rs#f",
            "uses-member src/lib.rs:8 -> src/lib.rs#m::g",
            "uses-member src/lib.rs:9 -> src/lib.rs#f",
            "uses-type src/lib.rs:10 -> src/lib.rs#Lamp",
            "uses-member src/lib.rs:10 -> src/lib.rs#Lamp::new",
        ]
    );
}

#[test]
fn rust_edges_follow_every_existing_edge_and_no_fragment_changes_nothing() {
    let root = std::env::temp_dir().join("scout-rsgraph-edges-order");
    let mut graph = crate::resolve::resolve_graph_with_model(&root, &[], &[], None, None);
    graph.edges.push(Edge::UsesType {
        from_file: "src/A.cs".into(),
        from_line: 1,
        to: "App.B".into(),
        to_file: "src/B.cs".into(),
        heuristic: false,
    });
    let before = serde_json::to_string(&graph).unwrap();
    crate::rsgraph::append_rust_edges(&mut graph, &[], None);
    assert_eq!(serde_json::to_string(&graph).unwrap(), before);
    let fragments = vec![(
        "src/lib.rs".to_string(),
        extract_rs_file("fn f() {}\nfn t() { f(); }\n").fragment,
    )];
    crate::rsgraph::append_rust(&mut graph, &fragments);
    crate::rsgraph::append_rust_edges(&mut graph, &fragments, None);
    let kinds: Vec<String> = graph.edges.iter().map(brief).collect();
    assert_eq!(kinds[1], "uses-member src/lib.rs:2 -> src/lib.rs#f");
    assert!(kinds[0].starts_with("UsesType") || kinds[0].starts_with("uses-type src/A.cs"));
}

#[test]
fn each_test_function_carries_its_own_name_and_a_helper_file_carries_none() {
    let files = [
        ("Cargo.toml", "[package]\nname = \"p\"\n"),
        (
            "src/lib.rs",
            "pub fn glow() {}\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn lit() { super::glow(); }\n}\n#[cfg(test)]\nmod more;\n",
        ),
        ("src/more.rs", "#[test]\nfn dim() {}\nfn helper() {}\n"),
        ("tests/it.rs", "mod common;\n#[test]\nfn from_outside() { common::setup(); p::glow(); }\n"),
        ("tests/common/mod.rs", "pub fn setup() {}\n"),
    ];
    let graph = graph_for(&files);
    let carriers: Vec<(&str, &[String])> = graph
        .defs
        .iter()
        .filter(|d| !d.test_methods.is_empty())
        .map(|d| (d.id.as_str(), d.test_methods.as_slice()))
        .collect();
    assert_eq!(
        carriers,
        vec![
            ("src/lib.rs#tests::lit", &["lit".to_string()][..]),
            ("src/more.rs#dim", &["dim".to_string()][..]),
            (
                "tests/it.rs#from_outside",
                &["from_outside".to_string()][..]
            ),
        ]
    );
    assert_eq!(graph.stats.test_def_count, 3);
    assert_eq!(
        serde_json::to_string(graph.defs.iter().find(|d| d.name == "dim").unwrap()).unwrap(),
        r#"{"id":"src/more.rs#dim","name":"dim","namespace":"src/more.rs","kind":"fn","file":"src/more.rs","line":1,"methods":[],"testMethods":["dim"],"endLine":2}"#
    );
}