yew_macro/props/
prop_macro.rs1use std::convert::TryInto;
2
3use proc_macro2::TokenStream;
4use quote::{quote_spanned, ToTokens};
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, 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}
48impl Parse for PropValue {
49 fn parse(input: ParseStream) -> syn::Result<Self> {
50 let label = input.parse()?;
51 let value = if input.peek(Token![:]) {
52 let _colon_token: Token![:] = input.parse()?;
53 input.parse()?
54 } else {
55 syn::parse_quote!(#label)
56 };
57 Ok(Self { label, value })
58 }
59}
60
61impl From<PropValue> for Prop {
62 fn from(prop_value: PropValue) -> Prop {
63 let PropValue { label, value } = prop_value;
64 Prop {
65 label,
66 value,
67 directive: None,
68 }
69 }
70}
71
72struct PropsExpr {
73 ty: TypePath,
74 _brace_token: Brace,
75 fields: Punctuated<PropValue, Token![,]>,
76}
77impl Parse for PropsExpr {
78 fn parse(input: ParseStream) -> syn::Result<Self> {
79 let mut ty: TypePath = input.parse()?;
80
81 if ty.qself.is_none() && is_associated_properties(&ty) {
84 pop_last_punctuated(&mut ty.path.segments);
85 ty = syn::parse2(quote_spanned! {ty.span()=>
87 <#ty as ::yew::html::Component>::Properties
88 })?;
89 }
90
91 let content;
92 let brace_token = syn::braced!(content in input);
93 let fields = content.parse_terminated(PropValue::parse, Token![,])?;
94 Ok(Self {
95 ty,
96 _brace_token: brace_token,
97 fields,
98 })
99 }
100}
101
102pub struct PropsMacroInput {
103 ty: TypePath,
104 props: ComponentProps,
105}
106impl Parse for PropsMacroInput {
107 fn parse(input: ParseStream) -> syn::Result<Self> {
108 let PropsExpr { ty, fields, .. } = input.parse()?;
109 let prop_list = PropList::new(fields.into_iter().map(Into::into).collect());
110 let props: Props = prop_list.try_into()?;
111 props.special.check_all(|prop| {
112 let label = &prop.label;
113 Err(syn::Error::new_spanned(
114 label,
115 "special props cannot be specified in the `props!` macro",
116 ))
117 })?;
118 Ok(Self {
119 ty,
120 props: props.try_into()?,
121 })
122 }
123}
124impl ToTokens for PropsMacroInput {
125 fn to_tokens(&self, tokens: &mut TokenStream) {
126 let Self { ty, props } = self;
127
128 tokens.extend(props.build_properties_tokens(ty, None::<TokenStream>))
129 }
130}