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