1use crate::write::DocExtraction;
9use 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
61fn 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::Function(func_sig) => {
72 let path = build_path(base_path, &context, &func_sig.name.to_string());
73 let location = format!(
74 "{}:{}",
75 source_file.display(),
76 func_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::ImplBlock(impl_block) => {
86 extractions.extend(find_impl_paths(impl_block, context, base_path, source_file));
87 }
88
89 ModuleItem::Module(module) => {
90 extractions.extend(find_module_paths(module, context, base_path, source_file));
91 }
92
93 ModuleItem::Trait(trait_def) => {
94 extractions.extend(find_trait_paths(trait_def, context, base_path, source_file));
95 }
96
97 ModuleItem::Enum(enum_sig) => {
98 extractions.extend(find_enum_paths(enum_sig, context, base_path, source_file));
99 }
100
101 ModuleItem::Struct(struct_sig) => {
102 extractions.extend(find_struct_paths(
103 struct_sig,
104 context,
105 base_path,
106 source_file,
107 ));
108 }
109
110 ModuleItem::TypeAlias(type_alias) => {
111 let path = build_path(base_path, &context, &type_alias.name.to_string());
112 let location = format!(
113 "{}:{}",
114 source_file.display(),
115 type_alias.name.span().start().line
116 );
117 extractions.push(DocExtraction::new(
118 PathBuf::from(path),
119 String::new(),
120 location,
121 ));
122 }
123
124 ModuleItem::Const(const_sig) => {
125 let path = build_path(base_path, &context, &const_sig.name.to_string());
126 let location = format!(
127 "{}:{}",
128 source_file.display(),
129 const_sig.name.span().start().line
130 );
131 extractions.push(DocExtraction::new(
132 PathBuf::from(path),
133 String::new(),
134 location,
135 ));
136 }
137
138 ModuleItem::Static(static_sig) => {
139 let path = build_path(base_path, &context, &static_sig.name.to_string());
140 let location = format!(
141 "{}:{}",
142 source_file.display(),
143 static_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::Other(_) => {}
153 }
154
155 extractions
156}
157
158fn find_impl_paths(
159 impl_block: &ImplBlockSig,
160 context: Vec<String>,
161 base_path: &str,
162 source_file: &Path,
163) -> Vec<DocExtraction> {
164 let mut extractions = Vec::new();
165
166 let impl_context = if let Some(for_trait) = &impl_block.for_trait {
170 let trait_name = if let Some(first) = impl_block.target_type.0.first() {
173 if let proc_macro2::TokenTree::Ident(ident) = &first.value.second {
174 ident.to_string()
175 } else {
176 "Unknown".to_string()
177 }
178 } else {
179 "Unknown".to_string()
180 };
181
182 let type_name = if let Some(first) = for_trait.second.0.first() {
184 if let proc_macro2::TokenTree::Ident(ident) = &first.value.second {
185 ident.to_string()
186 } else {
187 "Unknown".to_string()
188 }
189 } else {
190 "Unknown".to_string()
191 };
192
193 vec![type_name, trait_name]
195 } else {
196 let type_name = if let Some(first) = impl_block.target_type.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 vec![type_name]
207 };
208
209 let mut new_context = context;
210 new_context.extend(impl_context);
211
212 let module_content = &impl_block.items.content;
213 for item_delimited in &module_content.items.0 {
214 extractions.extend(find_item_paths(
215 &item_delimited.value,
216 new_context.clone(),
217 base_path,
218 source_file,
219 ));
220 }
221
222 extractions
223}
224
225fn find_module_paths(
226 module: &ModuleSig,
227 context: Vec<String>,
228 base_path: &str,
229 source_file: &Path,
230) -> Vec<DocExtraction> {
231 let mut extractions = Vec::new();
232
233 let path = build_path(base_path, &context, &module.name.to_string());
234 let location = format!(
235 "{}:{}",
236 source_file.display(),
237 module.name.span().start().line
238 );
239 extractions.push(DocExtraction::new(
240 PathBuf::from(path),
241 String::new(),
242 location,
243 ));
244
245 let mut new_context = context;
246 new_context.push(module.name.to_string());
247
248 let module_content = &module.items.content;
249 for item_delimited in &module_content.items.0 {
250 extractions.extend(find_item_paths(
251 &item_delimited.value,
252 new_context.clone(),
253 base_path,
254 source_file,
255 ));
256 }
257
258 extractions
259}
260
261fn find_trait_paths(
262 trait_def: &TraitSig,
263 context: Vec<String>,
264 base_path: &str,
265 source_file: &Path,
266) -> Vec<DocExtraction> {
267 let mut extractions = Vec::new();
268
269 let path = build_path(base_path, &context, &trait_def.name.to_string());
270 let location = format!(
271 "{}:{}",
272 source_file.display(),
273 trait_def.name.span().start().line
274 );
275 extractions.push(DocExtraction::new(
276 PathBuf::from(path),
277 String::new(),
278 location,
279 ));
280
281 let mut new_context = context;
282 new_context.push(trait_def.name.to_string());
283
284 let module_content = &trait_def.items.content;
285 for item_delimited in &module_content.items.0 {
286 extractions.extend(find_item_paths(
287 &item_delimited.value,
288 new_context.clone(),
289 base_path,
290 source_file,
291 ));
292 }
293
294 extractions
295}
296
297fn find_enum_paths(
298 enum_sig: &EnumSig,
299 context: Vec<String>,
300 base_path: &str,
301 source_file: &Path,
302) -> Vec<DocExtraction> {
303 let mut extractions = Vec::new();
304 let enum_name = enum_sig.name.to_string();
305
306 let path = build_path(base_path, &context, &enum_name);
307 let location = format!(
308 "{}:{}",
309 source_file.display(),
310 enum_sig.name.span().start().line
311 );
312 extractions.push(DocExtraction::new(
313 PathBuf::from(path),
314 String::new(),
315 location,
316 ));
317
318 if let Some(variants_cdv) = enum_sig.variants.content.as_ref() {
320 for variant_delimited in &variants_cdv.0 {
321 let variant = &variant_delimited.value;
322 let path = build_path(
323 base_path,
324 &context,
325 &format!("{}/{}", enum_name, variant.name),
326 );
327 extractions.push(DocExtraction::new(
328 PathBuf::from(path),
329 String::new(),
330 format!(
331 "{}:{}",
332 source_file.display(),
333 variant.name.span().start().line
334 ),
335 ));
336
337 if let Some(EnumVariantData::Struct(fields_containing)) = &variant.data {
339 if let Some(fields_cdv) = fields_containing.content.as_ref() {
340 for field_delimited in &fields_cdv.0 {
341 let field = &field_delimited.value;
342 let path = build_path(
343 base_path,
344 &context,
345 &format!("{}/{}/{}", enum_name, variant.name, field.name),
346 );
347 extractions.push(DocExtraction::new(
348 PathBuf::from(path),
349 String::new(),
350 format!(
351 "{}:{}",
352 source_file.display(),
353 field.name.span().start().line
354 ),
355 ));
356 }
357 }
358 }
359 }
360 }
361
362 extractions
363}
364
365fn find_struct_paths(
366 struct_sig: &StructSig,
367 context: Vec<String>,
368 base_path: &str,
369 source_file: &Path,
370) -> Vec<DocExtraction> {
371 let mut extractions = Vec::new();
372 let struct_name = struct_sig.name.to_string();
373
374 let path = build_path(base_path, &context, &struct_name);
375 let location = format!(
376 "{}:{}",
377 source_file.display(),
378 struct_sig.name.span().start().line
379 );
380 extractions.push(DocExtraction::new(
381 PathBuf::from(path),
382 String::new(),
383 location,
384 ));
385
386 if let syncdoc_core::parse::StructBody::Named(fields_containing) = &struct_sig.body {
387 if let Some(fields_cdv) = fields_containing.content.as_ref() {
388 for field_delimited in &fields_cdv.0 {
389 let field = &field_delimited.value;
390 let path = build_path(
391 base_path,
392 &context,
393 &format!("{}/{}", struct_name, field.name),
394 );
395 extractions.push(DocExtraction::new(
396 PathBuf::from(path),
397 String::new(),
398 format!(
399 "{}:{}",
400 source_file.display(),
401 field.name.span().start().line
402 ),
403 ));
404 }
405 }
406 }
407
408 extractions
409}
410
411fn build_path(base_path: &str, context: &[String], item_name: &str) -> String {
412 let mut parts = vec![base_path.to_string()];
413 parts.extend(context.iter().cloned());
414 parts.push(format!("{}.md", item_name));
415 parts.join("/")
416}
417
418#[cfg(test)]
419mod expected_tests;