1 use proc_macro2::TokenStream; 2 use quote::quote; 3 use syn::{ 4 parenthesized, 5 parse::{Parse, ParseStream}, 6 Fields, Token, 7 }; 8 9 use crate::{ 10 diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, 11 utils::{display_pat_members, gen_all_variants_with, gen_unused_pat}, 12 }; 13 use crate::{ 14 fmt::{self, Display}, 15 forward::WhichFn, 16 }; 17 18 pub enum Url { 19 Display(Display), 20 DocsRs, 21 } 22 23 impl Parse for Url { parse(input: ParseStream) -> syn::Result<Self>24 fn parse(input: ParseStream) -> syn::Result<Self> { 25 let ident = input.parse::<syn::Ident>()?; 26 if ident == "url" { 27 let la = input.lookahead1(); 28 if la.peek(syn::token::Paren) { 29 let content; 30 parenthesized!(content in input); 31 if content.peek(syn::LitStr) { 32 let fmt = content.parse()?; 33 let args = if content.is_empty() { 34 TokenStream::new() 35 } else { 36 fmt::parse_token_expr(&content, false)? 37 }; 38 let display = Display { 39 fmt, 40 args, 41 has_bonus_display: false, 42 }; 43 Ok(Url::Display(display)) 44 } else { 45 let option = content.parse::<syn::Ident>()?; 46 if option == "docsrs" { 47 Ok(Url::DocsRs) 48 } else { 49 Err(syn::Error::new(option.span(), "Invalid argument to url() sub-attribute. It must be either a string or a plain `docsrs` identifier")) 50 } 51 } 52 } else { 53 input.parse::<Token![=]>()?; 54 Ok(Url::Display(Display { 55 fmt: input.parse()?, 56 args: TokenStream::new(), 57 has_bonus_display: false, 58 })) 59 } 60 } else { 61 Err(syn::Error::new(ident.span(), "not a url")) 62 } 63 } 64 } 65 66 impl Url { gen_enum( enum_name: &syn::Ident, variants: &[DiagnosticDef], ) -> Option<TokenStream>67 pub(crate) fn gen_enum( 68 enum_name: &syn::Ident, 69 variants: &[DiagnosticDef], 70 ) -> Option<TokenStream> { 71 gen_all_variants_with( 72 variants, 73 WhichFn::Url, 74 |ident, fields, DiagnosticConcreteArgs { url, .. }| { 75 let (pat, fmt, args) = match url.as_ref()? { 76 // fall through to `_ => None` below 77 Url::Display(display) => { 78 let (display_pat, display_members) = display_pat_members(fields); 79 let (fmt, args) = display.expand_shorthand_cloned(&display_members); 80 (display_pat, fmt.value(), args) 81 } 82 Url::DocsRs => { 83 let pat = gen_unused_pat(fields); 84 let fmt = 85 "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}" 86 .into(); 87 let item_path = format!("enum.{}.html#variant.{}", enum_name, ident); 88 let args = quote! { 89 , 90 crate_name=env!("CARGO_PKG_NAME"), 91 crate_version=env!("CARGO_PKG_VERSION"), 92 mod_name=env!("CARGO_PKG_NAME").replace('-', "_"), 93 item_path=#item_path 94 }; 95 (pat, fmt, args) 96 } 97 }; 98 Some(quote! { 99 Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))), 100 }) 101 }, 102 ) 103 } 104 gen_struct( &self, struct_name: &syn::Ident, fields: &Fields, ) -> Option<TokenStream>105 pub(crate) fn gen_struct( 106 &self, 107 struct_name: &syn::Ident, 108 fields: &Fields, 109 ) -> Option<TokenStream> { 110 let (pat, fmt, args) = match self { 111 Url::Display(display) => { 112 let (display_pat, display_members) = display_pat_members(fields); 113 let (fmt, args) = display.expand_shorthand_cloned(&display_members); 114 (display_pat, fmt.value(), args) 115 } 116 Url::DocsRs => { 117 let pat = gen_unused_pat(fields); 118 let fmt = 119 "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}".into(); 120 let item_path = format!("struct.{}.html", struct_name); 121 let args = quote! { 122 , 123 crate_name=env!("CARGO_PKG_NAME"), 124 crate_version=env!("CARGO_PKG_VERSION"), 125 mod_name=env!("CARGO_PKG_NAME").replace('-', "_"), 126 item_path=#item_path 127 }; 128 (pat, fmt, args) 129 } 130 }; 131 Some(quote! { 132 fn url(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> { 133 #[allow(unused_variables, deprecated)] 134 let Self #pat = self; 135 std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))) 136 } 137 }) 138 } 139 } 140