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