1use proc_macro2::{Span, TokenStream};
2use quote::{quote, ToTokens};
3use syn::parse::{Parse, ParseStream};
4use syn::punctuated::Punctuated;
5use syn::token::{Comma, Fn};
6use syn::{
7 parse_quote, parse_quote_spanned, visit_mut, Attribute, Block, FnArg, Generics, Ident, Item,
8 ItemFn, LitStr, ReturnType, Type, Visibility,
9};
10
11use crate::hook::BodyRewriter;
12
13#[derive(Clone)]
14pub struct FunctionComponent {
15 block: Box<Block>,
16 props_type: Box<Type>,
17 arg: FnArg,
18 generics: Generics,
19 vis: Visibility,
20 attrs: Vec<Attribute>,
21 name: Ident,
22 return_type: Box<Type>,
23 fn_token: Fn,
24
25 component_name: Option<Ident>,
26}
27
28impl Parse for FunctionComponent {
29 fn parse(input: ParseStream) -> syn::Result<Self> {
30 let parsed: Item = input.parse()?;
31
32 let func = match parsed {
33 Item::Fn(m) => m,
34
35 item => {
36 return Err(syn::Error::new_spanned(
37 item,
38 "`function_component` attribute can only be applied to functions",
39 ))
40 }
41 };
42
43 let ItemFn {
44 attrs,
45 vis,
46 sig,
47 block,
48 } = func;
49
50 if sig.generics.lifetimes().next().is_some() {
51 return Err(syn::Error::new_spanned(
52 sig.generics,
53 "function components can't have generic lifetime parameters",
54 ));
55 }
56
57 if sig.asyncness.is_some() {
58 return Err(syn::Error::new_spanned(
59 sig.asyncness,
60 "function components can't be async",
61 ));
62 }
63
64 if sig.constness.is_some() {
65 return Err(syn::Error::new_spanned(
66 sig.constness,
67 "const functions can't be function components",
68 ));
69 }
70
71 if sig.abi.is_some() {
72 return Err(syn::Error::new_spanned(
73 sig.abi,
74 "extern functions can't be function components",
75 ));
76 }
77
78 let return_type = match sig.output {
79 ReturnType::Default => {
80 return Err(syn::Error::new_spanned(
81 sig,
82 "function components must return `yew::Html` or `yew::HtmlResult`",
83 ))
84 }
85 ReturnType::Type(_, ty) => ty,
86 };
87
88 let mut inputs = sig.inputs.into_iter();
89 let arg = inputs
90 .next()
91 .unwrap_or_else(|| syn::parse_quote! { _: &() });
92
93 let ty = match &arg {
94 FnArg::Typed(arg) => match &*arg.ty {
95 Type::Reference(ty) => {
96 if ty.lifetime.is_some() {
97 return Err(syn::Error::new_spanned(
98 &ty.lifetime,
99 "reference must not have a lifetime",
100 ));
101 }
102
103 if ty.mutability.is_some() {
104 return Err(syn::Error::new_spanned(
105 ty.mutability,
106 "reference must not be mutable",
107 ));
108 }
109
110 ty.elem.clone()
111 }
112 ty => {
113 let msg = format!(
114 "expected a reference to a `Properties` type (try: `&{}`)",
115 ty.to_token_stream()
116 );
117 return Err(syn::Error::new_spanned(ty, msg));
118 }
119 },
120
121 FnArg::Receiver(_) => {
122 return Err(syn::Error::new_spanned(
123 arg,
124 "function components can't accept a receiver",
125 ));
126 }
127 };
128
129 if inputs.len() > 0 {
133 let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect();
134 return Err(syn::Error::new_spanned(
135 params,
136 "function components can accept at most one parameter for the props",
137 ));
138 }
139
140 Ok(Self {
141 props_type: ty,
142 block,
143 arg,
144 generics: sig.generics,
145 vis,
146 attrs,
147 name: sig.ident,
148 return_type,
149 fn_token: sig.fn_token,
150 component_name: None,
151 })
152 }
153}
154
155impl FunctionComponent {
156 fn filter_attrs_for_component_struct(&self) -> Vec<Attribute> {
158 self.attrs
159 .iter()
160 .filter_map(|m| {
161 m.path()
162 .get_ident()
163 .and_then(|ident| match ident.to_string().as_str() {
164 "doc" | "allow" => Some(m.clone()),
165 _ => None,
166 })
167 })
168 .collect()
169 }
170
171 fn filter_attrs_for_component_impl(&self) -> Vec<Attribute> {
173 self.attrs
174 .iter()
175 .filter_map(|m| {
176 m.path()
177 .get_ident()
178 .and_then(|ident| match ident.to_string().as_str() {
179 "allow" => Some(m.clone()),
180 _ => None,
181 })
182 })
183 .collect()
184 }
185
186 fn phantom_generics(&self) -> Punctuated<Ident, Comma> {
187 self.generics
188 .type_params()
189 .map(|ty_param| ty_param.ident.clone()) .collect::<Punctuated<_, Comma>>()
191 }
192
193 fn merge_component_name(&mut self, name: FunctionComponentName) -> syn::Result<()> {
194 if let Some(ref m) = name.component_name {
195 if m == &self.name {
196 return Err(syn::Error::new_spanned(
197 m,
198 "the component must not have the same name as the function",
199 ));
200 }
201 }
202
203 self.component_name = name.component_name;
204
205 Ok(())
206 }
207
208 fn inner_fn_ident(&self) -> Ident {
209 if self.component_name.is_some() {
210 self.name.clone()
211 } else {
212 Ident::new("inner", Span::mixed_site())
213 }
214 }
215
216 fn component_name(&self) -> Ident {
217 self.component_name
218 .clone()
219 .unwrap_or_else(|| self.name.clone())
220 }
221
222 fn create_static_component_generics(&self) -> Generics {
224 let mut generics = self.generics.clone();
225
226 let where_clause = generics.make_where_clause();
227 for ty_generic in self.generics.type_params() {
228 let ident = &ty_generic.ident;
229 let bound = parse_quote_spanned! { ident.span() =>
230 #ident: 'static
231 };
232
233 where_clause.predicates.push(bound);
234 }
235
236 where_clause.predicates.push(parse_quote! { Self: 'static });
237
238 generics
239 }
240
241 fn print_inner_fn(&self) -> TokenStream {
243 let name = self.inner_fn_ident();
244 let FunctionComponent {
245 ref fn_token,
246 ref attrs,
247 ref block,
248 ref return_type,
249 ref generics,
250 ref arg,
251 ..
252 } = self;
253 let mut block = *block.clone();
254 let (impl_generics, _ty_generics, where_clause) = generics.split_for_impl();
255
256 let ctx_ident = Ident::new("_ctx", Span::mixed_site());
259
260 let mut body_rewriter = BodyRewriter::new(ctx_ident.clone());
261 visit_mut::visit_block_mut(&mut body_rewriter, &mut block);
262
263 quote! {
264 #(#attrs)*
265 #fn_token #name #impl_generics (#ctx_ident: &mut ::yew::functional::HookContext, #arg) -> #return_type
266 #where_clause
267 {
268 #block
269 }
270 }
271 }
272
273 fn print_base_component_impl(&self) -> TokenStream {
274 let component_name = self.component_name();
275 let props_type = &self.props_type;
276 let static_comp_generics = self.create_static_component_generics();
277
278 let (impl_generics, ty_generics, where_clause) = static_comp_generics.split_for_impl();
279
280 quote! {
282 #[automatically_derived]
283 impl #impl_generics ::yew::html::BaseComponent for #component_name #ty_generics #where_clause {
284 type Message = ();
285 type Properties = #props_type;
286
287 #[inline]
288 fn create(ctx: &::yew::html::Context<Self>) -> Self {
289 Self {
290 _marker: ::std::marker::PhantomData,
291 function_component: ::yew::functional::FunctionComponent::<Self>::new(ctx),
292 }
293 }
294
295 #[inline]
296 fn update(&mut self, _ctx: &::yew::html::Context<Self>, _msg: Self::Message) -> ::std::primitive::bool {
297 true
298 }
299
300 #[inline]
301 fn changed(&mut self, _ctx: &::yew::html::Context<Self>, _old_props: &Self::Properties) -> ::std::primitive::bool {
302 true
303 }
304
305 #[inline]
306 fn view(&self, ctx: &::yew::html::Context<Self>) -> ::yew::html::HtmlResult {
307 ::yew::functional::FunctionComponent::<Self>::render(
308 &self.function_component,
309 ::yew::html::Context::<Self>::props(ctx)
310 )
311 }
312
313 #[inline]
314 fn rendered(&mut self, _ctx: &::yew::html::Context<Self>, _first_render: ::std::primitive::bool) {
315 ::yew::functional::FunctionComponent::<Self>::rendered(&self.function_component)
316 }
317
318 #[inline]
319 fn destroy(&mut self, _ctx: &::yew::html::Context<Self>) {
320 ::yew::functional::FunctionComponent::<Self>::destroy(&self.function_component)
321 }
322
323 #[inline]
324 fn prepare_state(&self) -> ::std::option::Option<::std::string::String> {
325 ::yew::functional::FunctionComponent::<Self>::prepare_state(&self.function_component)
326 }
327 }
328 }
329 }
330
331 fn print_debug_impl(&self) -> TokenStream {
332 let component_name = self.component_name();
333 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
334
335 let component_name_lit = LitStr::new(&format!("{component_name}<_>"), Span::mixed_site());
336
337 quote! {
338 #[automatically_derived]
339 impl #impl_generics ::std::fmt::Debug for #component_name #ty_generics #where_clause {
340 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
341 ::std::write!(f, #component_name_lit)
342 }
343 }
344 }
345 }
346
347 fn print_fn_provider_impl(&self) -> TokenStream {
348 let func = self.print_inner_fn();
349 let component_impl_attrs = self.filter_attrs_for_component_impl();
350 let component_name = self.component_name();
351 let fn_name = self.inner_fn_ident();
352 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
353 let props_type = &self.props_type;
354 let fn_generics = ty_generics.as_turbofish();
355
356 let component_props = Ident::new("props", Span::mixed_site());
357 let ctx_ident = Ident::new("ctx", Span::mixed_site());
358
359 quote! {
360 #(#component_impl_attrs)*
363 impl #impl_generics ::yew::functional::FunctionProvider for #component_name #ty_generics #where_clause {
364 type Properties = #props_type;
365
366 fn run(#ctx_ident: &mut ::yew::functional::HookContext, #component_props: &Self::Properties) -> ::yew::html::HtmlResult {
367 #func
368
369 ::yew::html::IntoHtmlResult::into_html_result(#fn_name #fn_generics (#ctx_ident, #component_props))
370 }
371 }
372 }
373 }
374
375 fn print_struct_def(&self) -> TokenStream {
376 let component_attrs = self.filter_attrs_for_component_struct();
377 let component_name = self.component_name();
378
379 let generics = &self.generics;
380 let (_impl_generics, _ty_generics, where_clause) = self.generics.split_for_impl();
381 let phantom_generics = self.phantom_generics();
382 let vis = &self.vis;
383
384 quote! {
385 #(#component_attrs)*
386 #[allow(unused_parens)]
387 #vis struct #component_name #generics #where_clause {
388 _marker: ::std::marker::PhantomData<(#phantom_generics)>,
389 function_component: ::yew::functional::FunctionComponent<Self>,
390 }
391 }
392 }
393}
394
395pub struct FunctionComponentName {
396 component_name: Option<Ident>,
397}
398
399impl Parse for FunctionComponentName {
400 fn parse(input: ParseStream) -> syn::Result<Self> {
401 if input.is_empty() {
402 return Ok(Self {
403 component_name: None,
404 });
405 }
406
407 let component_name = input.parse()?;
408
409 Ok(Self {
410 component_name: Some(component_name),
411 })
412 }
413}
414
415pub fn function_component_impl(
416 name: FunctionComponentName,
417 mut component: FunctionComponent,
418) -> syn::Result<TokenStream> {
419 component.merge_component_name(name)?;
420
421 let base_comp_impl = component.print_base_component_impl();
422 let debug_impl = component.print_debug_impl();
423 let provider_fn_impl = component.print_fn_provider_impl();
424 let struct_def = component.print_struct_def();
425
426 let quoted = quote! {
427 #struct_def
428
429 #provider_fn_impl
430 #debug_impl
431 #base_comp_impl
432 };
433
434 Ok(quoted)
435}