use std::path::Path;
use scryer_engine::parsers::{ParseMode, RustAstParser};
#[test]
fn test_dependency_public_surface_filtering() {
let code = r#"
//! Top-level module documentation for my_dep.
/// A public struct representing configuration.
pub struct MyConfig {
pub port: u16,
private_token: String,
}
struct InternalState {
secret: u64,
}
pub trait Service {
/// Dispatches a request.
fn call(&self, req: &str) -> String;
}
trait SecretTrait {
fn hidden(&self);
}
pub enum PublicStatus {
Active,
Disabled,
}
enum PrivateStatus {
Unknown,
}
pub type PortNumber = u16;
type SecretType = String;
pub const DEFAULT_PORT: u16 = 8080;
const PRIVATE_SEED: u64 = 12345;
/// Public helper function.
pub fn public_helper(input: &str) -> usize {
let mut intermediate_var = input.len();
let closure = |x: usize| x * 2;
closure(intermediate_var)
}
fn private_helper(input: &str) -> usize {
input.len()
}
impl MyConfig {
/// Creates a new config.
pub fn new(port: u16) -> Self {
let default_host = "localhost";
let closure = || println!("{}", default_host);
closure();
Self {
port,
private_token: String::new(),
}
}
fn internal_keygen(&self) -> String {
format!("token-{}", self.port)
}
}
impl InternalState {
fn internal_step(&self) {}
}
pub mod nested {
pub fn nested_public() {
let scoped_x = 1;
}
fn nested_private() {}
}
mod private_mod {
pub fn unreachable_fn() {}
}
"#;
let dep_payload = RustAstParser::parse(
Path::new("src/lib.rs"),
code.as_bytes(),
"hash_dep",
ParseMode::DependencyPublicSurface {
crate_name: "my_dep".to_string(),
},
)
.expect("Dependency public surface parsing should succeed");
assert_eq!(dep_payload.language, "rust");
let symbol_names: Vec<&str> = dep_payload
.symbols
.iter()
.map(|s| s.name.as_str())
.collect();
assert!(
symbol_names.contains(&"MyConfig"),
"pub struct MyConfig should exist"
);
assert!(
symbol_names.contains(&"Service"),
"pub trait Service should exist"
);
assert!(
symbol_names.contains(&"PublicStatus"),
"pub enum PublicStatus should exist"
);
assert!(
symbol_names.contains(&"PortNumber"),
"pub type PortNumber should exist"
);
assert!(
symbol_names.contains(&"DEFAULT_PORT"),
"pub const DEFAULT_PORT should exist"
);
assert!(
symbol_names.contains(&"public_helper"),
"pub fn public_helper should exist"
);
assert!(
symbol_names.contains(&"new"),
"pub fn new inside impl should exist"
);
assert!(
symbol_names.contains(&"nested"),
"pub mod nested should exist"
);
assert!(
symbol_names.contains(&"nested_public"),
"pub fn nested_public should exist"
);
assert!(
!symbol_names.contains(&"InternalState"),
"private struct must be omitted"
);
assert!(
!symbol_names.contains(&"SecretTrait"),
"private trait must be omitted"
);
assert!(
!symbol_names.contains(&"PrivateStatus"),
"private enum must be omitted"
);
assert!(
!symbol_names.contains(&"SecretType"),
"private type alias must be omitted"
);
assert!(
!symbol_names.contains(&"PRIVATE_SEED"),
"private const must be omitted"
);
assert!(
!symbol_names.contains(&"private_helper"),
"private fn must be omitted"
);
assert!(
!symbol_names.contains(&"internal_keygen"),
"private method in impl must be omitted"
);
assert!(
!symbol_names.contains(&"internal_step"),
"private method in impl must be omitted"
);
assert!(
!symbol_names.contains(&"nested_private"),
"private fn in nested mod must be omitted"
);
assert!(
!symbol_names.contains(&"private_mod"),
"private mod must be omitted"
);
assert!(
!symbol_names.contains(&"unreachable_fn"),
"fn in private mod must be omitted"
);
let config_sym = dep_payload
.symbols
.iter()
.find(|s| s.name == "MyConfig")
.unwrap();
assert_eq!(config_sym.qualified_name, "my_dep::MyConfig");
assert_eq!(config_sym.visibility, "public");
assert!(
config_sym
.docstring
.as_ref()
.unwrap()
.contains("A public struct representing configuration.")
);
let helper_sym = dep_payload
.symbols
.iter()
.find(|s| s.name == "public_helper")
.unwrap();
assert_eq!(helper_sym.qualified_name, "my_dep::public_helper");
assert!(helper_sym.signature.contains("pub fn public_helper"));
assert!(
helper_sym
.docstring
.as_ref()
.unwrap()
.contains("Public helper function.")
);
let method_sym = dep_payload
.symbols
.iter()
.find(|s| s.name == "new")
.unwrap();
assert_eq!(method_sym.qualified_name, "my_dep::MyConfig::new");
assert!(
dep_payload.scopes.is_empty(),
"Expected scopes to be empty in DependencyPublicSurface mode, got {}",
dep_payload.scopes.len()
);
let ws_payload = RustAstParser::parse(
Path::new("src/lib.rs"),
code.as_bytes(),
"hash_ws",
ParseMode::Workspace,
)
.expect("Workspace parsing should succeed");
let ws_symbol_names: Vec<&str> = ws_payload.symbols.iter().map(|s| s.name.as_str()).collect();
assert!(ws_symbol_names.contains(&"private_helper"));
assert!(ws_symbol_names.contains(&"InternalState"));
assert!(
ws_payload.scopes.len() > 5,
"Workspace mode must extract lexical scopes"
);
let dep_symbol_count = dep_payload.symbols.len();
let ws_symbol_count = ws_payload.symbols.len();
println!("Dependency symbols: {dep_symbol_count}, Workspace symbols: {ws_symbol_count}");
let dep_total_entities = dep_payload.symbols.len() + dep_payload.scopes.len();
let ws_total_entities = ws_payload.symbols.len() + ws_payload.scopes.len();
let reduction_pct = 100.0 * (1.0 - (dep_total_entities as f64 / ws_total_entities as f64));
println!(
"Entity reduction: {dep_total_entities} vs {ws_total_entities} ({reduction_pct:.1}% reduction)"
);
assert!(
reduction_pct >= 60.0,
"Expected >= 60% entity reduction on this sample (got {reduction_pct:.1}%)"
);
}
#[test]
fn test_dependency_module_path_resolution() {
let cases = [
("src/lib.rs", "tokio", "tokio"),
("src/sync/mod.rs", "tokio", "tokio::sync"),
("src/sync/mpsc.rs", "tokio", "tokio::sync::mpsc"),
("src/sync/mpsc/mod.rs", "tokio", "tokio::sync::mpsc"),
("lib.rs", "serde-json", "serde_json"),
("value/mod.rs", "serde-json", "serde_json::value"),
("value/ser.rs", "serde-json", "serde_json::value::ser"),
];
for (rel_path, crate_name, expected) in cases {
let computed =
scryer_engine::parsers::dependency_module_path_for(Path::new(rel_path), crate_name);
assert_eq!(
computed, expected,
"Failed for path={rel_path}, crate={crate_name}"
);
}
}