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 pub includes: Features,
42 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_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_assignment_vars: Vec<VarDeclarator>,
63
64 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 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 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 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 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 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 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 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 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 class.visit_mut_children_with(self);
402
403 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 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 n.visit_mut_children_with(self);
425
426 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 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 n.visit_mut_children_with(self);
455
456 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 fn visit_mut_block_stmt(&mut self, s: &mut BlockStmt) {
472 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 s.visit_mut_children_with(self);
482
483 if let Some(old_vars) = old_es2020_nullish_coalescing_vars {
485 self.es2020_nullish_coalescing_vars = old_vars;
486 }
487 }
488
489 fn visit_mut_switch_case(&mut self, s: &mut SwitchCase) {
491 s.test.visit_mut_with(self);
493
494 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 s.cons.visit_mut_with(self);
504
505 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 p.left.visit_mut_with(self);
514
515 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 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 e.visit_mut_children_with(self);
568
569 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 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 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 if self
610 .config
611 .includes
612 .contains(Features::EXPORT_NAMESPACE_FROM)
613 {
614 self.transform_export_namespace_from(ns);
615 }
616
617 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 if need_nullish_var_hoisting {
636 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 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 buf.push(item);
657 }
658
659 *ns = buf;
660
661 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 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 ns.visit_mut_children_with(self);
697 }
698
699 self.es2021_logical_assignment_vars = saved_logical_vars;
701 self.es2020_nullish_coalescing_vars = saved_nullish_vars;
702
703 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 n.visit_mut_children_with(self);
714
715 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 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 if need_nullish_var_hoisting {
747 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 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 buf.push(stmt);
768 }
769
770 *s = buf;
771
772 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 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 s.visit_mut_children_with(self);
804 }
805
806 self.es2021_logical_assignment_vars = saved_logical_vars;
808 self.es2020_nullish_coalescing_vars = saved_nullish_vars;
809
810 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 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}