yew_macro/props/
prop_macro.rs1use 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
14fn pop_last_punctuated<T, P>(punctuated: &mut Punctuated<T, P>) -> Option<T> {
16 let value = punctuated.pop().map(|pair| pair.into_value());
17 if let Some(pair) = punctuated.pop() {
19 punctuated.push_value(pair.into_value());
20 }
21 value
22}
23
24fn 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 seg.ident == "Properties" {
30 if let Some(seg) = segments_it.next_back() {
31 if !crate::non_capitalized_ascii(&seg.ident.to_string()) {
33 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 ty.qself.is_none() && is_associated_properties(&ty) {
86 pop_last_punctuated(&mut ty.path.segments);
87 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}