yew_macro/derive_props/
mod.rs1mod builder;
2mod field;
3mod generics;
4mod wrapper;
5
6use std::convert::TryInto;
7
8use builder::PropsBuilder;
9use field::PropField;
10use proc_macro2::{Ident, Span};
11use quote::{format_ident, quote, ToTokens};
12use syn::parse::{Parse, ParseStream, Result};
13use syn::punctuated::Pair;
14use syn::visit_mut::VisitMut;
15use syn::{
16 AngleBracketedGenericArguments, Attribute, ConstParam, DeriveInput, GenericArgument,
17 GenericParam, Generics, Path, PathArguments, PathSegment, Type, TypeParam, TypePath,
18 Visibility,
19};
20use wrapper::PropsWrapper;
21
22use self::field::PropAttr;
23use self::generics::to_arguments;
24
25pub struct DerivePropsInput {
26 vis: Visibility,
27 generics: Generics,
28 props_name: Ident,
29 prop_fields: Vec<PropField>,
30 preserved_attrs: Vec<Attribute>,
31}
32
33struct Normaliser<'ast> {
35 new_self: &'ast Ident,
36 generics: &'ast Generics,
37 new_self_full: Option<PathSegment>,
39}
40
41impl<'ast> Normaliser<'ast> {
42 pub fn new(new_self: &'ast Ident, generics: &'ast Generics) -> Self {
43 Self {
44 new_self,
45 generics,
46 new_self_full: None,
47 }
48 }
49
50 fn get_new_self(&mut self) -> PathSegment {
51 self.new_self_full
52 .get_or_insert_with(|| {
53 PathSegment {
54 ident: self.new_self.clone(),
55 arguments: if self.generics.lt_token.is_some() {
56 PathArguments::AngleBracketed(AngleBracketedGenericArguments {
57 colon2_token: Some(Default::default()),
58 lt_token: Default::default(),
59 args: self
60 .generics
61 .params
62 .pairs()
63 .map(|pair| {
64 let (value, punct) = pair.cloned().into_tuple();
65 let value = match value {
66 GenericParam::Lifetime(param) => {
67 GenericArgument::Lifetime(param.lifetime)
68 }
69 GenericParam::Type(TypeParam { ident, .. })
70 | GenericParam::Const(ConstParam { ident, .. }) => {
71 GenericArgument::Type(Type::Path(TypePath {
72 qself: None,
73 path: ident.into(),
74 }))
75 }
76 };
77 Pair::new(value, punct)
78 })
79 .collect(),
80 gt_token: Default::default(),
81 })
82 } else {
83 PathArguments::None
85 },
86 }
87 })
88 .clone()
89 }
90}
91
92impl VisitMut for Normaliser<'_> {
93 fn visit_path_mut(&mut self, path: &mut Path) {
94 if let Some(first) = path.segments.first_mut() {
95 if first.ident == "Self" {
96 *first = self.get_new_self();
97 }
98 syn::visit_mut::visit_path_mut(self, path)
99 }
100 }
101}
102
103fn should_preserve_attr(attr: &Attribute) -> bool {
106 let path = attr.path();
111 path.is_ident("allow") || path.is_ident("deny") || path.is_ident("cfg")
112}
113
114impl Parse for DerivePropsInput {
115 fn parse(input: ParseStream) -> Result<Self> {
116 let input: DeriveInput = input.parse()?;
117 let prop_fields = match input.data {
118 syn::Data::Struct(data) => match data.fields {
119 syn::Fields::Named(fields) => {
120 let mut prop_fields: Vec<PropField> = fields
121 .named
122 .into_iter()
123 .map(|f| f.try_into())
124 .collect::<Result<Vec<PropField>>>()?;
125
126 prop_fields.sort();
128
129 prop_fields
130 }
131 syn::Fields::Unit => Vec::new(),
132 _ => unimplemented!("only structs are supported"),
133 },
134 _ => unimplemented!("only structs are supported"),
135 };
136
137 let preserved_attrs = input
138 .attrs
139 .iter()
140 .filter(|a| should_preserve_attr(a))
141 .cloned()
142 .collect();
143
144 Ok(Self {
145 vis: input.vis,
146 props_name: input.ident,
147 generics: input.generics,
148 prop_fields,
149 preserved_attrs,
150 })
151 }
152}
153
154impl DerivePropsInput {
155 pub fn normalise(&mut self) {
158 let mut normaliser = Normaliser::new(&self.props_name, &self.generics);
159 for field in &mut self.prop_fields {
160 normaliser.visit_type_mut(&mut field.ty);
161 if let PropAttr::PropOr(expr) | PropAttr::PropOrElse(expr) = &mut field.attr {
162 normaliser.visit_expr_mut(expr)
163 }
164 }
165 }
166}
167
168impl ToTokens for DerivePropsInput {
169 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
170 let Self {
171 generics,
172 props_name,
173 prop_fields,
174 preserved_attrs,
175 ..
176 } = self;
177
178 let wrapper_name = format_ident!("{}Wrapper", props_name, span = Span::mixed_site());
180 let wrapper = PropsWrapper::new(&wrapper_name, generics, prop_fields, preserved_attrs);
181 tokens.extend(wrapper.into_token_stream());
182
183 let builder_name = format_ident!("{}Builder", props_name, span = Span::mixed_site());
185 let check_all_props_name =
186 format_ident!("Check{}All", props_name, span = Span::mixed_site());
187 let builder = PropsBuilder::new(
188 &builder_name,
189 self,
190 &wrapper_name,
191 &check_all_props_name,
192 preserved_attrs,
193 );
194 let generic_args = to_arguments(generics);
195 tokens.extend(builder.into_token_stream());
196
197 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
199 let properties = quote! {
200 impl #impl_generics ::yew::html::Properties for #props_name #ty_generics #where_clause {
201 type Builder = #builder_name<#generic_args>;
202
203 fn builder() -> Self::Builder {
204 #builder_name {
205 wrapped: ::std::boxed::Box::new(::std::default::Default::default()),
206 }
207 }
208 }
209 };
210 tokens.extend(properties);
211 }
212}