component.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. //! Parse components into the VNode::Component variant
  2. //!
  3. //! Uses the regular robust RsxBlock parser and then validates the component, emitting errors as
  4. //! diagnostics. This was refactored from a straightforward parser to this validation approach so
  5. //! that we can emit errors as diagnostics instead of returning results.
  6. //!
  7. //! Using this approach we can provide *much* better errors as well as partial expansion wherever
  8. //! possible.
  9. //!
  10. //! It does lead to the code actually being larger than it was before, but it should be much easier
  11. //! to work with and extend. To add new syntax, we add it to the RsxBlock parser and then add a
  12. //! validation step here. This does make using the component as a source of truth not as good, but
  13. //! oddly enoughly, we want the tree to actually be capable of being technically invalid. This is not
  14. //! usual for building in Rust - you want strongly typed things to be valid - but in this case, we
  15. //! want to accept all sorts of malformed input and then provide the best possible error messages.
  16. //!
  17. //! If you're generally parsing things, you'll just want to parse and then check if it's valid.
  18. use crate::innerlude::*;
  19. use proc_macro2::TokenStream as TokenStream2;
  20. use proc_macro2_diagnostics::SpanDiagnosticExt;
  21. use quote::{quote, ToTokens, TokenStreamExt};
  22. use std::{collections::HashSet, vec};
  23. use syn::{
  24. parse::{Parse, ParseStream},
  25. spanned::Spanned,
  26. token, AngleBracketedGenericArguments, Expr, PathArguments, Result,
  27. };
  28. #[derive(PartialEq, Eq, Clone, Debug)]
  29. pub struct Component {
  30. pub name: syn::Path,
  31. pub generics: Option<AngleBracketedGenericArguments>,
  32. pub fields: Vec<Attribute>,
  33. pub component_literal_dyn_idx: Vec<DynIdx>,
  34. pub spreads: Vec<Spread>,
  35. pub brace: Option<token::Brace>,
  36. pub children: TemplateBody,
  37. pub dyn_idx: DynIdx,
  38. pub diagnostics: Diagnostics,
  39. }
  40. impl Parse for Component {
  41. fn parse(input: ParseStream) -> Result<Self> {
  42. let mut name = input.parse::<syn::Path>()?;
  43. let generics = normalize_path(&mut name);
  44. if !input.peek(token::Brace) {
  45. return Ok(Self::empty(name, generics));
  46. };
  47. let RsxBlock {
  48. attributes: fields,
  49. children,
  50. brace,
  51. spreads,
  52. diagnostics,
  53. } = input.parse::<RsxBlock>()?;
  54. let literal_properties_count = fields
  55. .iter()
  56. .filter(|attr| matches!(attr.value, AttributeValue::AttrLiteral(_)))
  57. .count();
  58. let component_literal_dyn_idx = vec![DynIdx::default(); literal_properties_count];
  59. let mut component = Self {
  60. dyn_idx: DynIdx::default(),
  61. children: TemplateBody::new(children),
  62. name,
  63. generics,
  64. fields,
  65. brace: Some(brace),
  66. component_literal_dyn_idx,
  67. spreads,
  68. diagnostics,
  69. };
  70. // We've received a valid rsx block, but it's not necessarily a valid component
  71. // validating it will dump diagnostics into the output
  72. component.validate_component_path();
  73. component.validate_fields();
  74. component.validate_component_spread();
  75. Ok(component)
  76. }
  77. }
  78. impl ToTokens for Component {
  79. fn to_tokens(&self, tokens: &mut TokenStream2) {
  80. let Self { name, generics, .. } = self;
  81. // Create props either from manual props or from the builder approach
  82. let props = self.create_props();
  83. // Make sure we emit any errors
  84. let diagnostics = &self.diagnostics;
  85. tokens.append_all(quote! {
  86. dioxus_core::DynamicNode::Component({
  87. // todo: ensure going through the trait actually works
  88. // we want to avoid importing traits
  89. // use dioxus_core::prelude::Properties;
  90. use dioxus_core::prelude::Properties;
  91. let __comp = ({
  92. #props
  93. }).into_vcomponent(
  94. #name #generics,
  95. );
  96. #diagnostics
  97. __comp
  98. })
  99. })
  100. }
  101. }
  102. impl Component {
  103. // Make sure this a proper component path (uppercase ident, a path, or contains an underscorea)
  104. // This should be validated by the RsxBlock parser when it peeks bodynodes
  105. fn validate_component_path(&mut self) {
  106. let path = &self.name;
  107. // First, ensure the path is not a single lowercase ident with no underscores
  108. if path.segments.len() == 1 {
  109. let seg = path.segments.first().unwrap();
  110. if seg.ident.to_string().chars().next().unwrap().is_lowercase()
  111. && !seg.ident.to_string().contains('_')
  112. {
  113. self.diagnostics.push(seg.ident.span().error(
  114. "Component names must be uppercase, contain an underscore, or abe a path.",
  115. ));
  116. }
  117. }
  118. // ensure path segments doesn't have PathArguments, only the last
  119. // segment is allowed to have one.
  120. if path
  121. .segments
  122. .iter()
  123. .take(path.segments.len() - 1)
  124. .any(|seg| seg.arguments != PathArguments::None)
  125. {
  126. self.diagnostics.push(path.span().error(
  127. "Component names must not have path arguments. Only the last segment is allowed to have one.",
  128. ));
  129. }
  130. // ensure last segment only have value of None or AngleBracketed
  131. if !matches!(
  132. path.segments.last().unwrap().arguments,
  133. PathArguments::None | PathArguments::AngleBracketed(_)
  134. ) {
  135. self.diagnostics.push(
  136. path.span()
  137. .error("Component names must have no arguments or angle bracketed arguments."),
  138. );
  139. }
  140. }
  141. // Make sure the spread argument is being used as props spreading
  142. fn validate_component_spread(&mut self) {
  143. // Next, ensure that there's only one spread argument in the attributes *and* it's the last one
  144. for spread in self.spreads.iter().skip(1) {
  145. self.diagnostics.push(
  146. spread
  147. .expr
  148. .span()
  149. .error("Only one set of manual props is allowed for a component."),
  150. );
  151. }
  152. }
  153. pub fn get_key(&self) -> Option<&AttributeValue> {
  154. self.fields.iter().find_map(|attr| match &attr.name {
  155. AttributeName::BuiltIn(key) if key == "key" => Some(&attr.value),
  156. _ => None,
  157. })
  158. }
  159. /// Ensure there's no duplicate props - this will be a compile error but we can move it to a
  160. /// diagnostic, thankfully
  161. ///
  162. /// Also ensure there's no stringly typed props
  163. fn validate_fields(&mut self) {
  164. let mut seen = HashSet::new();
  165. for field in self.fields.iter() {
  166. match &field.name {
  167. AttributeName::Custom(name) => self.diagnostics.push(
  168. name.span()
  169. .error("Custom attributes are not supported for Components. Only known attributes are allowed."),
  170. ),
  171. AttributeName::BuiltIn(k) => {
  172. if !seen.contains(k) {
  173. seen.insert(k);
  174. } else {
  175. self.diagnostics.push(
  176. k.span()
  177. .error("Duplicate prop field found. Only one prop field per name is allowed."),
  178. );
  179. }
  180. },
  181. AttributeName::Spread(_) => {
  182. unreachable!("Spread attributes should be handled in the spread validation step.")
  183. }
  184. }
  185. }
  186. }
  187. /// Create the tokens we'll use for the props of the component
  188. ///
  189. /// todo: don't create the tokenstream from scratch and instead dump it into the existing streama
  190. fn create_props(&self) -> TokenStream2 {
  191. let manual_props = self.manual_props();
  192. let name = &self.name;
  193. let generics = &self.generics;
  194. let mut tokens = if let Some(props) = manual_props.as_ref() {
  195. quote! { let mut __manual_props = #props; }
  196. } else {
  197. quote! { fc_to_builder(#name #generics) }
  198. };
  199. for (name, value) in self.make_field_idents() {
  200. if manual_props.is_some() {
  201. tokens.append_all(quote! { __manual_props.#name = #value; })
  202. } else {
  203. tokens.append_all(quote! { .#name(#value) })
  204. }
  205. }
  206. if !self.children.is_empty() {
  207. let children = &self.children;
  208. if manual_props.is_some() {
  209. tokens.append_all(quote! { __manual_props.children = { #children }; })
  210. } else {
  211. tokens.append_all(quote! { .children( { #children } ) })
  212. }
  213. }
  214. if manual_props.is_some() {
  215. tokens.append_all(quote! { __manual_props })
  216. } else {
  217. tokens.append_all(quote! { .build() })
  218. }
  219. tokens
  220. }
  221. fn manual_props(&self) -> Option<&Expr> {
  222. self.spreads.first().map(|spread| &spread.expr)
  223. }
  224. fn make_field_idents(&self) -> Vec<(TokenStream2, TokenStream2)> {
  225. let mut dynamic_literal_index = 0;
  226. self.fields
  227. .iter()
  228. .filter_map(move |attr| {
  229. let Attribute { name, value, .. } = attr;
  230. let attr = match name {
  231. AttributeName::BuiltIn(k) => {
  232. if k == "key" {
  233. return None;
  234. }
  235. quote! { #k }
  236. }
  237. AttributeName::Custom(_) => return None,
  238. AttributeName::Spread(_) => return None,
  239. };
  240. let release_value = value.to_token_stream();
  241. // In debug mode, we try to grab the value from the dynamic literal pool if possible
  242. let value = if let AttributeValue::AttrLiteral(literal) = &value {
  243. let idx = self.component_literal_dyn_idx[dynamic_literal_index].get();
  244. dynamic_literal_index += 1;
  245. let debug_value = quote! { __dynamic_literal_pool.component_property(#idx, &*__template_read, #literal) };
  246. quote! {
  247. {
  248. #[cfg(debug_assertions)]
  249. {
  250. #debug_value
  251. }
  252. #[cfg(not(debug_assertions))]
  253. {
  254. #release_value
  255. }
  256. }
  257. }
  258. } else {
  259. release_value
  260. };
  261. Some((attr, value))
  262. })
  263. .collect()
  264. }
  265. fn empty(name: syn::Path, generics: Option<AngleBracketedGenericArguments>) -> Self {
  266. let mut diagnostics = Diagnostics::new();
  267. diagnostics.push(
  268. name.span()
  269. .error("Components must have a body")
  270. .help("Components must have a body, for example `Component {}`"),
  271. );
  272. Component {
  273. name,
  274. generics,
  275. brace: None,
  276. fields: vec![],
  277. spreads: vec![],
  278. children: TemplateBody::new(vec![]),
  279. component_literal_dyn_idx: vec![],
  280. dyn_idx: DynIdx::default(),
  281. diagnostics,
  282. }
  283. }
  284. }
  285. /// Normalize the generics of a path
  286. ///
  287. /// Ensure there's a `::` after the last segment if there are generics
  288. fn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {
  289. let seg = name.segments.last_mut()?;
  290. let mut generics = match seg.arguments.clone() {
  291. PathArguments::AngleBracketed(args) => {
  292. seg.arguments = PathArguments::None;
  293. Some(args)
  294. }
  295. _ => None,
  296. };
  297. if let Some(generics) = generics.as_mut() {
  298. use syn::Token;
  299. generics.colon2_token = Some(Token![::](proc_macro2::Span::call_site()));
  300. }
  301. generics
  302. }
  303. /// Ensure we can parse a component
  304. #[test]
  305. fn parses() {
  306. let input = quote! {
  307. MyComponent {
  308. key: "value {something}",
  309. prop: "value",
  310. ..props,
  311. div {
  312. "Hello, world!"
  313. }
  314. }
  315. };
  316. let component: Component = syn::parse2(input).unwrap();
  317. dbg!(component);
  318. let input_without_manual_props = quote! {
  319. MyComponent {
  320. key: "value {something}",
  321. prop: "value",
  322. div { "Hello, world!" }
  323. }
  324. };
  325. let component: Component = syn::parse2(input_without_manual_props).unwrap();
  326. dbg!(component);
  327. }
  328. /// Ensure we reject invalid forms
  329. ///
  330. /// Maybe want to snapshot the errors?
  331. #[test]
  332. fn rejects() {
  333. let input = quote! {
  334. myComponent {
  335. key: "value",
  336. prop: "value",
  337. prop: "other",
  338. ..props,
  339. ..other_props,
  340. div {
  341. "Hello, world!"
  342. }
  343. }
  344. };
  345. let component: Component = syn::parse2(input).unwrap();
  346. dbg!(component.diagnostics);
  347. }
  348. #[test]
  349. fn to_tokens_properly() {
  350. let input = quote! {
  351. MyComponent {
  352. key: "value {something}",
  353. prop: "value",
  354. prop: "value",
  355. prop: "value",
  356. prop: "value",
  357. prop: 123,
  358. ..props,
  359. div { "Hello, world!" }
  360. }
  361. };
  362. let component: Component = syn::parse2(input).unwrap();
  363. println!("{}", component.to_token_stream());
  364. }
  365. #[test]
  366. fn to_tokens_no_manual_props() {
  367. let input_without_manual_props = quote! {
  368. MyComponent {
  369. key: "value {something}",
  370. named: "value {something}",
  371. prop: "value",
  372. count: 1,
  373. div { "Hello, world!" }
  374. }
  375. };
  376. let component: Component = syn::parse2(input_without_manual_props).unwrap();
  377. println!("{}", component.to_token_stream().pretty_unparse());
  378. }
  379. #[test]
  380. fn generics_params() {
  381. let input_without_children = quote! {
  382. Outlet::<R> {}
  383. };
  384. let component: crate::CallBody = syn::parse2(input_without_children).unwrap();
  385. println!("{}", component.to_token_stream().pretty_unparse());
  386. }
  387. #[test]
  388. fn generics_no_fish() {
  389. let name = quote! { Outlet<R> };
  390. let mut p = syn::parse2::<syn::Path>(name).unwrap();
  391. let generics = normalize_path(&mut p);
  392. assert!(generics.is_some());
  393. let input_without_children = quote! {
  394. div {
  395. Component<Generic> {}
  396. }
  397. };
  398. let component: BodyNode = syn::parse2(input_without_children).unwrap();
  399. println!("{}", component.to_token_stream().pretty_unparse());
  400. }
  401. #[test]
  402. fn fmt_passes_properly() {
  403. let input = quote! {
  404. Link { to: Route::List, class: "pure-button", "Go back" }
  405. };
  406. let component: Component = syn::parse2(input).unwrap();
  407. println!("{}", component.to_token_stream().pretty_unparse());
  408. }
  409. #[test]
  410. fn incomplete_components() {
  411. let input = quote::quote! {
  412. some::cool::Component
  413. };
  414. let _parsed: Component = syn::parse2(input).unwrap();
  415. let input = quote::quote! {
  416. some::cool::C
  417. };
  418. let _parsed: syn::Path = syn::parse2(input).unwrap();
  419. }