component.rs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. //! Parse components into the VNode::Component variant
  2. //! ==========================================
  3. //!
  4. //! We can be reasonably sure that whatever enters this parsing path is in the right format.
  5. //! This feature must support
  6. //! - [x] Namespaced components
  7. //! - [x] Fields
  8. //! - [x] Componentbuilder synax
  9. //! - [x] Optional commas
  10. //! - [ ] Children
  11. //! - [ ] Keys
  12. //! - [ ] Properties spreading with with `..` syntax
  13. use super::*;
  14. use proc_macro2::TokenStream as TokenStream2;
  15. use quote::{quote, ToTokens, TokenStreamExt};
  16. use syn::{
  17. ext::IdentExt,
  18. parse::{Parse, ParseBuffer, ParseStream},
  19. spanned::Spanned,
  20. AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
  21. };
  22. #[derive(PartialEq, Eq, Clone, Debug, Hash)]
  23. pub struct Component {
  24. pub name: syn::Path,
  25. pub prop_gen_args: Option<AngleBracketedGenericArguments>,
  26. pub fields: Vec<ComponentField>,
  27. pub children: Vec<BodyNode>,
  28. pub manual_props: Option<Expr>,
  29. pub brace: syn::token::Brace,
  30. }
  31. impl Component {
  32. pub fn validate_component_path(path: &syn::Path) -> Result<()> {
  33. // ensure path segments doesn't have PathArguments, only the last
  34. // segment is allowed to have one.
  35. if path
  36. .segments
  37. .iter()
  38. .take(path.segments.len() - 1)
  39. .any(|seg| seg.arguments != PathArguments::None)
  40. {
  41. component_path_cannot_have_arguments!(path.span());
  42. }
  43. // ensure last segment only have value of None or AngleBracketed
  44. if !matches!(
  45. path.segments.last().unwrap().arguments,
  46. PathArguments::None | PathArguments::AngleBracketed(_)
  47. ) {
  48. invalid_component_path!(path.span());
  49. }
  50. Ok(())
  51. }
  52. pub fn key(&self) -> Option<&IfmtInput> {
  53. match self
  54. .fields
  55. .iter()
  56. .find(|f| f.name == "key")
  57. .map(|f| &f.content)
  58. {
  59. Some(ContentField::Formatted(fmt)) => Some(fmt),
  60. _ => None,
  61. }
  62. }
  63. }
  64. impl Parse for Component {
  65. fn parse(stream: ParseStream) -> Result<Self> {
  66. let mut name = stream.parse::<syn::Path>()?;
  67. Component::validate_component_path(&name)?;
  68. // extract the path arguments from the path into prop_gen_args
  69. let prop_gen_args = name.segments.last_mut().and_then(|seg| {
  70. if let PathArguments::AngleBracketed(args) = seg.arguments.clone() {
  71. seg.arguments = PathArguments::None;
  72. Some(args)
  73. } else {
  74. None
  75. }
  76. });
  77. let content: ParseBuffer;
  78. // if we see a `{` then we have a block
  79. // else parse as a function-like call
  80. let brace = syn::braced!(content in stream);
  81. let mut fields = Vec::new();
  82. let mut children = Vec::new();
  83. let mut manual_props = None;
  84. while !content.is_empty() {
  85. // if we splat into a component then we're merging properties
  86. if content.peek(Token![..]) {
  87. content.parse::<Token![..]>()?;
  88. manual_props = Some(content.parse::<Expr>()?);
  89. } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
  90. fields.push(content.parse::<ComponentField>()?);
  91. } else {
  92. children.push(content.parse::<BodyNode>()?);
  93. }
  94. if content.peek(Token![,]) {
  95. let _ = content.parse::<Token![,]>();
  96. }
  97. }
  98. Ok(Self {
  99. name,
  100. prop_gen_args,
  101. fields,
  102. children,
  103. manual_props,
  104. brace,
  105. })
  106. }
  107. }
  108. impl ToTokens for Component {
  109. fn to_tokens(&self, tokens: &mut TokenStream2) {
  110. let name = &self.name;
  111. let prop_gen_args = &self.prop_gen_args;
  112. let builder = match &self.manual_props {
  113. Some(manual_props) => {
  114. let mut toks = quote! {
  115. let mut __manual_props = #manual_props;
  116. };
  117. for field in &self.fields {
  118. if field.name == "key" {
  119. // skip keys
  120. } else {
  121. let name = &field.name;
  122. let val = &field.content;
  123. toks.append_all(quote! {
  124. __manual_props.#name = #val;
  125. });
  126. }
  127. }
  128. toks.append_all(quote! {
  129. __manual_props
  130. });
  131. quote! {{
  132. #toks
  133. }}
  134. }
  135. None => {
  136. let mut toks = match prop_gen_args {
  137. Some(gen_args) => quote! { fc_to_builder(#name #gen_args) },
  138. None => quote! { fc_to_builder(#name) },
  139. };
  140. for field in &self.fields {
  141. match field.name.to_string().as_str() {
  142. "key" => {}
  143. _ => toks.append_all(quote! {#field}),
  144. }
  145. }
  146. if !self.children.is_empty() {
  147. let renderer: TemplateRenderer = TemplateRenderer {
  148. roots: &self.children,
  149. location: None,
  150. };
  151. toks.append_all(quote! {
  152. .children(
  153. Some({ #renderer })
  154. )
  155. });
  156. }
  157. toks.append_all(quote! {
  158. .build()
  159. });
  160. toks
  161. }
  162. };
  163. let fn_name = self.name.segments.last().unwrap().ident.to_string();
  164. let gen_name = match &self.prop_gen_args {
  165. Some(gen) => quote! { #name #gen },
  166. None => quote! { #name },
  167. };
  168. tokens.append_all(quote! {
  169. ::dioxus::core::DynamicNode::Component(::dioxus::core::VComponent::new(
  170. #gen_name,
  171. #builder,
  172. #fn_name
  173. ))
  174. })
  175. }
  176. }
  177. // the struct's fields info
  178. #[derive(PartialEq, Eq, Clone, Debug, Hash)]
  179. pub struct ComponentField {
  180. pub name: Ident,
  181. pub content: ContentField,
  182. }
  183. #[derive(PartialEq, Eq, Clone, Debug, Hash)]
  184. pub enum ContentField {
  185. ManExpr(Expr),
  186. Formatted(IfmtInput),
  187. OnHandlerRaw(Expr),
  188. }
  189. impl ToTokens for ContentField {
  190. fn to_tokens(&self, tokens: &mut TokenStream2) {
  191. match self {
  192. ContentField::ManExpr(e) => e.to_tokens(tokens),
  193. ContentField::Formatted(s) => tokens.append_all(quote! {
  194. #s.to_string()
  195. }),
  196. ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
  197. EventHandler::new(#e)
  198. }),
  199. }
  200. }
  201. }
  202. impl Parse for ComponentField {
  203. fn parse(input: ParseStream) -> Result<Self> {
  204. let name = Ident::parse_any(input)?;
  205. input.parse::<Token![:]>()?;
  206. let content = {
  207. if name.to_string().starts_with("on") {
  208. ContentField::OnHandlerRaw(input.parse()?)
  209. } else if name == "key" {
  210. let content = ContentField::Formatted(input.parse()?);
  211. return Ok(Self { name, content });
  212. } else if input.peek(LitStr) {
  213. let forked = input.fork();
  214. let t: LitStr = forked.parse()?;
  215. // the string literal must either be the end of the input or a followed by a comma
  216. if (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
  217. ContentField::Formatted(input.parse()?)
  218. } else {
  219. ContentField::ManExpr(input.parse()?)
  220. }
  221. } else {
  222. ContentField::ManExpr(input.parse()?)
  223. }
  224. };
  225. if input.peek(LitStr) || input.peek(Ident) {
  226. missing_trailing_comma!(content.span());
  227. }
  228. Ok(Self { name, content })
  229. }
  230. }
  231. impl ToTokens for ComponentField {
  232. fn to_tokens(&self, tokens: &mut TokenStream2) {
  233. let ComponentField { name, content, .. } = self;
  234. tokens.append_all(quote! {
  235. .#name(#content)
  236. })
  237. }
  238. }
  239. fn is_literal_foramtted(lit: &LitStr) -> bool {
  240. let s = lit.value();
  241. let mut chars = s.chars();
  242. while let Some(next) = chars.next() {
  243. if next == '{' {
  244. let nen = chars.next();
  245. if nen != Some('{') {
  246. return true;
  247. }
  248. }
  249. }
  250. false
  251. }