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

yew_macro/props/
prop_macro.rs

1use std::convert::TryInto;
2
3use proc_macro2::TokenStream;
4use quote::{ToTokens, quote_spanned};
5use syn::parse::{Parse, ParseStream};
6use syn::punctuated::Punctuated;
7use syn::spanned::Spanned;
8use syn::token::Brace;
9use syn::{Expr, Token, TypePath};
10
11use super::{ComponentProps, Prop, PropLabel, PropList, Props};
12use crate::html_tree::HtmlDashedName;
13
14/// Pop from `Punctuated` without leaving it in a state where it has trailing punctuation.
15fn pop_last_punctuated<T, P>(punctuated: &mut Punctuated<T, P>) -> Option<T> {
16    let value = punctuated.pop().map(|pair| pair.into_value());
17    // remove the 2nd last value and push it right back to remove the trailing punctuation
18    if let Some(pair) = punctuated.pop() {
19        punctuated.push_value(pair.into_value());
20    }
21    value
22}
23
24/// Check if the given type path looks like an associated `Properties` type.
25fn is_associated_properties(ty: &TypePath) -> bool {
26    let mut segments_it = ty.path.segments.iter();
27    if let Some(seg) = segments_it.next_back() {
28        // if the last segment is `Properties` ...
29        if seg.ident == "Properties" {
30            if let Some(seg) = segments_it.next_back() {
31                // ... and we can be reasonably sure that the previous segment is a component ...
32                if !crate::non_capitalized_ascii(&seg.ident.to_string()) {
33                    // ... then we assume that this is an associated type like
34                    // `Component::Properties`
35                    return true;
36                }
37            }
38        }
39    }
40
41    false
42}
43
44struct PropValue {
45    label: HtmlDashedName,
46    value: Expr,
47}
48
49impl Parse for PropValue {
50    fn parse(input: ParseStream) -> syn::Result<Self> {
51        let label = input.parse()?;
52        let value = if input.peek(Token![:]) {
53            let _colon_token: Token![:] = input.parse()?;
54            input.parse()?
55        } else {
56            syn::parse_quote!(#label)
57        };
58        Ok(Self { label, value })
59    }
60}
61
62impl From<PropValue> for Prop {
63    fn from(prop_value: PropValue) -> Prop {
64        let PropValue { label, value } = prop_value;
65        Prop {
66            label: PropLabel::Static(label),
67            value,
68            directive: None,
69        }
70    }
71}
72
73struct PropsExpr {
74    ty: TypePath,
75    _brace_token: Brace,
76    fields: Punctuated<PropValue, Token![,]>,
77}
78
79impl Parse for PropsExpr {
80    fn parse(input: ParseStream) -> syn::Result<Self> {
81        let mut ty: TypePath = input.parse()?;
82
83        // if the type isn't already qualified (`<x as y>`) and it's an associated type
84        // (`MyComp::Properties`) ...
85        if ty.qself.is_none() && is_associated_properties(&ty) {
86            pop_last_punctuated(&mut ty.path.segments);
87            // .. transform it into a "qualified-self" type
88            ty = syn::parse2(quote_spanned! {ty.span()=>
89                <#ty as ::yew::html::Component>::Properties
90            })?;
91        }
92
93        let content;
94        let brace_token = syn::braced!(content in input);
95        let fields = content.parse_terminated(PropValue::parse, Token![,])?;
96        Ok(Self {
97            ty,
98            _brace_token: brace_token,
99            fields,
100        })
101    }
102}
103
104pub struct PropsMacroInput {
105    ty: TypePath,
106    props: ComponentProps,
107}
108
109impl Parse for PropsMacroInput {
110    fn parse(input: ParseStream) -> syn::Result<Self> {
111        let PropsExpr { ty, fields, .. } = input.parse()?;
112        let prop_list = PropList::new(fields.into_iter().map(Into::into).collect());
113        let props: Props = prop_list.try_into()?;
114        props.special.check_all(|prop| {
115            let label = &prop.label;
116            Err(syn::Error::new_spanned(
117                label,
118                "special props cannot be specified in the `props!` macro",
119            ))
120        })?;
121        Ok(Self {
122            ty,
123            props: props.try_into()?,
124        })
125    }
126}
127
128impl ToTokens for PropsMacroInput {
129    fn to_tokens(&self, tokens: &mut TokenStream) {
130        let Self { ty, props } = self;
131
132        tokens.extend(props.build_properties_tokens(ty, None::<TokenStream>))
133    }
134}