use proc_macro2::TokenStream; use quote::quote; use syn::{Attribute, DeriveInput, Error, Lit, LitStr, Meta}; pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result { let docs = get_docs(&input.attrs)?; let name = input.ident; let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl(); Ok(quote! { #[automatically_derived] impl #impl_generics ruff_db::RustDoc for #name #ty_generics #where_clause { fn rust_doc() -> &'static str { #docs } } }) } /// Collect all doc comment attributes into a string fn get_docs(attrs: &[Attribute]) -> syn::Result { let mut explanation = String::new(); for attr in attrs { if attr.path().is_ident("doc") { if let Some(lit) = parse_attr(["doc"], attr) { let value = lit.value(); // `/// ` adds let line = value.strip_prefix(' ').unwrap_or(&value); explanation.push_str(line); explanation.push('\n'); } else { return Err(Error::new_spanned(attr, "unimplemented doc comment style")); } } } Ok(explanation) } fn parse_attr<'a, const LEN: usize>( path: [&'static str; LEN], attr: &'a Attribute, ) -> Option<&'a LitStr> { if let Meta::NameValue(name_value) = &attr.meta { let path_idents = name_value .path .segments .iter() .map(|segment| &segment.ident); if path_idents.eq(path) { if let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(lit), .. }) = &name_value.value { return Some(lit); } } } None }