Skip to main content

cheers_ast/
syntax.rs

1use std::collections::BTreeSet;
2
3use syn::{
4    Error, Ident, LitBool, LitChar, LitFloat, LitInt, LitStr, Token, braced,
5    ext::IdentExt,
6    parse::{Parse, ParseStream},
7    token::{Brace, Bracket, Paren},
8};
9
10use crate::{
11    Component, Element, ElementBody, ElementNode, Group, UnquotedName,
12    component::{ComponentAttribute, ComponentDefaultAttributes},
13};
14
15fn ensure_unique_component_attrs(
16    attrs: &[ComponentAttribute],
17    default_attrs: Option<&ComponentDefaultAttributes>,
18) -> Result<(), Error> {
19    let mut seen = BTreeSet::new();
20
21    for attr in attrs.iter().chain(
22        default_attrs
23            .into_iter()
24            .flat_map(|default_attrs| default_attrs.attrs.iter()),
25    ) {
26        let name = attr.name.unraw().to_string();
27        if !seen.insert(name.clone()) {
28            return Err(Error::new_spanned(
29                &attr.name,
30                format!("duplicate component prop `{name}`"),
31            ));
32        }
33    }
34
35    Ok(())
36}
37
38impl Parse for ElementNode {
39    fn parse(input: ParseStream) -> syn::Result<Self> {
40        let lookahead = input.lookahead1();
41
42        if lookahead.peek(Ident::peek_any) {
43            if input.fork().parse::<UnquotedName>()?.is_component() {
44                input.parse().map(Self::Component)
45            } else {
46                input.parse().map(Self::Element)
47            }
48        } else if lookahead.peek(LitStr)
49            || lookahead.peek(LitInt)
50            || lookahead.peek(LitBool)
51            || lookahead.peek(LitFloat)
52            || lookahead.peek(LitChar)
53        {
54            input.parse().map(Self::Literal)
55        } else if lookahead.peek(Token![@]) {
56            input.parse().map(Self::Control)
57        } else if lookahead.peek(Paren) {
58            input.parse().map(Self::Expr)
59        } else if lookahead.peek(Brace) {
60            input.parse().map(Self::Group)
61        } else {
62            Err(lookahead.error())
63        }
64    }
65}
66
67impl Parse for Group<ElementNode> {
68    fn parse(input: ParseStream) -> syn::Result<Self> {
69        let content;
70        let brace_token = braced!(content in input);
71
72        Ok(Self {
73            brace_token,
74            nodes: content.parse()?,
75        })
76    }
77}
78
79impl Parse for Element {
80    fn parse(input: ParseStream) -> syn::Result<Self> {
81        Ok(Self {
82            name: input.parse()?,
83            attrs: {
84                let mut attrs = Vec::new();
85
86                while !(input.peek(Token![;]) || input.peek(Brace)) {
87                    attrs.push(input.parse()?);
88                }
89
90                attrs
91            },
92            body: input.parse()?,
93        })
94    }
95}
96
97impl Parse for ElementBody {
98    fn parse(input: ParseStream) -> syn::Result<Self> {
99        let lookahead = input.lookahead1();
100
101        if lookahead.peek(Brace) {
102            let content;
103            let brace_token = braced!(content in input);
104            Ok(Self::Normal {
105                brace_token,
106                children: content.parse()?,
107            })
108        } else if lookahead.peek(Token![;]) {
109            input
110                .parse::<Token![;]>()
111                .map(|semi_token| Self::Void { semi_token })
112        } else {
113            Err(lookahead.error())
114        }
115    }
116}
117
118impl Parse for Component {
119    fn parse(input: ParseStream) -> syn::Result<Self> {
120        let name = input.parse()?;
121        let mut attrs = Vec::new();
122
123        while !(input.peek(Bracket)
124            || input.peek(Token![..])
125            || input.peek(Token![;])
126            || input.peek(Brace))
127        {
128            attrs.push(input.parse()?);
129        }
130
131        let default_attrs = if input.peek(Bracket) {
132            Some(input.parse::<ComponentDefaultAttributes>()?)
133        } else {
134            None
135        };
136
137        let dotdot = input.parse::<Option<Token![..]>>()?;
138
139        if let (Some(_), Some(dotdot)) = (&default_attrs, &dotdot) {
140            return Err(Error::new_spanned(
141                dotdot,
142                "component optional props `[...]` cannot be combined with `..`",
143            ));
144        }
145
146        ensure_unique_component_attrs(&attrs, default_attrs.as_ref())?;
147
148        Ok(Self {
149            name,
150            attrs,
151            default_attrs,
152            dotdot,
153            body: input.parse()?,
154        })
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use syn::parse_str;
161
162    use crate::Component;
163
164    #[test]
165    fn component_rejects_optional_props_with_dotdot() {
166        let err = match parse_str::<Component>("Badge [] ..;") {
167            Ok(_) => panic!("expected parse error"),
168            Err(err) => err,
169        };
170
171        assert_eq!(
172            err.to_string(),
173            "component optional props `[...]` cannot be combined with `..`"
174        );
175    }
176}