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}