1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Expr, ExprPath, Fields};
4use syn_rsx::{parse2, Node, NodeName};
5
6#[proc_macro]
12pub fn rsx(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
13 let tree = parse2(tokens.into()).expect("Failed to parse RSX");
14
15 transform_children(tree).into()
16}
17
18fn transform_node(node: Node) -> TokenStream {
19 match node {
20 Node::Element(element) => match &element.name {
21 NodeName::Block(_) => transform_tag(element.name, element.attributes, element.children),
22 NodeName::Path(path) => {
23 if let Some(ident) = path.path.get_ident() {
24 if ident
25 .to_string()
26 .chars()
27 .nth(0)
28 .expect("Cannot render empty identifier")
29 .is_lowercase()
30 {
31 transform_tag(element.name, element.attributes, element.children)
32 } else {
33 transform_component(path, element.attributes, element.children)
34 }
35 } else {
36 transform_component(path, element.attributes, element.children)
37 }
38 }
39 NodeName::Punctuated(_) => {
40 transform_tag(element.name, element.attributes, element.children)
41 }
42 },
43 Node::Attribute(_) => {
44 panic!("Invalid attribute")
45 }
46 Node::Block(block) => {
47 let value: &Expr = block.value.as_ref();
48 quote! {
49 #value.into()
50 }
51 }
52 Node::Comment(_) => TokenStream::new(),
53 Node::Doctype(_) => TokenStream::new(),
54 Node::Fragment(fragment) => transform_children(fragment.children),
55 Node::Text(text) => {
56 let _text: &Expr = text.value.as_ref();
57 quote! { #_text.to_string().into() }
58 }
59 }
60}
61
62fn transform_attributes(attributes: Vec<Node>) -> TokenStream {
63 let mut attrs = TokenStream::new();
64 attributes
65 .into_iter()
66 .map(|attribute| match attribute {
67 Node::Attribute(attribute) => {
68 let name = attribute.key.to_string();
69
70 if name == "ref" {
71 let _value: Expr = attribute.value.expect("Refs must be Arc<DomRef>").into();
72 quote! {
73 .dom_ref(#_value)
74 }
75 } else if name.starts_with("on_") {
76 let _value: Expr = attribute.value.expect("Callbacks must be functions").into();
77 let name = name[3..].to_string();
78 quote! {
79 .on(#name, #_value)
80 }
81 } else {
82 if let Some(value) = attribute.value {
83 let _value: Expr = value.into();
84 quote! {
85 .attr(#name, #_value)
86 }
87 } else {
88 quote! {
89 .attr(#name, true)
90 }
91 }
92 }
93 }
94 _ => panic!("not an attribute"),
95 })
96 .for_each(|attr| attrs.extend(attr));
97 attrs
98}
99
100fn transform_props(attributes: Vec<Node>) -> TokenStream {
101 let mut props = TokenStream::new();
102 attributes
103 .into_iter()
104 .map(|attribute| match attribute {
105 Node::Attribute(attribute) => {
106 let name = attribute.key.to_string();
107
108 if let Some(value) = attribute.value {
109 let value: Expr = value.into();
110 quote! {
111 .#name(#value)
112 }
113 } else {
114 quote! {
115 .#name(true)
116 }
117 }
118 }
119 _ => panic!("not an attribute"),
120 })
121 .for_each(|attr| props.extend(attr));
122 props
123}
124
125fn transform_children(nodes: Vec<Node>) -> proc_macro2::TokenStream {
126 let nodes = nodes.into_iter().map(transform_node);
127 let len = nodes.len();
128
129 quote! {
130 {
131 let mut children = Vec::with_capacity(#len);
132 #(children.push(#nodes);)*
133 children.into()
134 }
135 }
136}
137
138fn transform_component(tag: &ExprPath, attributes: Vec<Node>, children: Vec<Node>) -> TokenStream {
139 let attributes = transform_props(attributes);
140 let children = if children.is_empty() {
141 TokenStream::new()
142 } else {
143 let children = transform_children(children);
144 quote! {
145 .children(#children)
146 }
147 };
148 quote! {
149 <#tag>::new()#children #attributes.build().into()
150 }
151}
152
153fn transform_tag(tag: NodeName, attributes: Vec<Node>, children: Vec<Node>) -> TokenStream {
154 let attributes = transform_attributes(attributes);
155 let children = if children.is_empty() {
156 quote! {
157 .into()
158 }
159 } else {
160 let children = transform_children(children);
161 quote! {
162 .children(#children)
163 }
164 };
165 let tag = tag.to_string();
166 quote! {
167 tag(#tag)#attributes.build()#children
168 }
169}
170
171#[proc_macro_derive(NoopBuilder)]
172pub fn derive_noop_builder(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
173 let DeriveInput { ident, data, .. } = parse_macro_input!(item);
174
175 if let Data::Struct(DataStruct { fields, .. }) = data {
176 assert_eq!(
177 fields,
178 Fields::Unit,
179 "NoopBuilder can only be derived for unit structs"
180 );
181 } else {
182 panic!("NoopBuilder can only be derived for unit structs")
183 }
184
185 quote! {
186 impl #ident {
187 fn new() -> Self {
188 Self
189 }
190
191 fn build(self) -> Self {
192 self
193 }
194 }
195 }
196 .into()
197}
198
199#[cfg(test)]
200mod tests {
201 use quote::quote;
202
203 #[test]
204 fn transform_text() {
205 let actual = super::transform_node(
206 syn_rsx::parse2(quote! { "hello world" })
207 .unwrap()
208 .into_iter()
209 .nth(0)
210 .unwrap(),
211 );
212 assert_eq!(
213 actual.to_string(),
214 "\"hello world\" . to_string () . into ()"
215 );
216 }
217
218 #[test]
219 fn pass_ref() {
220 let actual = super::transform_node(
221 syn_rsx::parse2(quote! { <div ref={my_ref}></div> })
222 .unwrap()
223 .into_iter()
224 .nth(0)
225 .unwrap(),
226 );
227 assert_eq!(
228 actual.to_string(),
229 "tag (\"div\") . dom_ref ({ my_ref }) . build () . into ()"
230 );
231 }
232
233 #[test]
234 fn render_component() {
235 let actual = super::transform_node(
236 syn_rsx::parse2(quote! {
237 <MyComponent number_prop=123 boolean_prop>
238 <div id="child" />
239 </MyComponent>
240 })
241 .unwrap()
242 .into_iter()
243 .nth(0)
244 .unwrap(),
245 );
246 assert_eq!(actual.to_string(), "< MyComponent > :: new () . children ({ let mut children = Vec :: with_capacity (1usize) ; children . push (tag (\"div\") . attr (\"id\" , \"child\") . build () . into ()) ; children . into () }) . \"number_prop\" (123) . \"boolean_prop\" (true) . build () . into ()")
247 }
248}