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}