use scryer_engine::parsers::{ParseMode, RustAstParser};
use std::path::Path;
#[test]
fn test_parse_rust_functions_and_signatures() {
let code = r#"
/// Computes the square of a number.
/// Multi-line doc test.
pub async fn compute_square<T>(x: T) -> Result<T, Error>
where
T: std::ops::Mul<Output = T> + Copy,
{
let helper = |val: T| val * val;
Ok(helper(x))
}
fn private_fn() {
let _x = 42;
}
"#;
let payload = RustAstParser::parse(
Path::new("src/math.rs"),
code.as_bytes(),
"hash1",
ParseMode::Workspace,
)
.expect("Parsing should succeed");
assert_eq!(payload.language, "rust");
assert_eq!(payload.line_count, 14);
let compute_fn = payload
.symbols
.iter()
.find(|s| s.name == "compute_square")
.expect("compute_square symbol should exist");
assert_eq!(compute_fn.kind, "fn");
assert_eq!(compute_fn.visibility, "public");
assert_eq!(compute_fn.qualified_name, "crate::math::compute_square");
assert!(
compute_fn
.signature
.contains("pub async fn compute_square<T>(x: T) -> Result<T, Error>")
);
assert!(compute_fn.signature.contains("where"));
let doc = compute_fn
.docstring
.as_ref()
.expect("Should have docstring");
assert!(doc.contains("Computes the square of a number."));
assert!(doc.contains("Multi-line doc test."));
assert_eq!(compute_fn.start_line, 4);
assert_eq!(compute_fn.end_line, 10);
let priv_fn = payload
.symbols
.iter()
.find(|s| s.name == "private_fn")
.expect("private_fn should exist");
assert_eq!(priv_fn.visibility, "private");
assert_eq!(priv_fn.kind, "fn");
assert_eq!(priv_fn.start_line, 12);
assert_eq!(priv_fn.end_line, 14);
let fn_scope = payload
.scopes
.iter()
.find(|s| s.scope_kind == "function")
.expect("Function scope should exist");
assert_eq!(fn_scope.start_line, 4);
let closure_scope = payload
.scopes
.iter()
.find(|s| s.scope_kind == "closure")
.expect("Closure scope should exist");
assert_eq!(closure_scope.parent_local_id, Some(fn_scope.local_id));
}
#[test]
fn test_parse_rust_struct_enum_trait_and_impl() {
let code = r#"
/// Configuration for the server.
pub struct ServerConfig {
pub port: u16,
host: String,
}
pub(crate) enum Status {
Active,
Inactive,
}
pub trait Runner {
fn run(&self);
}
impl Runner for ServerConfig {
fn run(&self) {
println!("Running on port {}", self.port);
}
}
pub type Port = u16;
pub const DEFAULT_PORT: Port = 8080;
"#;
let payload = RustAstParser::parse(
Path::new("src/server/config.rs"),
code.as_bytes(),
"hash2",
ParseMode::Workspace,
)
.expect("Parsing should succeed");
let struct_sym = payload
.symbols
.iter()
.find(|s| s.name == "ServerConfig")
.expect("ServerConfig should exist");
assert_eq!(struct_sym.kind, "struct");
assert_eq!(struct_sym.visibility, "public");
assert_eq!(
struct_sym.qualified_name,
"crate::server::config::ServerConfig"
);
assert_eq!(struct_sym.signature, "pub struct ServerConfig");
assert!(
struct_sym
.docstring
.as_ref()
.unwrap()
.contains("Configuration for the server.")
);
let enum_sym = payload
.symbols
.iter()
.find(|s| s.name == "Status")
.expect("Status should exist");
assert_eq!(enum_sym.kind, "enum");
assert_eq!(enum_sym.visibility, "crate");
let trait_sym = payload
.symbols
.iter()
.find(|s| s.name == "Runner")
.expect("Runner should exist");
assert_eq!(trait_sym.kind, "trait");
let run_method = payload
.symbols
.iter()
.find(|s| s.name == "run")
.expect("run method should exist");
assert_eq!(run_method.kind, "fn");
assert_eq!(
run_method.qualified_name,
"crate::server::config::ServerConfig::run"
);
let type_sym = payload
.symbols
.iter()
.find(|s| s.name == "Port")
.expect("Port should exist");
assert_eq!(type_sym.kind, "type");
let const_sym = payload
.symbols
.iter()
.find(|s| s.name == "DEFAULT_PORT")
.expect("DEFAULT_PORT should exist");
assert_eq!(const_sym.kind, "const");
}
#[test]
fn test_parse_nested_modules_and_blocks() {
let code = r#"
pub mod outer {
pub mod inner {
pub fn deeply_nested() {
{
let _scoped = 1;
}
}
}
}
"#;
let payload = RustAstParser::parse(
Path::new("src/lib.rs"),
code.as_bytes(),
"hash3",
ParseMode::Workspace,
)
.expect("Parsing should succeed");
let deep_fn = payload
.symbols
.iter()
.find(|s| s.name == "deeply_nested")
.expect("deeply_nested should exist");
assert_eq!(deep_fn.qualified_name, "crate::outer::inner::deeply_nested");
let block_scope = payload
.scopes
.iter()
.find(|s| s.scope_kind == "block")
.expect("Block scope should exist");
assert_eq!(block_scope.start_line, 5);
}
#[test]
fn test_parse_rust_unions_statics_and_inner_docs() {
let code = r#"//! Module level inner doc comment.
//! Second line of module doc.
#[repr(C)]
pub union ValueUnion {
pub int_val: i32,
pub float_val: f32,
}
pub static GLOBAL_NAME: &str = "Scryer";
"#;
let payload = RustAstParser::parse(
Path::new("src/types.rs"),
code.as_bytes(),
"hash4",
ParseMode::Workspace,
)
.expect("Parsing should succeed");
let union_sym = payload
.symbols
.iter()
.find(|s| s.name == "ValueUnion")
.expect("ValueUnion should exist");
assert_eq!(union_sym.kind, "struct");
assert_eq!(union_sym.visibility, "public");
let static_sym = payload
.symbols
.iter()
.find(|s| s.name == "GLOBAL_NAME")
.expect("GLOBAL_NAME should exist");
assert_eq!(static_sym.kind, "const");
assert_eq!(static_sym.visibility, "public");
}
#[test]
fn test_parse_python_functions_classes_and_scopes() {
use scryer_engine::parsers::PythonAstParser;
let code = r#"
"""Module documentation string."""
MAX_RETRIES = 5
def compute_metrics(data: list) -> dict:
"""Calculate aggregate summary metrics from data."""
return {"count": len(data)}
class DataPipeline:
"""Main data processing pipeline."""
def __init__(self, endpoint: str):
self.endpoint = endpoint
def run(self):
"""Execute the pipeline steps."""
pass
def _private_step(self):
pass
"#;
let payload = PythonAstParser::parse(Path::new("pkg/pipeline.py"), code.as_bytes(), "pyhash")
.expect("Python parsing should succeed");
assert_eq!(payload.language, "python");
let func = payload
.symbols
.iter()
.find(|s| s.name == "compute_metrics")
.expect("compute_metrics should exist");
assert_eq!(func.kind, "fn");
assert_eq!(func.visibility, "public");
assert_eq!(func.qualified_name, "pkg.pipeline.compute_metrics");
assert!(
func.signature
.contains("def compute_metrics(data: list) -> dict")
);
assert_eq!(
func.docstring.as_deref(),
Some("Calculate aggregate summary metrics from data.")
);
let class_sym = payload
.symbols
.iter()
.find(|s| s.name == "DataPipeline")
.expect("DataPipeline class should exist");
assert_eq!(class_sym.kind, "class");
assert_eq!(class_sym.visibility, "public");
assert_eq!(class_sym.qualified_name, "pkg.pipeline.DataPipeline");
assert_eq!(
class_sym.docstring.as_deref(),
Some("Main data processing pipeline.")
);
let priv_method = payload
.symbols
.iter()
.find(|s| s.name == "_private_step")
.expect("_private_step should exist");
assert_eq!(priv_method.kind, "fn");
assert_eq!(priv_method.visibility, "private");
assert_eq!(
priv_method.qualified_name,
"pkg.pipeline.DataPipeline._private_step"
);
let const_sym = payload
.symbols
.iter()
.find(|s| s.name == "MAX_RETRIES")
.expect("MAX_RETRIES should exist");
assert_eq!(const_sym.kind, "const");
assert!(payload.scopes.iter().any(|s| s.scope_kind == "module"));
assert!(payload.scopes.iter().any(|s| s.scope_kind == "class"));
assert!(payload.scopes.iter().any(|s| s.scope_kind == "function"));
}
#[test]
fn test_parse_typescript_interfaces_classes_and_functions() {
use scryer_engine::parsers::TypeScriptAstParser;
let code = r#"
/**
* User data representation.
*/
export interface User {
id: string;
name: string;
}
export type UserId = string | number;
/**
* Service managing user operations.
*/
export class UserService {
private cache: Map<string, User> = new Map();
/** Fetch user by ID */
public async getUser(id: string): Promise<User | null> {
return this.cache.get(id) ?? null;
}
}
export const formatUserName = (user: User): string => {
return user.name.toUpperCase();
};
"#;
let payload = TypeScriptAstParser::parse(
Path::new("src/services/UserService.ts"),
code.as_bytes(),
"tshash",
)
.expect("TypeScript parsing should succeed");
assert_eq!(payload.language, "typescript");
let iface = payload
.symbols
.iter()
.find(|s| s.name == "User")
.expect("User interface should exist");
assert_eq!(iface.kind, "interface");
assert_eq!(iface.visibility, "public");
assert!(
iface
.docstring
.as_deref()
.unwrap()
.contains("User data representation.")
);
let type_alias = payload
.symbols
.iter()
.find(|s| s.name == "UserId")
.expect("UserId type should exist");
assert_eq!(type_alias.kind, "type");
assert_eq!(type_alias.visibility, "public");
let class_sym = payload
.symbols
.iter()
.find(|s| s.name == "UserService")
.expect("UserService should exist");
assert_eq!(class_sym.kind, "class");
assert_eq!(class_sym.visibility, "public");
let method = payload
.symbols
.iter()
.find(|s| s.name == "getUser")
.expect("getUser should exist");
assert_eq!(method.kind, "method");
assert_eq!(method.visibility, "public");
assert!(
method
.qualified_name
.contains("src/services/UserService.UserService.getUser")
);
let arrow_fn = payload
.symbols
.iter()
.find(|s| s.name == "formatUserName")
.expect("formatUserName should exist");
assert_eq!(arrow_fn.kind, "fn");
assert_eq!(arrow_fn.visibility, "public");
assert!(payload.scopes.iter().any(|s| s.scope_kind == "module"));
assert!(payload.scopes.iter().any(|s| s.scope_kind == "interface"));
assert!(payload.scopes.iter().any(|s| s.scope_kind == "class"));
assert!(payload.scopes.iter().any(|s| s.scope_kind == "function"));
}
#[test]
fn test_module_paths_follow_cargo_layout() {
use scryer_engine::parsers::{crate_root_for, module_path_for};
assert_eq!(module_path_for(Path::new("src/lib.rs")), "crate");
assert_eq!(module_path_for(Path::new("src/foo/mod.rs")), "crate::foo");
assert_eq!(
module_path_for(Path::new("crates/my-pkg/src/a/b.rs")),
"my_pkg::a::b"
);
assert_eq!(
module_path_for(Path::new("crates/my-pkg/src/main.rs")),
"my_pkg"
);
assert_eq!(
module_path_for(Path::new("crates/my-pkg/tests/it.rs")),
"my_pkg::tests::it"
);
assert_eq!(module_path_for(Path::new("tests/it.rs")), "test::it");
assert_eq!(
crate_root_for(Path::new("crates/my-pkg/src/a/b.rs")),
"my_pkg"
);
}
#[test]
fn test_normalize_rust_path() {
use scryer_engine::stack_graph::normalize_rust_path;
let file = Path::new("crates/my-pkg/src/a/b.rs");
let norm = |p: &str| normalize_rust_path(p, file);
assert_eq!(norm("crate::x::f").as_deref(), Some("my_pkg::x::f"));
assert_eq!(norm("self::f").as_deref(), Some("my_pkg::a::b::f"));
assert_eq!(norm("super::f").as_deref(), Some("my_pkg::a::f"));
assert_eq!(norm("super::super::f").as_deref(), Some("my_pkg::f"));
assert_eq!(norm("Self::new").as_deref(), Some("new"));
assert_eq!(norm("other::m::f").as_deref(), Some("other::m::f"));
}
#[test]
fn test_extract_type_members() {
use scryer_engine::parsers::extract_type_members;
let src = r#"
pub struct Pair(pub u32, String);
/// Shapes.
pub enum Shape {
Circle {
radius: f64, // inline comment
},
#[allow(dead_code)]
Unit,
}
pub trait Area {
type Unit;
fn area(&self) -> f64;
fn double(&self) -> f64 { self.area() * 2.0 }
}
impl Area for Shape {
type Unit = f64;
fn area(&self) -> f64 { 0.0 }
}
"#;
assert_eq!(
extract_type_members(src, "Shape"),
[
"variant: Circle { radius: f64 }",
"variant: Unit",
"impl: Area"
]
);
assert_eq!(
extract_type_members(src, "Pair"),
["field: 0: u32", "field: 1: String"]
);
assert_eq!(
extract_type_members(src, "Area"),
[
"type: type Unit;",
"method: fn area(&self) -> f64",
"method: fn double(&self) -> f64",
]
);
assert!(extract_type_members(src, "Missing").is_empty());
}