use regex::Regex;
use std::sync::LazyLock;
static COMMENT_SINGLE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"//.*$").unwrap());
static COMMENT_MULTI: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?s)/\*.*?\*/").unwrap());
static PUB_DECL: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"pub(?:\(crate\))?\s+(?:async\s+)?(?:unsafe\s+)?(?:fn|struct|enum|trait|type|const|static|mod)\s+(\w+)",
)
.unwrap()
});
pub fn extract_exports(content: &str) -> Vec<String> {
let stripped = COMMENT_SINGLE.replace_all(content, "");
let stripped = COMMENT_MULTI.replace_all(&stripped, "");
let mut symbols = Vec::new();
for caps in PUB_DECL.captures_iter(&stripped) {
if let Some(name) = caps.get(1) {
symbols.push(name.as_str().to_string());
}
}
symbols
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rust_exports() {
let src = r#"
pub fn create_auth(config: Config) -> Auth {}
pub struct AuthService {}
pub enum AuthStatus { Active, Expired }
pub trait Authenticator {}
pub type Token = String;
pub const DEFAULT_TTL: u64 = 3600;
pub static INSTANCE: Lazy<Auth> = Lazy::new(|| Auth::new());
fn private_fn() {}
struct PrivateStruct {}
"#;
let symbols = extract_exports(src);
assert_eq!(
symbols,
vec![
"create_auth",
"AuthService",
"AuthStatus",
"Authenticator",
"Token",
"DEFAULT_TTL",
"INSTANCE"
]
);
}
#[test]
fn test_pub_crate() {
let src = r#"
pub(crate) fn internal_fn() {}
pub(crate) struct InternalStruct {}
"#;
let symbols = extract_exports(src);
assert_eq!(symbols, vec!["internal_fn", "InternalStruct"]);
}
#[test]
fn test_async_unsafe() {
let src = r#"
pub async fn async_fn() {}
pub unsafe fn unsafe_fn() {}
"#;
let symbols = extract_exports(src);
assert_eq!(symbols, vec!["async_fn", "unsafe_fn"]);
}
}