swc_ecma_compat_es2020/
nullish_coalescing.rs

1use std::mem::take;
2
3use serde::Deserialize;
4use swc_common::{util::take::Take, Span, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_utils::{alias_ident_for_simple_assign_tatget, alias_if_required, StmtLike};
7use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
8
9pub fn nullish_coalescing(c: Config) -> impl Pass + 'static {
10    visit_mut_pass(NullishCoalescing {
11        c,
12        ..Default::default()
13    })
14}
15
16#[derive(Debug, Default)]
17struct NullishCoalescing {
18    vars: Vec<VarDeclarator>,
19    c: Config,
20}
21
22#[derive(Debug, Clone, Copy, Default, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct Config {
25    #[serde(default)]
26    pub no_document_all: bool,
27}
28
29impl NullishCoalescing {
30    fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
31    where
32        T: VisitMutWith<Self> + StmtLike,
33    {
34        let mut buf = Vec::with_capacity(stmts.len() + 2);
35
36        for mut stmt in stmts.take() {
37            stmt.visit_mut_with(self);
38
39            if !self.vars.is_empty() {
40                buf.push(T::from(
41                    VarDecl {
42                        span: DUMMY_SP,
43                        kind: VarDeclKind::Var,
44                        decls: take(&mut self.vars),
45                        declare: false,
46                        ..Default::default()
47                    }
48                    .into(),
49                ));
50            }
51
52            buf.push(stmt);
53        }
54
55        *stmts = buf
56    }
57}
58
59impl VisitMut for NullishCoalescing {
60    noop_visit_mut_type!(fail);
61
62    /// Prevents #1123
63    fn visit_mut_block_stmt(&mut self, s: &mut BlockStmt) {
64        let old_vars = self.vars.take();
65        s.visit_mut_children_with(self);
66        self.vars = old_vars;
67    }
68
69    /// Prevents #1123
70    fn visit_mut_switch_case(&mut self, s: &mut SwitchCase) {
71        // Prevents #6328
72        s.test.visit_mut_with(self);
73        let old_vars = self.vars.take();
74        s.cons.visit_mut_with(self);
75        self.vars = old_vars;
76    }
77
78    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
79        self.visit_mut_stmt_like(n)
80    }
81
82    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
83        self.visit_mut_stmt_like(n)
84    }
85
86    fn visit_mut_expr(&mut self, e: &mut Expr) {
87        e.visit_mut_children_with(self);
88
89        match e {
90            Expr::Bin(BinExpr {
91                span,
92                left,
93                op: op!("??"),
94                right,
95            }) => {
96                //
97                let (l, aliased) = alias_if_required(left, "ref");
98
99                if aliased {
100                    self.vars.push(VarDeclarator {
101                        span: DUMMY_SP,
102                        name: l.clone().into(),
103                        init: None,
104                        definite: false,
105                    });
106                }
107
108                let var_expr = if aliased {
109                    AssignExpr {
110                        span: DUMMY_SP,
111                        op: op!("="),
112                        left: l.clone().into(),
113                        right: left.take(),
114                    }
115                    .into()
116                } else {
117                    l.clone().into()
118                };
119
120                *e = make_cond(self.c, *span, &l, var_expr, right.take());
121            }
122
123            Expr::Assign(ref mut assign @ AssignExpr { op: op!("??="), .. }) => {
124                match &mut assign.left {
125                    AssignTarget::Simple(SimpleAssignTarget::Ident(i)) => {
126                        *e = AssignExpr {
127                            span: assign.span,
128                            op: op!("="),
129                            left: i.clone().into(),
130                            right: Box::new(make_cond(
131                                self.c,
132                                assign.span,
133                                &Ident::from(&*i),
134                                Expr::Ident(Ident::from(&*i)),
135                                assign.right.take(),
136                            )),
137                        }
138                        .into();
139                    }
140
141                    AssignTarget::Simple(left) => {
142                        let alias = alias_ident_for_simple_assign_tatget(left, "refs");
143                        self.vars.push(VarDeclarator {
144                            span: DUMMY_SP,
145                            name: alias.clone().into(),
146                            init: None,
147                            definite: false,
148                        });
149
150                        // TODO: Check for computed.
151                        let right_expr = AssignExpr {
152                            span: assign.span,
153                            left: left.clone().into(),
154                            op: op!("="),
155                            right: assign.right.take(),
156                        }
157                        .into();
158
159                        let var_expr = AssignExpr {
160                            span: DUMMY_SP,
161                            op: op!("="),
162                            left: alias.clone().into(),
163                            right: left.take().into(),
164                        }
165                        .into();
166
167                        *e = AssignExpr {
168                            span: assign.span,
169                            op: op!("="),
170                            left: alias.clone().into(),
171                            right: Box::new(make_cond(
172                                self.c,
173                                assign.span,
174                                &alias,
175                                var_expr,
176                                right_expr,
177                            )),
178                        }
179                        .into();
180                    }
181
182                    _ => {}
183                }
184            }
185
186            _ => {}
187        }
188    }
189
190    fn visit_mut_block_stmt_or_expr(&mut self, n: &mut BlockStmtOrExpr) {
191        let vars = self.vars.take();
192        n.visit_mut_children_with(self);
193
194        if !self.vars.is_empty() {
195            if let BlockStmtOrExpr::Expr(expr) = n {
196                // expr
197                // { var decl = init; return expr; }
198                let stmts = vec![
199                    VarDecl {
200                        span: DUMMY_SP,
201                        kind: VarDeclKind::Var,
202                        decls: self.vars.take(),
203                        declare: false,
204                        ..Default::default()
205                    }
206                    .into(),
207                    Stmt::Return(ReturnStmt {
208                        span: DUMMY_SP,
209                        arg: Some(expr.take()),
210                    }),
211                ];
212                *n = BlockStmtOrExpr::BlockStmt(BlockStmt {
213                    span: DUMMY_SP,
214                    stmts,
215                    ..Default::default()
216                });
217            }
218        }
219
220        self.vars = vars;
221    }
222}
223
224#[tracing::instrument(level = "debug", skip_all)]
225fn make_cond(c: Config, span: Span, alias: &Ident, var_expr: Expr, init: Box<Expr>) -> Expr {
226    if c.no_document_all {
227        CondExpr {
228            span,
229            test: BinExpr {
230                span: DUMMY_SP,
231                left: Box::new(var_expr),
232                op: op!("!="),
233                right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
234            }
235            .into(),
236            cons: alias.clone().into(),
237            alt: init,
238        }
239    } else {
240        CondExpr {
241            span,
242            test: BinExpr {
243                span: DUMMY_SP,
244                left: Box::new(Expr::Bin(BinExpr {
245                    span: DUMMY_SP,
246                    left: Box::new(var_expr),
247                    op: op!("!=="),
248                    right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
249                })),
250                op: op!("&&"),
251                right: Box::new(Expr::Bin(BinExpr {
252                    span: DUMMY_SP,
253                    left: Box::new(Expr::Ident(alias.clone())),
254                    op: op!("!=="),
255                    right: Expr::undefined(DUMMY_SP),
256                })),
257            }
258            .into(),
259            cons: alias.clone().into(),
260            alt: init,
261        }
262    }
263    .into()
264}