hooks_derive_core/
detect.rs

1use quote::ToTokens;
2use syn::spanned::Spanned;
3
4use crate::{
5    tlpc::{tlpc, ExprCallPath},
6    DetectedHookCall,
7};
8
9pub fn expr_path_is_hook(path: &syn::ExprPath) -> bool {
10    if let Some(last) = path.path.segments.last() {
11        last.ident.to_string().starts_with("use_")
12    } else {
13        false
14    }
15}
16
17pub fn mut_expr_of_statement(stmt: &mut syn::Stmt) -> Option<&mut syn::Expr> {
18    match stmt {
19        syn::Stmt::Local(local) => {
20            if let Some((_, expr)) = &mut local.init {
21                Some(&mut *expr)
22            } else {
23                None
24            }
25        }
26        syn::Stmt::Item(_) => {
27            // Items are untouched
28            None
29        }
30        syn::Stmt::Expr(expr) => Some(expr),
31        syn::Stmt::Semi(expr, _) => Some(expr),
32    }
33}
34
35pub fn transform_path_call(
36    e: ExprCallPath,
37    hooks_core_path: impl ToTokens,
38    gen_ident: impl FnOnce(&syn::ExprPath) -> syn::Ident,
39) -> syn::ExprCall {
40    let paren_token = *e.paren_token;
41
42    // ::hooks::core::Hook::<_>::use_hook
43    let path_use_hook: syn::ExprPath = syn::parse_quote! {
44        #hooks_core_path ::Hook::<_>::use_hook
45    };
46    let func_path = std::mem::replace(e.func_path, path_use_hook);
47
48    let ident = gen_ident(&func_path);
49
50    let ident = syn::ExprPath {
51        attrs: vec![],
52        qself: None,
53        path: ident.into(),
54    };
55
56    let hook_args = std::mem::take(e.args);
57    // Hook::<_>::use_hook(ident, (args,))
58    e.args.extend([
59        syn::Expr::Path(ident),
60        syn::Expr::Tuple(syn::ExprTuple {
61            attrs: vec![],
62            paren_token,
63            elems: hook_args,
64        }),
65    ]);
66
67    syn::ExprCall {
68        attrs: vec![],
69        func: Box::new(syn::Expr::Path(func_path)),
70        paren_token,
71        args: Default::default(),
72    }
73}
74
75pub fn detect_hooks<'a>(
76    stmts: impl Iterator<Item = &'a mut syn::Stmt>,
77    hooks_core_path: &impl ToTokens,
78) -> Vec<crate::DetectedHookCall> {
79    let mut used_hooks = vec![];
80
81    let mut mutate_func_path = |e: ExprCallPath| {
82        if expr_path_is_hook(e.func_path) {
83            let mut hook_ident = None;
84            let expr_call = transform_path_call(e, hooks_core_path, |func_path| {
85                let idx = used_hooks.len();
86                let ident = syn::Ident::new(&format!("__hooks_hook_{idx}"), func_path.span());
87                hook_ident = Some(ident.clone());
88                ident
89            });
90            let ident = hook_ident.unwrap();
91            used_hooks.push(DetectedHookCall { ident, expr_call })
92        }
93    };
94
95    for stmt in stmts {
96        if let Some(expr) = mut_expr_of_statement(stmt) {
97            tlpc(expr, &mut mutate_func_path);
98        }
99    }
100
101    used_hooks
102}