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
222 match *next {
223 Expr::OptChain(next) => {
224 current = next;
225 }
226 mut base => {
227 base.visit_mut_children_with(self);
228 return (base, count, chain);
229 }
230 }
231 }
232 }
233
234 fn construct(&mut self, data: (Expr, usize, Vec<Gathering>), is_delete: bool) -> Expr {
237 let (mut current, count, chain) = data;
238
239 let mut committed_cond = Vec::with_capacity(count);
241
242 let mut ctx = None;
250
251 for v in chain.into_iter().rev() {
259 current = match v {
260 Gathering::Call(mut c) => {
261 c.callee = current.as_callee();
262 ctx = None;
263 c.into()
264 }
265 Gathering::Member(mut m) => {
266 m.obj = Box::new(current);
267 ctx = None;
268 m.into()
269 }
270 Gathering::OptCall(mut c, memo) => {
271 let mut call = false;
272
273 match &mut current {
275 Expr::Member(m) => {
276 call = true;
277 let this = ctx.unwrap_or_else(|| {
278 let this = self.memoize(&m.obj, true);
279
280 match &this {
281 Memo::Cache(i) => {
282 m.obj = AssignExpr {
283 span: DUMMY_SP,
284 op: op!("="),
285 left: i.clone().into(),
286 right: m.obj.take(),
287 }
288 .into();
289 this
290 }
291 Memo::Raw(_) => this,
292 }
293 });
294 c.args.insert(0, this.into_expr().as_arg());
295 }
296 Expr::SuperProp(s) => {
297 call = true;
298 c.args.insert(0, ThisExpr { span: s.obj.span }.as_arg());
299 }
300 _ => {}
301 }
302
303 committed_cond.push(CondExpr {
304 span: DUMMY_SP,
305 test: init_and_eq_null_or_undefined(&memo, current, self.c.no_document_all),
306 cons: if is_delete {
307 true.into()
308 } else {
309 Expr::undefined(DUMMY_SP)
310 },
311 alt: Take::dummy(),
312 });
313 c.callee = if call {
314 memo.into_expr()
315 .make_member(quote_ident!("call"))
316 .as_callee()
317 } else {
318 memo.into_expr().as_callee()
319 };
320 ctx = None;
321 c.into()
322 }
323 Gathering::OptMember(mut m, memo) => {
324 committed_cond.push(CondExpr {
325 span: DUMMY_SP,
326 test: init_and_eq_null_or_undefined(&memo, current, self.c.no_document_all),
327 cons: if is_delete {
328 true.into()
329 } else {
330 Expr::undefined(DUMMY_SP)
331 },
332 alt: Take::dummy(),
333 });
334 ctx = Some(memo.clone());
335 m.obj = memo.into_expr().into();
336 m.into()
337 }
338 };
339 }
340
341 if is_delete {
343 current = UnaryExpr {
344 span: DUMMY_SP,
345 op: op!("delete"),
346 arg: Box::new(current),
347 }
348 .into();
349 }
350
351 for mut cond in committed_cond.into_iter().rev() {
353 cond.alt = Box::new(current);
354 current = cond.into()
355 }
356 current
357 }
358
359 fn should_memo(&self, expr: &Expr, is_call: bool) -> bool {
360 fn is_simple_member(e: &Expr) -> bool {
361 match e {
362 Expr::This(..) => true,
363 Expr::Ident(_) => true,
364 Expr::SuperProp(s) if !s.prop.is_computed() => true,
365 Expr::Member(m) if !m.prop.is_computed() => is_simple_member(&m.obj),
366 _ => false,
367 }
368 }
369
370 match expr {
371 Expr::Ident(i) if i.ctxt != self.unresolved_ctxt => false,
372 _ => {
373 if is_call && self.c.pure_getter {
374 !is_simple_member(expr)
375 } else {
376 true
377 }
378 }
379 }
380 }
381
382 fn memoize(&mut self, expr: &Expr, is_call: bool) -> Memo {
383 if self.should_memo(expr, is_call) {
384 let memo = alias_ident_for(expr, "_this");
385 self.vars.push(VarDeclarator {
386 span: DUMMY_SP,
387 name: memo.clone().into(),
388 init: None,
389 definite: false,
390 });
391 Memo::Cache(memo)
392 } else {
393 Memo::Raw(Box::new(expr.to_owned()))
394 }
395 }
396
397 fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
398 where
399 T: Send + Sync + StmtLike + VisitMutWith<Self>,
400 Vec<T>: VisitMutWith<Self>,
401 {
402 let uninit = self.vars.take();
403 for stmt in stmts.iter_mut() {
404 stmt.visit_mut_with(self);
405 }
406
407 if !self.vars.is_empty() {
408 prepend_stmt(
409 stmts,
410 T::from(
411 VarDecl {
412 span: DUMMY_SP,
413 declare: false,
414 kind: VarDeclKind::Var,
415 decls: mem::take(&mut self.vars),
416 ..Default::default()
417 }
418 .into(),
419 ),
420 );
421 }
422
423 self.vars = uninit;
424 }
425}
426
427fn init_and_eq_null_or_undefined(i: &Memo, init: Expr, no_document_all: bool) -> Box<Expr> {
428 let lhs = match i {
429 Memo::Cache(i) => AssignExpr {
430 span: DUMMY_SP,
431 op: op!("="),
432 left: i.clone().into(),
433 right: Box::new(init),
434 }
435 .into(),
436 Memo::Raw(e) => e.to_owned(),
437 };
438
439 if no_document_all {
440 return BinExpr {
441 span: DUMMY_SP,
442 left: lhs,
443 op: op!("=="),
444 right: Box::new(Lit::Null(Null { span: DUMMY_SP }).into()),
445 }
446 .into();
447 }
448
449 let null_cmp = BinExpr {
450 span: DUMMY_SP,
451 left: lhs,
452 op: op!("==="),
453 right: Box::new(Lit::Null(Null { span: DUMMY_SP }).into()),
454 }
455 .into();
456
457 let left_expr = match i {
458 Memo::Cache(i) => Box::new(i.clone().into()),
459 Memo::Raw(e) => e.to_owned(),
460 };
461
462 let void_cmp = BinExpr {
463 span: DUMMY_SP,
464 left: left_expr,
465 op: op!("==="),
466 right: Expr::undefined(DUMMY_SP),
467 }
468 .into();
469
470 BinExpr {
471 span: DUMMY_SP,
472 left: null_cmp,
473 op: op!("||"),
474 right: void_cmp,
475 }
476 .into()
477}