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