syncdoc_migrate/
rewrite.rs

1// syncdoc-migrate/src/rewrite.rs
2
3pub(crate) mod inject;
4pub mod reformat;
5pub(crate) mod strip;
6
7pub use inject::{inject_module_doc_attr, inject_omnidoc_attr};
8pub use strip::{strip_doc_attrs, strip_inner_doc_attrs};
9pub(crate) use unsynn::*;
10
11use crate::config::DocsPathMode;
12use crate::discover::ParsedFile;
13use crate::rewrite::inject::has_module_doc_macro;
14pub(crate) use proc_macro2::TokenStream;
15use reformat::rewrite_preserving_format;
16use strip::strip_doc_attrs_from_items;
17use syncdoc_core::parse::ModuleItem;
18
19pub fn rewrite_file(
20    parsed: &ParsedFile,
21    docs_root: &str,
22    docs_mode: DocsPathMode,
23    strip: bool,
24    annotate: bool,
25) -> Option<String> {
26    if !strip && !annotate {
27        return None;
28    }
29
30    let mut output = if strip {
31        strip_doc_attrs_from_items(&parsed.content)
32    } else {
33        let mut ts = TokenStream::new();
34        quote::ToTokens::to_tokens(&parsed.content, &mut ts);
35        ts
36    };
37
38    if annotate {
39        // Re-parse to inject annotations
40        if let Ok(content) = output
41            .clone()
42            .into_token_iter()
43            .parse::<syncdoc_core::parse::ModuleContent>()
44        {
45            let mut annotated = TokenStream::new();
46
47            // Check if module_doc already exists
48            let has_module_doc = if let Some(inner_attrs) = &content.inner_attrs {
49                let mut temp_ts = TokenStream::new();
50                unsynn::ToTokens::to_tokens(inner_attrs, &mut temp_ts);
51                has_module_doc_macro(&temp_ts)
52            } else {
53                false
54            };
55
56            // Inject module_doc first, if needed (for inner docs if any existed AND not already present)
57            if !has_module_doc
58                && (content.inner_attrs.is_some() || parsed.content.inner_attrs.is_some())
59            {
60                annotated.extend(inject_module_doc_attr(docs_root, docs_mode));
61            }
62
63            // Then add any remaining non-doc inner attributes
64            let stripped_inner = strip_inner_doc_attrs(&content.inner_attrs);
65            for attr in stripped_inner {
66                quote::ToTokens::to_tokens(&attr, &mut annotated);
67            }
68
69            // Then handle regular items
70            for item_delimited in &content.items.0 {
71                let mut item_ts = TokenStream::new();
72                quote::ToTokens::to_tokens(&item_delimited.value, &mut item_ts);
73
74                let should_annotate = matches!(
75                    &item_delimited.value,
76                    ModuleItem::Function(_)
77                        | ModuleItem::Enum(_)
78                        | ModuleItem::Struct(_)
79                        | ModuleItem::Module(_)
80                        | ModuleItem::Trait(_)
81                        | ModuleItem::ImplBlock(_)
82                        | ModuleItem::TypeAlias(_)
83                        | ModuleItem::Const(_)
84                        | ModuleItem::Static(_)
85                );
86
87                if should_annotate {
88                    // inject_omnidoc_attr now handles idempotency internally
89                    annotated.extend(inject_omnidoc_attr(item_ts, docs_root, docs_mode));
90                } else {
91                    annotated.extend(item_ts);
92                }
93            }
94            output = annotated;
95        }
96    }
97
98    let transformed = output.to_string();
99
100    // Apply format-preserving rewrite
101    rewrite_preserving_format(&parsed.original_source, &transformed).ok()
102}