use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{
LitStr, Result, Token,
parse::{Parse, ParseStream},
punctuated::Punctuated,
};
struct DiagCode {
parts: Vec<Ident>,
}
impl Parse for DiagCode {
fn parse(input: ParseStream) -> Result<Self> {
let mut parts = Vec::new();
loop {
parts.push(input.parse::<Ident>()?);
if input.peek(Token![.]) {
input.parse::<Token![.]>()?;
} else {
break;
}
}
Ok(DiagCode { parts })
}
}
struct DocGenArgs {
formats: Punctuated<LitStr, Token![,]>,
}
impl Parse for DocGenArgs {
fn parse(input: ParseStream) -> Result<Self> {
let formats = Punctuated::parse_terminated(input)?;
Ok(DocGenArgs { formats })
}
}
fn extract_diagnostic_code(tokens: &proc_macro2::TokenStream) -> Option<(String, Ident)> {
if let Ok(diag_code) = syn::parse2::<DiagCode>(tokens.clone()) {
let parts = diag_code.parts;
if parts.len() >= 4 {
let code = parts
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(".");
let const_name = format!(
"{}_{}_{}_{}_COMPLETE",
parts[0].to_string().to_uppercase(),
parts[1].to_string().to_uppercase(),
parts[2].to_string().to_uppercase(),
parts[3].to_string().to_uppercase()
);
return Some((code, Ident::new(&const_name, Span::call_site())));
}
}
None
}
pub fn expand(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(attr as DocGenArgs);
let format_names: Vec<String> = args.formats.iter().map(|lit| lit.value()).collect();
let item_tokens: proc_macro2::TokenStream = item.clone().into();
let (diagnostic_code, const_name) = match extract_diagnostic_code(&item_tokens) {
Some((code, name)) => (code, name),
None => {
let formats_array = format_names.iter().map(|s| s.as_str());
let output = quote! {
#item_tokens
#[cfg(feature = "metadata")]
pub mod __doc_gen_registry {
pub const REQUESTED_FORMATS: &[&str] = &[#(#formats_array),*];
}
};
return TokenStream::from(output);
}
};
let formats_array = format_names.iter().map(|s| s.as_str());
let register_fn_name = Ident::new(
&format!(
"register_{}_for_doc_gen",
const_name.to_string().to_lowercase()
),
Span::call_site(),
);
let output = quote! {
#item_tokens
#[cfg(feature = "metadata")]
pub mod __doc_gen_registry {
pub const REQUESTED_FORMATS: &[&str] = &[#(#formats_array),*];
pub fn register(registry: &mut ::waddling_errors::doc_generator::DocRegistry) {
registry.register_diagnostic_complete(&super::#const_name);
}
}
#[cfg(feature = "metadata")]
#[doc = concat!("Register ", #diagnostic_code, " for documentation generation")]
pub fn #register_fn_name(registry: &mut ::waddling_errors::doc_generator::DocRegistry) {
__doc_gen_registry::register(registry);
}
};
TokenStream::from(output)
}