swc_ecma_compat_es2022/class_properties/
private_field.rs

1use std::iter;
2
3use rustc_hash::FxHashMap;
4use swc_atoms::Atom;
5use swc_common::{errors::HANDLER, util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP};
6use swc_ecma_ast::*;
7use swc_ecma_transforms_base::helper;
8use swc_ecma_utils::{alias_ident_for, alias_if_required, prepend_stmt, quote_ident, ExprFactory};
9use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
10use swc_trace_macro::swc_trace;
11
12use super::Config;
13use crate::optional_chaining_impl::optional_chaining_impl;
14
15pub(super) struct Private {
16    pub mark: Mark,
17    pub class_name: Ident,
18    pub ident: FxHashMap<Atom, PrivateKind>,
19}
20
21pub(super) struct PrivateRecord(Vec<Private>);
22
23#[swc_trace]
24impl PrivateRecord {
25    pub fn new() -> Self {
26        PrivateRecord(Vec::new())
27    }
28
29    pub fn curr_class(&self) -> &Ident {
30        &self.0.last().unwrap().class_name
31    }
32
33    pub fn cur_mark(&self) -> Mark {
34        self.0.last().unwrap().mark
35    }
36
37    pub fn push(&mut self, p: Private) {
38        self.0.push(p)
39    }
40
41    pub fn pop(&mut self) {
42        self.0.pop();
43    }
44
45    pub fn get(&self, span: Span, name: &Atom) -> (Mark, PrivateKind, &Ident) {
46        for p in self.0.iter().rev() {
47            if let Some(kind) = p.ident.get(name) {
48                return (p.mark, *kind, &p.class_name);
49            }
50        }
51
52        let error = format!("private name #{name} is not defined.");
53        HANDLER.with(|handler| handler.struct_span_err(span, &error).emit());
54        (Mark::root(), PrivateKind::default(), &self.0[0].class_name)
55    }
56}
57
58#[derive(Copy, Clone, PartialEq, Default, Eq)]
59pub(super) struct PrivateKind {
60    pub is_static: bool,
61    pub is_method: bool,
62    pub has_getter: bool,
63    pub has_setter: bool,
64}
65
66impl PrivateKind {
67    fn is_readonly(&self) -> bool {
68        self.is_method && !self.has_setter
69    }
70
71    fn is_writeonly(&self) -> bool {
72        // a private method can still be read
73        self.is_method && !self.has_getter && self.has_setter
74    }
75
76    fn is_method(&self) -> bool {
77        self.is_method && !self.has_getter && !self.has_setter
78    }
79}
80
81pub(super) struct BrandCheckHandler<'a> {
82    pub private: &'a PrivateRecord,
83}
84
85#[swc_trace]
86impl VisitMut for BrandCheckHandler<'_> {
87    noop_visit_mut_type!(fail);
88
89    fn visit_mut_expr(&mut self, e: &mut Expr) {
90        e.visit_mut_children_with(self);
91
92        match e {
93            Expr::Bin(BinExpr {
94                span,
95                op: op!("in"),
96                left,
97                right,
98            }) if left.is_private_name() => {
99                let n = left.as_private_name().unwrap();
100                if let Expr::Ident(right) = &**right {
101                    let curr_class = self.private.curr_class();
102                    if curr_class.sym == right.sym && curr_class.ctxt == right.ctxt {
103                        *e = BinExpr {
104                            span: *span,
105                            op: op!("==="),
106                            left: curr_class.clone().into(),
107                            right: right.clone().into(),
108                        }
109                        .into();
110                        return;
111                    }
112                }
113
114                let (mark, kind, class_name) = self.private.get(n.span, &n.name);
115
116                if mark == Mark::root() {
117                    return;
118                }
119
120                if kind.is_static {
121                    *e = BinExpr {
122                        span: *span,
123                        op: op!("==="),
124                        left: right.take(),
125                        right: class_name.clone().into(),
126                    }
127                    .into();
128                    return;
129                }
130
131                let weak_coll_ident = Ident::new(
132                    format!("_{}", n.name).into(),
133                    n.span,
134                    SyntaxContext::empty().apply_mark(mark),
135                );
136
137                *e = CallExpr {
138                    span: *span,
139                    callee: weak_coll_ident.make_member(quote_ident!("has")).as_callee(),
140                    args: vec![right.take().as_arg()],
141
142                    ..Default::default()
143                }
144                .into();
145            }
146
147            _ => {}
148        }
149    }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
153pub(super) enum PrivateAccessType {
154    Get,
155    DestructureSet,
156    Update,
157}
158
159impl Default for PrivateAccessType {
160    fn default() -> Self {
161        Self::Get
162    }
163}
164
165pub(super) struct PrivateAccessVisitor<'a> {
166    pub vars: Vec<VarDeclarator>,
167    pub private: &'a PrivateRecord,
168    pub private_access_type: PrivateAccessType,
169    pub c: Config,
170    pub unresolved_mark: Mark,
171}
172
173macro_rules! take_vars {
174    ($name:ident, $T:tt) => {
175        fn $name(&mut self, f: &mut $T) {
176            let old_var = self.vars.take();
177
178            if f.body.is_none() {
179                return;
180            }
181
182            f.visit_mut_children_with(self);
183
184            if !self.vars.is_empty() {
185                prepend_stmt(
186                    &mut f.body.as_mut().unwrap().stmts,
187                    VarDecl {
188                        span: DUMMY_SP,
189                        kind: VarDeclKind::Var,
190                        decls: self.vars.take(),
191
192                        ..Default::default()
193                    }
194                    .into(),
195                )
196            }
197
198            self.vars = old_var;
199        }
200    };
201}
202
203// super.#sdsa is invalid
204#[swc_trace]
205impl VisitMut for PrivateAccessVisitor<'_> {
206    noop_visit_mut_type!(fail);
207
208    take_vars!(visit_mut_function, Function);
209
210    take_vars!(visit_mut_constructor, Constructor);
211
212    fn visit_mut_expr(&mut self, e: &mut Expr) {
213        if let Expr::OptChain(opt) = e {
214            let is_private_access = match &*opt.base {
215                OptChainBase::Member(MemberExpr {
216                    prop: MemberProp::PrivateName(..),
217                    ..
218                }) => true,
219                OptChainBase::Call(OptCall { callee, .. }) => matches!(
220                    &**callee,
221                    Expr::Member(MemberExpr {
222                        prop: MemberProp::PrivateName(..),
223                        ..
224                    })
225                ),
226                _ => false,
227            };
228
229            if is_private_access {
230                let mut v = optional_chaining_impl(
231                    crate::optional_chaining_impl::Config {
232                        no_document_all: self.c.no_document_all,
233                        pure_getter: self.c.pure_getter,
234                    },
235                    self.unresolved_mark,
236                );
237                e.visit_mut_with(&mut v);
238                assert!(!e.is_opt_chain(), "optional chaining should be removed");
239                self.vars.extend(v.take_vars());
240            }
241        }
242
243        if self.c.private_as_properties {
244            if let Expr::Member(MemberExpr {
245                span,
246                obj,
247                prop: MemberProp::PrivateName(n),
248            }) = e
249            {
250                obj.visit_mut_children_with(self);
251                let (mark, _, _) = self.private.get(n.span, &n.name);
252                let ident = Ident::new(
253                    format!("_{}", n.name).into(),
254                    n.span,
255                    SyntaxContext::empty().apply_mark(mark),
256                );
257
258                *e = CallExpr {
259                    callee: helper!(class_private_field_loose_base),
260                    span: *span,
261                    args: vec![obj.take().as_arg(), ident.clone().as_arg()],
262                    ..Default::default()
263                }
264                .computed_member(ident)
265                .into();
266            } else {
267                e.visit_mut_children_with(self)
268            }
269            return;
270        }
271
272        match e {
273            Expr::Update(UpdateExpr { arg, .. }) if arg.is_member() => {
274                let old_access_type = self.private_access_type;
275
276                self.private_access_type = PrivateAccessType::Update;
277                arg.visit_mut_with(self);
278                self.private_access_type = old_access_type;
279            }
280
281            Expr::Assign(AssignExpr {
282                span,
283                left,
284                op,
285                right,
286            }) if left.as_simple().is_some() && left.as_simple().unwrap().is_member() => {
287                let mut left: MemberExpr = left.take().expect_simple().expect_member();
288                left.visit_mut_with(self);
289                right.visit_mut_with(self);
290
291                let n = match &left.prop {
292                    MemberProp::PrivateName(n) => n.clone(),
293                    _ => {
294                        *e = AssignExpr {
295                            span: *span,
296                            left: left.into(),
297                            op: *op,
298                            right: right.take(),
299                        }
300                        .into();
301
302                        return;
303                    }
304                };
305
306                let obj = left.obj.clone();
307
308                let (mark, kind, class_name) = self.private.get(n.span, &n.name);
309                if mark == Mark::root() {
310                    return;
311                }
312
313                let ident = Ident::new(
314                    format!("_{}", n.name).into(),
315                    n.span,
316                    SyntaxContext::empty().apply_mark(mark),
317                );
318
319                let var = alias_ident_for(&obj, "_ref");
320
321                let this = if matches!(*obj, Expr::This(..)) {
322                    Box::new(ThisExpr { span: DUMMY_SP }.into())
323                } else if *op == op!("=") {
324                    obj
325                } else {
326                    self.vars.push(VarDeclarator {
327                        span: DUMMY_SP,
328                        name: var.clone().into(),
329                        init: None,
330                        definite: false,
331                    });
332                    Box::new(
333                        AssignExpr {
334                            span: obj.span(),
335                            left: var.clone().into(),
336                            op: op!("="),
337                            right: obj,
338                        }
339                        .into(),
340                    )
341                };
342
343                let value = if *op == op!("=") {
344                    right.take()
345                } else {
346                    let left = Box::new(self.visit_mut_private_get(&mut left, Some(var)).0);
347
348                    Box::new(
349                        BinExpr {
350                            span: DUMMY_SP,
351                            left,
352                            op: op.to_update().unwrap(),
353                            right: right.take(),
354                        }
355                        .into(),
356                    )
357                };
358
359                if kind.is_static {
360                    *e = CallExpr {
361                        span: DUMMY_SP,
362                        callee: helper!(class_static_private_field_spec_set),
363                        args: vec![
364                            this.as_arg(),
365                            class_name.clone().as_arg(),
366                            ident.as_arg(),
367                            value.as_arg(),
368                        ],
369
370                        ..Default::default()
371                    }
372                    .into();
373                } else if kind.is_readonly() {
374                    let err = CallExpr {
375                        span: DUMMY_SP,
376                        callee: helper!(read_only_error),
377                        args: vec![format!("#{}", n.name).as_arg()],
378                        ..Default::default()
379                    }
380                    .into();
381                    *e = SeqExpr {
382                        span: *span,
383                        exprs: vec![this, value, err],
384                    }
385                    .into();
386                } else {
387                    let set = helper!(class_private_field_set);
388
389                    *e = CallExpr {
390                        span: DUMMY_SP,
391                        callee: set,
392                        args: vec![this.as_arg(), ident.as_arg(), value.as_arg()],
393
394                        ..Default::default()
395                    }
396                    .into();
397                }
398            }
399
400            Expr::Assign(AssignExpr {
401                left: AssignTarget::Pat(left),
402
403                right,
404                ..
405            }) => {
406                right.visit_mut_with(self);
407                let old_access_type = self.private_access_type;
408
409                self.private_access_type = PrivateAccessType::DestructureSet;
410                left.visit_mut_with(self);
411                self.private_access_type = old_access_type;
412            }
413
414            // Actually this is a call and we should bind `this`.
415            Expr::TaggedTpl(TaggedTpl { span, tag, tpl, .. }) if tag.is_member() => {
416                let mut tag = tag.take().member().unwrap();
417                tag.visit_mut_with(self);
418                tpl.visit_mut_with(self);
419
420                let (expr, this) = self.visit_mut_private_get(&mut tag, None);
421
422                if let Some(this) = this {
423                    *e = TaggedTpl {
424                        span: *span,
425                        tag: CallExpr {
426                            span: DUMMY_SP,
427                            callee: expr.make_member(quote_ident!("bind")).as_callee(),
428                            args: vec![this.as_arg()],
429                            ..Default::default()
430                        }
431                        .into(),
432                        tpl: tpl.take(),
433                        ..Default::default()
434                    }
435                    .into();
436                } else {
437                    *e = TaggedTpl {
438                        span: *span,
439                        tag: Box::new(expr),
440                        tpl: tpl.take(),
441                        ..Default::default()
442                    }
443                    .into();
444                }
445            }
446
447            Expr::Call(CallExpr {
448                span,
449                callee: Callee::Expr(callee),
450                args,
451                ..
452            }) if callee.is_member() => {
453                let mut callee = callee.take().member().unwrap();
454                callee.visit_mut_with(self);
455                args.visit_mut_with(self);
456
457                let (expr, this) = self.visit_mut_private_get(&mut callee, None);
458                if let Some(this) = this {
459                    *e = CallExpr {
460                        span: *span,
461                        callee: expr.make_member(quote_ident!("call")).as_callee(),
462                        args: iter::once(this.as_arg()).chain(args.take()).collect(),
463                        ..Default::default()
464                    }
465                    .into();
466                } else {
467                    *e = CallExpr {
468                        span: *span,
469                        callee: expr.as_callee(),
470                        args: args.take(),
471                        ..Default::default()
472                    }
473                    .into();
474                }
475            }
476
477            Expr::Member(member_expr) => {
478                member_expr.visit_mut_children_with(self);
479                *e = self.visit_mut_private_get(member_expr, None).0;
480            }
481
482            _ => e.visit_mut_children_with(self),
483        };
484    }
485
486    fn visit_mut_simple_assign_target(&mut self, e: &mut SimpleAssignTarget) {
487        if let SimpleAssignTarget::OptChain(opt) = e {
488            let is_private_access = match &*opt.base {
489                OptChainBase::Member(MemberExpr {
490                    prop: MemberProp::PrivateName(..),
491                    ..
492                }) => true,
493                OptChainBase::Call(OptCall { callee, .. }) => matches!(
494                    &**callee,
495                    Expr::Member(MemberExpr {
496                        prop: MemberProp::PrivateName(..),
497                        ..
498                    })
499                ),
500                _ => false,
501            };
502
503            if is_private_access {
504                let mut v = optional_chaining_impl(
505                    crate::optional_chaining_impl::Config {
506                        no_document_all: self.c.no_document_all,
507                        pure_getter: self.c.pure_getter,
508                    },
509                    self.unresolved_mark,
510                );
511                e.visit_mut_with(&mut v);
512                assert!(!e.is_opt_chain(), "optional chaining should be removed");
513                self.vars.extend(v.take_vars());
514            }
515        }
516
517        if self.c.private_as_properties {
518            if let SimpleAssignTarget::Member(MemberExpr {
519                span,
520                obj,
521                prop: MemberProp::PrivateName(n),
522            }) = e
523            {
524                obj.visit_mut_children_with(self);
525                let (mark, _, _) = self.private.get(n.span, &n.name);
526                let ident = Ident::new(
527                    format!("_{}", n.name).into(),
528                    n.span,
529                    SyntaxContext::empty().apply_mark(mark),
530                );
531
532                *e = CallExpr {
533                    callee: helper!(class_private_field_loose_base),
534                    span: *span,
535                    args: vec![obj.take().as_arg(), ident.clone().as_arg()],
536                    ..Default::default()
537                }
538                .computed_member(ident)
539                .into();
540            } else {
541                e.visit_mut_children_with(self)
542            }
543            return;
544        }
545
546        e.visit_mut_children_with(self)
547    }
548
549    fn visit_mut_pat(&mut self, p: &mut Pat) {
550        if let Pat::Expr(expr) = &p {
551            if let Expr::Member(me) = &**expr {
552                if let MemberProp::PrivateName(..) = &me.prop {
553                    let old_access_type = self.private_access_type;
554                    self.private_access_type = PrivateAccessType::DestructureSet;
555                    p.visit_mut_children_with(self);
556                    self.private_access_type = old_access_type;
557
558                    return;
559                }
560            }
561        }
562
563        self.private_access_type = Default::default();
564        p.visit_mut_children_with(self);
565    }
566}
567
568pub(super) fn visit_private_in_expr(
569    expr: &mut Expr,
570    private: &PrivateRecord,
571    config: Config,
572    unresolved_mark: Mark,
573) -> Vec<VarDeclarator> {
574    let mut priv_visitor = PrivateAccessVisitor {
575        private,
576        vars: Vec::new(),
577        private_access_type: Default::default(),
578        c: config,
579        unresolved_mark,
580    };
581
582    expr.visit_mut_with(&mut priv_visitor);
583
584    priv_visitor.vars
585}
586
587#[swc_trace]
588impl PrivateAccessVisitor<'_> {
589    /// Returns `(expr, thisObject)`
590    ///
591    ///   - `obj_alias`: If alias is already declared, this method will use
592    ///     `obj_alias` instead of declaring a new one.
593    fn visit_mut_private_get(
594        &mut self,
595        e: &mut MemberExpr,
596        obj_alias: Option<Ident>,
597    ) -> (Expr, Option<Expr>) {
598        let is_alias_initialized = obj_alias.is_some();
599
600        let n = match &e.prop {
601            MemberProp::PrivateName(n) => n,
602            _ => return (e.take().into(), None),
603        };
604
605        let mut obj = e.obj.take();
606
607        let (mark, kind, class_name) = self.private.get(n.span, &n.name);
608        if mark == Mark::root() {
609            return (Expr::dummy(), None);
610        }
611
612        let method_name = Ident::new(
613            if n.name.is_reserved_in_any() {
614                format!("__{}", n.name).into()
615            } else {
616                n.name.clone()
617            },
618            n.span,
619            SyntaxContext::empty().apply_mark(mark),
620        );
621        let ident = Ident::new(
622            format!("_{}", n.name).into(),
623            n.span,
624            SyntaxContext::empty().apply_mark(mark),
625        );
626
627        if kind.is_static {
628            match self.private_access_type {
629                PrivateAccessType::DestructureSet => {
630                    let set = helper!(class_static_private_field_destructure);
631
632                    return (
633                        CallExpr {
634                            span: DUMMY_SP,
635                            callee: set,
636                            args: vec![
637                                obj.clone().as_arg(),
638                                class_name.clone().as_arg(),
639                                ident.as_arg(),
640                            ],
641                            ..Default::default()
642                        }
643                        .make_member(quote_ident!("value"))
644                        .into(),
645                        Some(*obj),
646                    );
647                }
648                PrivateAccessType::Update => {
649                    let set = helper!(class_static_private_field_update);
650
651                    return (
652                        CallExpr {
653                            span: DUMMY_SP,
654                            callee: set,
655                            args: vec![
656                                obj.clone().as_arg(),
657                                class_name.clone().as_arg(),
658                                ident.as_arg(),
659                            ],
660
661                            ..Default::default()
662                        }
663                        .make_member(quote_ident!("value"))
664                        .into(),
665                        Some(*obj),
666                    );
667                }
668                _ => {}
669            }
670
671            if kind.is_method() {
672                let h = helper!(class_static_private_method_get);
673
674                return (
675                    CallExpr {
676                        span: DUMMY_SP,
677                        callee: h,
678                        args: vec![
679                            obj.as_arg(),
680                            class_name.clone().as_arg(),
681                            method_name.as_arg(),
682                        ],
683                        ..Default::default()
684                    }
685                    .into(),
686                    Some(class_name.clone().into()),
687                );
688            }
689
690            let get = helper!(class_static_private_field_spec_get);
691
692            (
693                CallExpr {
694                    span: DUMMY_SP,
695                    callee: get,
696                    args: vec![obj.as_arg(), class_name.clone().as_arg(), ident.as_arg()],
697                    ..Default::default()
698                }
699                .into(),
700                Some(class_name.clone().into()),
701            )
702        } else {
703            match self.private_access_type {
704                PrivateAccessType::DestructureSet => {
705                    let set = helper!(class_private_field_destructure);
706
707                    (
708                        CallExpr {
709                            span: DUMMY_SP,
710                            callee: set,
711                            args: vec![obj.clone().as_arg(), ident.as_arg()],
712
713                            ..Default::default()
714                        }
715                        .make_member(quote_ident!("value"))
716                        .into(),
717                        Some(*obj),
718                    )
719                }
720                PrivateAccessType::Update => {
721                    let set = helper!(class_private_field_update);
722
723                    (
724                        CallExpr {
725                            span: DUMMY_SP,
726                            callee: set,
727                            args: vec![obj.clone().as_arg(), ident.as_arg()],
728
729                            ..Default::default()
730                        }
731                        .make_member(quote_ident!("value"))
732                        .into(),
733                        Some(*obj),
734                    )
735                }
736
737                PrivateAccessType::Get if kind.is_writeonly() => {
738                    let helper = helper!(write_only_error);
739                    let expr = Box::new(
740                        CallExpr {
741                            span: DUMMY_SP,
742                            callee: helper,
743                            args: vec![format!("#{}", n.name).as_arg()],
744                            ..Default::default()
745                        }
746                        .into(),
747                    );
748                    (
749                        SeqExpr {
750                            span: DUMMY_SP,
751                            exprs: vec![obj.clone(), expr],
752                        }
753                        .into(),
754                        Some(*obj),
755                    )
756                }
757
758                PrivateAccessType::Get => {
759                    let get = if self.c.private_as_properties {
760                        helper!(class_private_field_loose_base)
761                    } else if kind.is_method() {
762                        helper!(class_private_method_get)
763                    } else {
764                        helper!(class_private_field_get)
765                    };
766
767                    match &*obj {
768                        Expr::This(this) => (
769                            if kind.is_method() && !self.c.private_as_properties {
770                                CallExpr {
771                                    span: DUMMY_SP,
772                                    callee: get,
773                                    args: vec![
774                                        obj.clone().as_arg(),
775                                        ident.as_arg(),
776                                        method_name.as_arg(),
777                                    ],
778                                    ..Default::default()
779                                }
780                                .into()
781                            } else {
782                                CallExpr {
783                                    span: DUMMY_SP,
784                                    callee: get,
785                                    args: vec![this.as_arg(), ident.as_arg()],
786                                    ..Default::default()
787                                }
788                                .into()
789                            },
790                            Some(Expr::This(*this)),
791                        ),
792                        _ => {
793                            let mut aliased = false;
794                            let var = obj_alias.unwrap_or_else(|| {
795                                let (var, a) = alias_if_required(&obj, "_ref");
796                                if a {
797                                    aliased = true;
798                                    self.vars.push(VarDeclarator {
799                                        span: DUMMY_SP,
800                                        name: var.clone().into(),
801                                        init: None,
802                                        definite: false,
803                                    });
804                                }
805                                var
806                            });
807
808                            let first_arg = if is_alias_initialized {
809                                var.clone().as_arg()
810                            } else if aliased {
811                                AssignExpr {
812                                    span: DUMMY_SP,
813                                    left: var.clone().into(),
814                                    op: op!("="),
815                                    right: obj.take(),
816                                }
817                                .as_arg()
818                            } else {
819                                var.clone().as_arg()
820                            };
821
822                            let args = if kind.is_method() {
823                                vec![first_arg, ident.as_arg(), method_name.as_arg()]
824                            } else {
825                                vec![first_arg, ident.as_arg()]
826                            };
827
828                            (
829                                CallExpr {
830                                    span: DUMMY_SP,
831                                    callee: get,
832                                    args,
833                                    ..Default::default()
834                                }
835                                .into(),
836                                Some(var.into()),
837                            )
838                        }
839                    }
840                }
841            }
842        }
843    }
844}
845
846/// only getter and setter in same scope could coexist
847pub(super) fn dup_private_method(kind: &PrivateKind, method: &PrivateMethod) -> bool {
848    if !kind.is_method || kind.is_static != method.is_static || method.kind == MethodKind::Method {
849        return true;
850    }
851
852    !matches!(
853        (method.kind, kind.has_getter, kind.has_setter),
854        (MethodKind::Getter, false, true) | (MethodKind::Setter, true, false)
855    )
856}