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