use {
crate::support::{
ast::RustAst,
attributes::reject_duplicate_attribute,
documentation_parameters::{
DocumentationParameter,
DocumentationParameters,
},
parsing::parse_parameter_documentation_pairs,
},
proc_macro2::TokenStream,
syn::{
Error,
parse_quote,
spanned::Spanned,
},
};
pub fn generate_doc_comments<F>(
attr: TokenStream,
item_tokens: TokenStream,
section_title: &str,
attribute_name: &str,
get_targets: F,
) -> crate::core::Result<TokenStream>
where
F: FnOnce(&RustAst) -> Result<Vec<String>, Error>, {
let mut generic_item = RustAst::parse(item_tokens).map_err(crate::core::Error::Parse)?;
reject_duplicate_attribute(generic_item.attributes(), attribute_name)?;
let args =
syn::parse2::<DocumentationParameters>(attr.clone()).map_err(crate::core::Error::Parse)?;
let targets = get_targets(&generic_item)?;
let entries: Vec<_> = args.entries.into_iter().collect();
let pairs = parse_parameter_documentation_pairs(targets, entries, attr.span())?;
let mut doc_comments = Vec::new();
doc_comments.push((String::new(), format!("### {section_title}\n")));
for (name_from_target, entry) in pairs {
let (name, desc) = match entry {
DocumentationParameter::Override(n, d) => (n.value(), d.value()),
DocumentationParameter::Description(d) => (name_from_target, d.value()),
};
doc_comments.push((name, desc));
}
let attrs = generic_item.attributes();
let insert_idx = find_insertion_index(attrs, attr.span());
insert_doc_comments_batch(attrs, doc_comments, insert_idx);
Ok(quote::quote! {
#generic_item
})
}
pub fn format_parameter_doc(
name: &str,
description: &str,
) -> String {
if name.is_empty() { description.to_string() } else { format!("* `{name}`: {description}") }
}
pub fn insert_doc_comment(
attrs: &mut Vec<syn::Attribute>,
doc_comment: String,
macro_span: proc_macro2::Span,
) {
let insert_idx = find_insertion_index(attrs, macro_span);
let doc_attr: syn::Attribute = parse_quote!(#[doc = #doc_comment]);
attrs.insert(insert_idx, doc_attr);
}
pub fn find_insertion_index(
attrs: &[syn::Attribute],
macro_span: proc_macro2::Span,
) -> usize {
attrs
.iter()
.position(|attr| attr.span().start().line > macro_span.start().line)
.unwrap_or(attrs.len())
}
pub fn insert_doc_comments_batch(
attrs: &mut Vec<syn::Attribute>,
docs: Vec<(String, String)>,
base_index: usize,
) {
for (i, (name, desc)) in docs.into_iter().enumerate() {
let doc_comment = format_parameter_doc(&name, &desc);
let doc_attr: syn::Attribute = parse_quote!(#[doc = #doc_comment]);
attrs.insert(base_index + i, doc_attr);
}
}
#[cfg(test)]
#[expect(clippy::panic, reason = "Test helper that panics on invalid input")]
pub fn get_doc(attr: &syn::Attribute) -> String {
if let syn::Meta::NameValue(nv) = &attr.meta
&& let syn::Expr::Lit(lit) = &nv.value
&& let syn::Lit::Str(s) = &lit.lit
{
return s.value();
}
panic!("Not a doc comment: {:?}", attr);
}