component.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. token, AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result,
  21. Token,
  22. };
  23. #[derive(PartialEq, Eq, Clone, Debug, Hash)]
  24. pub struct Component {
  25. pub name: syn::Path,
  26. pub prop_gen_args: Option<AngleBracketedGenericArguments>,
  27. pub fields: Vec<ComponentField>,
  28. pub children: Vec<BodyNode>,
  29. pub manual_props: Option<Expr>,
  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.to_string() == "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. if stream.peek(token::Brace) {
  81. syn::braced!(content in stream);
  82. } else {
  83. syn::parenthesized!(content in stream);
  84. }
  85. let mut body = Vec::new();
  86. let mut children = Vec::new();
  87. let mut manual_props = None;
  88. while !content.is_empty() {
  89. // if we splat into a component then we're merging properties
  90. if content.peek(Token![..]) {
  91. content.parse::<Token![..]>()?;
  92. manual_props = Some(content.parse::<Expr>()?);
  93. } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
  94. body.push(content.parse::<ComponentField>()?);
  95. } else {
  96. children.push(content.parse::<BodyNode>()?);
  97. }
  98. if content.peek(Token![,]) {
  99. let _ = content.parse::<Token![,]>();
  100. }
  101. }
  102. Ok(Self {
  103. name,
  104. prop_gen_args,
  105. fields: body,
  106. children,
  107. manual_props,
  108. })
  109. }
  110. }
  111. impl ToTokens for Component {
  112. fn to_tokens(&self, tokens: &mut TokenStream2) {
  113. let name = &self.name;
  114. let prop_gen_args = &self.prop_gen_args;
  115. let builder = match &self.manual_props {
  116. Some(manual_props) => {
  117. let mut toks = quote! {
  118. let mut __manual_props = #manual_props;
  119. };
  120. for field in &self.fields {
  121. if field.name == "key" {
  122. // skip keys
  123. } else {
  124. let name = &field.name;
  125. let val = &field.content;
  126. toks.append_all(quote! {
  127. __manual_props.#name = #val;
  128. });
  129. }
  130. }
  131. toks.append_all(quote! {
  132. __manual_props
  133. });
  134. quote! {{
  135. #toks
  136. }}
  137. }
  138. None => {
  139. let mut toks = match prop_gen_args {
  140. Some(gen_args) => quote! { fc_to_builder #gen_args(#name) },
  141. None => quote! { fc_to_builder(#name) },
  142. };
  143. for field in &self.fields {
  144. match field.name.to_string().as_str() {
  145. "key" => {}
  146. _ => toks.append_all(quote! {#field}),
  147. }
  148. }
  149. if !self.children.is_empty() {
  150. let renderer = TemplateRenderer {
  151. roots: &self.children,
  152. };
  153. toks.append_all(quote! {
  154. .children(
  155. Some({
  156. #renderer
  157. })
  158. )
  159. });
  160. }
  161. toks.append_all(quote! {
  162. .build()
  163. });
  164. toks
  165. }
  166. };
  167. let fn_name = self.name.segments.last().unwrap().ident.to_string();
  168. tokens.append_all(quote! {
  169. __cx.component(
  170. #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. __cx.raw_text(#s).0
  195. }),
  196. ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
  197. __cx.event_handler(#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. if name.to_string().starts_with("on") {
  207. let content = ContentField::OnHandlerRaw(input.parse()?);
  208. return Ok(Self { name, content });
  209. }
  210. if name == "key" {
  211. let content = ContentField::Formatted(input.parse()?);
  212. return Ok(Self { name, content });
  213. }
  214. if input.peek(LitStr) {
  215. let forked = input.fork();
  216. let t: LitStr = forked.parse()?;
  217. // the string literal must either be the end of the input or a followed by a comma
  218. if (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
  219. let content = ContentField::Formatted(input.parse()?);
  220. return Ok(Self { name, content });
  221. }
  222. }
  223. if input.peek(LitStr) && input.peek2(LitStr) {
  224. missing_trailing_comma!(input.span());
  225. }
  226. let content = ContentField::ManExpr(input.parse()?);
  227. Ok(Self { name, content })
  228. }
  229. }
  230. impl ToTokens for ComponentField {
  231. fn to_tokens(&self, tokens: &mut TokenStream2) {
  232. let ComponentField { name, content, .. } = self;
  233. tokens.append_all(quote! {
  234. .#name(#content)
  235. })
  236. }
  237. }
  238. fn is_literal_foramtted(lit: &LitStr) -> bool {
  239. let s = lit.value();
  240. let mut chars = s.chars();
  241. while let Some(next) = chars.next() {
  242. if next == '{' {
  243. let nen = chars.next();
  244. if nen != Some('{') {
  245. return true;
  246. }
  247. }
  248. }
  249. false
  250. }