rstml/node/
attribute.rs

1use proc_macro2::TokenStream;
2use quote::ToTokens;
3use syn::{
4    parse::{discouraged::Speculative, Parse, ParseStream},
5    parse_quote,
6    punctuated::Punctuated,
7    spanned::Spanned,
8    token::{Brace, Comma, Paren},
9    Attribute, Expr, Lit, Pat, PatType, Token,
10};
11
12use super::{parse::parse_valid_block_expr, InvalidBlock};
13use crate::{
14    node::{NodeBlock, NodeName},
15    parser::recoverable::{ParseRecoverable, RecoverableContext},
16};
17
18#[derive(Clone, Debug, syn_derive::ToTokens)]
19pub struct AttributeValueExpr {
20    pub token_eq: Token![=],
21    pub value: KVAttributeValue,
22}
23
24#[derive(Clone, Debug, syn_derive::ToTokens)]
25pub enum KVAttributeValue {
26    Expr(Expr),
27    InvalidBraced(InvalidBlock),
28}
29
30impl AttributeValueExpr {
31    ///
32    /// Returns string representation of inner value,
33    /// if value expression contain something that can be treated as displayable
34    /// literal.
35    ///
36    /// Example of displayable literals:
37    /// `"string"`      // string
38    /// `'c'`           // char
39    /// `0x12`, `1231`  // integer - converted to decimal form
40    /// `0.12`          // float point value - converted to decimal form
41    /// `true`, `false` // booleans
42    ///
43    /// Examples of literals that also will be non-displayable:
44    /// `b'a'`     // byte
45    /// `b"asdad"` // byte-string
46    ///
47    /// Examples of non-static non-displayable expressions:
48    /// `{ x + 1}`     // block of code
49    /// `y`            // usage of variable
50    /// `|v| v + 1`    // closure is valid expression too
51    /// `[1, 2, 3]`    // arrays,
52    /// `for/while/if` // any controll flow
53    /// .. and this list can be extended
54    ///
55    /// Adapted from leptos
56    pub fn value_literal_string(&self) -> Option<String> {
57        match &self.value {
58            KVAttributeValue::Expr(Expr::Lit(l)) => match &l.lit {
59                Lit::Str(s) => Some(s.value()),
60                Lit::Char(c) => Some(c.value().to_string()),
61                Lit::Int(i) => Some(i.base10_digits().to_string()),
62                Lit::Float(f) => Some(f.base10_digits().to_string()),
63                Lit::Bool(b) => Some(b.value.to_string()),
64                _ => None,
65            },
66            _ => None,
67        }
68    }
69}
70
71#[derive(Clone, Debug, syn_derive::ToTokens)]
72pub enum KeyedAttributeValue {
73    Binding(FnBinding),
74    Value(AttributeValueExpr),
75    None,
76}
77
78impl KeyedAttributeValue {
79    pub fn to_value(&self) -> Option<&AttributeValueExpr> {
80        match self {
81            KeyedAttributeValue::Value(v) => Some(v),
82            KeyedAttributeValue::None => None,
83            KeyedAttributeValue::Binding(_) => None,
84        }
85    }
86}
87///
88/// Element attribute with fixed key.
89///
90/// Example:
91/// key=value // attribute with ident as value
92/// key // attribute without value
93#[derive(Clone, Debug, syn_derive::ToTokens)]
94pub struct KeyedAttribute {
95    /// Key of the element attribute.
96    pub key: NodeName,
97    /// Value of the element attribute.
98    pub possible_value: KeyedAttributeValue,
99}
100impl KeyedAttribute {
101    ///
102    /// Returns string representation of inner value,
103    /// if value expression contain something that can be treated as displayable
104    /// literal.
105    ///
106    /// Example of displayable literals:
107    /// `"string"`      // string
108    /// `'c'`           // char
109    /// `0x12`, `1231`  // integer - converted to decimal form
110    /// `0.12`          // float point value - converted to decimal form
111    /// `true`, `false` // booleans
112    ///
113    /// Examples of literals that also will be non-displayable:
114    /// `b'a'`     // byte
115    /// `b"asdad"` // byte-string
116    ///
117    /// Examples of non-static non-displayable expressions:
118    /// `{ x + 1}`     // block of code
119    /// `y`            // usage of variable
120    /// `|v| v + 1`    // closure is valid expression too
121    /// `[1, 2, 3]`    // arrays,
122    /// `for/while/if` // any controll flow
123    /// .. and this list can be extended
124    ///
125    /// Adapted from leptos
126    pub fn value_literal_string(&self) -> Option<String> {
127        self.possible_value
128            .to_value()
129            .and_then(|v| v.value_literal_string())
130    }
131
132    pub fn value(&self) -> Option<&Expr> {
133        self.possible_value
134            .to_value()
135            .map(|v| match &v.value {
136                KVAttributeValue::Expr(expr) => Some(expr),
137                KVAttributeValue::InvalidBraced(_) => None,
138            })
139            .flatten()
140    }
141
142    // Checks if error is about eof.
143    // This error is known to report Span::call_site.
144    // Correct them to point to ParseStream
145    pub(crate) fn correct_expr_error_span(error: syn::Error, input: ParseStream) -> syn::Error {
146        let error_str = error.to_string();
147        if error_str.starts_with("unexpected end of input") {
148            let stream = input
149                .parse::<TokenStream>()
150                .expect("BUG: Token stream should always be parsable");
151            return syn::Error::new(
152                stream.span(),
153                format!("failed to parse expression: {}", error),
154            );
155        }
156        error
157    }
158}
159
160/// Represent arguments of closure.
161/// One can use it to represent variable binding from one scope to another.
162#[derive(Clone, Debug)]
163pub struct FnBinding {
164    pub paren: Paren,
165    pub inputs: Punctuated<Pat, Comma>,
166}
167
168// Copy - pasted from syn1 closure argument parsing
169fn closure_arg(input: ParseStream) -> syn::Result<Pat> {
170    let attrs = input.call(Attribute::parse_outer)?;
171    let mut pat: Pat = Pat::parse_single(input)?;
172
173    if input.peek(Token![:]) {
174        Ok(Pat::Type(PatType {
175            attrs,
176            pat: Box::new(pat),
177            colon_token: input.parse()?,
178            ty: input.parse()?,
179        }))
180    } else {
181        match &mut pat {
182            Pat::Ident(pat) => pat.attrs = attrs,
183            Pat::Lit(pat) => pat.attrs = attrs,
184            Pat::Macro(pat) => pat.attrs = attrs,
185            Pat::Or(pat) => pat.attrs = attrs,
186            Pat::Path(pat) => pat.attrs = attrs,
187            Pat::Range(pat) => pat.attrs = attrs,
188            Pat::Reference(pat) => pat.attrs = attrs,
189            Pat::Rest(pat) => pat.attrs = attrs,
190            Pat::Slice(pat) => pat.attrs = attrs,
191            Pat::Struct(pat) => pat.attrs = attrs,
192            Pat::Tuple(pat) => pat.attrs = attrs,
193            Pat::TupleStruct(pat) => pat.attrs = attrs,
194            Pat::Type(_) => unreachable!("BUG: Type handled in if"),
195            Pat::Verbatim(_) => {}
196            Pat::Wild(pat) => pat.attrs = attrs,
197            _ => unreachable!(),
198        }
199        Ok(pat)
200    }
201}
202
203/// Sum type for Dyn and Keyed attributes.
204///
205/// Attributes is stored in opening tags.
206#[derive(Clone, Debug, syn_derive::ToTokens)]
207pub enum NodeAttribute {
208    ///
209    /// Element attribute that is computed from rust code block.
210    ///
211    /// Example:
212    /// `<div {"some-fixed-key"}>` // attribute without value
213    /// that is computed from string
214    Block(NodeBlock),
215    ///
216    /// Element attribute with key, and possible value.
217    /// Value is a valid Rust expression.
218    ///
219    /// Example:
220    /// - `<div attr>`
221    /// - `<div attr = value>`
222    ///
223    /// Value can be also in parens after key, but then it is parsed as closure
224    /// arguments. Example:
225    /// - `<div attr(x)>`
226    /// - `<div attr(x: Type)>`
227    Attribute(KeyedAttribute),
228}
229
230impl ParseRecoverable for KeyedAttribute {
231    fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
232        let key = NodeName::parse(input)
233            .map_err(|e| parser.push_diagnostic(e))
234            .ok()?;
235
236        let possible_value = if input.peek(Paren) {
237            KeyedAttributeValue::Binding(
238                FnBinding::parse(input)
239                    .map_err(|e| parser.push_diagnostic(e))
240                    .ok()?,
241            )
242        } else if input.peek(Token![=]) {
243            let eq = input
244                .parse::<Token![=]>()
245                .map_err(|e| parser.push_diagnostic(e))
246                .ok()?;
247            if input.is_empty() {
248                parser.push_diagnostic(syn::Error::new(eq.span(), "missing attribute value"));
249                return None;
250            }
251
252            let fork = input.fork();
253
254            let rs = match parse_valid_block_expr(parser, &fork) {
255                Ok(vbl) => {
256                    input.advance_to(&fork);
257                    KVAttributeValue::Expr(parse_quote!(#vbl))
258                }
259
260                Err(err) if input.fork().peek(Brace) && parser.config().recover_block => {
261                    parser.push_diagnostic(err);
262                    let ivb = parser.parse_simple(input)?;
263                    KVAttributeValue::InvalidBraced(ivb)
264                }
265                Err(_) => {
266                    let res = fork
267                        .parse::<Expr>()
268                        .map_err(|e| {
269                            // if we stuck on end of input, span that is created will be call_site,
270                            // so we need to correct it, in order to
271                            // make it more IDE friendly.
272                            if fork.is_empty() {
273                                KeyedAttribute::correct_expr_error_span(e, input)
274                            } else {
275                                e
276                            }
277                        })
278                        .map_err(|e| parser.push_diagnostic(e))
279                        .ok()?;
280
281                    input.advance_to(&fork);
282                    KVAttributeValue::Expr(res)
283                }
284            };
285
286            KeyedAttributeValue::Value(AttributeValueExpr {
287                token_eq: eq,
288                value: rs,
289            })
290        } else {
291            KeyedAttributeValue::None
292        };
293
294        Some(KeyedAttribute {
295            key,
296            possible_value,
297        })
298    }
299}
300
301impl ParseRecoverable for NodeAttribute {
302    fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
303        let node = if input.peek(Brace) {
304            NodeAttribute::Block(parser.parse_recoverable(input)?)
305        } else {
306            NodeAttribute::Attribute(parser.parse_recoverable(input)?)
307        };
308        Some(node)
309    }
310}
311
312impl Parse for FnBinding {
313    fn parse(stream: ParseStream) -> syn::Result<Self> {
314        let content;
315        let paren = syn::parenthesized!(content in stream);
316        let inputs = Punctuated::parse_terminated_with(&content, closure_arg)?;
317        Ok(Self { paren, inputs })
318    }
319}
320
321impl ToTokens for FnBinding {
322    fn to_tokens(&self, tokens: &mut TokenStream) {
323        self.paren.surround(tokens, |tokens| {
324            self.inputs.to_tokens(tokens);
325        })
326    }
327}