1use std::convert::TryFrom;
2use std::ops::{Deref, DerefMut};
3
4use proc_macro2::{Spacing, Span, TokenStream, TokenTree};
5use quote::{quote, quote_spanned};
6use syn::parse::{Parse, ParseBuffer, ParseStream};
7use syn::spanned::Spanned;
8use syn::token::Brace;
9use syn::{braced, Block, Expr, ExprBlock, ExprMacro, ExprPath, ExprRange, Stmt, Token};
10
11use crate::html_tree::HtmlDashedName;
12use crate::stringify::Stringify;
13
14#[derive(Copy, Clone)]
15pub enum PropDirective {
16 ApplyAsProperty(Token![~]),
17}
18
19pub struct Prop {
20 pub directive: Option<PropDirective>,
21 pub label: HtmlDashedName,
22 pub value: Expr,
24}
25
26impl Parse for Prop {
27 fn parse(input: ParseStream) -> syn::Result<Self> {
28 let directive = input
29 .parse::<Token![~]>()
30 .map(PropDirective::ApplyAsProperty)
31 .ok();
32 if input.peek(Brace) {
33 Self::parse_shorthand_prop_assignment(input, directive)
34 } else {
35 Self::parse_prop_assignment(input, directive)
36 }
37 }
38}
39
40impl Prop {
42 fn parse_shorthand_prop_assignment(
46 input: ParseStream,
47 directive: Option<PropDirective>,
48 ) -> syn::Result<Self> {
49 let value;
50 let _brace = braced!(value in input);
51 let expr = value.parse::<Expr>()?;
52 let label = if let Expr::Path(ExprPath {
53 ref attrs,
54 qself: None,
55 ref path,
56 }) = expr
57 {
58 if let (Some(ident), true) = (path.get_ident(), attrs.is_empty()) {
59 Ok(HtmlDashedName::from(ident.clone()))
60 } else {
61 Err(syn::Error::new_spanned(
62 path,
63 "only simple identifiers are allowed in the shorthand property syntax",
64 ))
65 }
66 } else {
67 return Err(syn::Error::new_spanned(
68 expr,
69 "missing label for property value. If trying to use the shorthand property \
70 syntax, only identifiers may be used",
71 ));
72 }?;
73
74 Ok(Self {
75 label,
76 value: expr,
77 directive,
78 })
79 }
80
81 fn parse_prop_assignment(
83 input: ParseStream,
84 directive: Option<PropDirective>,
85 ) -> syn::Result<Self> {
86 let label = input.parse::<HtmlDashedName>()?;
87 let equals = input.parse::<Token![=]>().map_err(|_| {
88 syn::Error::new_spanned(
89 &label,
90 format!(
91 "`{label}` doesn't have a value. (hint: set the value to `true` or `false` \
92 for boolean attributes)"
93 ),
94 )
95 })?;
96 if input.is_empty() {
97 return Err(syn::Error::new_spanned(
98 equals,
99 "expected an expression following this equals sign",
100 ));
101 }
102
103 let value = parse_prop_value(input)?;
104 Ok(Self {
105 label,
106 value,
107 directive,
108 })
109 }
110}
111
112fn parse_prop_value(input: &ParseBuffer) -> syn::Result<Expr> {
113 if input.peek(Brace) {
114 strip_braces(input.parse()?)
115 } else {
116 let expr = if let Some(ExprRange {
117 start: Some(start), ..
118 }) = range_expression_peek(input)
119 {
120 advance_until_next_dot2(input)?;
123 *start
124 } else {
125 input.parse()?
126 };
127
128 match &expr {
129 Expr::Lit(_) => Ok(expr),
130 ref exp => Err(syn::Error::new_spanned(
131 &expr,
132 format!(
133 "the property value must be either a literal or enclosed in braces. Consider \
134 adding braces around your expression.: {exp:#?}"
135 ),
136 )),
137 }
138 }
139}
140
141fn strip_braces(block: ExprBlock) -> syn::Result<Expr> {
142 match block {
143 ExprBlock {
144 block: Block { mut stmts, .. },
145 ..
146 } if stmts.len() == 1 => {
147 let stmt = stmts.remove(0);
148 match stmt {
149 Stmt::Expr(expr, None) => Ok(expr),
150 Stmt::Macro(mac) => Ok(Expr::Macro(ExprMacro {
151 attrs: vec![],
152 mac: mac.mac,
153 })),
154 Stmt::Item(syn::Item::Macro(mac))
156 if mac.ident.is_none() && mac.semi_token.is_none() =>
157 {
158 Ok(Expr::Macro(syn::ExprMacro {
159 attrs: mac.attrs,
160 mac: mac.mac,
161 }))
162 }
163 Stmt::Expr(_, Some(semi)) => Err(syn::Error::new_spanned(
164 semi,
165 "only an expression may be assigned as a property. Consider removing this \
166 semicolon",
167 )),
168 _ => Err(syn::Error::new_spanned(
169 stmt,
170 "only an expression may be assigned as a property",
171 )),
172 }
173 }
174 block => Ok(Expr::Block(block)),
175 }
176}
177
178fn range_expression_peek(input: &ParseBuffer) -> Option<ExprRange> {
180 match input.fork().parse::<Expr>().ok()? {
181 Expr::Range(range) => Some(range),
182 _ => None,
183 }
184}
185
186fn advance_until_next_dot2(input: &ParseBuffer) -> syn::Result<()> {
187 input.step(|cursor| {
188 let mut rest = *cursor;
189 let mut first_dot = None;
190 while let Some((tt, next)) = rest.token_tree() {
191 match &tt {
192 TokenTree::Punct(punct) if punct.as_char() == '.' => {
193 if let Some(first_dot) = first_dot {
194 return Ok(((), first_dot));
195 } else {
196 first_dot = if punct.spacing() == Spacing::Joint {
198 Some(rest)
199 } else {
200 None
201 };
202 }
203 }
204 _ => {
205 first_dot = None;
206 }
207 }
208 rest = next;
209 }
210 Err(cursor.error("no `..` found in expression"))
211 })
212}
213
214pub struct PropList(Vec<Prop>);
221impl PropList {
222 pub fn new(props: Vec<Prop>) -> Self {
225 Self(props)
226 }
227
228 fn position(&self, key: &str) -> Option<usize> {
229 self.0.iter().position(|it| it.label.to_string() == key)
230 }
231
232 pub fn get_by_label(&self, key: &str) -> Option<&Prop> {
234 self.0.iter().find(|it| it.label.to_string() == key)
235 }
236
237 pub fn pop(&mut self, key: &str) -> Option<Prop> {
239 self.position(key).map(|i| self.0.remove(i))
240 }
241
242 pub fn pop_unique(&mut self, key: &str) -> syn::Result<Option<Prop>> {
244 let prop = self.pop(key);
245 if prop.is_some() {
246 if let Some(other_prop) = self.get_by_label(key) {
247 return Err(syn::Error::new_spanned(
248 &other_prop.label,
249 format!("`{key}` can only be specified once"),
250 ));
251 }
252 }
253
254 Ok(prop)
255 }
256
257 pub fn into_vec(self) -> Vec<Prop> {
259 self.0
260 }
261
262 fn iter_duplicates(&self) -> impl Iterator<Item = &Prop> {
264 self.0.windows(2).filter_map(|pair| {
265 let (a, b) = (&pair[0], &pair[1]);
266
267 if a.label == b.label {
268 Some(b)
269 } else {
270 None
271 }
272 })
273 }
274
275 pub fn drain_filter(&mut self, filter: impl FnMut(&Prop) -> bool) -> Self {
277 let (drained, others) = self.0.drain(..).partition(filter);
278 self.0 = others;
279 Self(drained)
280 }
281
282 pub fn check_all(&self, f: impl FnMut(&Prop) -> syn::Result<()>) -> syn::Result<()> {
285 crate::join_errors(self.0.iter().map(f).filter_map(Result::err))
286 }
287
288 pub fn check_no_duplicates(&self) -> syn::Result<()> {
290 crate::join_errors(self.iter_duplicates().map(|prop| {
291 syn::Error::new_spanned(
292 &prop.label,
293 format!(
294 "`{}` can only be specified once but is given here again",
295 prop.label
296 ),
297 )
298 }))
299 }
300}
301impl Parse for PropList {
302 fn parse(input: ParseStream) -> syn::Result<Self> {
303 let mut props: Vec<Prop> = Vec::new();
304 while !input.is_empty() && !input.peek(Token![..]) {
306 props.push(input.parse()?);
307 }
308
309 Ok(Self::new(props))
310 }
311}
312impl Deref for PropList {
313 type Target = [Prop];
314
315 fn deref(&self) -> &Self::Target {
316 &self.0
317 }
318}
319
320#[derive(Default)]
321pub struct SpecialProps {
322 pub node_ref: Option<Prop>,
323 pub key: Option<Prop>,
324}
325impl SpecialProps {
326 const KEY_LABEL: &'static str = "key";
327 const REF_LABEL: &'static str = "ref";
328
329 fn pop_from(props: &mut PropList) -> syn::Result<Self> {
330 let node_ref = props.pop_unique(Self::REF_LABEL)?;
331 let key = props.pop_unique(Self::KEY_LABEL)?;
332 Ok(Self { node_ref, key })
333 }
334
335 fn iter(&self) -> impl Iterator<Item = &Prop> {
336 self.node_ref.as_ref().into_iter().chain(self.key.as_ref())
337 }
338
339 pub fn check_all(&self, f: impl FnMut(&Prop) -> syn::Result<()>) -> syn::Result<()> {
342 crate::join_errors(self.iter().map(f).filter_map(Result::err))
343 }
344
345 pub fn wrap_node_ref_attr(&self) -> TokenStream {
346 self.node_ref
347 .as_ref()
348 .map(|attr| {
349 let value = &attr.value;
350 quote_spanned! {value.span().resolved_at(Span::call_site())=>
351 ::yew::html::IntoPropValue::<::yew::html::NodeRef>
352 ::into_prop_value(#value)
353 }
354 })
355 .unwrap_or(quote! { ::std::default::Default::default() })
356 }
357
358 pub fn wrap_key_attr(&self) -> TokenStream {
359 self.key
360 .as_ref()
361 .map(|attr| {
362 let value = attr.value.optimize_literals();
363 quote_spanned! {value.span().resolved_at(Span::call_site())=>
364 ::std::option::Option::Some(
365 ::std::convert::Into::<::yew::virtual_dom::Key>::into(#value)
366 )
367 }
368 })
369 .unwrap_or(quote! { ::std::option::Option::None })
370 }
371}
372
373pub struct Props {
374 pub special: SpecialProps,
375 pub prop_list: PropList,
376}
377impl Parse for Props {
378 fn parse(input: ParseStream) -> syn::Result<Self> {
379 Self::try_from(input.parse::<PropList>()?)
380 }
381}
382impl Deref for Props {
383 type Target = PropList;
384
385 fn deref(&self) -> &Self::Target {
386 &self.prop_list
387 }
388}
389impl DerefMut for Props {
390 fn deref_mut(&mut self) -> &mut Self::Target {
391 &mut self.prop_list
392 }
393}
394
395impl TryFrom<PropList> for Props {
396 type Error = syn::Error;
397
398 fn try_from(mut prop_list: PropList) -> Result<Self, Self::Error> {
399 let special = SpecialProps::pop_from(&mut prop_list)?;
400 Ok(Self { special, prop_list })
401 }
402}