1use crate::write::DocExtraction;
9pub(crate) use std::path::{Path, PathBuf};
10use syncdoc_core::parse::{
11 EnumSig, EnumVariantData, ImplBlockSig, ModuleItem, ModuleSig, StructSig, TraitSig,
12};
13
14use super::ParsedFile;
15
16pub fn find_expected_doc_paths(parsed: &ParsedFile, docs_root: &str) -> Vec<DocExtraction> {
21 let mut extractions = Vec::new();
22 let module_path = syncdoc_core::path_utils::extract_module_path(&parsed.path.to_string_lossy());
23
24 let file_stem = parsed
26 .path
27 .file_stem()
28 .and_then(|s| s.to_str())
29 .unwrap_or("module");
30
31 let path = if module_path.is_empty() {
32 format!("{}/{}.md", docs_root, file_stem)
33 } else {
34 format!("{}/{}.md", docs_root, module_path)
35 };
36
37 extractions.push(DocExtraction::new(
38 PathBuf::from(path),
39 String::new(),
40 format!("{}:1", parsed.path.display()),
41 ));
42
43 let mut context = Vec::new();
44 if !module_path.is_empty() {
45 context.push(module_path);
46 }
47
48 for item_delimited in &parsed.content.items.0 {
50 extractions.extend(find_item_paths(
51 &item_delimited.value,
52 context.clone(),
53 docs_root,
54 &parsed.path,
55 ));
56 }
57
58 extractions
59}
60
61pub(crate) fn find_item_paths(
63 item: &ModuleItem,
64 context: Vec<String>,
65 base_path: &str,
66 source_file: &Path,
67) -> Vec<DocExtraction> {
68 let mut extractions = Vec::new();
69
70 match item {
71 ModuleItem::TraitMethod(method_sig) => {
72 let path = build_path(base_path, &context, &method_sig.name.to_string());
73 let location = format!(
74 "{}:{}",
75 source_file.display(),
76 method_sig.name.span().start().line
77 );
78 extractions.push(DocExtraction::new(
79 PathBuf::from(path),
80 String::new(),
81 location,
82 ));
83 }
84
85 ModuleItem::Function(func_sig) => {
86 let path = build_path(base_path, &context, &func_sig.name.to_string());
87 let location = format!(
88 "{}:{}",
89 source_file.display(),
90 func_sig.name.span().start().line
91 );
92 extractions.push(DocExtraction::new(
93 PathBuf::from(path),
94 String::new(),
95 location,
96 ));
97 }
98
99 ModuleItem::ImplBlock(impl_block) => {
100 extractions.extend(find_impl_paths(impl_block, context, base_path, source_file));
101 }
102
103 ModuleItem::Module(module) => {
104 extractions.extend(find_module_paths(module, context, base_path, source_file));
105 }
106
107 ModuleItem::Trait(trait_def) => {
108 extractions.extend(find_trait_paths(trait_def, context, base_path, source_file));
109 }
110
111 ModuleItem::Enum(enum_sig) => {
112 extractions.extend(find_enum_paths(enum_sig, context, base_path, source_file));
113 }
114
115 ModuleItem::Struct(struct_sig) => {
116 extractions.extend(find_struct_paths(
117 struct_sig,
118 context,
119 base_path,
120 source_file,
121 ));
122 }
123
124 ModuleItem::TypeAlias(type_alias) => {
125 let path = build_path(base_path, &context, &type_alias.name.to_string());
126 let location = format!(
127 "{}:{}",
128 source_file.display(),
129 type_alias.name.span().start().line
130 );
131 extractions.push(DocExtraction::new(
132 PathBuf::from(path),
133 String::new(),
134 location,
135 ));
136 }
137
138 ModuleItem::Const(const_sig) => {
139 let path = build_path(base_path, &context, &const_sig.name.to_string());
140 let location = format!(
141 "{}:{}",
142 source_file.display(),
143 const_sig.name.span().start().line
144 );
145 extractions.push(DocExtraction::new(
146 PathBuf::from(path),
147 String::new(),
148 location,
149 ));
150 }
151
152 ModuleItem::Static(static_sig) => {
153 let path = build_path(base_path, &context, &static_sig.name.to_string());
154 let location = format!(
155 "{}:{}",
156 source_file.display(),
157 static_sig.name.span().start().line
158 );
159 extractions.push(DocExtraction::new(
160 PathBuf::from(path),
161 String::new(),
162 location,
163 ));
164 }
165
166 ModuleItem::Other(_) => {}
167 }
168
169 extractions
170}
171
172pub(crate) fn find_impl_paths(
173 impl_block: &ImplBlockSig,
174 context: Vec<String>,
175 base_path: &str,
176 source_file: &Path,
177) -> Vec<DocExtraction> {
178 let mut extractions = Vec::new();
179
180 let impl_context = if let Some(for_trait) = &impl_block.for_trait {
184 let trait_name = if let Some(first) = impl_block.target_type.0.first() {
187 if let proc_macro2::TokenTree::Ident(ident) = &first.value.second {
188 ident.to_string()
189 } else {
190 "Unknown".to_string()
191 }
192 } else {
193 "Unknown".to_string()
194 };
195
196 let type_name = if let Some(first) = for_trait.second.0.first() {
198 if let proc_macro2::TokenTree::Ident(ident) = &first.value.second {
199 ident.to_string()
200 } else {
201 "Unknown".to_string()
202 }
203 } else {
204 "Unknown".to_string()
205 };
206
207 vec![type_name, trait_name]
209 } else {
210 let type_name = if let Some(first) = impl_block.target_type.0.first() {
212 if let proc_macro2::TokenTree::Ident(ident) = &first.value.second {
213 ident.to_string()
214 } else {
215 "Unknown".to_string()
216 }
217 } else {
218 "Unknown".to_string()
219 };
220 vec![type_name]
221 };
222
223 let mut new_context = context;
224 new_context.extend(impl_context);
225
226 let module_content = &impl_block.items.content;
227 for item_delimited in &module_content.items.0 {
228 extractions.extend(find_item_paths(
229 &item_delimited.value,
230 new_context.clone(),
231 base_path,
232 source_file,
233 ));
234 }
235
236 extractions
237}
238
239pub(crate) fn find_module_paths(
240 module: &ModuleSig,
241 context: Vec<String>,
242 base_path: &str,
243 source_file: &Path,
244) -> Vec<DocExtraction> {
245 let mut extractions = Vec::new();
246
247 let path = build_path(base_path, &context, &module.name.to_string());
248 let location = format!(
249 "{}:{}",
250 source_file.display(),
251 module.name.span().start().line
252 );
253 extractions.push(DocExtraction::new(
254 PathBuf::from(path),
255 String::new(),
256 location,
257 ));
258
259 let mut new_context = context;
260 new_context.push(module.name.to_string());
261
262 let module_content = &module.items.content;
263 for item_delimited in &module_content.items.0 {
264 extractions.extend(find_item_paths(
265 &item_delimited.value,
266 new_context.clone(),
267 base_path,
268 source_file,
269 ));
270 }
271
272 extractions
273}
274
275pub(crate) fn find_trait_paths(
276 trait_def: &TraitSig,
277 context: Vec<String>,
278 base_path: &str,
279 source_file: &Path,
280) -> Vec<DocExtraction> {
281 let mut extractions = Vec::new();
282
283 let path = build_path(base_path, &context, &trait_def.name.to_string());
284 let location = format!(
285 "{}:{}",
286 source_file.display(),
287 trait_def.name.span().start().line
288 );
289 extractions.push(DocExtraction::new(
290 PathBuf::from(path),
291 String::new(),
292 location,
293 ));
294
295 let mut new_context = context;
296 new_context.push(trait_def.name.to_string());
297
298 let module_content = &trait_def.items.content;
299 for item_delimited in &module_content.items.0 {
300 extractions.extend(find_item_paths(
301 &item_delimited.value,
302 new_context.clone(),
303 base_path,
304 source_file,
305 ));
306 }
307
308 extractions
309}
310
311pub(crate) fn find_enum_paths(
312 enum_sig: &EnumSig,
313 context: Vec<String>,
314 base_path: &str,
315 source_file: &Path,
316) -> Vec<DocExtraction> {
317 let mut extractions = Vec::new();
318 let enum_name = enum_sig.name.to_string();
319
320 let path = build_path(base_path, &context, &enum_name);
321 let location = format!(
322 "{}:{}",
323 source_file.display(),
324 enum_sig.name.span().start().line
325 );
326 extractions.push(DocExtraction::new(
327 PathBuf::from(path),
328 String::new(),
329 location,
330 ));
331
332 if let Some(variants_cdv) = enum_sig.variants.content.as_ref() {
334 for variant_delimited in &variants_cdv.0 {
335 let variant = &variant_delimited.value;
336 let path = build_path(
337 base_path,
338 &context,
339 &format!("{}/{}", enum_name, variant.name),
340 );
341 extractions.push(DocExtraction::new(
342 PathBuf::from(path),
343 String::new(),
344 format!(
345 "{}:{}",
346 source_file.display(),
347 variant.name.span().start().line
348 ),
349 ));
350
351 if let Some(EnumVariantData::Struct(fields_containing)) = &variant.data {
353 if let Some(fields_cdv) = fields_containing.content.as_ref() {
354 for field_delimited in &fields_cdv.0 {
355 let field = &field_delimited.value;
356 let path = build_path(
357 base_path,
358 &context,
359 &format!("{}/{}/{}", enum_name, variant.name, field.name),
360 );
361 extractions.push(DocExtraction::new(
362 PathBuf::from(path),
363 String::new(),
364 format!(
365 "{}:{}",
366 source_file.display(),
367 field.name.span().start().line
368 ),
369 ));
370 }
371 }
372 }
373 }
374 }
375
376 extractions
377}
378
379pub(crate) fn find_struct_paths(
380 struct_sig: &StructSig,
381 context: Vec<String>,
382 base_path: &str,
383 source_file: &Path,
384) -> Vec<DocExtraction> {
385 let mut extractions = Vec::new();
386 let struct_name = struct_sig.name.to_string();
387
388 let path = build_path(base_path, &context, &struct_name);
389 let location = format!(
390 "{}:{}",
391 source_file.display(),
392 struct_sig.name.span().start().line
393 );
394 extractions.push(DocExtraction::new(
395 PathBuf::from(path),
396 String::new(),
397 location,
398 ));
399
400 if let syncdoc_core::parse::StructBody::Named(fields_containing) = &struct_sig.body {
401 if let Some(fields_cdv) = fields_containing.content.as_ref() {
402 for field_delimited in &fields_cdv.0 {
403 let field = &field_delimited.value;
404 let path = build_path(
405 base_path,
406 &context,
407 &format!("{}/{}", struct_name, field.name),
408 );
409 extractions.push(DocExtraction::new(
410 PathBuf::from(path),
411 String::new(),
412 format!(
413 "{}:{}",
414 source_file.display(),
415 field.name.span().start().line
416 ),
417 ));
418 }
419 }
420 }
421
422 extractions
423}
424
425pub(crate) fn build_path(base_path: &str, context: &[String], item_name: &str) -> String {
426 let mut parts = vec![base_path.to_string()];
427 parts.extend(context.iter().cloned());
428 parts.push(format!("{}.md", item_name));
429 parts.join("/")
430}