Browse Source

Chore #2028: radically clean up core-macro

Jonathan Kelley 1 năm trước cách đây
mục cha
commit
052fd774cf

+ 20 - 0
packages/core-macro/.vscode/settings.json

@@ -0,0 +1,20 @@
+{
+    "rust-analyzer.check.workspace": false,
+    // "rust-analyzer.check.command": "check alsjdlaskdjljkasd",
+    "rust-analyzer.check.allTargets": false,
+    "rust-analyzer.cargo.buildScripts.enable": true,
+    "rust-analyzer.cargo.buildScripts.overrideCommand": [
+        "cargo",
+        "check",
+        "--quiet",
+        // "--package",
+        // "dioxus-core-macro",
+        "--message-format",
+        "json",
+        "--all-targets"
+    ],
+    "rust-analyzer.cargo.buildScripts.rebuildOnSave": false,
+    "rust-analyzer.cargo.buildScripts.invocationLocation": "root",
+    "rust-analyzer.cargo.buildScripts.invocationStrategy": "once",
+    // "rust-analyzer.check.command": "check --package dioxus-core-macro"
+}

+ 358 - 0
packages/core-macro/src/component.rs

@@ -0,0 +1,358 @@
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::parse::{Parse, ParseStream};
+use syn::spanned::Spanned;
+use syn::*;
+
+/// General struct for parsing a component body.
+/// However, because it's ambiguous, it does not implement [`ToTokens`](quote::to_tokens::ToTokens).
+///
+/// Refer to the [module documentation](crate::component_body) for more.
+pub struct ComponentBody {
+    pub item_fn: ItemFn,
+}
+
+impl Parse for ComponentBody {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let item_fn: ItemFn = input.parse()?;
+        validate_component_fn_signature(&item_fn)?;
+        Ok(Self { item_fn })
+    }
+}
+
+impl ToTokens for ComponentBody {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let comp_fn = self.comp_fn();
+
+        let props_struct = match self.item_fn.sig.inputs.is_empty() {
+            true => quote! {},
+            false => {
+                let doc = format!("Properties for the [`{}`] component.", &comp_fn.sig.ident);
+                let props_struct = self.props_struct();
+                quote! {
+                    #[doc = #doc]
+                    #props_struct
+                }
+            }
+        };
+
+        tokens.append_all(quote! {
+            #props_struct
+
+            #[allow(non_snake_case)]
+            #comp_fn
+        });
+    }
+}
+
+impl ComponentBody {
+    // build a new item fn, transforming the original item fn
+    fn comp_fn(&self) -> ItemFn {
+        let ComponentBody { item_fn, .. } = self;
+        let ItemFn {
+            attrs,
+            vis,
+            sig,
+            block,
+        } = item_fn;
+        let Signature {
+            inputs,
+            ident: fn_ident,
+            generics,
+            output: fn_output,
+            asyncness,
+            ..
+        } = sig;
+        let Generics { where_clause, .. } = generics;
+
+        // We generate a struct with the same name as the component but called `Props`
+        let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
+
+        let struct_field_names = inputs.iter().filter_map(strip_mutability);
+        let (_, ty_generics, _) = generics.split_for_impl();
+        let props_docs = self.props_docs(inputs.iter().skip(1).collect());
+
+        let props_ident = match inputs.is_empty() {
+            true => quote! {},
+            false => quote! { mut __props: #struct_ident #ty_generics },
+        };
+
+        let expanded_struct = match inputs.is_empty() {
+            true => quote! {},
+            false => quote! { let #struct_ident { #(#struct_field_names),* } = __props; },
+        };
+
+        parse_quote! {
+            #(#attrs)*
+            #(#props_docs)*
+            #asyncness #vis fn #fn_ident #generics (#props_ident) #fn_output #where_clause {
+                #expanded_struct
+                #block
+            }
+        }
+    }
+
+    // Build the props struct
+    fn props_struct(&self) -> ItemStruct {
+        let ComponentBody { item_fn, .. } = &self;
+        let ItemFn { vis, sig, .. } = item_fn;
+        let Signature {
+            inputs,
+            ident,
+            generics,
+            ..
+        } = sig;
+
+        let struct_fields = inputs.iter().map(move |f| make_prop_struct_fields(f, vis));
+        let struct_ident = Ident::new(&format!("{ident}Props"), ident.span());
+
+        parse_quote! {
+            #[derive(Props, Clone, PartialEq)]
+            #[allow(non_camel_case_types)]
+            #vis struct #struct_ident #generics
+            { #(#struct_fields),* }
+        }
+    }
+
+    fn props_docs(&self, inputs: Vec<&FnArg>) -> Vec<Attribute> {
+        let fn_ident = &self.item_fn.sig.ident;
+
+        if inputs.len() <= 1 {
+            return Vec::new();
+        }
+
+        let arg_docs = inputs
+            .iter()
+            .filter_map(|f| match f {
+                FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
+                FnArg::Typed(pt) => {
+                    let arg_doc = pt
+                        .attrs
+                        .iter()
+                        .filter_map(|attr| {
+                            // TODO: Error reporting
+                            // Check if the path of the attribute is "doc"
+                            if !is_attr_doc(attr) {
+                                return None;
+                            };
+
+                            let Meta::NameValue(meta_name_value) = &attr.meta else {
+                                return None;
+                            };
+
+                            let Expr::Lit(doc_lit) = &meta_name_value.value else {
+                                return None;
+                            };
+
+                            let Lit::Str(doc_lit_str) = &doc_lit.lit else {
+                                return None;
+                            };
+
+                            Some(doc_lit_str.value())
+                        })
+                        .fold(String::new(), |mut doc, next_doc_line| {
+                            doc.push('\n');
+                            doc.push_str(&next_doc_line);
+                            doc
+                        });
+
+                    Some((
+                        &pt.pat,
+                        &pt.ty,
+                        pt.attrs.iter().find_map(|attr| {
+                            if attr.path() != &parse_quote!(deprecated) {
+                                return None;
+                            }
+
+                            let res = crate::utils::DeprecatedAttribute::from_meta(&attr.meta);
+
+                            match res {
+                                Err(e) => panic!("{}", e.to_string()),
+                                Ok(v) => Some(v),
+                            }
+                        }),
+                        arg_doc,
+                    ))
+                }
+            })
+            .collect::<Vec<_>>();
+
+        let mut props_docs = Vec::with_capacity(5);
+        let props_def_link = fn_ident.to_string() + "Props";
+        let header =
+            format!("# Props\n*For details, see the [props struct definition]({props_def_link}).*");
+
+        props_docs.push(parse_quote! {
+            #[doc = #header]
+        });
+
+        for (arg_name, arg_type, deprecation, input_arg_doc) in arg_docs {
+            let arg_name = arg_name.into_token_stream().to_string();
+            let arg_type = crate::utils::format_type_string(arg_type);
+
+            let input_arg_doc = keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\n')
+                .replace("\n\n", "</p><p>");
+            let prop_def_link = format!("{props_def_link}::{arg_name}");
+            let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link}) : `{arg_type}`");
+
+            if let Some(deprecation) = deprecation {
+                arg_doc.push_str("<p>👎 Deprecated");
+
+                if let Some(since) = deprecation.since {
+                    arg_doc.push_str(&format!(" since {since}"));
+                }
+
+                if let Some(note) = deprecation.note {
+                    let note = keep_up_to_n_consecutive_chars(&note, 1, '\n').replace('\n', " ");
+                    let note = keep_up_to_n_consecutive_chars(&note, 1, '\t').replace('\t', " ");
+
+                    arg_doc.push_str(&format!(": {note}"));
+                }
+
+                arg_doc.push_str("</p>");
+
+                if !input_arg_doc.is_empty() {
+                    arg_doc.push_str("<hr/>");
+                }
+            }
+
+            if !input_arg_doc.is_empty() {
+                arg_doc.push_str(&format!("<p>{input_arg_doc}</p>"));
+            }
+
+            props_docs.push(parse_quote! {
+                #[doc = #arg_doc]
+            });
+        }
+
+        props_docs
+    }
+}
+
+fn make_prop_struct_fields(f: &FnArg, vis: &Visibility) -> TokenStream {
+    match f {
+        FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
+        FnArg::Typed(pt) => {
+            let arg_pat = match pt.pat.as_ref() {
+                // rip off mutability
+                Pat::Ident(f) => {
+                    let mut f = f.clone();
+                    f.mutability = None;
+                    quote! { #f }
+                }
+                a => quote! { #a },
+            };
+
+            let arg_colon = &pt.colon_token;
+            let arg_ty = &pt.ty; // Type
+            let arg_attrs = &pt.attrs; // Attributes
+
+            quote! {
+                #(#arg_attrs)
+                *
+                #vis #arg_pat #arg_colon #arg_ty
+            }
+        }
+    }
+}
+
+fn strip_mutability(f: &FnArg) -> Option<TokenStream> {
+    match f {
+        FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
+        FnArg::Typed(pt) => {
+            let pat = &pt.pat;
+
+            let mut pat = pat.clone();
+
+            // rip off mutability, but still write it out eventually
+            if let Pat::Ident(ref mut pat_ident) = pat.as_mut() {
+                pat_ident.mutability = None;
+            }
+
+            Some(quote!(mut  #pat))
+        }
+    }
+}
+
+/// Checks if the attribute is a `#[doc]` attribute.
+fn is_attr_doc(attr: &Attribute) -> bool {
+    attr.path() == &parse_quote!(doc)
+}
+
+fn keep_up_to_n_consecutive_chars(
+    input: &str,
+    n_of_consecutive_chars_allowed: usize,
+    target_char: char,
+) -> String {
+    let mut output = String::new();
+    let mut prev_char: Option<char> = None;
+    let mut consecutive_count = 0;
+
+    for c in input.chars() {
+        match prev_char {
+            Some(prev) if c == target_char && prev == target_char => {
+                if consecutive_count < n_of_consecutive_chars_allowed {
+                    output.push(c);
+                    consecutive_count += 1;
+                }
+            }
+            _ => {
+                output.push(c);
+                prev_char = Some(c);
+                consecutive_count = 1;
+            }
+        }
+    }
+
+    output
+}
+
+fn validate_component_fn_signature(item_fn: &ItemFn) -> Result<()> {
+    // Do some validation....
+    // 1. Ensure the component returns *something*
+    if item_fn.sig.output == ReturnType::Default {
+        return Err(Error::new(
+            item_fn.sig.output.span(),
+            "Must return a <dioxus_core::Element>".to_string(),
+        ));
+    }
+
+    // 2. make sure there's no lifetimes on the component - we don't know how to handle those
+    if item_fn.sig.generics.lifetimes().count() > 0 {
+        return Err(Error::new(
+            item_fn.sig.generics.span(),
+            "Lifetimes are not supported in components".to_string(),
+        ));
+    }
+
+    // 3. we can't handle async components
+    if item_fn.sig.asyncness.is_some() {
+        return Err(Error::new(
+            item_fn.sig.asyncness.span(),
+            "Async components are not supported".to_string(),
+        ));
+    }
+
+    // 4. we can't handle const components
+    if item_fn.sig.constness.is_some() {
+        return Err(Error::new(
+            item_fn.sig.constness.span(),
+            "Const components are not supported".to_string(),
+        ));
+    }
+
+    // 5. no receiver parameters
+    if item_fn
+        .sig
+        .inputs
+        .iter()
+        .any(|f| matches!(f, FnArg::Receiver(_)))
+    {
+        return Err(Error::new(
+            item_fn.sig.inputs.span(),
+            "Receiver parameters are not supported".to_string(),
+        ));
+    }
+
+    Ok(())
+}

+ 0 - 204
packages/core-macro/src/component_body/mod.rs

@@ -1,204 +0,0 @@
-//! This module is used for parsing a component function into a struct that is subsequently
-//! deserialized into something useful using deserializer arguments.
-//!
-//! Let's break that down with a term glossary and examples which show usage and implementing.
-//!
-//! # Glossary
-//! * `component body` - The [`ComponentBody`] struct. It's used to parse a component function [`proc_macro::TokenStream`]
-//! to a reusable struct that deserializers use to modify the token stream.
-//! * `deserializer` - A struct that deserializes the [`ComponentBody`] into a [`DeserializerOutput`].
-//! It implements the [`DeserializerArgs`] trait, but as you can see, it's called "DeserializerArgs",
-//! not "Deserializer". Why?
-//! Because "args" makes more sense to the caller of [`ComponentBody::deserialize`], which
-//! takes an [`DeserializerArgs`] argument. However, you can think of "DeserializerArgs" as the deserializer.
-//! * `deserializer output` - A struct that implements the [`DeserializerOutput`] trait.
-//! This struct is what enables deserializers to use each other, since it contains the fields that
-//! a deserializer needs to turn a token stream to a different token stream.
-//! This means a deserializer can get the output of another deserializer, and use that output,
-//! thereby using the functionality of a different deserializer.
-//! This struct also implements [`ToTokens`], which means that this is the final stage of the whole process.
-//!
-//! # Examples
-//! *Not all imports might be included.*
-//!
-//! ## Usage in a procedural macro attribute
-//! ```rs,ignore
-//! use proc_macro::TokenStream;
-//!
-//! // Some documentation. You can reuse this in your deserializer structs.
-//! /// This attribute changes the name of a component function to whatever the first argument is.
-//! #[proc_macro_attribute]
-//! pub fn name_changer(args: TokenStream, input: TokenStream) -> TokenStream {
-//!     // Parse the component body.
-//!     let component_body = parse_macro_input!(input as ComponentBody);
-//!
-//!     // Parse the first argument, which is going to be the components new name.
-//!     let new_name: String = match Punctuated::<Path, Token![,]>::parse_terminated.parse(args) {
-//!         Err(e) => return e.to_compile_error().into(), // Convert to a compile error and return
-//!         Ok(args) => {
-//!             // If the argument exists, then convert it to a string
-//!             if let Some(first) = args.first() {
-//!                 first.to_token_stream().to_string()
-//!             } else {
-//!                 // If the argument doesn't exist, return an error with the appropriate message.
-//!                 // The "span" is the location of some code.
-//!                 // The error occurred in the "args" token stream, so point the error there.
-//!                 return Error::new(args.span(), "No new name provided").to_compile_error().into();
-//!             }
-//!         }
-//!     };
-//!
-//!     let new_name = &*new_name;
-//!
-//!     // Deserialize the component body to an output with the given args.
-//!     let output = component_body.deserialize(NameChangerDeserializerArgs { new_name });
-//!
-//!     // Error handling like before, except now you're ready to return the final value.
-//!     match output {
-//!         Err(e) => e.to_compile_error().into(),
-//!         Ok(output) => output.to_token_stream().into(),
-//!     }
-//! }
-//! ```
-//! ## Using the macro in Dioxus code:
-//! ```rs
-//! use your_proc_macro_library::name_changer;
-//! use dioxus::prelude::*;
-//!
-//! #[name_changer(CoolName)]
-//! pub fn LameName() -> Element {
-//!     rsx! { "I want a cool name!" }
-//! }
-//!
-//! pub fn App() -> Element {
-//!     rsx! { CoolName {} } // Renders: "I want a cool name!"
-//! }
-//! ```
-//! ## Implementing a component body deserializer
-//! ```rs
-//! use syn::{Result, ItemFn, Signature, Ident};
-//! use quote::quote;
-//!
-//! // Create a list of arguments.
-//! // If there was no args, just make it empty. The "args" struct is also the deserializer struct.
-//! // For the docs, you can basically copy paste this text and replace "name_changer" with your macro path.
-//! // Although unfortunately, the link does not work
-//! // Just make sure that your macro is well documented.
-//! /// The args and deserializing implementation for the [`name_changer`] macro.
-//! #[derive(Clone)]
-//! pub struct NameChangerDeserializerArgs<'a> {
-//!     pub new_name: &'a str,
-//! }
-//!
-//! // Create an output struct.
-//! // The ItemFn represents a modified component function.
-//! // To read what fields should be here, check out the `DeserializerOutput` struct docs.
-//! // For the docs, you can basically copy paste this text and replace "name_changer" with your macro path.
-//! // Just make sure that your macro is well documented.
-//! /// The output fields and [`ToTokens`] implementation for the [`name_changer`] macro.
-//! #[derive(Clone)]
-//! pub struct NameChangerDeserializerOutput {
-//!     pub comp_fn: ItemFn,
-//! }
-//!
-//! // Implement `ToTokens`, which is forced by `DeserializerOutput`.
-//! // This will usually be very simple like this, even for complex deserializers.
-//! // That's because of the way the `DeserializerOutput` is designed.
-//! impl ToTokens for NameChangerDeserializerOutput {
-//!     fn to_tokens(&self, tokens: &mut TokenStream) {
-//!         let comp_fn = &self.comp_fn;
-//!
-//!         tokens.append_all(quote! {
-//!             #comp_fn
-//!         });
-//!     }
-//! }
-//!
-//! impl DeserializerOutput for NameChangerDeserializerOutput {}
-//!
-//! // Implement `DeserializerArgs`. This is the core part of deserializers.
-//! impl<'a> DeserializerArgs<NameChangerDeserializerOutput> for NameChangerDeserializerArgs<'a> {
-//!     fn to_output(&self, component_body: &ComponentBody) -> Result<NameChangerDeserializerOutput> {
-//!         let old_fn = &component_body.item_fn;
-//!         let old_sig = &old_fn.sig;
-//!
-//!         // For more complex uses, you will probably use `quote::parse_quote!` in combination with
-//!         // creating the structs manually.
-//!         // However, create the structs manually if you can.
-//!         // It's more reliable, because you only modify a certain struct field
-//!         // and set the others to be the clone of the original component body.
-//!         // That ensures that no information will be accidentally removed.
-//!         let new_sig = Signature {
-//!             ident: Ident::new(self.new_name, old_sig.ident.span()),
-//!             ..old_sig.clone()
-//!         };
-//!         let new_fn = ItemFn {
-//!             sig: new_sig,
-//!             ..old_fn.clone()
-//!         };
-//!
-//!         Ok(NameChangerDeserializerOutput {
-//!             comp_fn: new_fn
-//!         })
-//!     }
-//! ```
-
-pub mod utils;
-
-pub use utils::DeserializerArgs;
-pub use utils::DeserializerOutput;
-
-use syn::parse::{Parse, ParseStream};
-use syn::spanned::Spanned;
-use syn::*;
-
-/// General struct for parsing a component body.
-/// However, because it's ambiguous, it does not implement [`ToTokens`](quote::to_tokens::ToTokens).
-///
-/// Refer to the [module documentation](crate::component_body) for more.
-pub struct ComponentBody {
-    /// The component function definition. You can parse this back into a [`ComponentBody`].
-    /// For example, you might modify it, parse it into a [`ComponentBody`], and deserialize that
-    /// using some deserializer. This is how deserializers use other deserializers.
-    ///
-    /// **`item_fn.sig.inputs` includes the context argument!**
-    /// Keep this in mind when creating deserializers, because you often might want to ignore it.
-    /// That might be annoying, but it would be bad design for this kind of struct to not be parsable from itself.
-    pub item_fn: ItemFn,
-    /// If the function has any arguments other than the context.
-    pub has_extra_args: bool,
-}
-
-impl ComponentBody {
-    /// Deserializes the body into the [`TOutput`] with the specific [`TArgs`].
-    /// Even if the args are empty, the [`TArg`] type still determines what [`TOutput`] will be generated.
-    pub fn deserialize<TOutput, TArgs>(&self, args: TArgs) -> Result<TOutput>
-    where
-        TOutput: DeserializerOutput,
-        TArgs: DeserializerArgs<TOutput>,
-    {
-        args.to_output(self)
-    }
-}
-
-impl Parse for ComponentBody {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let item_fn: ItemFn = input.parse()?;
-
-        let element_type_path = "dioxus_core::Element";
-
-        if item_fn.sig.output == ReturnType::Default {
-            return Err(Error::new(
-                item_fn.sig.output.span(),
-                format!("Must return a <{}>", element_type_path),
-            ));
-        }
-
-        let has_extra_args = !item_fn.sig.inputs.is_empty();
-
-        Ok(Self {
-            item_fn,
-            has_extra_args,
-        })
-    }
-}

+ 0 - 36
packages/core-macro/src/component_body/utils.rs

@@ -1,36 +0,0 @@
-use crate::component_body::ComponentBody;
-use quote::ToTokens;
-
-/// The output produced by a deserializer.
-///
-/// # For implementors
-/// Struct field guidelines:
-/// * Must be public, so that other deserializers can utilize them.
-/// * Should usually be [`Item`]s that you then simply combine in a [`quote!`]
-/// in the [`ComponentBodyDeserializer::output_to_token_stream2`] function.
-/// * If an [`Item`] might not be included, wrap it in an [`Option`].
-/// * Must be at the component function "level"/"context".
-/// For example, the [`InlinePropsDeserializer`](crate::component_body_deserializers::inline_props::InlinePropsDeserializer)
-/// produces two [`Item`]s; the function but with arguments turned into props, and the props struct.
-/// It does not return any [`Item`]s inside the struct or function.
-pub trait DeserializerOutput: ToTokens {}
-
-impl<T: ToTokens> DeserializerOutput for T {}
-
-/// The args passed to a [`ComponentBody`] when deserializing it.
-///
-/// It's also the struct that does the deserializing.
-/// It's called "DeserializerArgs", not "Deserializer". Why?
-/// Because "args" makes more sense to the caller of [`ComponentBody::deserialize`], which
-/// takes an [`DeserializerArgs`] argument. However, you can think of "DeserializerArgs" as the deserializer.
-pub trait DeserializerArgs<TOutput>: Clone
-where
-    TOutput: ToTokens,
-{
-    // There's a lot of Results out there... let's make sure that this is a syn::Result.
-    // Let's also make sure there's not a warning.
-    /// Creates a [`ToTokens`] struct from the `self` args and a [`ComponentBody`].
-    /// The [`ComponentBody::deserialize`] provides a cleaner way of calling this function.
-    #[allow(unused_qualifications)]
-    fn to_output(&self, component_body: &ComponentBody) -> syn::Result<TOutput>;
-}

+ 0 - 151
packages/core-macro/src/component_body_deserializers/component.rs

@@ -1,151 +0,0 @@
-use crate::component_body::{ComponentBody, DeserializerArgs};
-use crate::component_body_deserializers::inline_props::InlinePropsDeserializerArgs;
-use constcat::concat;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::*;
-
-pub(crate) const COMPONENT_ARG_CASE_CHECK_ERROR: &str = concat!(
-    "This component does not use PascalCase. \
-To ignore this check, pass the \"",
-    crate::COMPONENT_ARG_CASE_CHECK_OFF,
-    "\" argument, like so: #[component(",
-    crate::COMPONENT_ARG_CASE_CHECK_OFF,
-    ")]"
-);
-
-const INNER_FN_NAME: &str = "__dx_inner_comp";
-
-fn get_out_comp_fn(orig_comp_fn: &ItemFn) -> ItemFn {
-    let inner_comp_ident = Ident::new(INNER_FN_NAME, orig_comp_fn.sig.ident.span());
-
-    let inner_comp_fn = ItemFn {
-        sig: Signature {
-            ident: inner_comp_ident.clone(),
-            ..orig_comp_fn.sig.clone()
-        },
-        ..orig_comp_fn.clone()
-    };
-
-    let props_ident = match orig_comp_fn.sig.inputs.is_empty() {
-        true => quote! {},
-        false => quote! { __props },
-    };
-
-    ItemFn {
-        block: parse_quote! {
-            {
-                #[warn(non_snake_case)]
-                #[allow(clippy::inline_always)]
-                #[inline(always)]
-                #inner_comp_fn
-                #inner_comp_ident(#props_ident)
-            }
-        },
-        ..orig_comp_fn.clone()
-    }
-}
-
-/// The args and deserializing implementation for the [`crate::component`] macro.
-#[derive(Clone)]
-pub struct ComponentDeserializerArgs {
-    pub case_check: bool,
-}
-
-/// The output fields and [`ToTokens`] implementation for the [`crate::component`] macro.
-#[derive(Clone)]
-pub struct ComponentDeserializerOutput {
-    pub comp_fn: ItemFn,
-    pub props_struct: Option<ItemStruct>,
-}
-
-impl ToTokens for ComponentDeserializerOutput {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let comp_fn = &self.comp_fn;
-        let props_struct = &self.props_struct;
-        let fn_ident = &comp_fn.sig.ident;
-
-        let doc = format!("Properties for the [`{fn_ident}`] component.");
-        tokens.append_all(quote! {
-            #[doc = #doc]
-            #props_struct
-            #[allow(non_snake_case)]
-            #comp_fn
-        });
-    }
-}
-
-impl DeserializerArgs<ComponentDeserializerOutput> for ComponentDeserializerArgs {
-    fn to_output(&self, component_body: &ComponentBody) -> Result<ComponentDeserializerOutput> {
-        let Signature { ident, .. } = &component_body.item_fn.sig;
-
-        if self.case_check && !is_pascal_case(&ident.to_string()) {
-            return Err(Error::new(ident.span(), COMPONENT_ARG_CASE_CHECK_ERROR));
-        }
-
-        if component_body.has_extra_args {
-            Self::deserialize_with_props(component_body)
-        } else {
-            Ok(Self::deserialize_no_props(component_body))
-        }
-    }
-}
-
-impl ComponentDeserializerArgs {
-    fn deserialize_no_props(component_body: &ComponentBody) -> ComponentDeserializerOutput {
-        let ComponentBody { item_fn, .. } = component_body;
-
-        let comp_fn = get_out_comp_fn(item_fn);
-
-        ComponentDeserializerOutput {
-            comp_fn,
-            props_struct: None,
-        }
-    }
-
-    fn deserialize_with_props(
-        component_body: &ComponentBody,
-    ) -> Result<ComponentDeserializerOutput> {
-        let ComponentBody { item_fn, .. } = component_body;
-
-        let comp_parsed = match parse2::<ComponentBody>(quote!(#item_fn)) {
-            Ok(comp_body) => comp_body,
-            Err(e) => {
-                return Err(Error::new(
-                    e.span(),
-                    format!(
-                        "This is probably a bug in our code, please report it! Error: {}",
-                        e
-                    ),
-                ))
-            }
-        };
-
-        let inlined_props_output = comp_parsed.deserialize(InlinePropsDeserializerArgs {})?;
-        let props_struct = inlined_props_output.props_struct;
-        let props_fn = inlined_props_output.comp_fn;
-
-        let comp_fn = get_out_comp_fn(&props_fn);
-
-        Ok(ComponentDeserializerOutput {
-            comp_fn,
-            props_struct: Some(props_struct),
-        })
-    }
-}
-
-fn is_pascal_case(input: &str) -> bool {
-    let mut is_next_lowercase = false;
-
-    for c in input.chars() {
-        let is_upper = c.is_ascii_uppercase();
-
-        if (c == '_') || (is_upper && is_next_lowercase) {
-            return false;
-        }
-
-        is_next_lowercase = is_upper;
-    }
-
-    true
-}

+ 0 - 355
packages/core-macro/src/component_body_deserializers/inline_props.rs

@@ -1,355 +0,0 @@
-use crate::component_body::{ComponentBody, DeserializerArgs};
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::token::Comma;
-use syn::{punctuated::Punctuated, *};
-
-/// The args and deserializing implementation for the [`crate::inline_props`] macro.
-#[derive(Clone)]
-pub struct InlinePropsDeserializerArgs;
-
-/// The output fields and [`ToTokens`] implementation for the [`crate::inline_props`] macro.
-#[derive(Clone)]
-pub struct InlinePropsDeserializerOutput {
-    pub comp_fn: ItemFn,
-    pub props_struct: ItemStruct,
-}
-
-impl ToTokens for InlinePropsDeserializerOutput {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let function = &self.comp_fn;
-        let props_struct = &self.props_struct;
-
-        tokens.append_all(quote! {
-            #function
-            #props_struct
-        });
-    }
-}
-
-impl DeserializerArgs<InlinePropsDeserializerOutput> for InlinePropsDeserializerArgs {
-    fn to_output(&self, component_body: &ComponentBody) -> Result<InlinePropsDeserializerOutput> {
-        Ok(InlinePropsDeserializerOutput {
-            comp_fn: get_function(component_body),
-            props_struct: get_props_struct(component_body),
-        })
-    }
-}
-
-fn get_props_struct(component_body: &ComponentBody) -> ItemStruct {
-    let ComponentBody { item_fn, .. } = component_body;
-    let ItemFn { vis, sig, .. } = item_fn;
-    let Signature {
-        inputs,
-        ident: fn_ident,
-        generics,
-        ..
-    } = sig;
-
-    let struct_fields = inputs.iter().map(move |f| {
-        match f {
-            FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
-            FnArg::Typed(pt) => {
-                let arg_pat = match pt.pat.as_ref() {
-                    // rip off mutability
-                    Pat::Ident(f) => {
-                        let mut f = f.clone();
-                        f.mutability = None;
-                        quote! { #f }
-                    }
-                    a => quote! { #a },
-                };
-
-                let arg_colon = &pt.colon_token;
-                let arg_ty = &pt.ty; // Type
-                let arg_attrs = &pt.attrs; // Attributes
-
-                quote! {
-                    #(#arg_attrs)
-                    *
-                    #vis #arg_pat #arg_colon #arg_ty
-                }
-            }
-        }
-    });
-
-    let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
-
-    let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
-        Some(lt)
-    } else {
-        None
-    };
-
-    let struct_attrs = if first_lifetime.is_some() {
-        quote! { #[derive(Props, Clone)] }
-    } else {
-        quote! { #[derive(Props, Clone, PartialEq)] }
-    };
-
-    let struct_generics = if first_lifetime.is_some() {
-        let struct_generics: Punctuated<GenericParam, Comma> = component_body
-            .item_fn
-            .sig
-            .generics
-            .params
-            .iter()
-            .map(|it| match it {
-                GenericParam::Type(tp) => {
-                    let mut tp = tp.clone();
-                    tp.bounds.push(parse_quote!( 'a ));
-
-                    GenericParam::Type(tp)
-                }
-                _ => it.clone(),
-            })
-            .collect();
-
-        quote! { <#struct_generics> }
-    } else {
-        quote! { #generics }
-    };
-
-    parse_quote! {
-        #struct_attrs
-        #[allow(non_camel_case_types)]
-        #vis struct #struct_ident #struct_generics
-        {
-            #(#struct_fields),*
-        }
-    }
-}
-
-fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec<Attribute> {
-    if inputs.len() <= 1 {
-        return Vec::new();
-    }
-
-    let arg_docs = inputs
-        .iter()
-        .filter_map(|f| match f {
-            FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
-            FnArg::Typed(pt) => {
-                let arg_doc = pt
-                    .attrs
-                    .iter()
-                    .filter_map(|attr| {
-                        // TODO: Error reporting
-                        // Check if the path of the attribute is "doc"
-                        if !is_attr_doc(attr) {
-                            return None;
-                        };
-
-                        let Meta::NameValue(meta_name_value) = &attr.meta else {
-                            return None;
-                        };
-
-                        let Expr::Lit(doc_lit) = &meta_name_value.value else {
-                            return None;
-                        };
-
-                        let Lit::Str(doc_lit_str) = &doc_lit.lit else {
-                            return None;
-                        };
-
-                        Some(doc_lit_str.value())
-                    })
-                    .fold(String::new(), |mut doc, next_doc_line| {
-                        doc.push('\n');
-                        doc.push_str(&next_doc_line);
-                        doc
-                    });
-
-                Some((
-                    &pt.pat,
-                    &pt.ty,
-                    pt.attrs.iter().find_map(|attr| {
-                        if attr.path() != &parse_quote!(deprecated) {
-                            return None;
-                        }
-
-                        let res = crate::utils::DeprecatedAttribute::from_meta(&attr.meta);
-
-                        match res {
-                            Err(e) => panic!("{}", e.to_string()),
-                            Ok(v) => Some(v),
-                        }
-                    }),
-                    arg_doc,
-                ))
-            }
-        })
-        .collect::<Vec<_>>();
-
-    let mut props_docs = Vec::with_capacity(5);
-    let props_def_link = fn_ident.to_string() + "Props";
-    let header =
-        format!("# Props\n*For details, see the [props struct definition]({props_def_link}).*");
-
-    props_docs.push(parse_quote! {
-        #[doc = #header]
-    });
-
-    for (arg_name, arg_type, deprecation, input_arg_doc) in arg_docs {
-        let arg_name = arg_name.into_token_stream().to_string();
-        let arg_type = crate::utils::format_type_string(arg_type);
-
-        let input_arg_doc = keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\n')
-            .replace("\n\n", "</p><p>");
-        let prop_def_link = format!("{props_def_link}::{arg_name}");
-        let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link}) : `{arg_type}`");
-
-        if let Some(deprecation) = deprecation {
-            arg_doc.push_str("<p>👎 Deprecated");
-
-            if let Some(since) = deprecation.since {
-                arg_doc.push_str(&format!(" since {since}"));
-            }
-
-            if let Some(note) = deprecation.note {
-                let note = keep_up_to_n_consecutive_chars(&note, 1, '\n').replace('\n', " ");
-                let note = keep_up_to_n_consecutive_chars(&note, 1, '\t').replace('\t', " ");
-
-                arg_doc.push_str(&format!(": {note}"));
-            }
-
-            arg_doc.push_str("</p>");
-
-            if !input_arg_doc.is_empty() {
-                arg_doc.push_str("<hr/>");
-            }
-        }
-
-        if !input_arg_doc.is_empty() {
-            arg_doc.push_str(&format!("<p>{input_arg_doc}</p>"));
-        }
-
-        props_docs.push(parse_quote! {
-            #[doc = #arg_doc]
-        });
-    }
-
-    props_docs
-}
-
-fn get_function(component_body: &ComponentBody) -> ItemFn {
-    let ComponentBody { item_fn, .. } = component_body;
-    let ItemFn {
-        attrs: fn_attrs,
-        vis,
-        sig,
-        block: fn_block,
-    } = item_fn;
-    let Signature {
-        inputs,
-        ident: fn_ident,
-        generics,
-        output: fn_output,
-        asyncness,
-        ..
-    } = sig;
-    let Generics { where_clause, .. } = generics;
-
-    let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
-
-    // Skip first arg since that's the context
-    let struct_field_names = inputs.iter().filter_map(|f| match f {
-        FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
-        FnArg::Typed(pt) => {
-            let pat = &pt.pat;
-
-            let mut pat = pat.clone();
-
-            // rip off mutability, but still write it out eventually
-            if let Pat::Ident(ref mut pat_ident) = pat.as_mut() {
-                pat_ident.mutability = None;
-            }
-
-            Some(quote!(mut  #pat))
-        }
-    });
-
-    let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
-        Some(lt)
-    } else {
-        None
-    };
-
-    let (_scope_lifetime, fn_generics) = if let Some(lt) = first_lifetime {
-        (quote! { #lt, }, generics.clone())
-    } else {
-        let lifetime: LifetimeParam = parse_quote! { 'a };
-
-        let mut fn_generics = generics.clone();
-        fn_generics
-            .params
-            .insert(0, GenericParam::Lifetime(lifetime.clone()));
-
-        (quote! { #lifetime, }, fn_generics)
-    };
-
-    let generics_no_bounds = {
-        let mut generics = generics.clone();
-        generics.params = generics
-            .params
-            .iter()
-            .map(|it| match it {
-                GenericParam::Type(tp) => {
-                    let mut tp = tp.clone();
-                    tp.bounds.clear();
-
-                    GenericParam::Type(tp)
-                }
-                _ => it.clone(),
-            })
-            .collect();
-
-        generics
-    };
-
-    let props_docs = get_props_docs(fn_ident, inputs.iter().skip(1).collect());
-
-    parse_quote! {
-        #(#fn_attrs)*
-        #(#props_docs)*
-        #asyncness #vis fn #fn_ident #fn_generics (mut __props: #struct_ident #generics_no_bounds) #fn_output
-        #where_clause
-        {
-            let #struct_ident { #(#struct_field_names),* } = __props;
-            #fn_block
-        }
-    }
-}
-
-/// Checks if the attribute is a `#[doc]` attribute.
-fn is_attr_doc(attr: &Attribute) -> bool {
-    attr.path() == &parse_quote!(doc)
-}
-
-fn keep_up_to_n_consecutive_chars(
-    input: &str,
-    n_of_consecutive_chars_allowed: usize,
-    target_char: char,
-) -> String {
-    let mut output = String::new();
-    let mut prev_char: Option<char> = None;
-    let mut consecutive_count = 0;
-
-    for c in input.chars() {
-        match prev_char {
-            Some(prev) if c == target_char && prev == target_char => {
-                if consecutive_count < n_of_consecutive_chars_allowed {
-                    output.push(c);
-                    consecutive_count += 1;
-                }
-            }
-            _ => {
-                output.push(c);
-                prev_char = Some(c);
-                consecutive_count = 1;
-            }
-        }
-    }
-
-    output
-}

+ 0 - 4
packages/core-macro/src/component_body_deserializers/mod.rs

@@ -1,4 +0,0 @@
-//! This module contains all [`ComponentBody`](crate::component_body::ComponentBody) deserializers.
-
-pub mod component;
-pub mod inline_props;

+ 36 - 109
packages/core-macro/src/lib.rs

@@ -2,21 +2,15 @@
 #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
 #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
 
+use component::ComponentBody;
 use proc_macro::TokenStream;
 use quote::ToTokens;
-use syn::parse::Parser;
-use syn::punctuated::Punctuated;
-use syn::{parse_macro_input, Path, Token};
+use syn::parse_macro_input;
 
-mod component_body;
-mod component_body_deserializers;
+mod component;
 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]
@@ -54,45 +48,6 @@ pub fn render(tokens: TokenStream) -> TokenStream {
     rsx(tokens)
 }
 
-/// 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(bob: String) -> Element {
-///     rsx!("hello, {bob}"))
-/// }
-///
-/// // is equivalent to
-///
-/// #[derive(PartialEq, Props)]
-/// struct AppProps {
-///     bob: String,
-/// }
-///
-/// fn app(cx: Scope<AppProps>) -> Element {
-///     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.
@@ -113,16 +68,6 @@ pub(crate) const COMPONENT_ARG_CASE_CHECK_OFF: &str = "no_case_check";
 /// 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:
@@ -131,68 +76,50 @@ pub(crate) const COMPONENT_ARG_CASE_CHECK_OFF: &str = "no_case_check";
 /// fn GreetBob() -> Element {
 ///     rsx! { "hello, bob" }
 /// }
+/// ```
 ///
-/// // is equivalent to
-///
-/// #[allow(non_snake_case)]
-/// fn GreetBob() -> Element {
-///     #[warn(non_snake_case)]
-///     #[inline(always)]
-///     fn __dx_inner_comp() -> Element {
-///         rsx! { "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]
+/// fn GreetBob(bob: String) -> Element {
+///    rsx! { "hello, {bob}" }
 /// }
 /// ```
-/// * With props:
+#[proc_macro_attribute]
+pub fn component(_args: TokenStream, input: TokenStream) -> TokenStream {
+    parse_macro_input!(input as ComponentBody)
+        .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
-/// #[component(no_case_check)]
-/// fn GreetPerson(person: String) -> Element {
-///     rsx! { "hello, {person}" }
+/// #[inline_props]
+/// fn app(bob: String) -> Element {
+///     rsx!("hello, {bob}"))
 /// }
 ///
 /// // is equivalent to
 ///
-/// #[derive(Props, PartialEq)]
-/// #[allow(non_camel_case_types)]
-/// struct GreetPersonProps {
-///     person: String,
+/// #[derive(PartialEq, Props)]
+/// struct AppProps {
+///     bob: String,
 /// }
 ///
-/// #[allow(non_snake_case)]
-/// fn GreetPerson(props: GreetPersonProps>) -> Element {
-///     #[warn(non_snake_case)]
-///     #[inline(always)]
-///     fn __dx_inner_comp(props: GreetPersonProps>e) -> Element {
-///         let GreetPersonProps { person } = props;
-///         {
-///             rsx! { "hello, {person}" }
-///         }
-///     }
-///
-///     __dx_inner_comp(cx)
+/// fn app(cx: Scope<AppProps>) -> Element {
+///     rsx!("hello, {bob}"))
 /// }
 /// ```
-// 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(),
-    }
+#[deprecated(note = "Use `#[component]` instead.")]
+pub fn inline_props(args: TokenStream, input: TokenStream) -> TokenStream {
+    component(args, input)
 }