mod support;
use scryer_db::{ScryerDb, Symbol, SymbolReference};
use scryer_engine::IndexOptions;
use support::fixture::index_files;
async fn calls(
db: &ScryerDb,
project: u64,
name: &str,
) -> anyhow::Result<Vec<(u32, String, String)>> {
let mut conn = db.lock().await;
let symbols = Symbol::filter_by_project_id(project)
.exec(&mut *conn)
.await?;
let mut out: Vec<(u32, String, String)> = SymbolReference::filter_by_project_id(project)
.exec(&mut *conn)
.await?
.into_iter()
.filter(|r| r.role == "call")
.filter_map(|r| {
let target = symbols.iter().find(|s| s.id == r.symbol_id)?;
(target.name == name).then(|| (r.line_number, target.qualified_name.clone(), r.via))
})
.collect();
out.sort();
Ok(out)
}
fn row(line: u32, target: &str, via: &str) -> (u32, String, String) {
(line, target.to_string(), via.to_string())
}
#[tokio::test]
async fn rust_self_and_type_paths_pick_the_right_impl() -> anyhow::Result<()> {
let (db, project) = index_files(
&[(
"src/lib.rs",
"pub struct A;\n\
pub struct B;\n\
impl A {\n\
pub fn new() -> A { A }\n\
pub fn helper(&self) -> u32 { 1 }\n\
pub fn go(&self) -> u32 { self.helper() }\n\
pub fn make() -> A { Self::new() }\n\
}\n\
impl B {\n\
pub fn new() -> B { B }\n\
pub fn helper(&self) -> u32 { 2 }\n\
pub fn go(&self) -> u32 { self.helper() }\n\
}\n\
pub fn build() { A::new(); B::new(); }\n\
pub trait Tr { fn defaulted(&self) -> u32 { 3 } fn needs(&self) -> u32; }\n\
impl Tr for A { fn needs(&self) -> u32 { self.defaulted() } }\n\
pub fn unknown(x: B) -> u32 { x.helper() }\n",
)],
IndexOptions::default(),
)
.await?;
assert_eq!(
calls(&db, project, "new").await?,
vec![
row(7, "crate::A::new", "exact"),
row(14, "crate::A::new", "exact"),
row(14, "crate::B::new", "exact"),
]
);
let helper = calls(&db, project, "helper").await?;
assert_eq!(helper[0], row(6, "crate::A::helper", "exact"), "{helper:?}");
assert_eq!(
helper[1],
row(12, "crate::B::helper", "exact"),
"{helper:?}"
);
assert_eq!(helper[2].0, 17, "{helper:?}");
assert_eq!(helper[2].2, "name", "{helper:?}");
assert_eq!(
calls(&db, project, "defaulted").await?,
vec![row(16, "crate::Tr::defaulted", "name")]
);
Ok(())
}
#[tokio::test]
async fn python_self_picks_its_own_class() -> anyhow::Result<()> {
let (db, project) = index_files(
&[(
"m.py",
"class A:\n\
\x20 def helper(self):\n\
\x20 return 1\n\
\x20 def go(self):\n\
\x20 return self.helper()\n\
class B:\n\
\x20 def helper(self):\n\
\x20 return 2\n\
\x20 def go(self):\n\
\x20 return self.helper()\n",
)],
IndexOptions::default(),
)
.await?;
assert_eq!(
calls(&db, project, "helper").await?,
vec![
row(5, "m.A.helper", "exact"),
row(10, "m.B.helper", "exact")
]
);
Ok(())
}
#[tokio::test]
async fn typescript_this_picks_its_own_class() -> anyhow::Result<()> {
let (db, project) = index_files(
&[(
"m.ts",
"export class A {\n\
\x20 helper() { return 1; }\n\
\x20 go() { return this.helper(); }\n\
}\n\
export class B {\n\
\x20 helper() { return 2; }\n\
\x20 go() { return this.helper(); }\n\
}\n",
)],
IndexOptions::default(),
)
.await?;
let helper = calls(&db, project, "helper").await?;
assert_eq!(helper.len(), 2, "{helper:?}");
assert!(
helper[0].1.ends_with("A.helper") || helper[0].1.ends_with("A::helper"),
"{helper:?}"
);
assert!(
helper[1].1.ends_with("B.helper") || helper[1].1.ends_with("B::helper"),
"{helper:?}"
);
assert!(helper.iter().all(|r| r.2 == "exact"), "{helper:?}");
Ok(())
}