1use std::mem;
2
3use swc_common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{alias_ident_for, prepend_stmt, quote_ident, ExprFactory, StmtLike};
6use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
7
8pub fn optional_chaining_impl(c: Config, unresolved_mark: Mark) -> OptionalChaining {
10    OptionalChaining {
11        c,
12        unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
13        ..Default::default()
14    }
15}
16
17#[derive(Default)]
18pub struct OptionalChaining {
19    vars: Vec<VarDeclarator>,
20    unresolved_ctxt: SyntaxContext,
21    c: Config,
22}
23
24impl OptionalChaining {
25    pub fn take_vars(&mut self) -> Vec<VarDeclarator> {
26        mem::take(&mut self.vars)
27    }
28}
29
30#[derive(Debug, Clone, Copy, Default)]
32pub struct Config {
33    pub no_document_all: bool,
34    pub pure_getter: bool,
35}
36
37impl VisitMut for OptionalChaining {
38    noop_visit_mut_type!(fail);
39
40    fn visit_mut_block_stmt_or_expr(&mut self, expr: &mut BlockStmtOrExpr) {
41        if let BlockStmtOrExpr::Expr(e) = expr {
42            let mut stmt = BlockStmt {
43                span: DUMMY_SP,
44                stmts: vec![Stmt::Return(ReturnStmt {
45                    span: DUMMY_SP,
46                    arg: Some(e.take()),
47                })],
48                ..Default::default()
49            };
50            stmt.visit_mut_with(self);
51
52            match &mut stmt.stmts[..] {
56                [Stmt::Return(ReturnStmt { arg: Some(e), .. })] => {
57                    *expr = BlockStmtOrExpr::Expr(e.take())
58                }
59                _ => *expr = BlockStmtOrExpr::BlockStmt(stmt),
60            }
61        } else {
62            expr.visit_mut_children_with(self);
63        }
64    }
65
66    fn visit_mut_expr(&mut self, e: &mut Expr) {
67        match e {
68            Expr::OptChain(v) => {
70                let data = self.gather(v.take(), Vec::new());
71                *e = self.construct(data, false);
72            }
73
74            Expr::Unary(UnaryExpr {
75                arg,
76                op: op!("delete"),
77                ..
78            }) => {
79                match &mut **arg {
80                    Expr::OptChain(v) => {
82                        let data = self.gather(v.take(), Vec::new());
83                        *e = self.construct(data, true);
84                    }
85                    _ => e.visit_mut_children_with(self),
86                }
87            }
88
89            e => e.visit_mut_children_with(self),
90        }
91    }
92
93    fn visit_mut_pat(&mut self, n: &mut Pat) {
94        let Pat::Assign(a) = n else {
98            n.visit_mut_children_with(self);
99            return;
100        };
101
102        let uninit = self.vars.take();
103        a.right.visit_mut_with(self);
104
105        if !self.vars.is_empty() {
108            let stmts = vec![
109                Stmt::Decl(Decl::Var(Box::new(VarDecl {
110                    kind: VarDeclKind::Var,
111                    decls: mem::take(&mut self.vars),
112                    ..Default::default()
113                }))),
114                Stmt::Return(ReturnStmt {
115                    span: DUMMY_SP,
116                    arg: Some(a.right.take()),
117                }),
118            ];
119            a.right = CallExpr {
120                span: DUMMY_SP,
121                callee: ArrowExpr {
122                    span: DUMMY_SP,
123                    params: Vec::new(),
124                    body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
125                        span: DUMMY_SP,
126                        stmts,
127                        ..Default::default()
128                    })),
129                    is_async: false,
130                    is_generator: false,
131                    ..Default::default()
132                }
133                .as_callee(),
134                args: Vec::new(),
135                ..Default::default()
136            }
137            .into();
138        }
139
140        self.vars = uninit;
141        a.left.visit_mut_with(self);
142    }
143
144    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
145        self.visit_mut_stmt_like(n);
146    }
147
148    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
149        self.visit_mut_stmt_like(n);
150    }
151}
152
153#[derive(Debug, Clone)]
154enum Memo {
155    Cache(Ident),
156    Raw(Box<Expr>),
157}
158
159impl Memo {
160    fn into_expr(self) -> Expr {
161        match self {
162            Memo::Cache(i) => i.into(),
163            Memo::Raw(e) => *e,
164        }
165    }
166}
167
168#[derive(Debug)]
169enum Gathering {
170    Call(CallExpr),
171    Member(MemberExpr),
172    OptCall(CallExpr, Memo),
173    OptMember(MemberExpr, Memo),
174}
175
176impl OptionalChaining {
177    fn gather(
183        &mut self,
184        v: OptChainExpr,
185        mut chain: Vec<Gathering>,
186    ) -> (Expr, usize, Vec<Gathering>) {
187        let mut current = v;
188        let mut count = 0;
189        loop {
190            let OptChainExpr {
191                optional, mut base, ..
192            } = current;
193
194            if optional {
195                count += 1;
196            }
197
198            let next;
199            match &mut *base {
200                OptChainBase::Member(m) => {
201                    next = m.obj.take();
202                    m.prop.visit_mut_with(self);
203                    chain.push(if optional {
204                        Gathering::OptMember(m.take(), self.memoize(&next, false))
205                    } else {
206                        Gathering::Member(m.take())
207                    });
208                }
209
210                OptChainBase::Call(c) => {
211                    next = c.callee.take();
212                    c.args.visit_mut_with(self);
213                    chain.push(if optional {
215                        Gathering::OptCall(c.take().into(), self.memoize(&next, true))
216                    } else {
217                        Gathering::Call(c.take().into())
218                    });
219                }
220
221                #[cfg(swc_ast_unknown)]
222                _ => panic!("unable to access unknown nodes"),
223            }
224
225            match *next {
226                Expr::OptChain(next) => {
227                    current = next;
228                }
229                mut base => {
230                    base.visit_mut_children_with(self);
231                    return (base, count, chain);
232                }
233            }
234        }
235    }
236
237    fn construct(&mut self, data: (Expr, usize, Vec<Gathering>), is_delete: bool) -> Expr {
240        let (mut current, count, chain) = data;
241
242        let mut committed_cond = Vec::with_capacity(count);
244
245        let mut ctx = None;
253
254        for v in chain.into_iter().rev() {
262            current = match v {
263                Gathering::Call(mut c) => {
264                    c.callee = current.as_callee();
265                    ctx = None;
266                    c.into()
267                }
268                Gathering::Member(mut m) => {
269                    m.obj = Box::new(current);
270                    ctx = None;
271                    m.into()
272                }
273                Gathering::OptCall(mut c, memo) => {
274                    let mut call = false;
275
276                    match &mut current {
278                        Expr::Member(m) => {
279                            call = true;
280                            let this = ctx.unwrap_or_else(|| {
281                                let this = self.memoize(&m.obj, true);
282
283                                match &this {
284                                    Memo::Cache(i) => {
285                                        m.obj = AssignExpr {
286                                            span: DUMMY_SP,
287                                            op: op!("="),
288                                            left: i.clone().into(),
289                                            right: m.obj.take(),
290                                        }
291                                        .into();
292                                        this
293                                    }
294                                    Memo::Raw(_) => this,
295                                }
296                            });
297                            c.args.insert(0, this.into_expr().as_arg());
298                        }
299                        Expr::SuperProp(s) => {
300                            call = true;
301                            c.args.insert(0, ThisExpr { span: s.obj.span }.as_arg());
302                        }
303                        _ => {}
304                    }
305
306                    committed_cond.push(CondExpr {
307                        span: DUMMY_SP,
308                        test: init_and_eq_null_or_undefined(&memo, current, self.c.no_document_all),
309                        cons: if is_delete {
310                            true.into()
311                        } else {
312                            Expr::undefined(DUMMY_SP)
313                        },
314                        alt: Take::dummy(),
315                    });
316                    c.callee = if call {
317                        memo.into_expr()
318                            .make_member(quote_ident!("call"))
319                            .as_callee()
320                    } else {
321                        memo.into_expr().as_callee()
322                    };
323                    ctx = None;
324                    c.into()
325                }
326                Gathering::OptMember(mut m, memo) => {
327                    committed_cond.push(CondExpr {
328                        span: DUMMY_SP,
329                        test: init_and_eq_null_or_undefined(&memo, current, self.c.no_document_all),
330                        cons: if is_delete {
331                            true.into()
332                        } else {
333                            Expr::undefined(DUMMY_SP)
334                        },
335                        alt: Take::dummy(),
336                    });
337                    ctx = Some(memo.clone());
338                    m.obj = memo.into_expr().into();
339                    m.into()
340                }
341            };
342        }
343
344        if is_delete {
346            current = UnaryExpr {
347                span: DUMMY_SP,
348                op: op!("delete"),
349                arg: Box::new(current),
350            }
351            .into();
352        }
353
354        for mut cond in committed_cond.into_iter().rev() {
356            cond.alt = Box::new(current);
357            current = cond.into()
358        }
359        current
360    }
361
362    fn should_memo(&self, expr: &Expr, is_call: bool) -> bool {
363        fn is_simple_member(e: &Expr) -> bool {
364            match e {
365                Expr::This(..) => true,
366                Expr::Ident(_) => true,
367                Expr::SuperProp(s) if !s.prop.is_computed() => true,
368                Expr::Member(m) if !m.prop.is_computed() => is_simple_member(&m.obj),
369                _ => false,
370            }
371        }
372
373        match expr {
374            Expr::Ident(i) if i.ctxt != self.unresolved_ctxt => false,
375            _ => {
376                if is_call && self.c.pure_getter {
377                    !is_simple_member(expr)
378                } else {
379                    true
380                }
381            }
382        }
383    }
384
385    fn memoize(&mut self, expr: &Expr, is_call: bool) -> Memo {
386        if self.should_memo(expr, is_call) {
387            let memo = alias_ident_for(expr, "_this");
388            self.vars.push(VarDeclarator {
389                span: DUMMY_SP,
390                name: memo.clone().into(),
391                init: None,
392                definite: false,
393            });
394            Memo::Cache(memo)
395        } else {
396            Memo::Raw(Box::new(expr.to_owned()))
397        }
398    }
399
400    fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
401    where
402        T: Send + Sync + StmtLike + VisitMutWith<Self>,
403        Vec<T>: VisitMutWith<Self>,
404    {
405        let uninit = self.vars.take();
406        for stmt in stmts.iter_mut() {
407            stmt.visit_mut_with(self);
408        }
409
410        if !self.vars.is_empty() {
411            prepend_stmt(
412                stmts,
413                T::from(
414                    VarDecl {
415                        span: DUMMY_SP,
416                        declare: false,
417                        kind: VarDeclKind::Var,
418                        decls: mem::take(&mut self.vars),
419                        ..Default::default()
420                    }
421                    .into(),
422                ),
423            );
424        }
425
426        self.vars = uninit;
427    }
428}
429
430fn init_and_eq_null_or_undefined(i: &Memo, init: Expr, no_document_all: bool) -> Box<Expr> {
431    let lhs = match i {
432        Memo::Cache(i) => AssignExpr {
433            span: DUMMY_SP,
434            op: op!("="),
435            left: i.clone().into(),
436            right: Box::new(init),
437        }
438        .into(),
439        Memo::Raw(e) => e.to_owned(),
440    };
441
442    if no_document_all {
443        return BinExpr {
444            span: DUMMY_SP,
445            left: lhs,
446            op: op!("=="),
447            right: Box::new(Lit::Null(Null { span: DUMMY_SP }).into()),
448        }
449        .into();
450    }
451
452    let null_cmp = BinExpr {
453        span: DUMMY_SP,
454        left: lhs,
455        op: op!("==="),
456        right: Box::new(Lit::Null(Null { span: DUMMY_SP }).into()),
457    }
458    .into();
459
460    let left_expr = match i {
461        Memo::Cache(i) => Box::new(i.clone().into()),
462        Memo::Raw(e) => e.to_owned(),
463    };
464
465    let void_cmp = BinExpr {
466        span: DUMMY_SP,
467        left: left_expr,
468        op: op!("==="),
469        right: Expr::undefined(DUMMY_SP),
470    }
471    .into();
472
473    BinExpr {
474        span: DUMMY_SP,
475        left: null_cmp,
476        op: op!("||"),
477        right: void_cmp,
478    }
479    .into()
480}