node.rs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. use crate::innerlude::*;
  2. use proc_macro2::{Span, TokenStream as TokenStream2};
  3. use quote::ToTokens;
  4. use syn::{
  5. ext::IdentExt,
  6. parse::{Parse, ParseStream},
  7. spanned::Spanned,
  8. token::{self},
  9. Ident, LitStr, Result, Token,
  10. };
  11. #[derive(PartialEq, Eq, Clone, Debug)]
  12. pub enum BodyNode {
  13. /// div {}
  14. Element(Element),
  15. /// Component {}
  16. Component(Component),
  17. /// "text {formatted}"
  18. Text(TextNode),
  19. /// {expr}
  20. RawExpr(ExprNode),
  21. /// for item in items {}
  22. ForLoop(ForLoop),
  23. /// if cond {} else if cond {} (else {}?)
  24. IfChain(IfChain),
  25. }
  26. impl Parse for BodyNode {
  27. fn parse(stream: ParseStream) -> Result<Self> {
  28. if stream.peek(LitStr) {
  29. return Ok(BodyNode::Text(stream.parse()?));
  30. }
  31. // Transform for loops into into_iter calls
  32. if stream.peek(Token![for]) {
  33. return Ok(BodyNode::ForLoop(stream.parse()?));
  34. }
  35. // Transform unterminated if statements into terminated optional if statements
  36. if stream.peek(Token![if]) {
  37. return Ok(BodyNode::IfChain(stream.parse()?));
  38. }
  39. // Match statements are special but have no special arm syntax
  40. // we could allow arm syntax if we wanted.
  41. //
  42. // And it might even backwards compatible? - I think it is with the right fallback
  43. // -> parse as bodynode (BracedRawExpr will kick in on multiline arms)
  44. // -> if that fails parse as an expr, since that arm might be a one-liner
  45. //
  46. // ```
  47. // match expr {
  48. // val => rsx! { div {} },
  49. // other_val => rsx! { div {} }
  50. // }
  51. // ```
  52. if stream.peek(Token![match]) {
  53. return Ok(BodyNode::RawExpr(stream.parse()?));
  54. }
  55. // Raw expressions need to be wrapped in braces - let RawBracedExpr handle partial expansion
  56. if stream.peek(token::Brace) {
  57. return Ok(BodyNode::RawExpr(stream.parse()?));
  58. }
  59. // If there's an ident immediately followed by a dash, it's a web component
  60. // Web components support no namespacing, so just parse it as an element directly
  61. if stream.peek(Ident::peek_any) && stream.peek2(Token![-]) {
  62. return Ok(BodyNode::Element(stream.parse::<Element>()?));
  63. }
  64. // this is an Element if the path is:
  65. //
  66. // - one ident
  67. // - 1st char is lowercase
  68. // - no underscores (reserved for components)
  69. // And it is not:
  70. // - the start of a path with components
  71. //
  72. // example:
  73. // div {}
  74. if stream.peek(Ident::peek_any) && !stream.peek2(Token![::]) {
  75. let ident = parse_raw_ident(&stream.fork()).unwrap();
  76. let el_name = ident.to_string();
  77. let first_char = el_name.chars().next().unwrap();
  78. if first_char.is_ascii_lowercase() && !el_name.contains('_') {
  79. return Ok(BodyNode::Element(stream.parse::<Element>()?));
  80. }
  81. }
  82. // Otherwise this should be Component, allowed syntax:
  83. // - syn::Path
  84. // - PathArguments can only apper in last segment
  85. // - followed by `{` or `(`, note `(` cannot be used with one ident
  86. //
  87. // example
  88. // Div {}
  89. // ::Div {}
  90. // crate::Div {}
  91. // component {} <-- already handled by elements
  92. // ::component {}
  93. // crate::component{}
  94. // Input::<InputProps<'_, i32> {}
  95. // crate::Input::<InputProps<'_, i32> {}
  96. Ok(BodyNode::Component(stream.parse()?))
  97. }
  98. }
  99. impl ToTokens for BodyNode {
  100. fn to_tokens(&self, tokens: &mut TokenStream2) {
  101. match self {
  102. BodyNode::Element(ela) => ela.to_tokens(tokens),
  103. BodyNode::RawExpr(exp) => exp.to_tokens(tokens),
  104. BodyNode::Text(txt) => txt.to_tokens(tokens),
  105. BodyNode::ForLoop(floop) => floop.to_tokens(tokens),
  106. BodyNode::Component(comp) => comp.to_tokens(tokens),
  107. BodyNode::IfChain(ifchain) => ifchain.to_tokens(tokens),
  108. }
  109. }
  110. }
  111. impl BodyNode {
  112. /// Convert this BodyNode into a TemplateNode.
  113. ///
  114. /// dioxus-core uses this to understand templates at compiletime
  115. #[cfg(feature = "hot_reload")]
  116. pub fn to_template_node<Ctx: crate::HotReloadingContext>(&self) -> dioxus_core::TemplateNode {
  117. use dioxus_core::TemplateNode;
  118. match self {
  119. BodyNode::Element(el) => {
  120. let rust_name = el.name.to_string();
  121. let (tag, namespace) =
  122. Ctx::map_element(&rust_name).unwrap_or((intern(rust_name.as_str()), None));
  123. TemplateNode::Element {
  124. tag,
  125. namespace,
  126. children: intern(
  127. el.children
  128. .iter()
  129. .map(|c| c.to_template_node::<Ctx>())
  130. .collect::<Vec<_>>(),
  131. ),
  132. attrs: intern(
  133. el.merged_attributes
  134. .iter()
  135. .map(|attr| attr.to_template_attribute::<Ctx>())
  136. .collect::<Vec<_>>(),
  137. ),
  138. }
  139. }
  140. BodyNode::Text(text) => text.to_template_node(),
  141. BodyNode::RawExpr(exp) => TemplateNode::Dynamic {
  142. id: exp.dyn_idx.get(),
  143. },
  144. BodyNode::Component(comp) => TemplateNode::Dynamic {
  145. id: comp.dyn_idx.get(),
  146. },
  147. BodyNode::ForLoop(floop) => TemplateNode::Dynamic {
  148. id: floop.dyn_idx.get(),
  149. },
  150. BodyNode::IfChain(chain) => TemplateNode::Dynamic {
  151. id: chain.dyn_idx.get(),
  152. },
  153. }
  154. }
  155. pub fn get_dyn_idx(&self) -> usize {
  156. match self {
  157. BodyNode::Text(text) => text.dyn_idx.get(),
  158. BodyNode::RawExpr(exp) => exp.dyn_idx.get(),
  159. BodyNode::Component(comp) => comp.dyn_idx.get(),
  160. BodyNode::ForLoop(floop) => floop.dyn_idx.get(),
  161. BodyNode::IfChain(chain) => chain.dyn_idx.get(),
  162. BodyNode::Element(_) => panic!("Cannot get dyn_idx for this node"),
  163. }
  164. }
  165. pub fn set_dyn_idx(&self, idx: usize) {
  166. match self {
  167. BodyNode::Text(text) => text.dyn_idx.set(idx),
  168. BodyNode::RawExpr(exp) => exp.dyn_idx.set(idx),
  169. BodyNode::Component(comp) => comp.dyn_idx.set(idx),
  170. BodyNode::ForLoop(floop) => floop.dyn_idx.set(idx),
  171. BodyNode::IfChain(chain) => chain.dyn_idx.set(idx),
  172. BodyNode::Element(_) => panic!("Cannot set dyn_idx for this node"),
  173. }
  174. }
  175. pub fn is_litstr(&self) -> bool {
  176. matches!(self, BodyNode::Text { .. })
  177. }
  178. pub fn span(&self) -> Span {
  179. match self {
  180. BodyNode::Element(el) => el.name.span(),
  181. BodyNode::Component(component) => component.name.span(),
  182. BodyNode::Text(text) => text.input.span(),
  183. BodyNode::RawExpr(exp) => exp.span(),
  184. BodyNode::ForLoop(fl) => fl.for_token.span(),
  185. BodyNode::IfChain(f) => f.if_token.span(),
  186. }
  187. }
  188. pub fn element_children(&self) -> &[BodyNode] {
  189. match self {
  190. BodyNode::Element(el) => &el.children,
  191. _ => panic!("Children not available for this node"),
  192. }
  193. }
  194. pub fn el_name(&self) -> &ElementName {
  195. match self {
  196. BodyNode::Element(el) => &el.name,
  197. _ => panic!("Element name not available for this node"),
  198. }
  199. }
  200. }
  201. #[cfg(test)]
  202. mod tests {
  203. use super::*;
  204. use quote::quote;
  205. #[test]
  206. fn parsing_matches() {
  207. let element = quote! { div { class: "inline-block mr-4", icons::icon_14 {} } };
  208. assert!(matches!(
  209. syn::parse2::<BodyNode>(element).unwrap(),
  210. BodyNode::Element(_)
  211. ));
  212. let text = quote! { "Hello, world!" };
  213. assert!(matches!(
  214. syn::parse2::<BodyNode>(text).unwrap(),
  215. BodyNode::Text(_)
  216. ));
  217. let component = quote! { Component {} };
  218. assert!(matches!(
  219. syn::parse2::<BodyNode>(component).unwrap(),
  220. BodyNode::Component(_)
  221. ));
  222. let raw_expr = quote! { { 1 + 1 } };
  223. assert!(matches!(
  224. syn::parse2::<BodyNode>(raw_expr).unwrap(),
  225. BodyNode::RawExpr(_)
  226. ));
  227. let for_loop = quote! { for item in items {} };
  228. assert!(matches!(
  229. syn::parse2::<BodyNode>(for_loop).unwrap(),
  230. BodyNode::ForLoop(_)
  231. ));
  232. let if_chain = quote! { if cond {} else if cond {} };
  233. assert!(matches!(
  234. syn::parse2::<BodyNode>(if_chain).unwrap(),
  235. BodyNode::IfChain(_)
  236. ));
  237. let match_expr = quote! {
  238. match blah {
  239. val => rsx! { div {} },
  240. other_val => rsx! { div {} }
  241. }
  242. };
  243. assert!(matches!(
  244. syn::parse2::<BodyNode>(match_expr).unwrap(),
  245. BodyNode::RawExpr(_)
  246. ),);
  247. let incomplete_component = quote! {
  248. some::cool::Component
  249. };
  250. assert!(matches!(
  251. syn::parse2::<BodyNode>(incomplete_component).unwrap(),
  252. BodyNode::Component(_)
  253. ),);
  254. }
  255. }