node.rs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. pub fn get_dyn_idx(&self) -> usize {
  113. match self {
  114. BodyNode::Text(text) => text.dyn_idx.get(),
  115. BodyNode::RawExpr(exp) => exp.dyn_idx.get(),
  116. BodyNode::Component(comp) => comp.dyn_idx.get(),
  117. BodyNode::ForLoop(floop) => floop.dyn_idx.get(),
  118. BodyNode::IfChain(chain) => chain.dyn_idx.get(),
  119. BodyNode::Element(_) => panic!("Cannot get dyn_idx for this node"),
  120. }
  121. }
  122. pub fn set_dyn_idx(&self, idx: usize) {
  123. match self {
  124. BodyNode::Text(text) => text.dyn_idx.set(idx),
  125. BodyNode::RawExpr(exp) => exp.dyn_idx.set(idx),
  126. BodyNode::Component(comp) => comp.dyn_idx.set(idx),
  127. BodyNode::ForLoop(floop) => floop.dyn_idx.set(idx),
  128. BodyNode::IfChain(chain) => chain.dyn_idx.set(idx),
  129. BodyNode::Element(_) => panic!("Cannot set dyn_idx for this node"),
  130. }
  131. }
  132. pub fn is_litstr(&self) -> bool {
  133. matches!(self, BodyNode::Text { .. })
  134. }
  135. pub fn span(&self) -> Span {
  136. match self {
  137. BodyNode::Element(el) => el.name.span(),
  138. BodyNode::Component(component) => component.name.span(),
  139. BodyNode::Text(text) => text.input.span(),
  140. BodyNode::RawExpr(exp) => exp.span(),
  141. BodyNode::ForLoop(fl) => fl.for_token.span(),
  142. BodyNode::IfChain(f) => f.if_token.span(),
  143. }
  144. }
  145. pub fn element_children(&self) -> &[BodyNode] {
  146. match self {
  147. BodyNode::Element(el) => &el.children,
  148. _ => panic!("Children not available for this node"),
  149. }
  150. }
  151. pub fn el_name(&self) -> &ElementName {
  152. match self {
  153. BodyNode::Element(el) => &el.name,
  154. _ => panic!("Element name not available for this node"),
  155. }
  156. }
  157. }
  158. #[cfg(test)]
  159. mod tests {
  160. use super::*;
  161. use quote::quote;
  162. #[test]
  163. fn parsing_matches() {
  164. let element = quote! { div { class: "inline-block mr-4", icons::icon_14 {} } };
  165. assert!(matches!(
  166. syn::parse2::<BodyNode>(element).unwrap(),
  167. BodyNode::Element(_)
  168. ));
  169. let text = quote! { "Hello, world!" };
  170. assert!(matches!(
  171. syn::parse2::<BodyNode>(text).unwrap(),
  172. BodyNode::Text(_)
  173. ));
  174. let component = quote! { Component {} };
  175. assert!(matches!(
  176. syn::parse2::<BodyNode>(component).unwrap(),
  177. BodyNode::Component(_)
  178. ));
  179. let raw_expr = quote! { { 1 + 1 } };
  180. assert!(matches!(
  181. syn::parse2::<BodyNode>(raw_expr).unwrap(),
  182. BodyNode::RawExpr(_)
  183. ));
  184. let for_loop = quote! { for item in items {} };
  185. assert!(matches!(
  186. syn::parse2::<BodyNode>(for_loop).unwrap(),
  187. BodyNode::ForLoop(_)
  188. ));
  189. let if_chain = quote! { if cond {} else if cond {} };
  190. assert!(matches!(
  191. syn::parse2::<BodyNode>(if_chain).unwrap(),
  192. BodyNode::IfChain(_)
  193. ));
  194. let match_expr = quote! {
  195. match blah {
  196. val => rsx! { div {} },
  197. other_val => rsx! { div {} }
  198. }
  199. };
  200. assert!(matches!(
  201. syn::parse2::<BodyNode>(match_expr).unwrap(),
  202. BodyNode::RawExpr(_)
  203. ),);
  204. let incomplete_component = quote! {
  205. some::cool::Component
  206. };
  207. assert!(matches!(
  208. syn::parse2::<BodyNode>(incomplete_component).unwrap(),
  209. BodyNode::Component(_)
  210. ),);
  211. }
  212. }