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 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 fn visit_mut_switch_case(&mut self, s: &mut SwitchCase) {
71 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 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 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 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}