Преглед на файлове

fix(check): allow hook calls inside closure hooks

Brian Donovan преди 1 година
родител
ревизия
0e841f8411
променени са 1 файла, в които са добавени 75 реда и са изтрити 2 реда
  1. 75 2
      packages/check/src/check.rs

+ 75 - 2
packages/check/src/check.rs

@@ -1,6 +1,6 @@
 use std::path::PathBuf;
 
-use syn::{spanned::Spanned, visit::Visit};
+use syn::{spanned::Spanned, visit::Visit, Pat};
 
 use crate::{
     issues::{Issue, IssueReport},
@@ -74,6 +74,20 @@ fn is_component_fn(item_fn: &syn::ItemFn) -> bool {
     returns_element(&item_fn.sig.output)
 }
 
+fn get_closure_hook_body(local: &syn::Local) -> Option<&syn::Expr> {
+    if let Pat::Ident(ident) = &local.pat {
+        if is_hook_ident(&ident.ident) {
+            if let Some((_, expr)) = &local.init {
+                if let syn::Expr::Closure(closure) = &**expr {
+                    return Some(&closure.body);
+                }
+            }
+        }
+    }
+
+    None
+}
+
 fn fn_name_and_name_span(item_fn: &syn::ItemFn) -> (String, Span) {
     let name = item_fn.sig.ident.to_string();
     let name_span = item_fn.sig.ident.span().into();
@@ -173,6 +187,17 @@ impl<'ast> syn::visit::Visit<'ast> for VisitHooks {
         self.context.pop();
     }
 
+    fn visit_local(&mut self, i: &'ast syn::Local) {
+        if let Some(body) = get_closure_hook_body(i) {
+            // if the closure is a hook, we only visit the body of the closure.
+            // this prevents adding a ClosureInfo node to the context
+            syn::visit::visit_expr(self, body);
+        } else {
+            // otherwise visit the whole local
+            syn::visit::visit_local(self, i);
+        }
+    }
+
     fn visit_expr_if(&mut self, i: &'ast syn::ExprIf) {
         self.context.push(Node::If(IfInfo::new(
             i.span().into(),
@@ -252,7 +277,7 @@ mod tests {
     use super::*;
 
     #[test]
-    fn test_no_issues() {
+    fn test_no_hooks() {
         let contents = indoc! {r#"
             fn App(cx: Scope) -> Element {
                 rsx! {
@@ -266,6 +291,54 @@ mod tests {
         assert_eq!(report.issues, vec![]);
     }
 
+    #[test]
+    fn test_hook_correctly_used_inside_component() {
+        let contents = indoc! {r#"
+            fn App(cx: Scope) -> Element {
+                let count = use_state(cx, || 0);
+                rsx! {
+                    p { "Hello World: {count}" }
+                }
+            }
+        "#};
+
+        let report = check_file("app.rs".into(), contents);
+
+        assert_eq!(report.issues, vec![]);
+    }
+
+    #[test]
+    fn test_hook_correctly_used_inside_hook_fn() {
+        let contents = indoc! {r#"
+            fn use_thing(cx: Scope) -> UseState<i32> {
+                use_state(cx, || 0)
+            }
+        "#};
+
+        let report = check_file("use_thing.rs".into(), contents);
+
+        assert_eq!(report.issues, vec![]);
+    }
+
+    #[test]
+    fn test_hook_correctly_used_inside_hook_closure() {
+        let contents = indoc! {r#"
+            fn App(cx: Scope) -> Element {
+                let use_thing = || {
+                    use_state(cx, || 0)
+                };
+                let count = use_thing();
+                rsx! {
+                    p { "Hello World: {count}" }
+                }
+            }
+        "#};
+
+        let report = check_file("app.rs".into(), contents);
+
+        assert_eq!(report.issues, vec![]);
+    }
+
     #[test]
     fn test_conditional_hook_if() {
         let contents = indoc! {r#"