lib.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. #![doc = include_str!("../README.md")]
  2. #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
  3. #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
  4. use proc_macro::TokenStream;
  5. use quote::ToTokens;
  6. use rsx::RenderCallBody;
  7. use syn::parse::Parser;
  8. use syn::punctuated::Punctuated;
  9. use syn::{parse_macro_input, Path, Token};
  10. mod component_body;
  11. mod component_body_deserializers;
  12. mod props;
  13. mod utils;
  14. // mod rsx;
  15. use crate::component_body::ComponentBody;
  16. use crate::component_body_deserializers::component::ComponentDeserializerArgs;
  17. use crate::component_body_deserializers::inline_props::InlinePropsDeserializerArgs;
  18. use dioxus_rsx as rsx;
  19. #[proc_macro]
  20. pub fn format_args_f(input: TokenStream) -> TokenStream {
  21. use rsx::*;
  22. format_args_f_impl(parse_macro_input!(input as IfmtInput))
  23. .unwrap_or_else(|err| err.to_compile_error())
  24. .into()
  25. }
  26. #[proc_macro_derive(Props, attributes(props))]
  27. pub fn derive_typed_builder(input: TokenStream) -> TokenStream {
  28. let input = parse_macro_input!(input as syn::DeriveInput);
  29. match props::impl_my_derive(&input) {
  30. Ok(output) => output.into(),
  31. Err(error) => error.to_compile_error().into(),
  32. }
  33. }
  34. /// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
  35. #[proc_macro]
  36. pub fn rsx(tokens: TokenStream) -> TokenStream {
  37. match syn::parse::<rsx::CallBody>(tokens) {
  38. Err(err) => err.to_compile_error().into(),
  39. Ok(body) => RenderCallBody(body).into_token_stream().into(),
  40. }
  41. }
  42. /// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
  43. ///
  44. /// The render macro automatically renders rsx - making it unhygienic.
  45. #[deprecated(note = "Use `rsx!` instead.")]
  46. #[proc_macro]
  47. pub fn render(tokens: TokenStream) -> TokenStream {
  48. rsx(tokens)
  49. }
  50. /// Derive props for a component within the component definition.
  51. ///
  52. /// This macro provides a simple transformation from `Scope<{}>` to `Scope<P>`,
  53. /// removing some boilerplate when defining props.
  54. ///
  55. /// You don't *need* to use this macro at all, but it can be helpful in cases where
  56. /// you would be repeating a lot of the usual Rust boilerplate.
  57. ///
  58. /// # Example
  59. /// ```rust,ignore
  60. /// #[inline_props]
  61. /// fn app(bob: String) -> Element {
  62. /// rsx!("hello, {bob}"))
  63. /// }
  64. ///
  65. /// // is equivalent to
  66. ///
  67. /// #[derive(PartialEq, Props)]
  68. /// struct AppProps {
  69. /// bob: String,
  70. /// }
  71. ///
  72. /// fn app(cx: Scope<AppProps>) -> Element {
  73. /// rsx!("hello, {bob}"))
  74. /// }
  75. /// ```
  76. #[proc_macro_attribute]
  77. #[deprecated(note = "Use `#[component]` instead.")]
  78. pub fn inline_props(_args: TokenStream, s: TokenStream) -> TokenStream {
  79. let comp_body = parse_macro_input!(s as ComponentBody);
  80. match comp_body.deserialize(InlinePropsDeserializerArgs {}) {
  81. Err(e) => e.to_compile_error().into(),
  82. Ok(output) => output.to_token_stream().into(),
  83. }
  84. }
  85. pub(crate) const COMPONENT_ARG_CASE_CHECK_OFF: &str = "no_case_check";
  86. /// Streamlines component creation.
  87. /// This is the recommended way of creating components,
  88. /// though you might want lower-level control with more advanced uses.
  89. ///
  90. /// # Arguments
  91. /// * `no_case_check` - Doesn't enforce `PascalCase` on your component names.
  92. /// **This will be removed/deprecated in a future update in favor of a more complete Clippy-backed linting system.**
  93. /// The reasoning behind this is that Clippy allows more robust and powerful lints, whereas
  94. /// macros are extremely limited.
  95. ///
  96. /// # Features
  97. /// This attribute:
  98. /// * Enforces that your component uses `PascalCase`.
  99. /// No warnings are generated for the `PascalCase`
  100. /// function name, but everything else will still raise a warning if it's incorrectly `PascalCase`.
  101. /// Does not disable warnings anywhere else, so if you, for example,
  102. /// accidentally don't use `snake_case`
  103. /// for a variable name in the function, the compiler will still warn you.
  104. /// * Automatically uses `#[inline_props]` if there's more than 1 parameter in the function.
  105. /// * Verifies the validity of your component.
  106. /// E.g. if it has a [`Scope`](dioxus_core::Scope) argument.
  107. /// Notes:
  108. /// * This doesn't work 100% of the time, because of macro limitations.
  109. /// * Provides helpful messages if your component is not correct.
  110. /// Possible bugs (please, report these!):
  111. /// * There might be bugs where it incorrectly *denies* validity.
  112. /// This is bad as it means that you can't use the attribute or you have to change the component.
  113. /// * There might be bugs where it incorrectly *confirms* validity.
  114. /// You will still know if the component is invalid once you use it,
  115. /// but the error might be less helpful.
  116. ///
  117. /// # Examples
  118. /// * Without props:
  119. /// ```rust,ignore
  120. /// #[component]
  121. /// fn GreetBob() -> Element {
  122. /// rsx! { "hello, bob" }
  123. /// }
  124. ///
  125. /// // is equivalent to
  126. ///
  127. /// #[allow(non_snake_case)]
  128. /// fn GreetBob() -> Element {
  129. /// #[warn(non_snake_case)]
  130. /// #[inline(always)]
  131. /// fn __dx_inner_comp() -> Element {
  132. /// rsx! { "hello, bob" }
  133. /// }
  134. /// // There's no function call overhead since __dx_inner_comp has the #[inline(always)] attribute,
  135. /// // so don't worry about performance.
  136. /// __dx_inner_comp(cx)
  137. /// }
  138. /// ```
  139. /// * With props:
  140. /// ```rust,ignore
  141. /// #[component(no_case_check)]
  142. /// fn GreetPerson(person: String) -> Element {
  143. /// rsx! { "hello, {person}" }
  144. /// }
  145. ///
  146. /// // is equivalent to
  147. ///
  148. /// #[derive(Props, PartialEq)]
  149. /// #[allow(non_camel_case_types)]
  150. /// struct GreetPersonProps {
  151. /// person: String,
  152. /// }
  153. ///
  154. /// #[allow(non_snake_case)]
  155. /// fn GreetPerson(props: GreetPersonProps>) -> Element {
  156. /// #[warn(non_snake_case)]
  157. /// #[inline(always)]
  158. /// fn __dx_inner_comp(props: GreetPersonProps>e) -> Element {
  159. /// let GreetPersonProps { person } = props;
  160. /// {
  161. /// rsx! { "hello, {person}" }
  162. /// }
  163. /// }
  164. ///
  165. /// __dx_inner_comp(cx)
  166. /// }
  167. /// ```
  168. // TODO: Maybe add an option to input a custom component name through the args.
  169. // I think that's unnecessary, but there might be some scenario where it could be useful.
  170. #[proc_macro_attribute]
  171. pub fn component(args: TokenStream, input: TokenStream) -> TokenStream {
  172. let component_body = parse_macro_input!(input as ComponentBody);
  173. let case_check = match Punctuated::<Path, Token![,]>::parse_terminated.parse(args) {
  174. Err(e) => return e.to_compile_error().into(),
  175. Ok(args) => {
  176. if let Some(first) = args.first() {
  177. !first.is_ident(COMPONENT_ARG_CASE_CHECK_OFF)
  178. } else {
  179. true
  180. }
  181. }
  182. };
  183. match component_body.deserialize(ComponentDeserializerArgs { case_check }) {
  184. Err(e) => e.to_compile_error().into(),
  185. Ok(output) => output.to_token_stream().into(),
  186. }
  187. }