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    // Logical assignments transformation state
62    es2021_logical_assignment_vars: Vec<VarDeclarator>,
63}
64
65#[swc_trace]
66impl<'a> CompilerImpl<'a> {
67    fn new(config: &'a Config) -> Self {
68        Self {
69            config,
70            es2022_private_field_helper_vars: Vec::new(),
71            es2022_private_field_init_exprs: Vec::new(),
72            es2022_injected_weakset_vars: FxHashSet::default(),
73            es2022_current_class_data: ClassData::default(),
74            es2021_logical_assignment_vars: Vec::new(),
75        }
76    }
77
78    /// ES2022: Transform static blocks to static private fields
79    fn es2022_static_blocks_to_private_fields(&mut self, class: &mut Class) {
80        let mut private_names = FxHashSet::default();
81        for member in &class.body {
82            if let ClassMember::PrivateProp(private_property) = member {
83                private_names.insert(private_property.key.name.clone());
84            }
85        }
86
87        let mut count = 0;
88        for member in class.body.iter_mut() {
89            if let ClassMember::StaticBlock(static_block) = member {
90                if static_block.body.stmts.is_empty() {
91                    *member = ClassMember::dummy();
92                    continue;
93                }
94
95                let static_block_private_id = generate_uid(&private_names, &mut count);
96                *member = self
97                    .transform_static_block(static_block.take(), static_block_private_id)
98                    .into();
99            };
100        }
101    }
102
103    /// ES2022: Analyze class private fields for 'private in object'
104    /// transformation
105    fn es2022_analyze_private_fields_for_in_operator(&mut self, class: &Class) {
106        class.visit_children_with(&mut ClassAnalyzer {
107            brand_check_names: &mut self.es2022_current_class_data.names_used_for_brand_checks,
108            ignore_class: true,
109        });
110
111        for m in &class.body {
112            match m {
113                ClassMember::PrivateMethod(m) => {
114                    self.es2022_current_class_data
115                        .privates
116                        .insert(m.key.name.clone());
117                    self.es2022_current_class_data
118                        .methods
119                        .push(m.key.name.clone());
120
121                    if m.is_static {
122                        self.es2022_current_class_data
123                            .statics
124                            .push(m.key.name.clone());
125                    }
126                }
127
128                ClassMember::PrivateProp(m) => {
129                    self.es2022_current_class_data
130                        .privates
131                        .insert(m.key.name.clone());
132
133                    if m.is_static {
134                        self.es2022_current_class_data
135                            .statics
136                            .push(m.key.name.clone());
137                    }
138                }
139
140                _ => {}
141            }
142        }
143    }
144
145    /// ES2022: Inject WeakSet initialization into constructor for private field
146    /// brand checks
147    fn es2022_inject_weakset_init_for_private_fields(&mut self, class: &mut Class) {
148        if self.es2022_current_class_data.constructor_exprs.is_empty() {
149            return;
150        }
151
152        let has_constructor = class
153            .body
154            .iter()
155            .any(|m| matches!(m, ClassMember::Constructor(_)));
156
157        if !has_constructor {
158            let has_super = class.super_class.is_some();
159            class
160                .body
161                .push(ClassMember::Constructor(default_constructor_with_span(
162                    has_super, class.span,
163                )));
164        }
165
166        for m in &mut class.body {
167            if let ClassMember::Constructor(Constructor {
168                body: Some(body), ..
169            }) = m
170            {
171                for expr in take(&mut self.es2022_current_class_data.constructor_exprs) {
172                    body.stmts.push(
173                        ExprStmt {
174                            span: DUMMY_SP,
175                            expr,
176                        }
177                        .into(),
178                    );
179                }
180            }
181        }
182    }
183
184    /// ES2022: Transform 'private in object' expressions to WeakSet.has() calls
185    fn es2022_transform_private_in_to_weakset_has(&mut self, e: &mut Expr) -> bool {
186        if let Expr::Bin(BinExpr {
187            span,
188            op: op!("in"),
189            left,
190            right,
191        }) = e
192        {
193            if left.is_private_name() {
194                let left = left.take().expect_private_name();
195
196                let is_static = self.es2022_current_class_data.statics.contains(&left.name);
197                let is_method = self.es2022_current_class_data.methods.contains(&left.name);
198
199                if let Some(cls_ident) = self.es2022_current_class_data.ident.clone() {
200                    if is_static && is_method {
201                        *e = BinExpr {
202                            span: *span,
203                            op: op!("==="),
204                            left: cls_ident.into(),
205                            right: right.take(),
206                        }
207                        .into();
208                        return true;
209                    }
210                }
211
212                let var_name =
213                    self.var_name_for_brand_check(&left, &self.es2022_current_class_data);
214
215                if self.es2022_current_class_data.privates.contains(&left.name)
216                    && self.es2022_injected_weakset_vars.insert(var_name.to_id())
217                {
218                    self.es2022_current_class_data.vars.push_var(
219                        var_name.clone(),
220                        Some(
221                            NewExpr {
222                                span: DUMMY_SP,
223                                callee: Box::new(quote_ident!("WeakSet").into()),
224                                args: Some(Default::default()),
225                                ..Default::default()
226                            }
227                            .into(),
228                        ),
229                    );
230
231                    if is_method {
232                        self.es2022_current_class_data.constructor_exprs.push(
233                            CallExpr {
234                                span: DUMMY_SP,
235                                callee: var_name
236                                    .clone()
237                                    .make_member(IdentName::new("add".into(), DUMMY_SP))
238                                    .as_callee(),
239                                args: vec![ExprOrSpread {
240                                    spread: None,
241                                    expr: Box::new(ThisExpr { span: DUMMY_SP }.into()),
242                                }],
243                                ..Default::default()
244                            }
245                            .into(),
246                        );
247                    }
248                }
249
250                *e = CallExpr {
251                    span: *span,
252                    callee: var_name
253                        .make_member(IdentName::new("has".into(), DUMMY_SP))
254                        .as_callee(),
255                    args: vec![ExprOrSpread {
256                        spread: None,
257                        expr: right.take(),
258                    }],
259                    ..Default::default()
260                }
261                .into();
262                return true;
263            }
264        }
265        false
266    }
267
268    /// ES2022: Prepend private field helper variables to statements
269    fn es2022_prepend_private_field_vars(&mut self, stmts: &mut Vec<Stmt>) {
270        if self.es2022_private_field_helper_vars.is_empty() {
271            return;
272        }
273
274        prepend_stmt(
275            stmts,
276            VarDecl {
277                span: DUMMY_SP,
278                kind: VarDeclKind::Var,
279                declare: Default::default(),
280                decls: take(&mut self.es2022_private_field_helper_vars),
281                ..Default::default()
282            }
283            .into(),
284        );
285    }
286
287    /// ES2022: Prepend private field helper variables to module items
288    fn es2022_prepend_private_field_vars_module(&mut self, items: &mut Vec<ModuleItem>) {
289        if self.es2022_private_field_helper_vars.is_empty() {
290            return;
291        }
292
293        prepend_stmt(
294            items,
295            VarDecl {
296                span: DUMMY_SP,
297                kind: VarDeclKind::Var,
298                declare: Default::default(),
299                decls: take(&mut self.es2022_private_field_helper_vars),
300                ..Default::default()
301            }
302            .into(),
303        );
304    }
305
306    /// ES2022: Add WeakSet.add() calls to private properties for brand checking
307    fn es2022_add_weakset_to_private_props(&mut self, n: &mut PrivateProp) {
308        if !self
309            .es2022_current_class_data
310            .names_used_for_brand_checks
311            .contains(&n.key.name)
312        {
313            return;
314        }
315
316        let var_name = self.var_name_for_brand_check(&n.key, &self.es2022_current_class_data);
317
318        match &mut n.value {
319            Some(init) => {
320                let init_span = init.span();
321
322                let tmp = private_ident!("_tmp");
323
324                self.es2022_current_class_data
325                    .vars
326                    .push_var(tmp.clone(), None);
327
328                let assign = AssignExpr {
329                    span: DUMMY_SP,
330                    op: op!("="),
331                    left: tmp.clone().into(),
332                    right: init.take(),
333                }
334                .into();
335
336                let add_to_checker = CallExpr {
337                    span: DUMMY_SP,
338                    callee: var_name
339                        .make_member(IdentName::new("add".into(), DUMMY_SP))
340                        .as_callee(),
341                    args: vec![ExprOrSpread {
342                        spread: None,
343                        expr: Box::new(ThisExpr { span: DUMMY_SP }.into()),
344                    }],
345                    ..Default::default()
346                }
347                .into();
348
349                *init = SeqExpr {
350                    span: init_span,
351                    exprs: vec![assign, add_to_checker, Box::new(tmp.into())],
352                }
353                .into();
354            }
355            None => {
356                n.value = Some(
357                    UnaryExpr {
358                        span: DUMMY_SP,
359                        op: op!("void"),
360                        arg: Box::new(
361                            CallExpr {
362                                span: DUMMY_SP,
363                                callee: var_name
364                                    .make_member(IdentName::new("add".into(), DUMMY_SP))
365                                    .as_callee(),
366                                args: vec![ExprOrSpread {
367                                    spread: None,
368                                    expr: Box::new(ThisExpr { span: DUMMY_SP }.into()),
369                                }],
370                                ..Default::default()
371                            }
372                            .into(),
373                        ),
374                    }
375                    .into(),
376                )
377            }
378        }
379    }
380}
381
382#[swc_trace]
383impl<'a> VisitMut for CompilerImpl<'a> {
384    noop_visit_mut_type!(fail);
385
386    fn visit_mut_class(&mut self, class: &mut Class) {
387        // Pre-processing transformations
388        if self.config.includes.contains(Features::STATIC_BLOCKS) {
389            self.es2022_static_blocks_to_private_fields(class);
390        }
391
392        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
393            self.es2022_analyze_private_fields_for_in_operator(class);
394        }
395
396        // Single recursive visit
397        class.visit_mut_children_with(self);
398
399        // Post-processing transformations
400        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
401            self.es2022_inject_weakset_init_for_private_fields(class);
402        }
403    }
404
405    fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) {
406        // Setup phase for private fields
407        let old_cls = if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
408            let old = take(&mut self.es2022_current_class_data);
409            self.es2022_current_class_data.mark = Mark::fresh(Mark::root());
410            self.es2022_current_class_data.ident = Some(n.ident.clone());
411            self.es2022_current_class_data.vars = Mode::ClassDecl {
412                vars: Default::default(),
413            };
414            Some(old)
415        } else {
416            None
417        };
418
419        // Single recursive visit
420        n.visit_mut_children_with(self);
421
422        // Cleanup phase for private fields
423        if let Some(old_cls) = old_cls {
424            match &mut self.es2022_current_class_data.vars {
425                Mode::ClassDecl { vars } => {
426                    self.es2022_private_field_helper_vars.extend(take(vars));
427                }
428                _ => unreachable!(),
429            }
430            self.es2022_current_class_data = old_cls;
431        }
432    }
433
434    fn visit_mut_class_expr(&mut self, n: &mut ClassExpr) {
435        // Setup phase for private fields
436        let old_cls = if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
437            let old = take(&mut self.es2022_current_class_data);
438            self.es2022_current_class_data.mark = Mark::fresh(Mark::root());
439            self.es2022_current_class_data.ident.clone_from(&n.ident);
440            self.es2022_current_class_data.vars = Mode::ClassExpr {
441                vars: Default::default(),
442                init_exprs: Default::default(),
443            };
444            Some(old)
445        } else {
446            None
447        };
448
449        // Single recursive visit
450        n.visit_mut_children_with(self);
451
452        // Cleanup phase for private fields
453        if let Some(old_cls) = old_cls {
454            match &mut self.es2022_current_class_data.vars {
455                Mode::ClassExpr { vars, init_exprs } => {
456                    self.es2022_private_field_helper_vars.extend(take(vars));
457                    self.es2022_private_field_init_exprs
458                        .extend(take(init_exprs));
459                }
460                _ => unreachable!(),
461            }
462            self.es2022_current_class_data = old_cls;
463        }
464    }
465
466    /// Prevents #1123 for nullish coalescing
467    fn visit_mut_block_stmt(&mut self, s: &mut BlockStmt) {
468        // Single recursive visit
469        s.visit_mut_children_with(self);
470    }
471
472    /// Prevents #1123 and #6328 for nullish coalescing
473    fn visit_mut_switch_case(&mut self, s: &mut SwitchCase) {
474        s.visit_mut_children_with(self);
475    }
476
477    fn visit_mut_assign_pat(&mut self, p: &mut AssignPat) {
478        // Visit left side first
479        p.left.visit_mut_with(self);
480
481        // Handle private field brand checks
482        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
483            let mut buf = FxHashSet::default();
484            let mut v = ClassAnalyzer {
485                brand_check_names: &mut buf,
486                ignore_class: false,
487            };
488            p.right.visit_with(&mut v);
489
490            if !buf.is_empty() {
491                let mut bs = BlockStmt {
492                    span: DUMMY_SP,
493                    stmts: vec![ReturnStmt {
494                        span: DUMMY_SP,
495                        arg: Some(p.right.take()),
496                    }
497                    .into()],
498                    ..Default::default()
499                };
500                bs.visit_mut_with(self);
501
502                p.right = CallExpr {
503                    span: DUMMY_SP,
504                    callee: ArrowExpr {
505                        span: DUMMY_SP,
506                        params: Default::default(),
507                        body: Box::new(BlockStmtOrExpr::BlockStmt(bs)),
508                        is_async: false,
509                        is_generator: false,
510                        ..Default::default()
511                    }
512                    .as_callee(),
513                    args: Default::default(),
514                    ..Default::default()
515                }
516                .into();
517                return;
518            }
519        }
520
521        p.right.visit_mut_with(self);
522    }
523
524    fn visit_mut_expr(&mut self, e: &mut Expr) {
525        // Phase 1: Pre-processing - Check and apply transformations that replace the
526        // expression
527        let logical_transformed = self.config.includes.contains(Features::LOGICAL_ASSIGNMENTS)
528            && self.transform_logical_assignment(e);
529
530        // Phase 2: Setup for private field expressions
531        let prev_prepend_exprs = if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
532            Some(take(&mut self.es2022_private_field_init_exprs))
533        } else {
534            None
535        };
536
537        // Phase 3: Single recursive visit
538        e.visit_mut_children_with(self);
539
540        // Phase 4: Post-processing transformations
541        // Handle private field expressions
542        if let Some(prev_prepend_exprs) = prev_prepend_exprs {
543            let mut prepend_exprs = std::mem::replace(
544                &mut self.es2022_private_field_init_exprs,
545                prev_prepend_exprs,
546            );
547
548            if !prepend_exprs.is_empty() {
549                match e {
550                    Expr::Seq(e) => {
551                        e.exprs = prepend_exprs.into_iter().chain(e.exprs.take()).collect();
552                    }
553                    _ => {
554                        prepend_exprs.push(Box::new(e.take()));
555                        *e = SeqExpr {
556                            span: DUMMY_SP,
557                            exprs: prepend_exprs,
558                        }
559                        .into();
560                    }
561                }
562            } else if !logical_transformed {
563                // Transform private in expressions only if no other transformation occurred
564                self.es2022_transform_private_in_to_weakset_has(e);
565            }
566        }
567    }
568
569    fn visit_mut_module_items(&mut self, ns: &mut Vec<ModuleItem>) {
570        // Pre-processing: Export namespace transformation
571        if self
572            .config
573            .includes
574            .contains(Features::EXPORT_NAMESPACE_FROM)
575        {
576            self.transform_export_namespace_from(ns);
577        }
578
579        // Setup for variable hoisting
580        let need_var_hoisting = self.config.includes.contains(Features::LOGICAL_ASSIGNMENTS);
581
582        let saved_logical_vars = if need_var_hoisting {
583            self.es2021_logical_assignment_vars.take()
584        } else {
585            vec![]
586        };
587
588        // Single recursive visit
589        ns.visit_mut_children_with(self);
590
591        // Post-processing: Handle variable hoisting
592        if need_var_hoisting {
593            let logical_vars =
594                std::mem::replace(&mut self.es2021_logical_assignment_vars, saved_logical_vars);
595
596            let mut all_vars = Vec::new();
597            all_vars.extend(logical_vars);
598
599            if !all_vars.is_empty() {
600                prepend_stmt(
601                    ns,
602                    VarDecl {
603                        span: DUMMY_SP,
604                        kind: VarDeclKind::Var,
605                        decls: all_vars,
606                        ..Default::default()
607                    }
608                    .into(),
609                );
610            }
611        }
612
613        // Post-processing: Private field variables
614        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT)
615            && !self.es2022_private_field_helper_vars.is_empty()
616        {
617            self.es2022_prepend_private_field_vars_module(ns);
618        }
619    }
620
621    fn visit_mut_private_prop(&mut self, n: &mut PrivateProp) {
622        // Single recursive visit
623        n.visit_mut_children_with(self);
624
625        // Post-processing: Add WeakSet for brand checks
626        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT) {
627            self.es2022_add_weakset_to_private_props(n);
628        }
629    }
630
631    fn visit_mut_prop_name(&mut self, n: &mut PropName) {
632        if let PropName::Computed(_) = n {
633            n.visit_mut_children_with(self);
634        }
635    }
636
637    fn visit_mut_stmts(&mut self, s: &mut Vec<Stmt>) {
638        // Setup for variable hoisting
639        let need_var_hoisting = self.config.includes.contains(Features::LOGICAL_ASSIGNMENTS);
640
641        let saved_logical_vars = if need_var_hoisting {
642            self.es2021_logical_assignment_vars.take()
643        } else {
644            vec![]
645        };
646
647        // Single recursive visit
648        s.visit_mut_children_with(self);
649
650        // Post-processing: Handle variable hoisting
651        if need_var_hoisting {
652            let logical_vars =
653                std::mem::replace(&mut self.es2021_logical_assignment_vars, saved_logical_vars);
654
655            let mut all_vars = Vec::new();
656            all_vars.extend(logical_vars);
657
658            if !all_vars.is_empty() {
659                prepend_stmt(
660                    s,
661                    VarDecl {
662                        span: DUMMY_SP,
663                        kind: VarDeclKind::Var,
664                        decls: all_vars,
665                        ..Default::default()
666                    }
667                    .into(),
668                );
669            }
670        }
671
672        // Post-processing: Private field variables
673        if self.config.includes.contains(Features::PRIVATE_IN_OBJECT)
674            && !self.es2022_private_field_helper_vars.is_empty()
675        {
676            self.es2022_prepend_private_field_vars(s);
677        }
678    }
679}