1use rustc_hash::FxHashSet;
2use swc_common::{
3 comments::Comments, sync::Lrc, util::take::Take, BytePos, Mark, SourceMap, SourceMapper, Span,
4 Spanned, SyntaxContext, DUMMY_SP,
5};
6use swc_ecma_ast::*;
7use swc_ecma_utils::{private_ident, quote_ident, quote_str, ExprFactory};
8use swc_ecma_visit::{visit_mut_pass, Visit, VisitMut, VisitMutWith};
9
10use self::{
11 hook::HookRegister,
12 util::{collect_ident_in_jsx, is_body_arrow_fn, is_import_or_require, make_assign_stmt},
13};
14
15pub mod options;
16use options::RefreshOptions;
17mod hook;
18mod util;
19
20#[cfg(test)]
21mod tests;
22
23struct Hoc {
24 insert: bool,
25 reg: Vec<(Ident, Id)>,
26 hook: Option<HocHook>,
27}
28struct HocHook {
29 callee: Callee,
30 rest_arg: Vec<ExprOrSpread>,
31}
32enum Persist {
33 Hoc(Hoc),
34 Component(Ident),
35 None,
36}
37fn get_persistent_id(ident: &Ident) -> Persist {
38 if ident.sym.starts_with(|c: char| c.is_ascii_uppercase()) {
39 if cfg!(debug_assertions) && ident.ctxt == SyntaxContext::empty() {
40 panic!("`{}` should be resolved", ident)
41 }
42 Persist::Component(ident.clone())
43 } else {
44 Persist::None
45 }
46}
47
48pub fn refresh<C: Comments>(
51 dev: bool,
52 options: Option<RefreshOptions>,
53 cm: Lrc<SourceMap>,
54 comments: Option<C>,
55 global_mark: Mark,
56) -> impl Pass {
57 visit_mut_pass(Refresh {
58 enable: dev && options.is_some(),
59 cm,
60 comments,
61 should_reset: false,
62 options: options.unwrap_or_default(),
63 global_mark,
64 })
65}
66
67struct Refresh<C: Comments> {
68 enable: bool,
69 options: RefreshOptions,
70 cm: Lrc<SourceMap>,
71 should_reset: bool,
72 comments: Option<C>,
73 global_mark: Mark,
74}
75
76impl<C: Comments> Refresh<C> {
77 fn get_persistent_id_from_var_decl(
78 &self,
79 var_decl: &mut VarDecl,
80 used_in_jsx: &FxHashSet<Id>,
81 hook_reg: &mut HookRegister,
82 ) -> Persist {
83 if let [VarDeclarator {
85 name: Pat::Ident(binding),
86 init: Some(init_expr),
87 ..
88 }] = var_decl.decls.as_mut_slice()
89 {
90 if used_in_jsx.contains(&binding.to_id()) && !is_import_or_require(init_expr) {
91 match init_expr.as_ref() {
92 Expr::Arrow(_) | Expr::Fn(_) | Expr::TaggedTpl(_) | Expr::Call(_) => {
94 return Persist::Component(Ident::from(&*binding))
95 }
96 _ => (),
97 }
98 }
99
100 if let Persist::Component(persistent_id) = get_persistent_id(&Ident::from(&*binding)) {
101 return match init_expr.as_mut() {
102 Expr::Fn(_) => Persist::Component(persistent_id),
103 Expr::Arrow(ArrowExpr { body, .. }) => {
104 if is_body_arrow_fn(body) {
107 Persist::None
108 } else {
109 Persist::Component(persistent_id)
110 }
111 }
112 Expr::Call(call_expr) => {
114 let res = self.get_persistent_id_from_possible_hoc(
115 call_expr,
116 vec![(private_ident!("_c"), persistent_id.to_id())],
117 hook_reg,
118 );
119 if let Persist::Hoc(Hoc {
120 insert,
121 reg,
122 hook: Some(hook),
123 }) = res
124 {
125 make_hook_reg(init_expr.as_mut(), hook);
126 Persist::Hoc(Hoc {
127 insert,
128 reg,
129 hook: None,
130 })
131 } else {
132 res
133 }
134 }
135 _ => Persist::None,
136 };
137 }
138 }
139 Persist::None
140 }
141
142 fn get_persistent_id_from_possible_hoc(
143 &self,
144 call_expr: &mut CallExpr,
145 mut reg: Vec<(Ident, Id)>,
146 hook_reg: &mut HookRegister,
147 ) -> Persist {
148 let first_arg = match call_expr.args.as_mut_slice() {
149 [first, ..] => &mut first.expr,
150 _ => return Persist::None,
151 };
152 let callee = if let Callee::Expr(expr) = &call_expr.callee {
153 expr
154 } else {
155 return Persist::None;
156 };
157 let hoc_name = match callee.as_ref() {
158 Expr::Ident(fn_name) => fn_name.sym.to_string(),
159 Expr::Member(member) => self.cm.span_to_snippet(member.span).unwrap_or_default(),
161 _ => return Persist::None,
162 };
163 let reg_str = (
164 format!("{}${}", reg.last().unwrap().1 .0, &hoc_name).into(),
165 SyntaxContext::empty(),
166 );
167 match first_arg.as_mut() {
168 Expr::Call(expr) => {
169 let reg_ident = private_ident!("_c");
170 reg.push((reg_ident.clone(), reg_str));
171 if let Persist::Hoc(hoc) =
172 self.get_persistent_id_from_possible_hoc(expr, reg, hook_reg)
173 {
174 let mut first = first_arg.take();
175 if let Some(HocHook { callee, rest_arg }) = &hoc.hook {
176 let span = first.span();
177 let mut args = vec![first.as_arg()];
178 args.extend(rest_arg.clone());
179 first = CallExpr {
180 span,
181 callee: callee.clone(),
182 args,
183 ..Default::default()
184 }
185 .into()
186 }
187 *first_arg = Box::new(make_assign_stmt(reg_ident, first));
188
189 Persist::Hoc(hoc)
190 } else {
191 Persist::None
192 }
193 }
194 Expr::Fn(_) | Expr::Arrow(_) => {
195 let reg_ident = private_ident!("_c");
196 let mut first = first_arg.take();
197 first.visit_mut_with(hook_reg);
198 let hook = if let Expr::Call(call) = first.as_ref() {
199 let res = Some(HocHook {
200 callee: call.callee.clone(),
201 rest_arg: call.args[1..].to_owned(),
202 });
203 *first_arg = Box::new(make_assign_stmt(reg_ident.clone(), first));
204 res
205 } else {
206 *first_arg = Box::new(make_assign_stmt(reg_ident.clone(), first));
207 None
208 };
209 reg.push((reg_ident, reg_str));
210 Persist::Hoc(Hoc {
211 reg,
212 insert: true,
213 hook,
214 })
215 }
216 Expr::Ident(ident) => {
219 if let Persist::Component(_) = get_persistent_id(ident) {
220 Persist::Hoc(Hoc {
221 reg,
222 insert: true,
223 hook: None,
224 })
225 } else {
226 Persist::None
227 }
228 }
229 _ => Persist::None,
230 }
231 }
232}
233
234impl<C> Visit for Refresh<C>
236where
237 C: Comments,
238{
239 fn visit_span(&mut self, n: &Span) {
240 if self.should_reset {
241 return;
242 }
243
244 let mut should_refresh = self.should_reset;
245 if let Some(comments) = &self.comments {
246 if !n.hi.is_dummy() {
247 comments.with_leading(n.hi - BytePos(1), |comments| {
248 if comments.iter().any(|c| c.text.contains("@refresh reset")) {
249 should_refresh = true
250 }
251 });
252 }
253
254 comments.with_leading(n.lo, |comments| {
255 if comments.iter().any(|c| c.text.contains("@refresh reset")) {
256 should_refresh = true
257 }
258 });
259
260 comments.with_trailing(n.lo, |comments| {
261 if comments.iter().any(|c| c.text.contains("@refresh reset")) {
262 should_refresh = true
263 }
264 });
265 }
266
267 self.should_reset = should_refresh;
268 }
269}
270
271impl<C: Comments> VisitMut for Refresh<C> {
273 fn visit_mut_module(&mut self, n: &mut Module) {
277 if !self.enable {
278 return;
279 }
280
281 self.visit_module(n);
283
284 self.visit_mut_module_items(&mut n.body);
285 }
286
287 fn visit_mut_module_items(&mut self, module_items: &mut Vec<ModuleItem>) {
288 let used_in_jsx = collect_ident_in_jsx(module_items);
289
290 let mut items = Vec::with_capacity(module_items.len());
291 let mut refresh_regs = Vec::<(Ident, Id)>::new();
292
293 let mut hook_visitor = HookRegister {
294 options: &self.options,
295 ident: Vec::new(),
296 extra_stmt: Vec::new(),
297 current_scope: vec![SyntaxContext::empty().apply_mark(self.global_mark)],
298 cm: &self.cm,
299 should_reset: self.should_reset,
300 };
301
302 for mut item in module_items.take() {
303 let persistent_id = match &mut item {
304 ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl { ident, .. }))) => {
306 get_persistent_id(ident)
307 }
308
309 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
311 decl: Decl::Fn(FnDecl { ident, .. }),
312 ..
313 })) => get_persistent_id(ident),
314
315 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
317 decl:
318 DefaultDecl::Fn(FnExpr {
319 ident: Some(ident),
321 ..
322 }),
323 ..
324 })) => get_persistent_id(ident),
325
326 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl)))
329 | ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
330 decl: Decl::Var(var_decl),
331 ..
332 })) => {
333 self.get_persistent_id_from_var_decl(var_decl, &used_in_jsx, &mut hook_visitor)
334 }
335
336 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
341 expr,
342 span,
343 })) => {
344 if let Expr::Call(call) = expr.as_mut() {
345 if let Persist::Hoc(Hoc { reg, hook, .. }) = self
346 .get_persistent_id_from_possible_hoc(
347 call,
348 vec![(
349 private_ident!("_c"),
350 ("%default%".into(), SyntaxContext::empty()),
351 )],
352 &mut hook_visitor,
353 )
354 {
355 if let Some(hook) = hook {
356 make_hook_reg(expr.as_mut(), hook)
357 }
358 item = ExportDefaultExpr {
359 expr: Box::new(make_assign_stmt(reg[0].0.clone(), expr.take())),
360 span: *span,
361 }
362 .into();
363 Persist::Hoc(Hoc {
364 insert: false,
365 reg,
366 hook: None,
367 })
368 } else {
369 Persist::None
370 }
371 } else {
372 Persist::None
373 }
374 }
375
376 _ => Persist::None,
377 };
378
379 if let Persist::Hoc(_) = persistent_id {
380 items.push(item);
383 } else {
384 item.visit_mut_children_with(&mut hook_visitor);
385
386 items.push(item);
387 items.extend(
388 hook_visitor
389 .extra_stmt
390 .take()
391 .into_iter()
392 .map(ModuleItem::Stmt),
393 );
394 }
395
396 match persistent_id {
397 Persist::None => (),
398 Persist::Component(persistent_id) => {
399 let registration_handle = private_ident!("_c");
400
401 refresh_regs.push((registration_handle.clone(), persistent_id.to_id()));
402
403 items.push(
404 ExprStmt {
405 span: DUMMY_SP,
406 expr: Box::new(make_assign_stmt(
407 registration_handle,
408 persistent_id.into(),
409 )),
410 }
411 .into(),
412 );
413 }
414
415 Persist::Hoc(mut hoc) => {
416 hoc.reg = hoc.reg.into_iter().rev().collect();
417 if hoc.insert {
418 let (ident, name) = hoc.reg.last().unwrap();
419 items.push(
420 ExprStmt {
421 span: DUMMY_SP,
422 expr: Box::new(make_assign_stmt(
423 ident.clone(),
424 Ident::new(name.0.clone(), DUMMY_SP, name.1).into(),
425 )),
426 }
427 .into(),
428 )
429 }
430 refresh_regs.append(&mut hoc.reg);
431 }
432 }
433 }
434
435 if !hook_visitor.ident.is_empty() {
436 items.insert(0, hook_visitor.gen_hook_handle().into());
437 }
438
439 if !refresh_regs.is_empty() {
444 items.push(
445 VarDecl {
446 span: DUMMY_SP,
447 kind: VarDeclKind::Var,
448 declare: false,
449 decls: refresh_regs
450 .iter()
451 .map(|(handle, _)| VarDeclarator {
452 span: DUMMY_SP,
453 name: handle.clone().into(),
454 init: None,
455 definite: false,
456 })
457 .collect(),
458 ..Default::default()
459 }
460 .into(),
461 );
462 }
463
464 let refresh_reg = self.options.refresh_reg.as_str();
470 for (handle, persistent_id) in refresh_regs {
471 items.push(
472 ExprStmt {
473 span: DUMMY_SP,
474 expr: CallExpr {
475 callee: quote_ident!(refresh_reg).as_callee(),
476 args: vec![handle.as_arg(), quote_str!(persistent_id.0).as_arg()],
477 ..Default::default()
478 }
479 .into(),
480 }
481 .into(),
482 );
483 }
484
485 *module_items = items
486 }
487
488 fn visit_mut_ts_module_decl(&mut self, _: &mut TsModuleDecl) {}
489}
490
491fn make_hook_reg(expr: &mut Expr, mut hook: HocHook) {
492 let span = expr.span();
493 let mut args = vec![expr.take().as_arg()];
494 args.append(&mut hook.rest_arg);
495 *expr = CallExpr {
496 span,
497 callee: hook.callee,
498 args,
499 ..Default::default()
500 }
501 .into();
502}