1use std::collections::HashSet;
4use std::path::{Path, PathBuf};
5
6use syn::ItemMod;
7
8use crate::graph::ModulePath;
9use crate::parser::get_path_attribute;
10use crate::ResolveError;
11
12#[must_use]
14pub fn find_crate_root(crate_dir: &Path) -> Option<PathBuf> {
15 let src_dir = crate_dir.join("src");
16
17 let lib_rs = src_dir.join("lib.rs");
18 if lib_rs.exists() {
19 return Some(lib_rs);
20 }
21
22 let main_rs = src_dir.join("main.rs");
23 if main_rs.exists() {
24 return Some(main_rs);
25 }
26
27 None
28}
29
30pub fn resolve_module_file(
35 parent_dir: &Path,
36 mod_name: &str,
37 item_mod: &ItemMod,
38) -> Result<PathBuf, ResolveError> {
39 if let Some(custom_path) = get_path_attribute(item_mod) {
41 return Ok(parent_dir.join(custom_path));
42 }
43
44 let direct = parent_dir.join(format!("{mod_name}.rs"));
46 if direct.exists() {
47 return Ok(direct);
48 }
49
50 let nested = parent_dir.join(mod_name).join("mod.rs");
51 if nested.exists() {
52 return Ok(nested);
53 }
54
55 Err(ResolveError::ModuleNotFound {
56 module_name: mod_name.to_string(),
57 expected_paths: vec![direct, nested],
58 })
59}
60
61#[must_use]
63pub fn is_inline_module(item_mod: &ItemMod) -> bool {
64 item_mod.content.is_some()
65}
66
67#[must_use]
69pub fn is_internal_path(segments: &[String]) -> bool {
70 if segments.is_empty() {
71 return false;
72 }
73 matches!(segments[0].as_str(), "crate" | "self" | "super")
74}
75
76#[must_use]
79#[allow(clippy::implicit_hasher)]
80pub fn resolve_use_target(
81 segments: &[String],
82 current_module: &ModulePath,
83 known_modules: &HashSet<ModulePath>,
84) -> Option<ModulePath> {
85 if segments.is_empty() {
86 return None;
87 }
88
89 match segments[0].as_str() {
90 "crate" => {
91 resolve_from_crate_root(&segments[1..], known_modules)
93 }
94 "self" => {
95 resolve_relative(current_module, &segments[1..], known_modules)
97 }
98 "super" => {
99 let parent = get_parent_module(current_module)?;
101 let mut current = parent;
103 let mut remaining = &segments[1..];
104 while !remaining.is_empty() && remaining[0] == "super" {
105 current = get_parent_module(¤t)?;
106 remaining = &remaining[1..];
107 }
108 resolve_relative(¤t, remaining, known_modules)
109 }
110 _ => {
111 None
113 }
114 }
115}
116
117fn resolve_from_crate_root(
119 segments: &[String],
120 known_modules: &HashSet<ModulePath>,
121) -> Option<ModulePath> {
122 resolve_relative(&ModulePath::crate_root(), segments, known_modules)
123}
124
125fn resolve_relative(
127 base: &ModulePath,
128 segments: &[String],
129 known_modules: &HashSet<ModulePath>,
130) -> Option<ModulePath> {
131 if segments.is_empty() {
132 if known_modules.contains(base) {
134 return Some(base.clone());
135 }
136 return None;
137 }
138
139 let mut current = base.clone();
141 let mut last_known = if known_modules.contains(¤t) {
142 Some(current.clone())
143 } else {
144 None
145 };
146
147 for segment in segments {
148 current = current.child(segment);
149 if known_modules.contains(¤t) {
150 last_known = Some(current.clone());
151 }
152 }
153
154 last_known
155}
156
157fn get_parent_module(module: &ModulePath) -> Option<ModulePath> {
159 let s = module.as_str();
160 if s == "crate" {
161 return None;
162 }
163 if let Some(pos) = s.rfind("::") {
164 Some(ModulePath::crate_root().child(&s[..pos]))
165 } else {
166 Some(ModulePath::crate_root())
168 }
169}