swc_plugin_inferno/refresh/
hook.rs1use std::{fmt::Write, mem};
2
3use base64::prelude::{Engine, BASE64_STANDARD};
4use sha1::{Digest, Sha1};
5use swc_core::common::util::take::Take;
6use swc_core::common::{SourceMap, SourceMapper, Spanned, SyntaxContext, DUMMY_SP};
7use swc_core::ecma::ast::*;
8use swc_core::ecma::utils::{private_ident, quote_ident, ExprFactory};
9use swc_core::ecma::visit::{
10 noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
11};
12
13use super::util::{is_builtin_hook, make_call_expr, make_call_stmt};
14use crate::RefreshOptions;
15
16struct HookSig {
18 handle: Ident,
19 hooks: Vec<Hook>,
21}
22
23impl HookSig {
24 fn new(hooks: Vec<Hook>) -> Self {
25 HookSig {
26 handle: private_ident!("_s"),
27 hooks,
28 }
29 }
30}
31
32struct Hook {
33 callee: HookCall,
34 key: String,
35}
36
37#[allow(clippy::large_enum_variant)]
39enum HookCall {
40 Ident(Ident),
41 Member(Expr, IdentName), }
43pub struct HookRegister<'a> {
44 pub options: &'a RefreshOptions,
45 pub ident: Vec<Ident>,
46 pub extra_stmt: Vec<Stmt>,
47 pub current_scope: Vec<SyntaxContext>,
48 pub cm: &'a SourceMap,
49 pub should_reset: bool,
50}
51
52impl<'a> HookRegister<'a> {
53 pub fn gen_hook_handle(&mut self) -> Stmt {
54 VarDecl {
55 span: DUMMY_SP,
56 kind: VarDeclKind::Var,
57 decls: self
58 .ident
59 .take()
60 .into_iter()
61 .map(|id| VarDeclarator {
62 span: DUMMY_SP,
63 name: id.into(),
64 init: Some(Box::new(make_call_expr(
65 quote_ident!(self.options.refresh_sig.clone()).into(),
66 ))),
67 definite: false,
68 })
69 .collect(),
70 declare: false,
71 ..Default::default()
72 }
73 .into()
74 }
75
76 fn wrap_with_register(&self, handle: Ident, func: Expr, hooks: Vec<Hook>) -> Expr {
80 let mut args = vec![func.as_arg()];
81 let mut sign = Vec::new();
82 let mut custom_hook = Vec::new();
83
84 for hook in hooks {
85 let name = match &hook.callee {
86 HookCall::Ident(i) => i.clone(),
87 HookCall::Member(_, i) => i.clone().into(),
88 };
89 sign.push(format!("{}{{{}}}", name.sym, hook.key));
90 match &hook.callee {
91 HookCall::Ident(ident) if !is_builtin_hook(&ident.sym) => {
92 custom_hook.push(hook.callee);
93 }
94 HookCall::Member(Expr::Ident(obj_ident), prop) if !is_builtin_hook(&prop.sym) => {
95 if obj_ident.sym.as_ref() != "React" {
96 custom_hook.push(hook.callee);
97 }
98 }
99 _ => (),
100 };
101 }
102
103 let sign = sign.join("\n");
104 let sign = if self.options.emit_full_signatures {
105 sign
106 } else {
107 let mut hasher = Sha1::new();
108 hasher.update(sign);
109 BASE64_STANDARD.encode(hasher.finalize())
110 };
111
112 args.push(
113 Lit::Str(Str {
114 span: DUMMY_SP,
115 raw: None,
116 value: sign.into(),
117 })
118 .as_arg(),
119 );
120
121 let mut should_reset = self.should_reset;
122
123 let mut custom_hook_in_scope = Vec::new();
124
125 for hook in custom_hook {
126 let ident = match &hook {
127 HookCall::Ident(ident) => Some(ident),
128 HookCall::Member(Expr::Ident(ident), _) => Some(ident),
129 _ => None,
130 };
131 if !ident
132 .map(|id| self.current_scope.contains(&id.ctxt))
133 .unwrap_or(false)
134 {
135 should_reset = true;
138 } else {
139 custom_hook_in_scope.push(hook);
140 }
141 }
142
143 if should_reset || !custom_hook_in_scope.is_empty() {
144 args.push(should_reset.as_arg());
145 }
146
147 if !custom_hook_in_scope.is_empty() {
148 let elems = custom_hook_in_scope
149 .into_iter()
150 .map(|hook| {
151 Some(
152 match hook {
153 HookCall::Ident(ident) => Expr::from(ident),
154 HookCall::Member(obj, prop) => MemberExpr {
155 span: DUMMY_SP,
156 obj: Box::new(obj),
157 prop: MemberProp::Ident(prop),
158 }
159 .into(),
160 }
161 .as_arg(),
162 )
163 })
164 .collect();
165 args.push(
166 Function {
167 is_generator: false,
168 is_async: false,
169 params: Vec::new(),
170 decorators: Vec::new(),
171 span: DUMMY_SP,
172 body: Some(BlockStmt {
173 span: DUMMY_SP,
174 stmts: vec![Stmt::Return(ReturnStmt {
175 span: DUMMY_SP,
176 arg: Some(Box::new(Expr::Array(ArrayLit {
177 span: DUMMY_SP,
178 elems,
179 }))),
180 })],
181 ..Default::default()
182 }),
183 ..Default::default()
184 }
185 .as_arg(),
186 );
187 }
188
189 CallExpr {
190 span: DUMMY_SP,
191 callee: handle.as_callee(),
192 args,
193 ..Default::default()
194 }
195 .into()
196 }
197
198 fn gen_hook_register_stmt(&mut self, ident: Ident, sig: HookSig) {
199 self.ident.push(sig.handle.clone());
200 self.extra_stmt.push(
201 ExprStmt {
202 span: DUMMY_SP,
203 expr: Box::new(self.wrap_with_register(sig.handle, ident.into(), sig.hooks)),
204 }
205 .into(),
206 )
207 }
208}
209
210impl<'a> VisitMut for HookRegister<'a> {
211 noop_visit_mut_type!();
212
213 fn visit_mut_block_stmt(&mut self, b: &mut BlockStmt) {
214 let old_ident = self.ident.take();
215 let old_stmts = self.extra_stmt.take();
216
217 self.current_scope.push(b.ctxt);
218
219 let stmt_count = b.stmts.len();
220 let stmts = mem::replace(&mut b.stmts, Vec::with_capacity(stmt_count));
221
222 for mut stmt in stmts {
223 stmt.visit_mut_children_with(self);
224
225 b.stmts.push(stmt);
226 b.stmts.append(&mut self.extra_stmt);
227 }
228
229 if !self.ident.is_empty() {
230 b.stmts.insert(0, self.gen_hook_handle())
231 }
232
233 self.current_scope.pop();
234 self.ident = old_ident;
235 self.extra_stmt = old_stmts;
236 }
237
238 fn visit_mut_expr(&mut self, e: &mut Expr) {
239 e.visit_mut_children_with(self);
240
241 match e {
242 Expr::Fn(FnExpr { function: f, .. }) if f.body.is_some() => {
243 let sig = collect_hooks(&mut f.body.as_mut().unwrap().stmts, self.cm);
244
245 if let Some(HookSig { handle, hooks }) = sig {
246 self.ident.push(handle.clone());
247 *e = self.wrap_with_register(handle, e.take(), hooks);
248 }
249 }
250 Expr::Arrow(ArrowExpr { body, .. }) => {
251 let sig = collect_hooks_arrow(body, self.cm);
252
253 if let Some(HookSig { handle, hooks }) = sig {
254 self.ident.push(handle.clone());
255 *e = self.wrap_with_register(handle, e.take(), hooks);
256 }
257 }
258 _ => (),
259 }
260 }
261
262 fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
263 for decl in n.decls.iter_mut() {
267 if let VarDeclarator {
268 name: Pat::Ident(id),
270 init: Some(init),
271 ..
272 } = decl
273 {
274 match init.as_mut() {
275 Expr::Fn(FnExpr { function: f, .. }) if f.body.is_some() => {
276 f.body.visit_mut_with(self);
277 if let Some(sig) =
278 collect_hooks(&mut f.body.as_mut().unwrap().stmts, self.cm)
279 {
280 self.gen_hook_register_stmt(Ident::from(&*id), sig);
281 }
282 }
283 Expr::Arrow(ArrowExpr { body, .. }) => {
284 body.visit_mut_with(self);
285 if let Some(sig) = collect_hooks_arrow(body, self.cm) {
286 self.gen_hook_register_stmt(Ident::from(&*id), sig);
287 }
288 }
289 _ => self.visit_mut_expr(init),
290 }
291 } else {
292 decl.visit_mut_children_with(self)
293 }
294 }
295 }
296
297 fn visit_mut_default_decl(&mut self, d: &mut DefaultDecl) {
298 d.visit_mut_children_with(self);
299
300 match d {
302 DefaultDecl::Fn(FnExpr {
303 ident: Some(ident),
304 function: f,
305 }) if f.body.is_some() => {
306 if let Some(sig) = collect_hooks(&mut f.body.as_mut().unwrap().stmts, self.cm) {
307 self.gen_hook_register_stmt(ident.clone(), sig);
308 }
309 }
310 _ => {}
311 }
312 }
313
314 fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
315 f.visit_mut_children_with(self);
316
317 if let Some(body) = &mut f.function.body {
318 if let Some(sig) = collect_hooks(&mut body.stmts, self.cm) {
319 self.gen_hook_register_stmt(f.ident.clone(), sig);
320 }
321 }
322 }
323}
324
325fn collect_hooks(stmts: &mut Vec<Stmt>, cm: &SourceMap) -> Option<HookSig> {
326 let mut hook = HookCollector {
327 state: Vec::new(),
328 cm,
329 };
330
331 stmts.visit_with(&mut hook);
332
333 if !hook.state.is_empty() {
334 let sig = HookSig::new(hook.state);
335 stmts.insert(0, make_call_stmt(sig.handle.clone()));
336
337 Some(sig)
338 } else {
339 None
340 }
341}
342
343fn collect_hooks_arrow(body: &mut BlockStmtOrExpr, cm: &SourceMap) -> Option<HookSig> {
344 match body {
345 BlockStmtOrExpr::BlockStmt(block) => collect_hooks(&mut block.stmts, cm),
346 BlockStmtOrExpr::Expr(expr) => {
347 let mut hook = HookCollector {
348 state: Vec::new(),
349 cm,
350 };
351
352 expr.visit_with(&mut hook);
353
354 if !hook.state.is_empty() {
355 let sig = HookSig::new(hook.state);
356 *body = BlockStmtOrExpr::BlockStmt(BlockStmt {
357 span: expr.span(),
358 stmts: vec![
359 make_call_stmt(sig.handle.clone()),
360 Stmt::Return(ReturnStmt {
361 span: expr.span(),
362 arg: Some(Box::new(expr.as_mut().take())),
363 }),
364 ],
365 ..Default::default()
366 });
367 Some(sig)
368 } else {
369 None
370 }
371 }
372 }
373}
374
375struct HookCollector<'a> {
376 state: Vec<Hook>,
377 cm: &'a SourceMap,
378}
379
380fn is_hook_like(s: &str) -> bool {
381 if let Some(s) = s.strip_prefix("use") {
382 s.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
383 } else {
384 false
385 }
386}
387
388impl<'a> HookCollector<'a> {
389 fn get_hook_from_call_expr(&self, expr: &CallExpr, lhs: Option<&Pat>) -> Option<Hook> {
390 let callee = if let Callee::Expr(callee) = &expr.callee {
391 Some(callee.as_ref())
392 } else {
393 None
394 }?;
395 let mut hook_call = None;
396 let ident = match callee {
397 Expr::Ident(ident) => {
398 hook_call = Some(HookCall::Ident(ident.clone()));
399 Some(&ident.sym)
400 }
401 Expr::Member(MemberExpr {
403 obj,
404 prop: MemberProp::Ident(ident),
405 ..
406 }) => {
407 hook_call = Some(HookCall::Member(*obj.clone(), ident.clone()));
408 Some(&ident.sym)
409 }
410 _ => None,
411 }?;
412 let name = if is_hook_like(ident) {
413 Some(ident)
414 } else {
415 None
416 }?;
417 let mut key = if let Some(name) = lhs {
418 self.cm.span_to_snippet(name.span()).unwrap_or_default()
419 } else {
420 String::new()
421 };
422 if *name == "useState" && !expr.args.is_empty() {
424 let _ = write!(
426 key,
427 "({})",
428 self.cm
429 .span_to_snippet(expr.args[0].span())
430 .unwrap_or_default()
431 );
432 } else if name == "useReducer" && expr.args.len() > 1 {
433 let _ = write!(
435 key,
436 "({})",
437 self.cm
438 .span_to_snippet(expr.args[1].span())
439 .unwrap_or_default()
440 );
441 }
442
443 let callee = hook_call?;
444 Some(Hook { callee, key })
445 }
446
447 fn get_hook_from_expr(&self, expr: &Expr, lhs: Option<&Pat>) -> Option<Hook> {
448 if let Expr::Call(call) = expr {
449 self.get_hook_from_call_expr(call, lhs)
450 } else {
451 None
452 }
453 }
454}
455
456impl<'a> Visit for HookCollector<'a> {
457 noop_visit_type!();
458
459 fn visit_block_stmt_or_expr(&mut self, _: &BlockStmtOrExpr) {}
460
461 fn visit_block_stmt(&mut self, _: &BlockStmt) {}
462
463 fn visit_expr(&mut self, expr: &Expr) {
464 expr.visit_children_with(self);
465
466 if let Expr::Call(call) = expr {
467 if let Some(hook) = self.get_hook_from_call_expr(call, None) {
468 self.state.push(hook)
469 }
470 }
471 }
472
473 fn visit_stmt(&mut self, stmt: &Stmt) {
474 match stmt {
475 Stmt::Expr(ExprStmt { expr, .. }) => {
476 if let Some(hook) = self.get_hook_from_expr(expr, None) {
477 self.state.push(hook)
478 } else {
479 stmt.visit_children_with(self)
480 }
481 }
482 Stmt::Decl(Decl::Var(var_decl)) => {
483 for decl in &var_decl.decls {
484 if let Some(init) = &decl.init {
485 if let Some(hook) = self.get_hook_from_expr(init, Some(&decl.name)) {
486 self.state.push(hook)
487 } else {
488 stmt.visit_children_with(self)
489 }
490 } else {
491 stmt.visit_children_with(self)
492 }
493 }
494 }
495 Stmt::Return(ReturnStmt { arg: Some(arg), .. }) => {
496 if let Some(hook) = self.get_hook_from_expr(arg.as_ref(), None) {
497 self.state.push(hook)
498 } else {
499 stmt.visit_children_with(self)
500 }
501 }
502 _ => stmt.visit_children_with(self),
503 }
504 }
505}