use std::path::Path;
use scryer_engine::parsers::{ParseMode, RustAstParser};
mod support;
use support::fingerprint::fingerprint;
use support::fixture::index_files;
fn parse(source: &str) -> scryer_engine::ParsedFilePayload {
RustAstParser::parse(
Path::new("src/lib.rs"),
source.as_bytes(),
"hash",
ParseMode::Workspace,
)
.expect("the file parses")
}
#[test]
fn unsafe_extern_blocks_parse() {
let payload =
parse("unsafe extern \"C\" {\n pub fn abs(x: i32) -> i32;\n}\n\npub fn after() {}\n");
assert!(!payload.syntax_errors, "unsafe extern is Rust 2024 syntax");
for name in ["abs", "after"] {
assert!(
payload.symbols.iter().any(|s| s.name == name),
"{name} is a symbol: {:?}",
payload.symbols
);
}
}
use scryer_engine::parsers::rust::prepare_source;
fn assert_same_shape(source: &str, prepared: &str) {
assert_eq!(source.len(), prepared.len(), "length");
let newlines = |s: &str| {
s.bytes()
.enumerate()
.filter(|(_, b)| *b == b'\n')
.map(|(i, _)| i)
.collect::<Vec<_>>()
};
assert_eq!(newlines(source), newlines(prepared), "newline positions");
}
#[test]
fn front_matter_is_blanked_and_the_rust_after_it_parses() {
let source = "#!/usr/bin/env -S cargo +nightly -Zscript\n---cargo\n[dependencies]\nclap = \"4\" # é\n---\n\nfn main() {\n run();\n}\nfn run() {}\n";
let prepared = prepare_source(source);
assert_same_shape(source, &prepared);
assert!(!prepared.contains("[dependencies]"), "{prepared}");
assert!(prepared.ends_with("fn main() {\n run();\n}\nfn run() {}\n"));
let payload = parse(source);
assert!(!payload.syntax_errors, "front matter is not an error");
for name in ["main", "run"] {
assert!(payload.symbols.iter().any(|s| s.name == name), "{name}");
}
}
#[test]
fn front_matter_without_a_shebang_or_a_closing_fence() {
let source = "---\n[package]\n---\nfn a() {}\n";
let prepared = prepare_source(source);
assert_same_shape(source, &prepared);
assert!(
prepared.starts_with(" \n \n \nfn a() {}"),
"{prepared:?}"
);
let open = "---\nfn a() {}\n";
assert_eq!(prepare_source(open), open);
let code = "fn a() {\n let x = 1 - -1;\n}\n// ---\n";
assert_eq!(prepare_source(code), code);
}
#[test]
fn str_macros_are_renamed_only_where_they_are_macro_calls() {
let source = "fn t() {\n check(str![[\"a\"]]);\n check(str!(\"b\"));\n check(str! { \"c\" });\n check(snapbox::str![\"d\"]);\n}\n";
let prepared = prepare_source(source);
assert_same_shape(source, &prepared);
assert_eq!(prepared.matches("Str!").count(), 4, "{prepared}");
assert!(!prepared.contains("str!"));
for untouched in [
"fn f(s: &str) -> bool { core::str::from_utf8(b\"\").is_ok() }\n",
"macro_rules! my_str { () => {} }\nfn g() { my_str!(); }\n",
"fn h(a: u8, b: u8) -> bool { let str = a; str!=b }\n",
] {
assert_eq!(prepare_source(untouched), untouched);
}
}
#[test]
fn a_file_with_str_macros_parses_cleanly_and_keeps_its_calls() {
let source = "use snapbox::str;\n\nfn expected() -> usize {\n helper(str![[\"x\"]]);\n helper(str![\"y\"])\n}\n\nfn helper<T>(_: T) -> usize { 0 }\n";
let payload = parse(source);
assert!(!payload.syntax_errors, "str![..] parses once renamed");
assert!(payload.symbols.iter().any(|s| s.name == "expected"));
assert!(payload.symbols.iter().any(|s| s.name == "helper"));
}
#[tokio::test]
async fn indexed_files_with_snapshots_and_front_matter_have_no_errors_and_keep_their_links()
-> anyhow::Result<()> {
let (db, project) = index_files(
&[
("src/lib.rs", "mod util;\nmod snap;\nmod script;\n"),
("src/util.rs", "pub fn helper<T>(_: T) -> usize { 0 }\n"),
(
"src/snap.rs",
"use crate::util::helper;\nuse snapbox::str;\n\npub fn check() -> usize {\n helper(str![[\"x\"]]) + helper(str![\"y\"]).max(1)\n}\n",
),
(
"src/script.rs",
"#!/usr/bin/env cargo\n---\n[dependencies]\n---\n\npub fn main_script() { crate::util::helper(1); }\n",
),
],
scryer_engine::IndexOptions::default(),
)
.await?;
let health = scryer_engine::index_health(&db, project).await?;
let errors = health
.stats
.map(|s| s.syntax_error_files)
.unwrap_or_default();
assert!(errors.is_empty(), "no syntax errors: {errors:?}");
let fp = fingerprint(&db, project).await?;
for file in ["src/snap.rs", "src/script.rs"] {
assert!(
fp.references
.iter()
.any(|r| r.starts_with(&format!("{file}:"))
&& r.contains(":call[")
&& r.ends_with("src/util.rs::crate::util::helper")),
"{file} calls helper: {:?}",
fp.references
);
}
Ok(())
}