scryer-engine 0.3.0

Tree-sitter AST indexing and reference resolution engine for Scryer code intelligence
//! `via` records how a reference was tied to its target: `exact` for a path or scope
//! resolution to the only matching symbol, `name` for a match by name alone.

mod support;

use scryer_db::{ScryerDb, Symbol, SymbolReference};
use scryer_engine::IndexOptions;
use support::fixture::index_files;

/// `via` of every call reference to a symbol named `name`, by line.
async fn via_of_calls(
    db: &ScryerDb,
    project: u64,
    name: &str,
) -> anyhow::Result<Vec<(u32, String)>> {
    let mut conn = db.lock().await;
    let ids: Vec<u64> = Symbol::filter_by_project_id(project)
        .exec(&mut *conn)
        .await?
        .into_iter()
        .filter(|s| s.name == name)
        .map(|s| s.id)
        .collect();
    let mut out: Vec<(u32, String)> = SymbolReference::filter_by_project_id(project)
        .exec(&mut *conn)
        .await?
        .into_iter()
        .filter(|r| r.role == "call" && ids.contains(&r.symbol_id))
        .map(|r| (r.line_number, r.via))
        .collect();
    out.sort();
    Ok(out)
}

#[tokio::test]
async fn a_path_call_is_exact_and_a_method_on_an_unknown_receiver_is_by_name() -> anyhow::Result<()>
{
    let (db, project) = index_files(
        &[(
            "src/lib.rs",
            "pub fn target() {}\n\
             pub struct S;\n\
             impl S { pub fn run(&self) {} }\n\
             pub fn by_path() { crate::target(); }\n\
             pub fn by_scope() { target(); }\n\
             pub fn by_method(s: Unknown) { s.run(); }\n",
        )],
        IndexOptions::default(),
    )
    .await?;

    assert_eq!(
        via_of_calls(&db, project, "target").await?,
        vec![(4, "exact".into()), (5, "exact".into())]
    );
    assert_eq!(
        via_of_calls(&db, project, "run").await?,
        vec![(6, "name".into())]
    );
    Ok(())
}

#[tokio::test]
async fn a_name_that_several_symbols_share_is_not_exact() -> anyhow::Result<()> {
    let (db, project) = index_files(
        &[
            ("a.py", "def go():\n    helper()\n"),
            ("b.py", "def helper():\n    return 1\n"),
            ("c.py", "def helper():\n    return 2\n"),
        ],
        IndexOptions::default(),
    )
    .await?;
    let vias = via_of_calls(&db, project, "helper").await?;
    assert_eq!(vias.len(), 1, "{vias:?}");
    assert_eq!(vias[0].1, "name", "{vias:?}");
    Ok(())
}