1#![warn(
2 clippy::all,
3 clippy::cargo,
4 clippy::nursery,
5 clippy::pedantic,
6 rust_2018_idioms
7)]
8#![forbid(unsafe_code)]
9#![doc = include_str!("../README.md")]
10
11use proc_macro::TokenStream;
12use quote::quote;
13use syn::fold::Fold;
14use syn::{
15 parse_macro_input, parse_quote, token, Attribute, Data, DataStruct, DeriveInput, Fields,
16 FieldsNamed, VisPublic, Visibility,
17};
18
19struct PokeAPIFields;
23
24impl Fold for PokeAPIFields {
25 fn fold_fields_named(&mut self, fields: FieldsNamed) -> FieldsNamed {
28 let serde_skip: Attribute = parse_quote!(#[serde(skip)]);
29
30 let brace_token = fields.brace_token;
31 let named = fields
32 .named
33 .into_iter()
34 .map(|mut field| {
35 let ident = field.ident.as_ref().unwrap();
37
38 if ident.to_string().starts_with('_') {
39 if !field.attrs.iter().any(|attr| attr == &serde_skip) {
40 field.attrs.push(serde_skip.clone());
41 }
42 }
43
44 if !field.attrs.iter().any(|attr| attr == &serde_skip) {
45 field.vis = Visibility::Public(VisPublic {
46 pub_token: token::Pub::default(),
47 });
48 }
49
50 field
51 })
52 .collect();
53
54 FieldsNamed { brace_token, named }
55 }
56}
57
58#[allow(clippy::doc_markdown)]
87#[proc_macro_attribute]
88pub fn pokeapi_struct(_attr: TokenStream, item: TokenStream) -> TokenStream {
89 let DeriveInput {
92 attrs,
93 ident,
94 generics,
95 data,
96 ..
97 } = parse_macro_input!(item as DeriveInput);
98
99 let ident_lower = ident.to_string().to_ascii_lowercase();
100 let doc_comment = format!(
101 "[{url}{ident_lower}]({url}{ident_lower})",
102 url = "https://pokeapi.co/docs/v2#"
103 );
104 let doc_attr: Attribute = parse_quote!(#[doc = #doc_comment]);
105
106 let fields = match data {
109 Data::Struct(DataStruct {
110 fields: Fields::Named(named_fields),
111 ..
112 }) => PokeAPIFields
113 .fold_fields_named(named_fields)
114 .named
115 .into_iter(),
116 _ => panic!("This attribute requires a struct with named fields."),
117 };
118
119 TokenStream::from(quote! {
121 #doc_attr
122 #(#attrs)*
123 #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
124 #[non_exhaustive]
125 pub struct #ident #generics {
126 #(#fields),*
127 }
128 })
129}