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