swc_ecma_compiler/
lib.rs

1#![allow(clippy::vec_box)]
2
3use std::mem::take;
4
5use rustc_hash::FxHashSet;
6use swc_common::{util::take::Take, Mark, Spanned, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_transforms_base::assumptions::Assumptions;
9use swc_ecma_utils::{
10    default_constructor_with_span, prepend_stmt, private_ident, quote_ident, ExprFactory,
11};
12use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
13use swc_trace_macro::swc_trace;
14
15use crate::es2022::{
16    private_in_object::{ClassAnalyzer, ClassData, Mode},
17    static_blocks::generate_uid,
18};
19pub use crate::features::Features;
20
21mod es2020;
22mod es2021;
23mod es2022;
24mod features;
25
26#[derive(Debug)]
27pub struct Compiler {
28    config: Config,
29}
30
31impl Compiler {
32    pub fn new(config: Config) -> Self {
33        Self { config }
34    }
35}
36
37#[derive(Debug, Default)]
38pub struct Config {
39    pub assumptions: Assumptions,
40    /// Always compile these syntaxes.
41    pub includes: Features,
42    /// Always preserve these syntaxes.
43    pub excludes: Features,
44}
45
46impl Pass for Compiler {
47    fn process(&mut self, program: &mut swc_ecma_ast::Program) {
48        program.visit_mut_with(&mut CompilerImpl::new(&self.config));
49    }
50}
51
52struct CompilerImpl<'a> {
53    config: &'a Config,
54
55    // ES2022: Private in object transformation state
56    es2022_private_field_helper_vars: Vec<VarDeclarator>,
57    es2022_private_field_init_exprs: Vec<Box<Expr>>,
58    es2022_injected_weakset_vars: FxHashSet<Id>,
59    es2022_current_class_data: ClassData,
60
61    // ES2021: Logical assignments transformation state
62    es2021_logical_assignment_vars: Vec<VarDeclarator>,
63
64    // ES2020: Nullish coalescing transformation state
65    es2020_nullish_coalescing_vars: Vec<VarDeclarator>,
66}
67
68#[swc_trace]
69impl<'a> CompilerImpl<'a> {
70    fn new(config: &'a Config) -> Self {
71        Self {
72            config,
73            es2022_private_field_helper_vars: Vec::new(),
74            es2022_private_field_init_exprs: Vec::new(),
75            es2022_injected_weakset_vars: FxHashSet::default(),
76            es2022_current_class_data: ClassData::default(),
77            es2021_logical_assignment_vars: Vec::new(),
78            es2020_nullish_coalescing_vars: Vec::new(),
79        }
80    }
81
82    /// ES2022: Transform static blocks to static private fields
83    fn es2022_static_blocks_to_private_fields(&mut self, class: &mut Class) {
84        let mut private_names = FxHashSet::default();
85        for member in &class.body {
86            if let ClassMember::PrivateProp(private_property) = member {
87                private_names.insert(private_property.key.name.clone());
88            }
89        }
90
91        let mut count = 0;
92        for member in class.body.iter_mut() {
93            if let ClassMember::StaticBlock(static_block) = member {
94                if static_block.body.stmts.is_empty() {
95                    *member = ClassMember::dummy();
96                    continue;
97                }
98
99                let static_block_private_id = generate_uid(&private_names, &mut count);
100                *member = self
101                    .transform_static_block(static_block.take(), static_block_private_id)
102                    .into();
103            };
104        }
105    }
106
107    /// ES2022: Analyze class private fields for 'private in object'
108    /// transformation
109    fn es2022_analyze_private_fields_for_in_operator(&mut self, class: &Class) {
110        class.visit_children_with(&mut ClassAnalyzer {
111            brand_check_names: &mut self.es2022_current_class_data.names_used_for_brand_checks,
112            ignore_class: true,
113        });
114
115        for m in &class.body {
116            match m {
117                ClassMember::PrivateMethod(m) => {
118                    self.es2022_current_class_data
119                        .privates
120                        .insert(m.key.name.clone());
121                    self.es2022_current_class_data
122                        .methods
123                        .push(m.key.name.clone());
124
125                    if m.is_static {
126                        self.es2022_current_class_data
127                            .statics
128                            .push(m.key.name.clone());
129                    }
130                }
131
132                ClassMember::PrivateProp(m) => {
133                    self.es2022_current_class_data
134                        .privates
135                        .insert(m.key.name.clone());
136
137                    if m.is_static {
138                        self.es2022_current_class_data
139                            .statics
140                            .push(m.key.name.clone());
141                    }
142                }
143
144                _ => {}
145            }
146        }
147    }
148
149    /// ES2022: Inject WeakSet initialization into constructor for private field
150    /// brand checks
151    fn es2022_inject_weakset_init_for_private_fields(&mut self, class: &mut Class) {
152        if self.es2022_current_class_data.constructor_exprs.is_empty() {
153            return;
154        }
155
156        let has_constructor = class
157            .body
158            .iter()
159            .any(|m| matches!(m, ClassMember::Constructor(_)));
160
161        if !has_constructor {
162            let has_super = class.super_class.is_some();
163            class
164                .body
165                .push(ClassMember::Constructor(default_constructor_with_span(
166                    has_super, class.span,
167                )));
168        }
169
170        for m in &mut class.body {
171            if let ClassMember::Constructor(Constructor {
172                body: Some(body), ..
173            }) = m
174            {
175                for expr in take(&mut self.es2022_current_class_data.constructor_exprs) {
176                    body.stmts.push(
177                        ExprStmt {
178                            span: DUMMY_SP,
179                            expr,
180                        }
181                        .into(),
182                    );
183                }
184            }
185        }
186    }
187
188    /// ES2022: Transform 'private in object' expressions to WeakSet.has() calls
189    fn es2022_transform_private_in_to_weakset_has(&mut self, e: &mut Expr) -> bool {
190        if let Expr::Bin(BinExpr {
191            span,
192            op: op!("in"),
193            left,
194            right,
195        }) = e
196        {
197            if left.is_private_name() {
198                let left = left.take().expect_private_name();
199
200                let is_static = self.es2022_current_class_data.statics.contains(&left.name);
201                let is_method = self.es2022_current_class_data.methods.contains(&left.name);
202
203                if let Some(cls_ident) = self.es2022_current_class_data.ident.clone() {
204                    if is_static && is_method {
205                        *e = BinExpr {
206                            span: *span,
207                            op: op!("==="),
208                            left: cls_ident.into(),
209                            right: right.take(),
210                        }
211                        .into();
212                        return true;
213                    }
214                }
215
216                let var_name =
217                    self.var_name_for_brand_check(&left, &self.es2022_current_class_data);
218
219                if self.es2022_current_class_data.privates.contains(&left.name)
220                    && self.es2022_injected_weakset_vars.insert(var_name.to_id())
221                {
222                    self.es2022_current_class_data.vars.push_var(
223                        var_name.clone(),
224                        Some(
225                            NewExpr {
226                                span: DUMMY_SP,
227                                callee: Box::new(quote_ident!("WeakSet").into()),
228                                args: Some(Default::default()),
229                                ..Default::default()
230                            }
231                            .into(),
232                        ),
233                    );
234
235                    if is_method {
236                        self.es2022_current_class_data.constructor_exprs.push(
237                            CallExpr {
238                                span: DUMMY_SP,
239                                callee: var_name
240                                    .clone()
241                                    .make_member(IdentName::new("add".into(), DUMMY_SP))
242                                    .as_callee(),
243                                args: vec![ExprOrSpread {
244                                    spread: None,
245                                    expr: Box::new(ThisExpr { span: DUMMY_SP }.into()),
246                                }],
247                                ..Default::default()
248                            }
249                            .into(),
250                        );
251                    }
252                }
253
254                *e = CallExpr {
255                    span: *span,
256                    callee: var_name
257                        .make_member(IdentName::new("has".into(), DUMMY_SP))
258                        .as_callee(),
259                    args: vec![ExprOrSpread {
260                        spread: None,
261                        expr: right.take(),
262                    }],
263                    ..Default::default()
264                }
265                .into();
266                return true;
267            }
268        }
269        false
270    }
271
272    /// ES2022: Prepend private field helper variables to statements
273    fn es2022_prepend_private_field_vars(&mut self, stmts: &mut Vec<Stmt>) {
274        if self.es2022_private_field_helper_vars.is_empty() {
275            return;
276        }
277
278        prepend_stmt(
279            stmts,
280            VarDecl {
281                span: DUMMY_SP,
282                kind: VarDeclKind::Var,
283                declare: Default::default(),
284                decls: take(&mut self.es2022_private_field_helper_vars),
285                ..Default::default()
286            }
287            .into(),
288        );
289    }
290
291    /// ES2022: Prepend private field helper variables to module items
292    fn es2022_prepend_private_field_vars_module(&mut self, items: &mut Vec<ModuleItem>) {
293        if self.es2022_private_field_helper_vars.is_empty() {
294            return;
295        }
296
297        prepend_stmt(
298            items,
299            VarDecl {
300                span: DUMMY_SP,
301                kind: VarDeclKind::Var,
302                declare: Default::default(),
303                decls: take(&mut self.es2022_private_field_helper_vars),
304                ..Default::default()
305            }
306            .into(),
307        );
308    }
309
310    /// ES2022: Add WeakSet.add() calls to private properties for brand checking
311    fn es2022_add_weakset_to_private_props(&mut self, n: &mut PrivateProp) {
312        if !self
313            .es2022_current_class_data
314            .names_used_for_brand_checks
315            .contains(&n.key.name)
316        {
317            return;
318        }
319
320        let var_name = self.var_name_for_brand_check(&n.key, &self.es2022_current_class_data);
321
322        match &mut n.value {
323            Some(init) => {
324                let init_span = init.span();
325
326                let tmp = private_ident!("_tmp");
327
328                self.es2022_current_class_data
329                    .vars
330                    .push_var(tmp.clone(), None);
331
332                let assign = AssignExpr {
333                    span: DUMMY_SP,
334                    op: op!("="),
335                    left: tmp.clone().into(),
336                    right: init.take(),
337                }
338                .into();
339
340                let add_to_checker = CallExpr {
341                    span: DUMMY_SP,
342                    callee: var_name
343                        .make_member(IdentName::new("add".into(), DUMMY_SP))
344                        .as_callee(),
345                    args: vec![ExprOrSpread {
346                        spread: None,
347                        expr: Box::new(ThisExpr { span: DUMMY_SP }.into()),
348                    }],
349                    ..Default::default()
350                }
351                .into();
352
353                *init = SeqExpr {
354                    span: init_span,
355                    exprs: vec![assign, add_to_checker, Box::new(tmp.into())],
356                }
357                .into();
358            }
359            None => {
360                n.value = Some(
361                    UnaryExpr {
362                        span: DUMMY_SP,
363                        op: op!("void"),
364                        arg: Box::new(
365                            CallExpr {
366                                span: DUMMY_SP,
367                                callee: var_name
368                                    .make_member(IdentName::new("add".into(), DUMMY_SP))
369                                    .as_callee(),
370                                args: vec![ExprOrSpread {
371                                    spread: None,
372                                    expr: Box::new(ThisExpr { span: DUMMY_SP }.into()),
373                                }],
374                                ..Default::default()
375                            }
376                            .into(),
377                        ),
378                    }
379                    .into(),
380                )
381            }
382        }
383    }
384}
385
386#[swc_trace]
387impl<'a> VisitMut for CompilerImpl<'a> {
388    noop_visit_mut_type!(fail);
389
390    fn visit_mut_class(&mut self, class: &mut Class) {
391        // Pre-processing transformations
392        if self.config.includes.contains(Features::STATIC_BLOCKS) {
393            self.es2022_static_blocks_to_private_fields(class);
394        }
395
396        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
397            self.es2022_analyze_private_fields_for_in_operator(class);
398        }
399
400        // Single recursive visit
401        class.visit_mut_children_with(self);
402
403        // Post-processing transformations
404        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
405            self.es2022_inject_weakset_init_for_private_fields(class);
406        }
407    }
408
409    fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) {
410        // Setup phase for private fields
411        let old_cls = if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
412            let old = take(&mut self.es2022_current_class_data);
413            self.es2022_current_class_data.mark = Mark::fresh(Mark::root());
414            self.es2022_current_class_data.ident = Some(n.ident.clone());
415            self.es2022_current_class_data.vars = Mode::ClassDecl {
416                vars: Default::default(),
417            };
418            Some(old)
419        } else {
420            None
421        };
422
423        // Single recursive visit
424        n.visit_mut_children_with(self);
425
426        // Cleanup phase for private fields
427        if let Some(old_cls) = old_cls {
428            match &mut self.es2022_current_class_data.vars {
429                Mode::ClassDecl { vars } => {
430                    self.es2022_private_field_helper_vars.extend(take(vars));
431                }
432                _ => unreachable!(),
433            }
434            self.es2022_current_class_data = old_cls;
435        }
436    }
437
438    fn visit_mut_class_expr(&mut self, n: &mut ClassExpr) {
439        // Setup phase for private fields
440        let old_cls = if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
441            let old = take(&mut self.es2022_current_class_data);
442            self.es2022_current_class_data.mark = Mark::fresh(Mark::root());
443            self.es2022_current_class_data.ident.clone_from(&n.ident);
444            self.es2022_current_class_data.vars = Mode::ClassExpr {
445                vars: Default::default(),
446                init_exprs: Default::default(),
447            };
448            Some(old)
449        } else {
450            None
451        };
452
453        // Single recursive visit
454        n.visit_mut_children_with(self);
455
456        // Cleanup phase for private fields
457        if let Some(old_cls) = old_cls {
458            match &mut self.es2022_current_class_data.vars {
459                Mode::ClassExpr { vars, init_exprs } => {
460                    self.es2022_private_field_helper_vars.extend(take(vars));
461                    self.es2022_private_field_init_exprs
462                        .extend(take(init_exprs));
463                }
464                _ => unreachable!(),
465            }
466            self.es2022_current_class_data = old_cls;
467        }
468    }
469
470    /// Prevents #1123 for nullish coalescing
471    fn visit_mut_block_stmt(&mut self, s: &mut BlockStmt) {
472        // Setup phase: Save nullish coalescing vars
473        let old_es2020_nullish_coalescing_vars =
474            if self.config.includes.contains(Features::NULLISH_COALESCING) {
475                Some(self.es2020_nullish_coalescing_vars.take())
476            } else {
477                None
478            };
479
480        // Single recursive visit
481        s.visit_mut_children_with(self);
482
483        // Cleanup phase: Restore nullish coalescing vars
484        if let Some(old_vars) = old_es2020_nullish_coalescing_vars {
485            self.es2020_nullish_coalescing_vars = old_vars;
486        }
487    }
488
489    /// Prevents #1123 and #6328 for nullish coalescing
490    fn visit_mut_switch_case(&mut self, s: &mut SwitchCase) {
491        // Prevents #6328
492        s.test.visit_mut_with(self);
493
494        // Setup phase: Save nullish coalescing vars
495        let old_es2020_nullish_coalescing_vars =
496            if self.config.includes.contains(Features::NULLISH_COALESCING) {
497                Some(self.es2020_nullish_coalescing_vars.take())
498            } else {
499                None
500            };
501
502        // Visit consequents
503        s.cons.visit_mut_with(self);
504
505        // Cleanup phase: Restore nullish coalescing vars
506        if let Some(old_vars) = old_es2020_nullish_coalescing_vars {
507            self.es2020_nullish_coalescing_vars = old_vars;
508        }
509    }
510
511    fn visit_mut_assign_pat(&mut self, p: &mut AssignPat) {
512        // Visit left side first
513        p.left.visit_mut_with(self);
514
515        // Handle private field brand checks
516        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
517            let mut buf = FxHashSet::default();
518            let mut v = ClassAnalyzer {
519                brand_check_names: &mut buf,
520                ignore_class: false,
521            };
522            p.right.visit_with(&mut v);
523
524            if !buf.is_empty() {
525                let mut bs = BlockStmt {
526                    span: DUMMY_SP,
527                    stmts: vec![ReturnStmt {
528                        span: DUMMY_SP,
529                        arg: Some(p.right.take()),
530                    }
531                    .into()],
532                    ..Default::default()
533                };
534                bs.visit_mut_with(self);
535
536                p.right = CallExpr {
537                    span: DUMMY_SP,
538                    callee: ArrowExpr {
539                        span: DUMMY_SP,
540                        params: Default::default(),
541                        body: Box::new(BlockStmtOrExpr::BlockStmt(bs)),
542                        is_async: false,
543                        is_generator: false,
544                        ..Default::default()
545                    }
546                    .as_callee(),
547                    args: Default::default(),
548                    ..Default::default()
549                }
550                .into();
551                return;
552            }
553        }
554
555        p.right.visit_mut_with(self);
556    }
557
558    fn visit_mut_expr(&mut self, e: &mut Expr) {
559        // Phase 1: Setup for private field expressions
560        let prev_prepend_exprs = if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
561            Some(take(&mut self.es2022_private_field_init_exprs))
562        } else {
563            None
564        };
565
566        // Phase 2: Single recursive visit - Visit children first
567        e.visit_mut_children_with(self);
568
569        // Phase 3: Post-processing transformations
570        // Apply transformations after visiting children (this matches the original
571        // order)
572        let logical_transformed = self.config.includes.contains(Features::LOGICAL_ASSIGNMENTS)
573            && self.transform_logical_assignment(e);
574
575        let nullish_transformed = !logical_transformed
576            && self.config.includes.contains(Features::NULLISH_COALESCING)
577            && self.transform_nullish_coalescing(e);
578
579        // Handle private field expressions
580        if let Some(prev_prepend_exprs) = prev_prepend_exprs {
581            let mut prepend_exprs = std::mem::replace(
582                &mut self.es2022_private_field_init_exprs,
583                prev_prepend_exprs,
584            );
585
586            if !prepend_exprs.is_empty() {
587                match e {
588                    Expr::Seq(e) => {
589                        e.exprs = prepend_exprs.into_iter().chain(e.exprs.take()).collect();
590                    }
591                    _ => {
592                        prepend_exprs.push(Box::new(e.take()));
593                        *e = SeqExpr {
594                            span: DUMMY_SP,
595                            exprs: prepend_exprs,
596                        }
597                        .into();
598                    }
599                }
600            } else if !logical_transformed && !nullish_transformed {
601                // Transform private in expressions only if no other transformation occurred
602                self.es2022_transform_private_in_to_weakset_has(e);
603            }
604        }
605    }
606
607    fn visit_mut_module_items(&mut self, ns: &mut Vec<ModuleItem>) {
608        // Pre-processing: Export namespace transformation
609        if self
610            .config
611            .includes
612            .contains(Features::EXPORT_NAMESPACE_FROM)
613        {
614            self.transform_export_namespace_from(ns);
615        }
616
617        // Setup for variable hoisting
618        let need_logical_var_hoisting =
619            self.config.includes.contains(Features::LOGICAL_ASSIGNMENTS);
620        let need_nullish_var_hoisting = self.config.includes.contains(Features::NULLISH_COALESCING);
621
622        let saved_logical_vars = if need_logical_var_hoisting {
623            self.es2021_logical_assignment_vars.take()
624        } else {
625            vec![]
626        };
627
628        let saved_nullish_vars = if need_nullish_var_hoisting {
629            self.es2020_nullish_coalescing_vars.take()
630        } else {
631            vec![]
632        };
633
634        // Process statements with different hoisting strategies
635        if need_nullish_var_hoisting {
636            // Nullish coalescing: Insert vars before each statement that generates them
637            let mut buf = Vec::with_capacity(ns.len() + 2);
638
639            for mut item in ns.take() {
640                item.visit_mut_with(self);
641
642                // Insert nullish vars before the statement
643                if !self.es2020_nullish_coalescing_vars.is_empty() {
644                    buf.push(ModuleItem::Stmt(
645                        VarDecl {
646                            span: DUMMY_SP,
647                            kind: VarDeclKind::Var,
648                            decls: self.es2020_nullish_coalescing_vars.take(),
649                            ..Default::default()
650                        }
651                        .into(),
652                    ));
653                }
654
655                // Collect logical vars but don't insert yet
656                buf.push(item);
657            }
658
659            *ns = buf;
660
661            // Logical assignments: Hoist all vars to the top
662            if need_logical_var_hoisting && !self.es2021_logical_assignment_vars.is_empty() {
663                prepend_stmt(
664                    ns,
665                    ModuleItem::Stmt(
666                        VarDecl {
667                            span: DUMMY_SP,
668                            kind: VarDeclKind::Var,
669                            decls: self.es2021_logical_assignment_vars.take(),
670                            ..Default::default()
671                        }
672                        .into(),
673                    ),
674                );
675            }
676        } else if need_logical_var_hoisting {
677            // Only logical assignments: Hoist all vars to the top
678            ns.visit_mut_children_with(self);
679
680            if !self.es2021_logical_assignment_vars.is_empty() {
681                prepend_stmt(
682                    ns,
683                    ModuleItem::Stmt(
684                        VarDecl {
685                            span: DUMMY_SP,
686                            kind: VarDeclKind::Var,
687                            decls: self.es2021_logical_assignment_vars.take(),
688                            ..Default::default()
689                        }
690                        .into(),
691                    ),
692                );
693            }
694        } else {
695            // Single recursive visit
696            ns.visit_mut_children_with(self);
697        }
698
699        // Restore saved vars
700        self.es2021_logical_assignment_vars = saved_logical_vars;
701        self.es2020_nullish_coalescing_vars = saved_nullish_vars;
702
703        // Post-processing: Private field variables
704        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT)
705            && !self.es2022_private_field_helper_vars.is_empty()
706        {
707            self.es2022_prepend_private_field_vars_module(ns);
708        }
709    }
710
711    fn visit_mut_private_prop(&mut self, n: &mut PrivateProp) {
712        // Single recursive visit
713        n.visit_mut_children_with(self);
714
715        // Post-processing: Add WeakSet for brand checks
716        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
717            self.es2022_add_weakset_to_private_props(n);
718        }
719    }
720
721    fn visit_mut_prop_name(&mut self, n: &mut PropName) {
722        if let PropName::Computed(_) = n {
723            n.visit_mut_children_with(self);
724        }
725    }
726
727    fn visit_mut_stmts(&mut self, s: &mut Vec<Stmt>) {
728        // Setup for variable hoisting
729        let need_logical_var_hoisting =
730            self.config.includes.contains(Features::LOGICAL_ASSIGNMENTS);
731        let need_nullish_var_hoisting = self.config.includes.contains(Features::NULLISH_COALESCING);
732
733        let saved_logical_vars = if need_logical_var_hoisting {
734            self.es2021_logical_assignment_vars.take()
735        } else {
736            vec![]
737        };
738
739        let saved_nullish_vars = if need_nullish_var_hoisting {
740            self.es2020_nullish_coalescing_vars.take()
741        } else {
742            vec![]
743        };
744
745        // Process statements with different hoisting strategies
746        if need_nullish_var_hoisting {
747            // Nullish coalescing: Insert vars before each statement that generates them
748            let mut buf = Vec::with_capacity(s.len() + 2);
749
750            for mut stmt in s.take() {
751                stmt.visit_mut_with(self);
752
753                // Insert nullish vars before the statement
754                if !self.es2020_nullish_coalescing_vars.is_empty() {
755                    buf.push(
756                        VarDecl {
757                            span: DUMMY_SP,
758                            kind: VarDeclKind::Var,
759                            decls: self.es2020_nullish_coalescing_vars.take(),
760                            ..Default::default()
761                        }
762                        .into(),
763                    );
764                }
765
766                // Collect logical vars but don't insert yet
767                buf.push(stmt);
768            }
769
770            *s = buf;
771
772            // Logical assignments: Hoist all vars to the top
773            if need_logical_var_hoisting && !self.es2021_logical_assignment_vars.is_empty() {
774                prepend_stmt(
775                    s,
776                    VarDecl {
777                        span: DUMMY_SP,
778                        kind: VarDeclKind::Var,
779                        decls: self.es2021_logical_assignment_vars.take(),
780                        ..Default::default()
781                    }
782                    .into(),
783                );
784            }
785        } else if need_logical_var_hoisting {
786            // Only logical assignments: Hoist all vars to the top
787            s.visit_mut_children_with(self);
788
789            if !self.es2021_logical_assignment_vars.is_empty() {
790                prepend_stmt(
791                    s,
792                    VarDecl {
793                        span: DUMMY_SP,
794                        kind: VarDeclKind::Var,
795                        decls: self.es2021_logical_assignment_vars.take(),
796                        ..Default::default()
797                    }
798                    .into(),
799                );
800            }
801        } else {
802            // Single recursive visit
803            s.visit_mut_children_with(self);
804        }
805
806        // Restore saved vars
807        self.es2021_logical_assignment_vars = saved_logical_vars;
808        self.es2020_nullish_coalescing_vars = saved_nullish_vars;
809
810        // Post-processing: Private field variables
811        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT)
812            && !self.es2022_private_field_helper_vars.is_empty()
813        {
814            self.es2022_prepend_private_field_vars(s);
815        }
816    }
817
818    fn visit_mut_block_stmt_or_expr(&mut self, n: &mut BlockStmtOrExpr) {
819        if !self.config.includes.contains(Features::NULLISH_COALESCING) {
820            n.visit_mut_children_with(self);
821            return;
822        }
823
824        let vars = self.es2020_nullish_coalescing_vars.take();
825        n.visit_mut_children_with(self);
826
827        if !self.es2020_nullish_coalescing_vars.is_empty() {
828            if let BlockStmtOrExpr::Expr(expr) = n {
829                // expr
830                // { var decl = init; return expr; }
831                let stmts = vec![
832                    VarDecl {
833                        span: DUMMY_SP,
834                        kind: VarDeclKind::Var,
835                        decls: self.es2020_nullish_coalescing_vars.take(),
836                        declare: false,
837                        ..Default::default()
838                    }
839                    .into(),
840                    Stmt::Return(ReturnStmt {
841                        span: DUMMY_SP,
842                        arg: Some(expr.take()),
843                    }),
844                ];
845                *n = BlockStmtOrExpr::BlockStmt(BlockStmt {
846                    span: DUMMY_SP,
847                    stmts,
848                    ..Default::default()
849                });
850            }
851        }
852
853        self.es2020_nullish_coalescing_vars = vars;
854    }
855}