lib.rs 6.9 KB

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