yew_macro/props/
component.rs1use 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 pub fn special(&self) -> &SpecialProps {
44 &self.props.special
45 }
46
47 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 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}