This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_macro/props/
component.rs

1use std::convert::TryFrom;
2
3use proc_macro2::{Ident, Span, TokenStream};
4use quote::{quote, quote_spanned, ToTokens};
5use syn::parse::{Parse, ParseStream};
6use syn::spanned::Spanned;
7use syn::token::DotDot;
8use syn::Expr;
9
10use super::{Prop, Props, SpecialProps, CHILDREN_LABEL};
11
12struct BaseExpr {
13    pub dot_dot: DotDot,
14    pub expr: Expr,
15}
16
17impl Parse for BaseExpr {
18    fn parse(input: ParseStream) -> syn::Result<Self> {
19        let dot_dot = input.parse()?;
20        let expr = input.parse().map_err(|expr_error| {
21            let mut error =
22                syn::Error::new_spanned(dot_dot, "expected base props expression after `..`");
23            error.combine(expr_error);
24            error
25        })?;
26        Ok(Self { dot_dot, expr })
27    }
28}
29
30impl ToTokens for BaseExpr {
31    fn to_tokens(&self, tokens: &mut TokenStream) {
32        let BaseExpr { dot_dot, expr } = self;
33        tokens.extend(quote! { #dot_dot #expr });
34    }
35}
36
37pub struct ComponentProps {
38    props: Props,
39    base_expr: Option<Expr>,
40}
41impl ComponentProps {
42    /// Get the special props supported by both variants
43    pub fn special(&self) -> &SpecialProps {
44        &self.props.special
45    }
46
47    // check if the `children` prop is given explicitly
48    pub fn children(&self) -> Option<&Prop> {
49        self.props.get_by_label(CHILDREN_LABEL)
50    }
51
52    fn prop_validation_tokens(&self, props_ty: impl ToTokens, has_children: bool) -> TokenStream {
53        let props_ident = Ident::new("__yew_props", props_ty.span());
54        let check_children = if has_children {
55            Some(quote_spanned! {props_ty.span()=>
56                let _ = #props_ident.children;
57            })
58        } else {
59            None
60        };
61
62        let check_props: TokenStream = self
63            .props
64            .iter()
65            .map(|Prop { label, .. }| {
66                quote_spanned! {Span::call_site().located_at(label.span())=>
67                    let _ = &#props_ident.#label;
68                }
69            })
70            .collect();
71
72        quote_spanned! {props_ty.span()=>
73            #[allow(clippy::no_effect)]
74            if false {
75                let _ = |#props_ident: #props_ty| {
76                    #check_children
77                    #check_props
78                };
79            };
80        }
81    }
82
83    pub fn build_properties_tokens<CR: ToTokens>(
84        &self,
85        props_ty: impl ToTokens,
86        children_renderer: Option<CR>,
87    ) -> TokenStream {
88        let has_children = children_renderer.is_some();
89        let validate_props = self.prop_validation_tokens(&props_ty, has_children);
90        let build_props = match &self.base_expr {
91            None => {
92                let builder_ident = Ident::new("__yew_props", props_ty.span());
93                let token_ident = Ident::new(
94                    "__yew_required_props_token",
95                    props_ty.span().resolved_at(Span::mixed_site()),
96                );
97
98                let init_builder = quote_spanned! {props_ty.span()=>
99                    let mut #builder_ident = <#props_ty as ::yew::html::Properties>::builder();
100                    let #token_ident = ::yew::html::AssertAllProps;
101                };
102                let set_props = self.props.iter().map(|Prop { label, value, .. }| {
103                    quote_spanned! {value.span()=>
104                        let #token_ident = #builder_ident.#label(#token_ident, #value);
105                    }
106                });
107                let set_children = children_renderer.map(|children| {
108                    quote_spanned! {props_ty.span()=>
109                        let #token_ident = #builder_ident.children(#token_ident, #children);
110                    }
111                });
112                let build_builder = quote_spanned! {props_ty.span()=>
113                    ::yew::html::Buildable::prepare_build(#builder_ident, &#token_ident).build()
114                };
115
116                quote! {
117                    #init_builder
118                    #( #set_props )*
119                    #set_children
120                    #build_builder
121                }
122            }
123            // Builder pattern is unnecessary in this case, since the base expression guarantees
124            // all values are initialized
125            Some(expr) => {
126                let ident = Ident::new("__yew_props", props_ty.span());
127                let set_props = self.props.iter().map(|Prop { label, value, .. }| {
128                    quote_spanned! {value.span().resolved_at(Span::call_site())=>
129                        #ident.#label = ::yew::html::IntoPropValue::into_prop_value(#value);
130                    }
131                });
132                let set_children = children_renderer.map(|children| {
133                    quote_spanned! {props_ty.span()=>
134                        #ident.children = ::yew::html::IntoPropValue::into_prop_value(#children);
135                    }
136                });
137                let init_base = quote_spanned! {expr.span().resolved_at(Span::call_site())=>
138                    let mut #ident: #props_ty = #expr;
139                };
140
141                quote! {
142                    #init_base
143                    #(#set_props)*
144                    #set_children
145                    #ident
146                }
147            }
148        };
149
150        quote! {
151            {
152                #validate_props
153                #build_props
154            }
155        }
156    }
157}
158
159impl Parse for ComponentProps {
160    fn parse(input: ParseStream) -> syn::Result<Self> {
161        let props = validate(input.parse()?)?;
162        let base_expr = if input.is_empty() {
163            None
164        } else {
165            Some(input.parse::<BaseExpr>()?)
166        };
167
168        if input.is_empty() {
169            let base_expr = base_expr.map(|base| base.expr);
170            Ok(Self { props, base_expr })
171        } else {
172            Err(syn::Error::new_spanned(
173                base_expr,
174                "base props expression must appear last in list of props",
175            ))
176        }
177    }
178}
179
180impl TryFrom<Props> for ComponentProps {
181    type Error = syn::Error;
182
183    fn try_from(props: Props) -> Result<Self, Self::Error> {
184        Ok(Self {
185            props: validate(props)?,
186            base_expr: None,
187        })
188    }
189}
190
191fn validate(props: Props) -> Result<Props, syn::Error> {
192    props.check_no_duplicates()?;
193    props.check_all(|prop| {
194        if !prop.label.extended.is_empty() {
195            Err(syn::Error::new_spanned(
196                &prop.label,
197                "expected a valid Rust identifier",
198            ))
199        } else {
200            Ok(())
201        }
202    })?;
203
204    Ok(props)
205}