123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- #![doc = include_str!("../README.md")]
- #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
- #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
- use proc_macro::TokenStream;
- use quote::ToTokens;
- use rsx::RenderCallBody;
- use syn::parse::Parser;
- use syn::punctuated::Punctuated;
- use syn::{parse_macro_input, Path, Token};
- mod component_body;
- mod component_body_deserializers;
- mod props;
- mod utils;
- // mod rsx;
- use crate::component_body::ComponentBody;
- use crate::component_body_deserializers::component::ComponentDeserializerArgs;
- use crate::component_body_deserializers::inline_props::InlinePropsDeserializerArgs;
- use dioxus_rsx as rsx;
- #[proc_macro]
- pub fn format_args_f(input: TokenStream) -> TokenStream {
- use rsx::*;
- format_args_f_impl(parse_macro_input!(input as IfmtInput))
- .unwrap_or_else(|err| err.to_compile_error())
- .into()
- }
- #[proc_macro_derive(Props, attributes(props))]
- pub fn derive_typed_builder(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as syn::DeriveInput);
- match props::impl_my_derive(&input) {
- Ok(output) => output.into(),
- Err(error) => error.to_compile_error().into(),
- }
- }
- /// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
- #[proc_macro]
- pub fn rsx(s: TokenStream) -> TokenStream {
- match syn::parse::<rsx::CallBody>(s) {
- Err(err) => err.to_compile_error().into(),
- Ok(body) => body.to_token_stream().into(),
- }
- }
- /// The render! macro makes it easy for developers to write jsx-style markup in their components.
- ///
- /// The render macro automatically renders rsx - making it unhygienic.
- #[proc_macro]
- pub fn render(s: TokenStream) -> TokenStream {
- match syn::parse::<rsx::CallBody>(s) {
- Err(err) => err.to_compile_error().into(),
- Ok(body) => RenderCallBody(body).into_token_stream().into(),
- }
- }
- /// Derive props for a component within the component definition.
- ///
- /// This macro provides a simple transformation from `Scope<{}>` to `Scope<P>`,
- /// removing some boilerplate when defining props.
- ///
- /// You don't *need* to use this macro at all, but it can be helpful in cases where
- /// you would be repeating a lot of the usual Rust boilerplate.
- ///
- /// # Example
- /// ```rust,ignore
- /// #[inline_props]
- /// fn app(cx: Scope, bob: String) -> Element {
- /// cx.render(rsx!("hello, {bob}"))
- /// }
- ///
- /// // is equivalent to
- ///
- /// #[derive(PartialEq, Props)]
- /// struct AppProps {
- /// bob: String,
- /// }
- ///
- /// fn app(cx: Scope<AppProps>) -> Element {
- /// cx.render(rsx!("hello, {bob}"))
- /// }
- /// ```
- #[proc_macro_attribute]
- #[deprecated(note = "Use `#[component]` instead.")]
- pub fn inline_props(_args: TokenStream, s: TokenStream) -> TokenStream {
- let comp_body = parse_macro_input!(s as ComponentBody);
- match comp_body.deserialize(InlinePropsDeserializerArgs {}) {
- Err(e) => e.to_compile_error().into(),
- Ok(output) => output.to_token_stream().into(),
- }
- }
- pub(crate) const COMPONENT_ARG_CASE_CHECK_OFF: &str = "no_case_check";
- /// Streamlines component creation.
- /// This is the recommended way of creating components,
- /// though you might want lower-level control with more advanced uses.
- ///
- /// # Arguments
- /// * `no_case_check` - Doesn't enforce `PascalCase` on your component names.
- /// **This will be removed/deprecated in a future update in favor of a more complete Clippy-backed linting system.**
- /// The reasoning behind this is that Clippy allows more robust and powerful lints, whereas
- /// macros are extremely limited.
- ///
- /// # Features
- /// This attribute:
- /// * Enforces that your component uses `PascalCase`.
- /// No warnings are generated for the `PascalCase`
- /// function name, but everything else will still raise a warning if it's incorrectly `PascalCase`.
- /// Does not disable warnings anywhere else, so if you, for example,
- /// accidentally don't use `snake_case`
- /// for a variable name in the function, the compiler will still warn you.
- /// * Automatically uses `#[inline_props]` if there's more than 1 parameter in the function.
- /// * Verifies the validity of your component.
- /// E.g. if it has a [`Scope`](dioxus_core::Scope) argument.
- /// Notes:
- /// * This doesn't work 100% of the time, because of macro limitations.
- /// * Provides helpful messages if your component is not correct.
- /// Possible bugs (please, report these!):
- /// * There might be bugs where it incorrectly *denies* validity.
- /// This is bad as it means that you can't use the attribute or you have to change the component.
- /// * There might be bugs where it incorrectly *confirms* validity.
- /// You will still know if the component is invalid once you use it,
- /// but the error might be less helpful.
- ///
- /// # Examples
- /// * Without props:
- /// ```rust,ignore
- /// #[component]
- /// fn GreetBob(cx: Scope) -> Element {
- /// render! { "hello, bob" }
- /// }
- ///
- /// // is equivalent to
- ///
- /// #[allow(non_snake_case)]
- /// fn GreetBob(cx: Scope) -> Element {
- /// #[warn(non_snake_case)]
- /// #[inline(always)]
- /// fn __dx_inner_comp(cx: Scope) -> Element {
- /// render! { "hello, bob" }
- /// }
- /// // There's no function call overhead since __dx_inner_comp has the #[inline(always)] attribute,
- /// // so don't worry about performance.
- /// __dx_inner_comp(cx)
- /// }
- /// ```
- /// * With props:
- /// ```rust,ignore
- /// #[component(no_case_check)]
- /// fn GreetPerson(cx: Scope, person: String) -> Element {
- /// render! { "hello, {person}" }
- /// }
- ///
- /// // is equivalent to
- ///
- /// #[derive(Props, PartialEq)]
- /// #[allow(non_camel_case_types)]
- /// struct GreetPersonProps {
- /// person: String,
- /// }
- ///
- /// #[allow(non_snake_case)]
- /// fn GreetPerson<'a>(cx: Scope<'a, GreetPersonProps>) -> Element {
- /// #[warn(non_snake_case)]
- /// #[inline(always)]
- /// fn __dx_inner_comp<'a>(cx: Scope<'a, GreetPersonProps>e) -> Element {
- /// let GreetPersonProps { person } = &cx.props;
- /// {
- /// render! { "hello, {person}" }
- /// }
- /// }
- ///
- /// __dx_inner_comp(cx)
- /// }
- /// ```
- // TODO: Maybe add an option to input a custom component name through the args.
- // I think that's unnecessary, but there might be some scenario where it could be useful.
- #[proc_macro_attribute]
- pub fn component(args: TokenStream, input: TokenStream) -> TokenStream {
- let component_body = parse_macro_input!(input as ComponentBody);
- let case_check = match Punctuated::<Path, Token![,]>::parse_terminated.parse(args) {
- Err(e) => return e.to_compile_error().into(),
- Ok(args) => {
- if let Some(first) = args.first() {
- !first.is_ident(COMPONENT_ARG_CASE_CHECK_OFF)
- } else {
- true
- }
- }
- };
- match component_body.deserialize(ComponentDeserializerArgs { case_check }) {
- Err(e) => e.to_compile_error().into(),
- Ok(output) => output.to_token_stream().into(),
- }
- }
|