use ryo_source::pure::{PureFile, PureItem, PureUseTree};
use ryo_symbol::{CrateName, ImportMap, SymbolPath};
pub fn build_import_map(
file: &PureFile,
crate_name: &CrateName,
module_path: &SymbolPath,
) -> ImportMap {
let mut import_map = ImportMap::new();
for item in &file.items {
if let PureItem::Use(use_stmt) = item {
process_use_tree(&use_stmt.tree, "", crate_name, module_path, &mut import_map);
}
}
import_map
}
fn process_use_tree(
tree: &PureUseTree,
prefix: &str,
crate_name: &CrateName,
module_path: &SymbolPath,
import_map: &mut ImportMap,
) {
match tree {
PureUseTree::Path { path, tree: inner } => {
let resolved = resolve_path_segment(path, prefix, crate_name, module_path);
process_use_tree(inner, &resolved, crate_name, module_path, import_map);
}
PureUseTree::Name(name) => {
let full_path = if prefix.is_empty() {
name.clone()
} else {
format!("{}::{}", prefix, name)
};
if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
import_map.add_import(name.clone(), symbol_path);
}
}
PureUseTree::Rename { name, rename } => {
let full_path = if prefix.is_empty() {
name.clone()
} else {
format!("{}::{}", prefix, name)
};
if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
import_map.add_rename(rename.clone(), symbol_path);
}
}
PureUseTree::Glob => {
if !prefix.is_empty() {
if let Ok(symbol_path) = SymbolPath::parse(prefix) {
import_map.add_glob(symbol_path);
}
}
}
PureUseTree::Group(trees) => {
for inner_tree in trees {
process_use_tree(inner_tree, prefix, crate_name, module_path, import_map);
}
}
}
}
pub struct ReExportEntry {
pub local_name: String,
pub full_path: SymbolPath,
}
pub fn collect_public_reexports(
file: &PureFile,
crate_name: &CrateName,
module_path: &SymbolPath,
) -> Vec<ReExportEntry> {
let mut entries = Vec::new();
for item in &file.items {
if let PureItem::Use(use_stmt) = item {
if matches!(
use_stmt.vis,
ryo_source::pure::PureVis::Public | ryo_source::pure::PureVis::Crate
) {
collect_reexport_entries(&use_stmt.tree, "", crate_name, module_path, &mut entries);
}
}
}
entries
}
fn collect_reexport_entries(
tree: &PureUseTree,
prefix: &str,
crate_name: &CrateName,
module_path: &SymbolPath,
entries: &mut Vec<ReExportEntry>,
) {
match tree {
PureUseTree::Path { path, tree: inner } => {
let resolved = resolve_path_segment(path, prefix, crate_name, module_path);
collect_reexport_entries(inner, &resolved, crate_name, module_path, entries);
}
PureUseTree::Name(name) => {
let full_path = if prefix.is_empty() {
name.clone()
} else {
format!("{}::{}", prefix, name)
};
if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
entries.push(ReExportEntry {
local_name: name.clone(),
full_path: symbol_path,
});
}
}
PureUseTree::Rename { name, rename } => {
let full_path = if prefix.is_empty() {
name.clone()
} else {
format!("{}::{}", prefix, name)
};
if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
entries.push(ReExportEntry {
local_name: rename.clone(),
full_path: symbol_path,
});
}
}
PureUseTree::Glob => {
}
PureUseTree::Group(trees) => {
for inner_tree in trees {
collect_reexport_entries(inner_tree, prefix, crate_name, module_path, entries);
}
}
}
}
fn resolve_path_segment(
segment: &str,
prefix: &str,
crate_name: &CrateName,
module_path: &SymbolPath,
) -> String {
match segment {
"crate" => {
crate_name.as_str().to_string()
}
"self" => {
if prefix.is_empty() {
module_path.to_string()
} else {
prefix.to_string()
}
}
"super" => {
if prefix.is_empty() {
module_path
.parent()
.map(|p| p.to_string())
.unwrap_or_else(|| crate_name.as_str().to_string())
} else {
if let Ok(path) = SymbolPath::parse(prefix) {
path.parent()
.map(|p| p.to_string())
.unwrap_or_else(|| prefix.to_string())
} else {
prefix.to_string()
}
}
}
_ => {
if prefix.is_empty() {
segment.to_string()
} else {
format!("{}::{}", prefix, segment)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ryo_source::pure::{PureUse, PureVis};
fn make_file_with_uses(uses: Vec<PureUse>) -> PureFile {
PureFile {
items: uses.into_iter().map(PureItem::Use).collect(),
attrs: vec![],
}
}
fn make_use(tree: PureUseTree) -> PureUse {
PureUse {
vis: PureVis::Private,
tree,
}
}
fn make_crate_name() -> CrateName {
CrateName::new_for_test("my_crate")
}
fn make_module_path() -> SymbolPath {
SymbolPath::parse("my_crate::handlers").unwrap()
}
#[test]
fn test_simple_import() {
let tree = PureUseTree::Path {
path: "std".to_string(),
tree: Box::new(PureUseTree::Path {
path: "collections".to_string(),
tree: Box::new(PureUseTree::Name("HashMap".to_string())),
}),
};
let file = make_file_with_uses(vec![make_use(tree)]);
let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
let expected = SymbolPath::parse("std::collections::HashMap").unwrap();
assert_eq!(import_map.resolve("HashMap"), Some(&expected));
}
#[test]
fn test_rename_import() {
let tree = PureUseTree::Path {
path: "std".to_string(),
tree: Box::new(PureUseTree::Path {
path: "collections".to_string(),
tree: Box::new(PureUseTree::Rename {
name: "HashMap".to_string(),
rename: "Map".to_string(),
}),
}),
};
let file = make_file_with_uses(vec![make_use(tree)]);
let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
let expected = SymbolPath::parse("std::collections::HashMap").unwrap();
assert_eq!(import_map.resolve("Map"), Some(&expected));
assert_eq!(import_map.resolve("HashMap"), None);
}
#[test]
fn test_glob_import() {
let tree = PureUseTree::Path {
path: "std".to_string(),
tree: Box::new(PureUseTree::Path {
path: "collections".to_string(),
tree: Box::new(PureUseTree::Glob),
}),
};
let file = make_file_with_uses(vec![make_use(tree)]);
let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
let expected = SymbolPath::parse("std::collections").unwrap();
assert!(import_map.glob_imports().contains(&expected));
}
#[test]
fn test_group_import() {
let tree = PureUseTree::Path {
path: "std".to_string(),
tree: Box::new(PureUseTree::Path {
path: "collections".to_string(),
tree: Box::new(PureUseTree::Group(vec![
PureUseTree::Name("HashMap".to_string()),
PureUseTree::Name("HashSet".to_string()),
])),
}),
};
let file = make_file_with_uses(vec![make_use(tree)]);
let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
let hashmap = SymbolPath::parse("std::collections::HashMap").unwrap();
let hashset = SymbolPath::parse("std::collections::HashSet").unwrap();
assert_eq!(import_map.resolve("HashMap"), Some(&hashmap));
assert_eq!(import_map.resolve("HashSet"), Some(&hashset));
}
#[test]
fn test_crate_import() {
let tree = PureUseTree::Path {
path: "crate".to_string(),
tree: Box::new(PureUseTree::Path {
path: "models".to_string(),
tree: Box::new(PureUseTree::Name("User".to_string())),
}),
};
let file = make_file_with_uses(vec![make_use(tree)]);
let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
let expected = SymbolPath::parse("my_crate::models::User").unwrap();
assert_eq!(import_map.resolve("User"), Some(&expected));
}
#[test]
fn test_super_import() {
let tree = PureUseTree::Path {
path: "super".to_string(),
tree: Box::new(PureUseTree::Name("Config".to_string())),
};
let file = make_file_with_uses(vec![make_use(tree)]);
let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
let expected = SymbolPath::parse("my_crate::Config").unwrap();
assert_eq!(import_map.resolve("Config"), Some(&expected));
}
#[test]
fn test_self_import() {
let tree = PureUseTree::Path {
path: "self".to_string(),
tree: Box::new(PureUseTree::Path {
path: "utils".to_string(),
tree: Box::new(PureUseTree::Name("Helper".to_string())),
}),
};
let file = make_file_with_uses(vec![make_use(tree)]);
let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
let expected = SymbolPath::parse("my_crate::handlers::utils::Helper").unwrap();
assert_eq!(import_map.resolve("Helper"), Some(&expected));
}
fn make_pub_use(tree: PureUseTree) -> PureUse {
PureUse {
vis: PureVis::Public,
tree,
}
}
#[test]
fn test_pub_reexport_collected() {
let tree = PureUseTree::Path {
path: "crate".to_string(),
tree: Box::new(PureUseTree::Path {
path: "sync".to_string(),
tree: Box::new(PureUseTree::Path {
path: "mutex".to_string(),
tree: Box::new(PureUseTree::Name("Mutex".to_string())),
}),
}),
};
let crate_name = CrateName::new_for_test("tokio");
let module_path = SymbolPath::parse("tokio::sync").unwrap();
let file = make_file_with_uses(vec![make_pub_use(tree)]);
let reexports = collect_public_reexports(&file, &crate_name, &module_path);
assert_eq!(reexports.len(), 1);
assert_eq!(reexports[0].local_name, "Mutex");
assert_eq!(
reexports[0].full_path,
SymbolPath::parse("tokio::sync::mutex::Mutex").unwrap()
);
}
#[test]
fn test_private_use_not_collected() {
let tree = PureUseTree::Path {
path: "crate".to_string(),
tree: Box::new(PureUseTree::Path {
path: "sync".to_string(),
tree: Box::new(PureUseTree::Name("Mutex".to_string())),
}),
};
let crate_name = CrateName::new_for_test("tokio");
let module_path = SymbolPath::parse("tokio::sync").unwrap();
let file = make_file_with_uses(vec![make_use(tree)]); let reexports = collect_public_reexports(&file, &crate_name, &module_path);
assert!(reexports.is_empty());
}
#[test]
fn test_pub_reexport_rename() {
let tree = PureUseTree::Path {
path: "parking_lot".to_string(),
tree: Box::new(PureUseTree::Rename {
name: "Mutex".to_string(),
rename: "ParkingMutex".to_string(),
}),
};
let crate_name = CrateName::new_for_test("my_crate");
let module_path = SymbolPath::parse("my_crate").unwrap();
let file = make_file_with_uses(vec![make_pub_use(tree)]);
let reexports = collect_public_reexports(&file, &crate_name, &module_path);
assert_eq!(reexports.len(), 1);
assert_eq!(reexports[0].local_name, "ParkingMutex");
assert_eq!(
reexports[0].full_path,
SymbolPath::parse("parking_lot::Mutex").unwrap()
);
}
#[test]
fn test_pub_reexport_group() {
let tree = PureUseTree::Path {
path: "crate".to_string(),
tree: Box::new(PureUseTree::Path {
path: "types".to_string(),
tree: Box::new(PureUseTree::Group(vec![
PureUseTree::Name("Config".to_string()),
PureUseTree::Name("State".to_string()),
])),
}),
};
let crate_name = CrateName::new_for_test("my_crate");
let module_path = SymbolPath::parse("my_crate").unwrap();
let file = make_file_with_uses(vec![make_pub_use(tree)]);
let reexports = collect_public_reexports(&file, &crate_name, &module_path);
assert_eq!(reexports.len(), 2);
let names: Vec<&str> = reexports.iter().map(|e| e.local_name.as_str()).collect();
assert!(names.contains(&"Config"));
assert!(names.contains(&"State"));
}
}