Jelajahi Sumber

Detect components called as functions (#2461)

* detect components called as functions
* use the existing component name information instead of a thread local
Evan Almloff 1 tahun lalu
induk
melakukan
5024139e01

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

@@ -84,6 +84,7 @@ impl ComponentBody {
         } = sig;
         let Generics { where_clause, .. } = generics;
         let (_, ty_generics, _) = generics.split_for_impl();
+        let generics_turbofish = ty_generics.as_turbofish();
 
         // 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());
@@ -108,6 +109,9 @@ impl ComponentBody {
             #(#attrs)*
             #(#props_docs)*
             #asyncness #vis fn #fn_ident #generics (#props_ident) #fn_output #where_clause {
+                // In debug mode we can detect if the user is calling the component like a function
+                dioxus_core::internal::verify_component_called_as_component(#fn_ident #generics_turbofish);
+
                 #expanded_struct
                 #block
             }

+ 6 - 0
packages/core/src/lib.rs

@@ -24,6 +24,12 @@ mod scopes;
 mod tasks;
 mod virtual_dom;
 
+/// Items exported from this module are used in macros and should not be used directly.
+#[doc(hidden)]
+pub mod internal {
+    pub use crate::properties::verify_component_called_as_component;
+}
+
 pub(crate) mod innerlude {
     pub(crate) use crate::any_props::*;
     pub use crate::arena::*;

+ 28 - 0
packages/core/src/properties.rs

@@ -117,6 +117,34 @@ where
     P::builder()
 }
 
+/// Make sure that this component is currently running as a component, not a function call
+#[doc(hidden)]
+#[allow(unused)]
+pub fn verify_component_called_as_component<C: ComponentFunction<P, M>, P, M>(component: C) {
+    #[cfg(debug_assertions)]
+    {
+        // We trim WithOwner from the end of the type name for component with a builder that include a special owner which may not match the function name directly
+        let mut type_name = std::any::type_name::<C>();
+        if let Some((_, after_colons)) = type_name.rsplit_once("::") {
+            type_name = after_colons;
+        }
+        let component_name = Runtime::with(|rt| {
+            current_scope_id()
+                .and_then(|id| rt.get_state(id))
+                .map(|scope| scope.name)
+        })
+        .flatten();
+
+        // If we are in a component, and the type name is the same as the active component name, then we can just return
+        if component_name == Some(type_name) {
+            return;
+        }
+
+        // Otherwise the component was called like a function, so we should log an error
+        tracing::error!("It looks like you called the component {type_name} like a function instead of a component. Components should be called with braces like `{type_name} {{ prop: value }}` instead of as a function");
+    }
+}
+
 /// Any component that implements the `ComponentFn` trait can be used as a component.
 ///
 /// This trait is automatically implemented for functions that are in one of the following forms: