1use ryo_source::pure::{PureFile, PureItem, PureUseTree};
6use ryo_symbol::{CrateName, ImportMap, SymbolPath};
7
8pub fn build_import_map(
18 file: &PureFile,
19 crate_name: &CrateName,
20 module_path: &SymbolPath,
21) -> ImportMap {
22 let mut import_map = ImportMap::new();
23
24 for item in &file.items {
25 if let PureItem::Use(use_stmt) = item {
26 process_use_tree(&use_stmt.tree, "", crate_name, module_path, &mut import_map);
27 }
28 }
29
30 import_map
31}
32
33fn process_use_tree(
35 tree: &PureUseTree,
36 prefix: &str,
37 crate_name: &CrateName,
38 module_path: &SymbolPath,
39 import_map: &mut ImportMap,
40) {
41 match tree {
42 PureUseTree::Path { path, tree: inner } => {
43 let resolved = resolve_path_segment(path, prefix, crate_name, module_path);
45 process_use_tree(inner, &resolved, crate_name, module_path, import_map);
46 }
47
48 PureUseTree::Name(name) => {
49 let full_path = if prefix.is_empty() {
51 name.clone()
52 } else {
53 format!("{}::{}", prefix, name)
54 };
55
56 if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
57 import_map.add_import(name.clone(), symbol_path);
58 }
59 }
60
61 PureUseTree::Rename { name, rename } => {
62 let full_path = if prefix.is_empty() {
64 name.clone()
65 } else {
66 format!("{}::{}", prefix, name)
67 };
68
69 if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
70 import_map.add_rename(rename.clone(), symbol_path);
71 }
72 }
73
74 PureUseTree::Glob => {
75 if !prefix.is_empty() {
77 if let Ok(symbol_path) = SymbolPath::parse(prefix) {
78 import_map.add_glob(symbol_path);
79 }
80 }
81 }
82
83 PureUseTree::Group(trees) => {
84 for inner_tree in trees {
86 process_use_tree(inner_tree, prefix, crate_name, module_path, import_map);
87 }
88 }
89 }
90}
91
92pub struct ReExportEntry {
94 pub local_name: String,
96 pub full_path: SymbolPath,
98}
99
100pub fn collect_public_reexports(
105 file: &PureFile,
106 crate_name: &CrateName,
107 module_path: &SymbolPath,
108) -> Vec<ReExportEntry> {
109 let mut entries = Vec::new();
110
111 for item in &file.items {
112 if let PureItem::Use(use_stmt) = item {
113 if matches!(
114 use_stmt.vis,
115 ryo_source::pure::PureVis::Public | ryo_source::pure::PureVis::Crate
116 ) {
117 collect_reexport_entries(&use_stmt.tree, "", crate_name, module_path, &mut entries);
118 }
119 }
120 }
121
122 entries
123}
124
125fn collect_reexport_entries(
127 tree: &PureUseTree,
128 prefix: &str,
129 crate_name: &CrateName,
130 module_path: &SymbolPath,
131 entries: &mut Vec<ReExportEntry>,
132) {
133 match tree {
134 PureUseTree::Path { path, tree: inner } => {
135 let resolved = resolve_path_segment(path, prefix, crate_name, module_path);
136 collect_reexport_entries(inner, &resolved, crate_name, module_path, entries);
137 }
138 PureUseTree::Name(name) => {
139 let full_path = if prefix.is_empty() {
140 name.clone()
141 } else {
142 format!("{}::{}", prefix, name)
143 };
144 if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
145 entries.push(ReExportEntry {
146 local_name: name.clone(),
147 full_path: symbol_path,
148 });
149 }
150 }
151 PureUseTree::Rename { name, rename } => {
152 let full_path = if prefix.is_empty() {
153 name.clone()
154 } else {
155 format!("{}::{}", prefix, name)
156 };
157 if let Ok(symbol_path) = SymbolPath::parse(&full_path) {
158 entries.push(ReExportEntry {
159 local_name: rename.clone(),
160 full_path: symbol_path,
161 });
162 }
163 }
164 PureUseTree::Glob => {
165 }
168 PureUseTree::Group(trees) => {
169 for inner_tree in trees {
170 collect_reexport_entries(inner_tree, prefix, crate_name, module_path, entries);
171 }
172 }
173 }
174}
175
176fn resolve_path_segment(
178 segment: &str,
179 prefix: &str,
180 crate_name: &CrateName,
181 module_path: &SymbolPath,
182) -> String {
183 match segment {
184 "crate" => {
185 crate_name.as_str().to_string()
187 }
188
189 "self" => {
190 if prefix.is_empty() {
192 module_path.to_string()
193 } else {
194 prefix.to_string()
195 }
196 }
197
198 "super" => {
199 if prefix.is_empty() {
201 module_path
202 .parent()
203 .map(|p| p.to_string())
204 .unwrap_or_else(|| crate_name.as_str().to_string())
205 } else {
206 if let Ok(path) = SymbolPath::parse(prefix) {
208 path.parent()
209 .map(|p| p.to_string())
210 .unwrap_or_else(|| prefix.to_string())
211 } else {
212 prefix.to_string()
213 }
214 }
215 }
216
217 _ => {
218 if prefix.is_empty() {
220 segment.to_string()
221 } else {
222 format!("{}::{}", prefix, segment)
223 }
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use ryo_source::pure::{PureUse, PureVis};
232
233 fn make_file_with_uses(uses: Vec<PureUse>) -> PureFile {
234 PureFile {
235 items: uses.into_iter().map(PureItem::Use).collect(),
236 attrs: vec![],
237 }
238 }
239
240 fn make_use(tree: PureUseTree) -> PureUse {
241 PureUse {
242 vis: PureVis::Private,
243 tree,
244 }
245 }
246
247 fn make_crate_name() -> CrateName {
248 CrateName::new_for_test("my_crate")
249 }
250
251 fn make_module_path() -> SymbolPath {
252 SymbolPath::parse("my_crate::handlers").unwrap()
253 }
254
255 #[test]
256 fn test_simple_import() {
257 let tree = PureUseTree::Path {
259 path: "std".to_string(),
260 tree: Box::new(PureUseTree::Path {
261 path: "collections".to_string(),
262 tree: Box::new(PureUseTree::Name("HashMap".to_string())),
263 }),
264 };
265
266 let file = make_file_with_uses(vec![make_use(tree)]);
267 let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
268
269 let expected = SymbolPath::parse("std::collections::HashMap").unwrap();
270 assert_eq!(import_map.resolve("HashMap"), Some(&expected));
271 }
272
273 #[test]
274 fn test_rename_import() {
275 let tree = PureUseTree::Path {
277 path: "std".to_string(),
278 tree: Box::new(PureUseTree::Path {
279 path: "collections".to_string(),
280 tree: Box::new(PureUseTree::Rename {
281 name: "HashMap".to_string(),
282 rename: "Map".to_string(),
283 }),
284 }),
285 };
286
287 let file = make_file_with_uses(vec![make_use(tree)]);
288 let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
289
290 let expected = SymbolPath::parse("std::collections::HashMap").unwrap();
291 assert_eq!(import_map.resolve("Map"), Some(&expected));
292 assert_eq!(import_map.resolve("HashMap"), None);
293 }
294
295 #[test]
296 fn test_glob_import() {
297 let tree = PureUseTree::Path {
299 path: "std".to_string(),
300 tree: Box::new(PureUseTree::Path {
301 path: "collections".to_string(),
302 tree: Box::new(PureUseTree::Glob),
303 }),
304 };
305
306 let file = make_file_with_uses(vec![make_use(tree)]);
307 let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
308
309 let expected = SymbolPath::parse("std::collections").unwrap();
310 assert!(import_map.glob_imports().contains(&expected));
311 }
312
313 #[test]
314 fn test_group_import() {
315 let tree = PureUseTree::Path {
317 path: "std".to_string(),
318 tree: Box::new(PureUseTree::Path {
319 path: "collections".to_string(),
320 tree: Box::new(PureUseTree::Group(vec![
321 PureUseTree::Name("HashMap".to_string()),
322 PureUseTree::Name("HashSet".to_string()),
323 ])),
324 }),
325 };
326
327 let file = make_file_with_uses(vec![make_use(tree)]);
328 let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
329
330 let hashmap = SymbolPath::parse("std::collections::HashMap").unwrap();
331 let hashset = SymbolPath::parse("std::collections::HashSet").unwrap();
332 assert_eq!(import_map.resolve("HashMap"), Some(&hashmap));
333 assert_eq!(import_map.resolve("HashSet"), Some(&hashset));
334 }
335
336 #[test]
337 fn test_crate_import() {
338 let tree = PureUseTree::Path {
340 path: "crate".to_string(),
341 tree: Box::new(PureUseTree::Path {
342 path: "models".to_string(),
343 tree: Box::new(PureUseTree::Name("User".to_string())),
344 }),
345 };
346
347 let file = make_file_with_uses(vec![make_use(tree)]);
348 let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
349
350 let expected = SymbolPath::parse("my_crate::models::User").unwrap();
351 assert_eq!(import_map.resolve("User"), Some(&expected));
352 }
353
354 #[test]
355 fn test_super_import() {
356 let tree = PureUseTree::Path {
359 path: "super".to_string(),
360 tree: Box::new(PureUseTree::Name("Config".to_string())),
361 };
362
363 let file = make_file_with_uses(vec![make_use(tree)]);
364 let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
365
366 let expected = SymbolPath::parse("my_crate::Config").unwrap();
367 assert_eq!(import_map.resolve("Config"), Some(&expected));
368 }
369
370 #[test]
371 fn test_self_import() {
372 let tree = PureUseTree::Path {
375 path: "self".to_string(),
376 tree: Box::new(PureUseTree::Path {
377 path: "utils".to_string(),
378 tree: Box::new(PureUseTree::Name("Helper".to_string())),
379 }),
380 };
381
382 let file = make_file_with_uses(vec![make_use(tree)]);
383 let import_map = build_import_map(&file, &make_crate_name(), &make_module_path());
384
385 let expected = SymbolPath::parse("my_crate::handlers::utils::Helper").unwrap();
386 assert_eq!(import_map.resolve("Helper"), Some(&expected));
387 }
388
389 fn make_pub_use(tree: PureUseTree) -> PureUse {
392 PureUse {
393 vis: PureVis::Public,
394 tree,
395 }
396 }
397
398 #[test]
399 fn test_pub_reexport_collected() {
400 let tree = PureUseTree::Path {
402 path: "crate".to_string(),
403 tree: Box::new(PureUseTree::Path {
404 path: "sync".to_string(),
405 tree: Box::new(PureUseTree::Path {
406 path: "mutex".to_string(),
407 tree: Box::new(PureUseTree::Name("Mutex".to_string())),
408 }),
409 }),
410 };
411
412 let crate_name = CrateName::new_for_test("tokio");
413 let module_path = SymbolPath::parse("tokio::sync").unwrap();
414 let file = make_file_with_uses(vec![make_pub_use(tree)]);
415 let reexports = collect_public_reexports(&file, &crate_name, &module_path);
416
417 assert_eq!(reexports.len(), 1);
418 assert_eq!(reexports[0].local_name, "Mutex");
419 assert_eq!(
420 reexports[0].full_path,
421 SymbolPath::parse("tokio::sync::mutex::Mutex").unwrap()
422 );
423 }
424
425 #[test]
426 fn test_private_use_not_collected() {
427 let tree = PureUseTree::Path {
429 path: "crate".to_string(),
430 tree: Box::new(PureUseTree::Path {
431 path: "sync".to_string(),
432 tree: Box::new(PureUseTree::Name("Mutex".to_string())),
433 }),
434 };
435
436 let crate_name = CrateName::new_for_test("tokio");
437 let module_path = SymbolPath::parse("tokio::sync").unwrap();
438 let file = make_file_with_uses(vec![make_use(tree)]); let reexports = collect_public_reexports(&file, &crate_name, &module_path);
440
441 assert!(reexports.is_empty());
442 }
443
444 #[test]
445 fn test_pub_reexport_rename() {
446 let tree = PureUseTree::Path {
448 path: "parking_lot".to_string(),
449 tree: Box::new(PureUseTree::Rename {
450 name: "Mutex".to_string(),
451 rename: "ParkingMutex".to_string(),
452 }),
453 };
454
455 let crate_name = CrateName::new_for_test("my_crate");
456 let module_path = SymbolPath::parse("my_crate").unwrap();
457 let file = make_file_with_uses(vec![make_pub_use(tree)]);
458 let reexports = collect_public_reexports(&file, &crate_name, &module_path);
459
460 assert_eq!(reexports.len(), 1);
461 assert_eq!(reexports[0].local_name, "ParkingMutex");
462 assert_eq!(
463 reexports[0].full_path,
464 SymbolPath::parse("parking_lot::Mutex").unwrap()
465 );
466 }
467
468 #[test]
469 fn test_pub_reexport_group() {
470 let tree = PureUseTree::Path {
472 path: "crate".to_string(),
473 tree: Box::new(PureUseTree::Path {
474 path: "types".to_string(),
475 tree: Box::new(PureUseTree::Group(vec![
476 PureUseTree::Name("Config".to_string()),
477 PureUseTree::Name("State".to_string()),
478 ])),
479 }),
480 };
481
482 let crate_name = CrateName::new_for_test("my_crate");
483 let module_path = SymbolPath::parse("my_crate").unwrap();
484 let file = make_file_with_uses(vec![make_pub_use(tree)]);
485 let reexports = collect_public_reexports(&file, &crate_name, &module_path);
486
487 assert_eq!(reexports.len(), 2);
488 let names: Vec<&str> = reexports.iter().map(|e| e.local_name.as_str()).collect();
489 assert!(names.contains(&"Config"));
490 assert!(names.contains(&"State"));
491 }
492}