diff --git a/README.md b/README.md
index 77b409d79..81ac7c194 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,8 @@
PT-BR | 日本語
+ |
+ Türkçe
@@ -145,7 +147,7 @@ There's tons of options for building apps, so why would you choose Dioxus?
Well, first and foremost, Dioxus prioritizes developer experience. This is reflected in a variety of features unique to Dioxus:
-- Autoformatting of our meta language (RSX) and accompanying VSCode extension
+- Autoformatting of our meta language (RSX) and accompanying [VSCode extension](https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus)
- Hotreloading using an interpreter of RSX for both desktop and web
- Emphasis on good docs - our guide is complete and our HTML elements are documented
- Significant research in simplifying
diff --git a/examples/readme.rs b/examples/readme.rs
index 7c5240086..8f412a749 100644
--- a/examples/readme.rs
+++ b/examples/readme.rs
@@ -1,7 +1,7 @@
//! The example from the readme!
//!
//! This example demonstrates how to create a simple counter app with dioxus. The `Signal` type wraps inner values,
-//! making them `Copy`, allowing them to be freely used in closures and and async functions. `Signal` also provides
+//! making them `Copy`, allowing them to be freely used in closures and async functions. `Signal` also provides
//! helper methods like AddAssign, SubAssign, toggle, etc, to make it easy to update the value without running
//! into lock issues.
diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs
index 3f9510839..bf6cb1684 100644
--- a/packages/cli/src/server/mod.rs
+++ b/packages/cli/src/server/mod.rs
@@ -47,8 +47,13 @@ async fn setup_file_watcher Result + Send + 'static>(
for path in &e.paths {
// if this is not a rust file, rebuild the whole project
- if path.extension().and_then(|p| p.to_str()) != Some("rs") {
+ let path_extension = path.extension().and_then(|p| p.to_str());
+ if path_extension != Some("rs") {
needs_full_rebuild = true;
+ // if backup file generated will impact normal hot-reload, so ignore it
+ if path_extension == Some("rs~") {
+ needs_full_rebuild = false;
+ }
break;
}
diff --git a/packages/config-macro/src/lib.rs b/packages/config-macro/src/lib.rs
index 8cfb807b6..0f318208f 100644
--- a/packages/config-macro/src/lib.rs
+++ b/packages/config-macro/src/lib.rs
@@ -15,7 +15,7 @@ pub fn server_only(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
@@ -30,7 +30,7 @@ pub fn client(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
@@ -45,7 +45,7 @@ pub fn web(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
@@ -60,7 +60,7 @@ pub fn desktop(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
@@ -75,7 +75,7 @@ pub fn fullstack(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
@@ -90,7 +90,7 @@ pub fn ssr(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
@@ -105,7 +105,7 @@ pub fn liveview(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
@@ -120,7 +120,7 @@ pub fn tui(input: TokenStream) -> TokenStream {
}
} else {
quote! {
- ()
+ || {}
}
}
.into()
diff --git a/packages/core-macro/.vscode/settings.json b/packages/core-macro/.vscode/settings.json
new file mode 100644
index 000000000..7149654fd
--- /dev/null
+++ b/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"
+}
diff --git a/packages/core-macro/src/component.rs b/packages/core-macro/src/component.rs
new file mode 100644
index 000000000..2421e4660
--- /dev/null
+++ b/packages/core-macro/src/component.rs
@@ -0,0 +1,387 @@
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::parse::{Parse, ParseStream};
+use syn::spanned::Spanned;
+use syn::*;
+
+pub struct ComponentBody {
+ pub item_fn: ItemFn,
+}
+
+impl Parse for ComponentBody {
+ fn parse(input: ParseStream) -> Result {
+ 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();
+
+ // If there's no props declared, we simply omit the props argument
+ // This is basically so you can annotate the App component with #[component] and still be compatible with the
+ // launch signatures that take fn() -> Element
+ let props_struct = match self.item_fn.sig.inputs.is_empty() {
+ // No props declared, so we don't need to generate a props struct
+ true => quote! {},
+
+ // Props declared, so we generate a props struct and thatn also attach the doc attributes to it
+ 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;
+ let (_, ty_generics, _) = generics.split_for_impl();
+
+ // 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());
+
+ // We pull in the field names from the original function signature, but need to strip off the mutability
+ let struct_field_names = inputs.iter().filter_map(rebind_mutability);
+
+ let props_docs = self.props_docs(inputs.iter().skip(1).collect());
+
+ // Don't generate the props argument if there are no inputs
+ // This means we need to skip adding the argument to the function signature, and also skip the expanded struct
+ 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 an associated struct for the props of the component
+ ///
+ /// This will expand to the typed-builder implementation that we have vendored in this crate.
+ /// TODO: don't vendor typed-builder and instead transform the tokens we give it before expansion.
+ /// TODO: cache these tokens since this codegen is rather expensive (lots of tokens)
+ ///
+ /// We try our best to transfer over any declared doc attributes from the original function signature onto the
+ /// props struct fields.
+ fn props_struct(&self) -> ItemStruct {
+ let ItemFn { vis, sig, .. } = &self.item_fn;
+ let Signature {
+ inputs,
+ ident,
+ generics,
+ ..
+ } = sig;
+
+ let struct_fields = inputs.iter().map(move |f| make_prop_struct_field(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),*
+ }
+ }
+ }
+
+ /// Convert a list of function arguments into a list of doc attributes for the props struct
+ ///
+ /// This lets us generate set of attributes that we can apply to the props struct to give it a nice docstring.
+ fn props_docs(&self, inputs: Vec<&FnArg>) -> Vec {
+ let fn_ident = &self.item_fn.sig.ident;
+
+ if inputs.len() <= 1 {
+ return Vec::new();
+ }
+
+ let arg_docs = inputs
+ .iter()
+ .filter_map(|f| build_doc_fields(f))
+ .collect::>();
+
+ 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 in arg_docs {
+ let DocField {
+ arg_name,
+ arg_type,
+ deprecation,
+ input_arg_doc,
+ } = arg;
+
+ 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", "
");
+ 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("
👎 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(¬e, 1, '\n').replace('\n', " ");
+ let note = keep_up_to_n_consecutive_chars(¬e, 1, '\t').replace('\t', " ");
+
+ arg_doc.push_str(&format!(": {note}"));
+ }
+
+ arg_doc.push_str("
");
+
+ if !input_arg_doc.is_empty() {
+ arg_doc.push_str("");
+ }
+ }
+
+ if !input_arg_doc.is_empty() {
+ arg_doc.push_str(&format!("
{input_arg_doc}
"));
+ }
+
+ props_docs.push(parse_quote! { #[doc = #arg_doc] });
+ }
+
+ props_docs
+ }
+}
+
+struct DocField<'a> {
+ arg_name: &'a Pat,
+ arg_type: &'a Type,
+ deprecation: Option,
+ input_arg_doc: String,
+}
+
+fn build_doc_fields(f: &FnArg) -> Option {
+ let FnArg::Typed(pt) = f else { unreachable!() };
+
+ 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(DocField {
+ arg_name: &pt.pat,
+ arg_type: &pt.ty,
+ deprecation: 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),
+ }
+ }),
+ input_arg_doc: arg_doc,
+ })
+}
+
+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 ".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(())
+}
+
+/// Convert a function arg with a given visibility (provided by the function) and then generate a field for the
+/// associated props struct.
+fn make_prop_struct_field(f: &FnArg, vis: &Visibility) -> TokenStream {
+ // There's no receivers (&self) allowed in the component body
+ let FnArg::Typed(pt) = f else { unreachable!() };
+
+ let arg_pat = match pt.pat.as_ref() {
+ // rip off mutability
+ // todo: we actually don't want any of the extra bits of the field pattern
+ Pat::Ident(f) => {
+ let mut f = f.clone();
+ f.mutability = None;
+ quote! { #f }
+ }
+ a => quote! { #a },
+ };
+
+ let PatType {
+ attrs,
+ ty,
+ colon_token,
+ ..
+ } = pt;
+
+ quote! {
+ #(#attrs)*
+ #vis #arg_pat #colon_token #ty
+ }
+}
+
+fn rebind_mutability(f: &FnArg) -> Option {
+ // There's no receivers (&self) allowed in the component body
+ let FnArg::Typed(pt) = f else { unreachable!() };
+
+ 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 = 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
+}
diff --git a/packages/core-macro/src/component_body/mod.rs b/packages/core-macro/src/component_body/mod.rs
deleted file mode 100644
index 2e1475657..000000000
--- a/packages/core-macro/src/component_body/mod.rs
+++ /dev/null
@@ -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::::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 for NameChangerDeserializerArgs<'a> {
-//! fn to_output(&self, component_body: &ComponentBody) -> Result {
-//! 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(&self, args: TArgs) -> Result
- where
- TOutput: DeserializerOutput,
- TArgs: DeserializerArgs,
- {
- args.to_output(self)
- }
-}
-
-impl Parse for ComponentBody {
- fn parse(input: ParseStream) -> Result {
- 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,
- })
- }
-}
diff --git a/packages/core-macro/src/component_body/utils.rs b/packages/core-macro/src/component_body/utils.rs
deleted file mode 100644
index decf95419..000000000
--- a/packages/core-macro/src/component_body/utils.rs
+++ /dev/null
@@ -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 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: 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;
-}
diff --git a/packages/core-macro/src/component_body_deserializers/component.rs b/packages/core-macro/src/component_body_deserializers/component.rs
deleted file mode 100644
index b6be85c9b..000000000
--- a/packages/core-macro/src/component_body_deserializers/component.rs
+++ /dev/null
@@ -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,
-}
-
-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 for ComponentDeserializerArgs {
- fn to_output(&self, component_body: &ComponentBody) -> Result {
- 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 {
- let ComponentBody { item_fn, .. } = component_body;
-
- let comp_parsed = match parse2::(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
-}
diff --git a/packages/core-macro/src/component_body_deserializers/inline_props.rs b/packages/core-macro/src/component_body_deserializers/inline_props.rs
deleted file mode 100644
index 6b9f2b599..000000000
--- a/packages/core-macro/src/component_body_deserializers/inline_props.rs
+++ /dev/null
@@ -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 for InlinePropsDeserializerArgs {
- fn to_output(&self, component_body: &ComponentBody) -> Result {
- 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 = 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 {
- 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::>();
-
- 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", "
");
- 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("
👎 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(¬e, 1, '\n').replace('\n', " ");
- let note = keep_up_to_n_consecutive_chars(¬e, 1, '\t').replace('\t', " ");
-
- arg_doc.push_str(&format!(": {note}"));
- }
-
- arg_doc.push_str("
");
-
- if !input_arg_doc.is_empty() {
- arg_doc.push_str("");
- }
- }
-
- if !input_arg_doc.is_empty() {
- arg_doc.push_str(&format!("
{input_arg_doc}
"));
- }
-
- 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 = 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
-}
diff --git a/packages/core-macro/src/component_body_deserializers/mod.rs b/packages/core-macro/src/component_body_deserializers/mod.rs
deleted file mode 100644
index a3027796d..000000000
--- a/packages/core-macro/src/component_body_deserializers/mod.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-//! This module contains all [`ComponentBody`](crate::component_body::ComponentBody) deserializers.
-
-pub mod component;
-pub mod inline_props;
diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs
index f9a29c0b1..94a4d0f49 100644
--- a/packages/core-macro/src/lib.rs
+++ b/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,6 +48,50 @@ pub fn render(tokens: TokenStream) -> TokenStream {
rsx(tokens)
}
+/// 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.
+///
+/// # Examples
+/// * Without props:
+/// ```rust,ignore
+/// #[component]
+/// fn GreetBob() -> Element {
+/// rsx! { "hello, bob" }
+/// }
+/// ```
+///
+/// * With props:
+/// ```rust,ignore
+/// #[component]
+/// fn GreetBob(bob: String) -> Element {
+/// rsx! { "hello, {bob}" }
+/// }
+/// ```
+#[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
`,
@@ -82,117 +120,6 @@ pub fn render(tokens: TokenStream) -> TokenStream {
/// ```
#[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() -> 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(no_case_check)]
-/// fn GreetPerson(person: String) -> Element {
-/// rsx! { "hello, {person}" }
-/// }
-///
-/// // is equivalent to
-///
-/// #[derive(Props, PartialEq)]
-/// #[allow(non_camel_case_types)]
-/// struct GreetPersonProps {
-/// person: 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)
-/// }
-/// ```
-// 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::::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(),
- }
+pub fn inline_props(args: TokenStream, input: TokenStream) -> TokenStream {
+ component(args, input)
}
diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs
index 7808239d7..fd438096f 100644
--- a/packages/core-macro/src/props/mod.rs
+++ b/packages/core-macro/src/props/mod.rs
@@ -1276,7 +1276,8 @@ Finally, call `.build()` to create the instance of `{name}`.
});
let (impl_generics, _, _) = generics.split_for_impl();
- let (_, ty_generics, where_clause) = self.generics.split_for_impl();
+ let (original_impl_generics, ty_generics, where_clause) =
+ self.generics.split_for_impl();
let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| {
args.insert(
@@ -1335,24 +1336,25 @@ Finally, call `.build()` to create the instance of `{name}`.
if self.has_signal_fields() {
let name = Ident::new(&format!("{}WithOwner", name), name.span());
let original_name = &self.name;
+ let vis = &self.vis;
quote! {
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, missing_docs)]
#[derive(Clone)]
- struct #name #ty_generics {
+ #vis struct #name #ty_generics {
inner: #original_name #ty_generics,
owner: Owner,
}
- impl #impl_generics PartialEq for #name #ty_generics #where_clause {
+ impl #original_impl_generics PartialEq for #name #ty_generics #where_clause {
fn eq(&self, other: &Self) -> bool {
self.inner.eq(&other.inner)
}
}
- impl #impl_generics #name #ty_generics #where_clause {
+ impl #original_impl_generics #name #ty_generics #where_clause {
/// Create a component from the props.
- fn into_vcomponent(
+ pub fn into_vcomponent(
self,
render_fn: impl dioxus_core::prelude::ComponentFunction<#original_name #ty_generics, M>,
component_name: &'static str,
@@ -1362,7 +1364,7 @@ Finally, call `.build()` to create the instance of `{name}`.
}
}
- impl #impl_generics dioxus_core::prelude::Properties for #name #ty_generics #where_clause {
+ impl #original_impl_generics dioxus_core::prelude::Properties for #name #ty_generics #where_clause {
type Builder = ();
fn builder() -> Self::Builder {
unreachable!()
diff --git a/packages/extension/package-lock.json b/packages/extension/package-lock.json
index dffe931c4..7e7639961 100644
--- a/packages/extension/package-lock.json
+++ b/packages/extension/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "dioxus",
- "version": "0.0.2",
+ "version": "0.0.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "dioxus",
- "version": "0.0.2",
+ "version": "0.0.3",
"license": "MIT",
"dependencies": {
"dioxus-ext": "./pkg",
diff --git a/packages/extension/src/lib.rs b/packages/extension/src/lib.rs
index 6581482ca..81e7906cc 100644
--- a/packages/extension/src/lib.rs
+++ b/packages/extension/src/lib.rs
@@ -22,10 +22,15 @@ pub fn format_rsx(raw: String, use_tabs: bool, indent_size: usize) -> String {
}
#[wasm_bindgen]
-pub fn format_selection(raw: String, use_tabs: bool, indent_size: usize) -> String {
+pub fn format_selection(
+ raw: String,
+ use_tabs: bool,
+ indent_size: usize,
+ base_indent: usize,
+) -> String {
let block = dioxus_autofmt::fmt_block(
&raw,
- 0,
+ base_indent,
IndentOptions::new(
if use_tabs {
IndentType::Tabs
diff --git a/packages/extension/src/main.ts b/packages/extension/src/main.ts
index 76b7261f1..682c56fc4 100644
--- a/packages/extension/src/main.ts
+++ b/packages/extension/src/main.ts
@@ -57,14 +57,75 @@ function fmtSelection() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
- const unformatted = editor.document.getText(editor.selection);
+ if (editor.document.languageId !== "rust") {
+ return;
+ }
- if (unformatted.length == 0) {
+ let end_line = editor.selection.end.line;
+
+ // Select full lines of selection
+ let selection_range = new vscode.Range(
+ editor.selection.start.line,
+ 0,
+ end_line,
+ editor.document.lineAt(end_line).range.end.character
+ );
+
+ let unformatted = editor.document.getText(selection_range);
+
+ if (unformatted.trim().length == 0) {
vscode.window.showWarningMessage("Please select rsx invoking this command!");
return;
}
- const fileDir = editor.document.fileName.slice(0, editor.document.fileName.lastIndexOf('\\'));
+ // If number of closing braces is lower than opening braces, expand selection to end of initial block
+ while ((unformatted.match(/{/g) || []).length > (unformatted.match(/}/g) || []).length && end_line < editor.document.lineCount - 1) {
+ end_line += 1;
+
+ selection_range = new vscode.Range(
+ editor.selection.start.line,
+ 0,
+ end_line,
+ editor.document.lineAt(end_line).range.end.character
+ );
+
+ unformatted = editor.document.getText(selection_range);
+ }
+
+ let tabSize: number;
+ if (typeof editor.options.tabSize === 'number') {
+ tabSize = editor.options.tabSize;
+ } else {
+ tabSize = 4;
+ }
+
+ let end_above = Math.max(editor.selection.start.line - 1, 0);
+
+ let lines_above = editor.document.getText(
+ new vscode.Range(
+ 0,
+ 0,
+ end_above,
+ editor.document.lineAt(end_above).range.end.character
+ )
+ );
+
+ // Calculate indent for current selection
+ let base_indentation = (lines_above.match(/{/g) || []).length - (lines_above.match(/}/g) || []).length - 1;
+
+ try {
+ let formatted = dioxus.format_selection(unformatted, !editor.options.insertSpaces, tabSize, base_indentation);
+ for(let i = 0; i <= base_indentation; i++) {
+ formatted = (editor.options.insertSpaces ? " ".repeat(tabSize) : "\t") + formatted;
+ }
+ if (formatted.length > 0) {
+ editor.edit(editBuilder => {
+ editBuilder.replace(selection_range, formatted);
+ });
+ }
+ } catch (error) {
+ vscode.window.showErrorMessage(`Errors occurred while formatting. Make sure you have the most recent Dioxus-CLI installed and you have selected valid rsx with your cursor! \n${error}`);
+ }
}
@@ -82,7 +143,7 @@ function fmtDocumentOnSave(e: vscode.TextDocumentWillSaveEvent) {
function fmtDocument(document: vscode.TextDocument) {
try {
- if (document.languageId !== "rust" || document.uri.scheme !== "file") {
+ if (document.languageId !== "rust") {
return;
}
diff --git a/packages/fullstack/src/axum_adapter.rs b/packages/fullstack/src/axum_adapter.rs
index f19748655..df1485372 100644
--- a/packages/fullstack/src/axum_adapter.rs
+++ b/packages/fullstack/src/axum_adapter.rs
@@ -487,7 +487,7 @@ async fn handle_server_fns_inner(
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
- // Location set, then redirect to to Referer
+ // Location set, then redirect to Referer
if accepts_html {
if let Some(referrer) = referrer {
let has_location = res.headers().get(LOCATION).is_some();
diff --git a/packages/fullstack/src/hooks/server_future.rs b/packages/fullstack/src/hooks/server_future.rs
index 1194f38d5..a79d0ed0c 100644
--- a/packages/fullstack/src/hooks/server_future.rs
+++ b/packages/fullstack/src/hooks/server_future.rs
@@ -14,8 +14,6 @@ where
let resource = use_resource(move || {
async move {
- // this is going to subscribe this resource to any reactivity given to use in the callback
- // We're doing this regardless so inputs get tracked, even if we drop the future before polling it
let user_fut = cb.call();
let currently_in_first_run = first_run.cloned();
@@ -28,6 +26,10 @@ where
#[cfg(feature = "web")]
if let Some(o) = crate::html_storage::deserialize::take_server_data::() {
+ // this is going to subscribe this resource to any reactivity given to use in the callback
+ // We're doing this regardless so inputs get tracked, even if we drop the future before polling it
+ kick_future(user_fut);
+
return o;
}
}
@@ -60,3 +62,17 @@ where
_ => Some(resource),
}
}
+
+#[inline]
+fn kick_future(user_fut: F)
+where
+ F: Future