1use std::mem;
2
3use serde::Deserialize;
4use swc_atoms::atom;
5use swc_common::{util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP};
6use swc_ecma_ast::*;
7use swc_ecma_transforms_base::{ext::ExprRefExt, helper, perf::Check};
8use swc_ecma_transforms_macros::fast_path;
9use swc_ecma_utils::{
10 alias_ident_for, member_expr, prepend_stmt, quote_ident, ExprFactory, StmtLike,
11};
12use swc_ecma_visit::{
13 noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
14};
15use swc_trace_macro::swc_trace;
16
17pub fn spread(c: Config, unresolved_mark: Mark) -> impl Pass {
18 let unresolved_ctxt = SyntaxContext::empty().apply_mark(unresolved_mark);
19 visit_mut_pass(Spread {
20 c,
21 unresolved_ctxt,
22 vars: Default::default(),
23 })
24}
25
26#[derive(Debug, Clone, Copy, Default, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct Config {
29 pub loose: bool,
30}
31
32struct Spread {
34 c: Config,
35 unresolved_ctxt: SyntaxContext,
36 vars: Vec<VarDeclarator>,
37}
38
39#[swc_trace]
40#[fast_path(SpreadFinder)]
41impl VisitMut for Spread {
42 noop_visit_mut_type!(fail);
43
44 fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
45 self.visit_mut_stmt_like(n);
46 }
47
48 fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
49 self.visit_mut_stmt_like(n);
50 }
51
52 fn visit_mut_expr(&mut self, e: &mut Expr) {
53 e.visit_mut_children_with(self);
54
55 match e {
56 Expr::Array(ArrayLit { span, elems }) => {
57 if !elems.iter().any(|e| {
58 matches!(
59 e,
60 Some(ExprOrSpread {
61 spread: Some(_),
62 ..
63 })
64 )
65 }) {
66 return;
67 }
68
69 *e = self.concat_args(*span, elems.take().into_iter(), true);
70 }
71
72 Expr::Call(CallExpr {
74 callee: Callee::Expr(callee),
75 args,
76 span,
77 ..
78 }) => {
79 let has_spread = args
80 .iter()
81 .any(|ExprOrSpread { spread, .. }| spread.is_some());
82 if !has_spread {
83 return;
84 }
85
86 let (this, callee_updated) = match &**callee {
87 Expr::SuperProp(SuperPropExpr {
88 obj: Super { span, .. },
89 ..
90 }) => (ThisExpr { span: *span }.into(), None),
91
92 Expr::Member(MemberExpr { obj, .. }) if obj.is_this() => (obj.clone(), None),
93
94 Expr::Member(MemberExpr { obj, .. })
96 if obj.as_ident().is_some() && obj.as_ident().unwrap().span.is_dummy() =>
97 {
98 (obj.as_ident().unwrap().clone().into(), None)
99 }
100
101 Expr::Ident(Ident { span, .. }) => (Expr::undefined(*span), None),
102
103 Expr::Member(MemberExpr { span, obj, prop }) => {
104 let ident = alias_ident_for(obj, "_instance");
105 self.vars.push(VarDeclarator {
106 span: DUMMY_SP,
107 definite: false,
108 name: ident.clone().into(),
110 init: None,
112 });
113
114 let this = ident.clone().into();
115 let callee: Expr = AssignExpr {
116 span: DUMMY_SP,
117 left: ident.into(),
118 op: op!("="),
119 right: obj.clone(),
120 }
121 .into();
122 (
123 this,
124 Some(
125 MemberExpr {
126 span: *span,
127 obj: callee.into(),
128 prop: prop.clone(),
129 }
130 .into(),
131 ),
132 )
133 }
134
135 _ => (
138 ThisExpr {
139 span: callee.span(),
140 }
141 .into(),
142 None,
143 ),
144 };
145
146 let args_array = if args.iter().all(|e| {
147 matches!(e, ExprOrSpread { spread: None, .. })
148 || matches!(e, ExprOrSpread { expr, .. } if expr.is_array())
149 }) {
150 ArrayLit {
151 span: *span,
152 elems: expand_literal_args(args.take().into_iter().map(Some)),
153 }
154 .into()
155 } else {
156 self.concat_args(*span, args.take().into_iter().map(Some), false)
157 };
158
159 let apply = MemberExpr {
160 span: DUMMY_SP,
161 obj: callee_updated.unwrap_or_else(|| callee.take()),
162 prop: quote_ident!("apply").into(),
163 };
164
165 *e = CallExpr {
166 span: *span,
167 callee: apply.as_callee(),
168 args: vec![this.as_arg(), args_array.as_arg()],
169 ..Default::default()
170 }
171 .into()
172 }
173 Expr::New(NewExpr {
174 callee,
175 args: Some(args),
176 span,
177 ..
178 }) => {
179 let has_spread = args
180 .iter()
181 .any(|ExprOrSpread { spread, .. }| spread.is_some());
182 if !has_spread {
183 return;
184 }
185
186 let args = self.concat_args(*span, args.take().into_iter().map(Some), true);
187
188 *e = CallExpr {
189 span: *span,
190 callee: helper!(construct),
191 args: vec![callee.take().as_arg(), args.as_arg()],
192 ..Default::default()
193 }
194 .into();
195 }
196 _ => {}
197 };
198 }
199}
200
201#[swc_trace]
202impl Spread {
203 fn visit_mut_stmt_like<T>(&mut self, items: &mut Vec<T>)
204 where
205 T: StmtLike,
206 Vec<T>: VisitMutWith<Self>,
207 {
208 let orig = self.vars.take();
209
210 items.visit_mut_children_with(self);
211
212 if !self.vars.is_empty() {
213 prepend_stmt(
214 items,
215 T::from(
216 VarDecl {
217 kind: VarDeclKind::Var,
218 decls: self.vars.take(),
219 ..Default::default()
220 }
221 .into(),
222 ),
223 );
224 }
225
226 self.vars = orig;
227 }
228}
229
230#[swc_trace]
231impl Spread {
232 fn concat_args(
233 &self,
234 span: Span,
235 args: impl ExactSizeIterator<Item = Option<ExprOrSpread>>,
236 need_array: bool,
237 ) -> Expr {
238 let mut first_arr = None;
242
243 let mut tmp_arr = Vec::new();
244 let mut buf = Vec::new();
245 let args_len = args.len();
246
247 macro_rules! make_arr {
248 () => {
249 let elems = mem::take(&mut tmp_arr);
250 match first_arr {
251 Some(_) => {
252 if !elems.is_empty() {
253 buf.push(ArrayLit { span, elems }.as_arg());
254 }
255 }
256 None => {
257 first_arr = Some(Expr::Array(ArrayLit { span, elems }));
258 }
259 }
260 };
261 }
262
263 if self.c.loose {
271 let mut arg_list = Vec::new();
272 let mut current_elems = Vec::new();
273 for arg in args.flatten() {
274 let expr = arg.expr;
275 match arg.spread {
276 Some(span) => {
277 if !current_elems.is_empty() {
278 arg_list.push(
279 ArrayLit {
280 span: DUMMY_SP,
281 elems: current_elems,
282 }
283 .as_arg(),
284 );
285 current_elems = Vec::new();
286 }
287 let expr = match *expr {
290 Expr::Ident(Ident { ref sym, ctxt, .. })
291 if &**sym == "arguments" && ctxt == self.unresolved_ctxt =>
292 {
293 CallExpr {
294 span,
295 callee: member_expr!(
296 Default::default(),
297 DUMMY_SP,
298 Array.prototype.slice.call
299 )
300 .as_callee(),
301 args: vec![expr.as_arg()],
302 ..Default::default()
303 }
304 .into()
305 }
306 _ => *expr,
307 };
308 arg_list.push(expr.as_arg());
309 }
310 None => {
311 current_elems.push(Some(expr.as_arg()));
312 }
313 }
314 }
315 if !current_elems.is_empty() {
316 arg_list.push(
317 ArrayLit {
318 span: DUMMY_SP,
319 elems: current_elems,
320 }
321 .as_arg(),
322 );
323 }
324
325 return CallExpr {
326 span: DUMMY_SP,
327 callee: ArrayLit {
328 span: DUMMY_SP,
329 elems: Vec::new(),
330 }
331 .make_member(quote_ident!("concat"))
332 .as_callee(),
333 args: arg_list,
334 ..Default::default()
335 }
336 .into();
337 }
338
339 for arg in args {
340 if let Some(arg) = arg {
341 let ExprOrSpread { expr, spread } = arg;
342
343 fn to_consumable_array(expr: Box<Expr>, span: Span) -> CallExpr {
344 if matches!(*expr, Expr::Lit(Lit::Str(..))) {
345 CallExpr {
346 span,
347 callee: quote_ident!("Array")
348 .make_member(quote_ident!("from"))
349 .as_callee(),
350 args: vec![expr.as_arg()],
351 ..Default::default()
352 }
353 } else {
354 CallExpr {
355 span,
356 callee: helper!(to_consumable_array),
357 args: vec![expr.as_arg()],
358 ..Default::default()
359 }
360 }
361 }
362
363 match spread {
364 Some(span) => {
366 make_arr!();
368
369 buf.push(match *expr {
370 Expr::Ident(Ident { ref sym, .. }) if &**sym == "arguments" => {
371 if args_len == 1 {
372 if need_array {
373 return CallExpr {
374 span,
375 callee: member_expr!(
376 Default::default(),
377 DUMMY_SP,
378 Array.prototype.slice.call
379 )
380 .as_callee(),
381 args: vec![expr.as_arg()],
382 ..Default::default()
383 }
384 .into();
385 } else {
386 return *expr;
387 }
388 } else {
389 CallExpr {
390 span,
391 callee: member_expr!(
392 Default::default(),
393 DUMMY_SP,
394 Array.prototype.slice.call
395 )
396 .as_callee(),
397 args: vec![expr.as_arg()],
398 ..Default::default()
399 }
400 .as_arg()
401 }
402 }
403 _ => {
404 if args_len == 1 && !need_array {
405 return if self.c.loose {
406 *expr
407 } else {
408 to_consumable_array(expr, span).into()
409 };
410 }
411 if args_len == 1 {
413 return if self.c.loose {
414 CallExpr {
415 span: DUMMY_SP,
416 callee: ArrayLit {
417 span: DUMMY_SP,
418 elems: Vec::new(),
419 }
420 .make_member(quote_ident!("concat"))
421 .as_callee(),
422 args: vec![expr.as_arg()],
423 ..Default::default()
424 }
425 .into()
426 } else {
427 to_consumable_array(expr, span).into()
428 };
429 }
430 to_consumable_array(expr, span).as_arg()
431 }
432 });
433 }
434 None => tmp_arr.push(Some(expr.as_arg())),
435 }
436 } else {
437 tmp_arr.push(None);
438 }
439 }
440 make_arr!();
441
442 if !buf.is_empty()
443 && match first_arr {
444 None => true,
445 Some(Expr::Array(ref arr)) if arr.elems.is_empty() => true,
446 _ => false,
447 }
448 {
449 let callee = buf
450 .remove(0)
451 .expr
452 .make_member(IdentName::new(atom!("concat"), DUMMY_SP))
453 .as_callee();
454
455 return CallExpr {
456 span,
457 callee,
458 args: buf,
459 ..Default::default()
460 }
461 .into();
462 }
463
464 CallExpr {
465 span,
467
468 callee: first_arr
469 .take()
470 .unwrap_or_else(|| {
471 Expr::Array(ArrayLit {
475 span,
476 elems: Vec::new(),
477 })
478 })
479 .make_member(IdentName::new(atom!("concat"), span))
480 .as_callee(),
481
482 args: buf,
483 ..Default::default()
484 }
485 .into()
486 }
487}
488
489#[tracing::instrument(level = "debug", skip_all)]
490fn expand_literal_args(
491 args: impl ExactSizeIterator<Item = Option<ExprOrSpread>>,
492) -> Vec<Option<ExprOrSpread>> {
493 fn expand(
494 buf: &mut Vec<Option<ExprOrSpread>>,
495 args: impl ExactSizeIterator<Item = Option<ExprOrSpread>>,
496 ) {
497 for mut arg in args {
498 if let Some(ExprOrSpread {
499 spread: Some(spread_span),
500 expr,
501 }) = arg
502 {
503 match *expr {
504 Expr::Array(arr) => {
505 expand(buf, arr.elems.into_iter());
506 continue;
507 }
508 _ => {
509 arg = Some(ExprOrSpread {
510 spread: Some(spread_span),
511 expr,
512 })
513 }
514 }
515 }
516
517 buf.push(arg)
518 }
519 }
520
521 let mut buf = Vec::with_capacity(args.len() + 4);
522 expand(&mut buf, args);
523 buf
524}
525
526#[derive(Default)]
527struct SpreadFinder {
528 found: bool,
529}
530
531impl Visit for SpreadFinder {
532 noop_visit_type!(fail);
533
534 fn visit_expr_or_spread(&mut self, n: &ExprOrSpread) {
535 n.visit_children_with(self);
536
537 self.found |= n.spread.is_some();
538 }
539}
540
541impl Check for SpreadFinder {
542 fn should_handle(&self) -> bool {
543 self.found
544 }
545}