Repository: swc-project/swc (rust)

Issue:
Spread Operator Failed to be Converted
### Describe the bug

The spread operator in the function parameter cannot be converted to Es5 code format.

### Input code

```typescript
v3 = String.raw({ raw: 'abcd' }, ...'___');
console.log(v3);
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false,
      "dynamicImport": false,
      "privateMethod": false,
      "functionBind": false,
      "exportDefaultFrom": false,
      "exportNamespaceFrom": false,
      "decorators": false,
      "decoratorsBeforeExport": false,
      "topLevelAwait": false,
      "importMeta": false
    },
    "transform": null,
    "target": "es5",
    "loose": false,
    "externalHelpers": false
  },
  "module": {
    "type": "commonjs",
    "strict": false,
    "strictMode": false,
    "lazy": false,
    "noInterop": false
  }
}
```


### Playground link

https://play.swc.rs/?version=1.2.161&code=H4sIAAAAAAAAAyszVrBVCC4pysxL1ytKLNeoVgCSVgrqiUnJKeoKtToKenp66vHx8eqa1lzJ%2BXnF%2BTmpejn56RplxprWAIqBgjk8AAAA&config=H4sIAAAAAAAAA0WNwQ6DQAhE%2F4Wz1178gvbgR5AtNmtY2AAmWuO%2Fd01tPcFMZt5sMHmCfoOK5mTH56sELtADpYKeLNeArsWaNSI77R2EofioVqCXmbkZaC%2BKo%2BO3FmZVpzPeAS1BJsh34krmF6boc2Y6NmOt7ULSUlQmbwgPyyn%2BjK8c9HlhGd%2FrX4g%2BpK1ovejZh5N%2FZkqWPP4q%2Bwfiymdk%2BgAAAA%3D%3D

### Expected behavior

The spread operator can be converted to ES5 code format.

### Actual behavior

_No response_

### Version

1.2.161

### Additional context

_No response_

Pull request:
fix(es/compat): handle string literial spread
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**
- fix #4190

Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/es2015/spread.rs b/crates/swc_ecma_transforms_compat/src/es2015/spread.rs
index 25f67d61a2..dd430283ec 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/spread.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/spread.rs
@@ -7,8 +7,7 @@ use swc_ecma_ast::*;
 use swc_ecma_transforms_base::{ext::ExprRefExt, helper, perf::Check};
 use swc_ecma_transforms_macros::fast_path;
 use swc_ecma_utils::{
-    alias_ident_for, is_literal, member_expr, prepend, quote_ident, undefined, ExprFactory,
-    StmtLike,
+    alias_ident_for, member_expr, prepend, quote_ident, undefined, ExprFactory, StmtLike,
 };
 use swc_ecma_visit::{
     as_folder, noop_visit_mut_type, noop_visit_type, Fold, Visit, VisitMut, VisitMutWith, VisitWith,
@@ -137,7 +136,10 @@ impl VisitMut for Spread {
                     ),
                 };
 
-                let args_array = if is_literal(args) {
+                let args_array = if args.iter().all(|e| {
+                    matches!(e, ExprOrSpread { spread: None, .. })
+                        || matches!(e, ExprOrSpread { expr, .. } if expr.is_array())
+                }) {
                     Expr::Array(ArrayLit {
                         span: *span,
                         elems: expand_literal_args(args.take().into_iter().map(Some)),
@@ -250,6 +252,26 @@ impl Spread {
             if let Some(arg) = arg {
                 let ExprOrSpread { expr, spread } = arg;
 
+                fn to_consumable_array(expr: Box<Expr>, span: Span) -> CallExpr {
+                    if matches!(*expr, Expr::Lit(Lit::Str(..))) {
+                        CallExpr {
+                            span,
+                            callee: quote_ident!("Array")
+                                .make_member(quote_ident!("from"))
+                                .as_callee(),
+                            args: vec![expr.as_arg()],
+                            type_args: Default::default(),
+                        }
+                    } else {
+                        CallExpr {
+                            span,
+                            callee: helper!(to_consumable_array, "toConsumableArray"),
+                            args: vec![expr.as_arg()],
+                            type_args: Default::default(),
+                        }
+                    }
+                }
+
                 match spread {
                     // ...b -> toConsumableArray(b)
                     Some(span) => {
@@ -292,15 +314,7 @@ impl Spread {
                                     return if self.c.loose {
                                         *expr
                                     } else {
-                                        Expr::Call(CallExpr {
-                                            span,
-                                            callee: helper!(
-                                                to_consumable_array,
-                                                "toConsumableArray"
-                                            ),
-                                            args: vec![expr.as_arg()],
-                                            type_args: Default::default(),
-                                        })
+                                        Expr::Call(to_consumable_array(expr, span))
                                     };
                                 }
                                 // [].concat(arr) is shorter than _toConsumableArray(arr)
@@ -318,25 +332,11 @@ impl Spread {
                                             type_args: Default::default(),
                                         })
                                     } else {
-                                        Expr::Call(CallExpr {
-                                            span,
-                                            callee: helper!(
-                                                to_consumable_array,
-                                                "toConsumableArray"
-                                            ),
-                                            args: vec![expr.as_arg()],
-                                            type_args: Default::default(),
-                                        })
+                                        Expr::Call(to_consumable_array(expr, span))
                                     };
                                 }
 
-                                CallExpr {
-                                    span,
-                                    callee: helper!(to_consumable_array, "toConsumableArray"),
-                                    args: vec![expr.as_arg()],
-                                    type_args: Default::default(),
-                                }
-                                .as_arg()
+                                to_consumable_array(expr, span).as_arg()
                             }
                         });
                     }
@@ -410,7 +410,7 @@ fn expand_literal_args(
                 match *expr {
                     Expr::Array(arr) => {
                         expand(buf, arr.elems.into_iter());
-                        return;
+                        continue;
                     }
                     _ => {
                         arg = Some(ExprOrSpread {


Test patch:
diff --git a/crates/swc/tests/tsc-references/callChain_es5.1.normal.js b/crates/swc/tests/tsc-references/callChain_es5.1.normal.js
index 413f5c3a67..b869f59086 100644
--- a/crates/swc/tests/tsc-references/callChain_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/callChain_es5.1.normal.js
@@ -1,4 +1,3 @@
-import * as swcHelpers from "@swc/helpers";
 var _o2, _o21, _o22, _o23, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
 var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, ref10, ref11, ref12;
 o1 === null || o1 === void 0 ? void 0 : o1();
@@ -10,7 +9,8 @@ o1 === null || o1 === void 0 ? void 0 : o1.apply(void 0, [
 o1 === null || o1 === void 0 ? void 0 : o1.apply(void 0, [
     1,
     2,
-    3
+    3,
+    4
 ]);
 o2 === null || o2 === void 0 ? void 0 : o2.b();
 o2 === null || o2 === void 0 ? void 0 : o2.b(1);
@@ -21,7 +21,8 @@ o2 === null || o2 === void 0 ? void 0 : (_o2 = o2).b.apply(_o2, [
 o2 === null || o2 === void 0 ? void 0 : (_o21 = o2).b.apply(_o21, [
     1,
     2,
-    3
+    3,
+    4
 ]);
 o2 === null || o2 === void 0 ? void 0 : o2["b"]();
 o2 === null || o2 === void 0 ? void 0 : o2["b"](1);
@@ -32,59 +33,51 @@ o2 === null || o2 === void 0 ? void 0 : (_o22 = o2)["b"].apply(_o22, [
 o2 === null || o2 === void 0 ? void 0 : (_o23 = o2)["b"].apply(_o23, [
     1,
     2,
-    3
+    3,
+    4
 ]);
 (ref = o3.b) === null || ref === void 0 ? void 0 : ref.call(o3).c;
 (ref1 = o3.b) === null || ref1 === void 0 ? void 0 : ref1.call(o3, 1).c;
 (ref2 = o3.b) === null || ref2 === void 0 ? void 0 : (_ref = ref2).call.apply(_ref, [
-    o3
-].concat(swcHelpers.toConsumableArray([
+    o3,
     1,
     2
-]))).c;
+]).c;
 (ref3 = o3.b) === null || ref3 === void 0 ? void 0 : (_ref1 = ref3).call.apply(_ref1, [
     o3,
-    1
-].concat(swcHelpers.toConsumableArray([
+    1,
     2,
-    3
-]), [
+    3,
     4
-])).c;
+]).c;
 (ref4 = o3.b) === null || ref4 === void 0 ? void 0 : ref4.call(o3)["c"];
 (ref5 = o3.b) === null || ref5 === void 0 ? void 0 : ref5.call(o3, 1)["c"];
 (ref6 = o3.b) === null || ref6 === void 0 ? void 0 : (_ref2 = ref6).call.apply(_ref2, [
-    o3
-].concat(swcHelpers.toConsumableArray([
+    o3,
     1,
     2
-])))["c"];
+])["c"];
 (ref7 = o3.b) === null || ref7 === void 0 ? void 0 : (_ref3 = ref7).call.apply(_ref3, [
     o3,
-    1
-].concat(swcHelpers.toConsumableArray([
+    1,
     2,
-    3
-]), [
+    3,
     4
-]))["c"];
+])["c"];
 (ref8 = o3["b"]) === null || ref8 === void 0 ? void 0 : ref8.call(o3).c;
 (ref9 = o3["b"]) === null || ref9 === void 0 ? void 0 : ref9.call(o3, 1).c;
 (ref10 = o3["b"]) === null || ref10 === void 0 ? void 0 : (_ref4 = ref10).call.apply(_ref4, [
-    o3
-].concat(swcHelpers.toConsumableArray([
+    o3,
     1,
     2
-]))).c;
+]).c;
 (ref11 = o3["b"]) === null || ref11 === void 0 ? void 0 : (_ref5 = ref11).call.apply(_ref5, [
     o3,
-    1
-].concat(swcHelpers.toConsumableArray([
+    1,
     2,
-    3
-]), [
+    3,
     4
-])).c;
+]).c;
 var v = o4 === null || o4 === void 0 ? void 0 : o4(incr);
 (ref12 = o5()) === null || ref12 === void 0 ? void 0 : ref12();
 // GH#36031
diff --git a/crates/swc/tests/tsc-references/callChain_es5.2.minified.js b/crates/swc/tests/tsc-references/callChain_es5.2.minified.js
index 45d6212bc5..8e606e2142 100644
--- a/crates/swc/tests/tsc-references/callChain_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/callChain_es5.2.minified.js
@@ -1,63 +1,56 @@
 var _o2, _o21, _o22, _o23, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, ref10, ref11, ref12;
-import * as swcHelpers from "@swc/helpers";
 null == o1 || o1(), null == o1 || o1(1), null == o1 || o1.apply(void 0, [
     1,
     2
 ]), null == o1 || o1.apply(void 0, [
     1,
     2,
-    3
+    3,
+    4
 ]), null == o2 || o2.b(), null == o2 || o2.b(1), null == o2 || (_o2 = o2).b.apply(_o2, [
     1,
     2
 ]), null == o2 || (_o21 = o2).b.apply(_o21, [
     1,
     2,
-    3
+    3,
+    4
 ]), null == o2 || o2.b(), null == o2 || o2.b(1), null == o2 || (_o22 = o2).b.apply(_o22, [
     1,
     2
 ]), null == o2 || (_o23 = o2).b.apply(_o23, [
     1,
     2,
-    3
+    3,
+    4
 ]), null === (ref = o3.b) || void 0 === ref || ref.call(o3).c, null === (ref1 = o3.b) || void 0 === ref1 || ref1.call(o3, 1).c, null === (ref2 = o3.b) || void 0 === ref2 || (_ref = ref2).call.apply(_ref, [
-    o3
-].concat(swcHelpers.toConsumableArray([
+    o3,
     1,
     2
-]))).c, null === (ref3 = o3.b) || void 0 === ref3 || (_ref1 = ref3).call.apply(_ref1, [
+]).c, null === (ref3 = o3.b) || void 0 === ref3 || (_ref1 = ref3).call.apply(_ref1, [
     o3,
-    1
-].concat(swcHelpers.toConsumableArray([
+    1,
     2,
-    3
-]), [
+    3,
     4
-])).c, null === (ref4 = o3.b) || void 0 === ref4 || ref4.call(o3).c, null === (ref5 = o3.b) || void 0 === ref5 || ref5.call(o3, 1).c, null === (ref6 = o3.b) || void 0 === ref6 || (_ref2 = ref6).call.apply(_ref2, [
-    o3
-].concat(swcHelpers.toConsumableArray([
+]).c, null === (ref4 = o3.b) || void 0 === ref4 || ref4.call(o3).c, null === (ref5 = o3.b) || void 0 === ref5 || ref5.call(o3, 1).c, null === (ref6 = o3.b) || void 0 === ref6 || (_ref2 = ref6).call.apply(_ref2, [
+    o3,
     1,
     2
-]))).c, null === (ref7 = o3.b) || void 0 === ref7 || (_ref3 = ref7).call.apply(_ref3, [
+]).c, null === (ref7 = o3.b) || void 0 === ref7 || (_ref3 = ref7).call.apply(_ref3, [
     o3,
-    1
-].concat(swcHelpers.toConsumableArray([
+    1,
     2,
-    3
-]), [
+    3,
     4
-])).c, null === (ref8 = o3.b) || void 0 === ref8 || ref8.call(o3).c, null === (ref9 = o3.b) || void 0 === ref9 || ref9.call(o3, 1).c, null === (ref10 = o3.b) || void 0 === ref10 || (_ref4 = ref10).call.apply(_ref4, [
-    o3
-].concat(swcHelpers.toConsumableArray([
+]).c, null === (ref8 = o3.b) || void 0 === ref8 || ref8.call(o3).c, null === (ref9 = o3.b) || void 0 === ref9 || ref9.call(o3, 1).c, null === (ref10 = o3.b) || void 0 === ref10 || (_ref4 = ref10).call.apply(_ref4, [
+    o3,
     1,
     2
-]))).c, null === (ref11 = o3.b) || void 0 === ref11 || (_ref5 = ref11).call.apply(_ref5, [
+]).c, null === (ref11 = o3.b) || void 0 === ref11 || (_ref5 = ref11).call.apply(_ref5, [
     o3,
-    1
-].concat(swcHelpers.toConsumableArray([
+    1,
     2,
-    3
-]), [
+    3,
     4
-])).c, null == o4 || o4(incr), null === (ref12 = o5()) || void 0 === ref12 || ref12(), null == o2 || o2.b().toString, null == o2 || o2.b().toString;
+]).c, null == o4 || o4(incr), null === (ref12 = o5()) || void 0 === ref12 || ref12(), null == o2 || o2.b().toString, null == o2 || o2.b().toString;
diff --git a/crates/swc/tests/tsc-references/callWithSpread4_es5.1.normal.js b/crates/swc/tests/tsc-references/callWithSpread4_es5.1.normal.js
index 0b28ecc8fe..0708e71e5e 100644
--- a/crates/swc/tests/tsc-references/callWithSpread4_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/callWithSpread4_es5.1.normal.js
@@ -8,8 +8,8 @@ pli.apply(void 0, [
     writes
 ]));
 test.apply(void 0, swcHelpers.toConsumableArray(anys));
-pli.apply(void 0, swcHelpers.toConsumableArray([
+pli.apply(void 0, [
     reads,
     writes,
     writes
-]));
+]);
diff --git a/crates/swc/tests/tsc-references/callWithSpread4_es5.2.minified.js b/crates/swc/tests/tsc-references/callWithSpread4_es5.2.minified.js
index 750f2ea31e..2e076a2abf 100644
--- a/crates/swc/tests/tsc-references/callWithSpread4_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/callWithSpread4_es5.2.minified.js
@@ -6,8 +6,8 @@ pli.apply(void 0, [
     fun
 ], swcHelpers.toConsumableArray(gz), [
     writes
-])), test.apply(void 0, swcHelpers.toConsumableArray(anys)), pli.apply(void 0, swcHelpers.toConsumableArray([
+])), test.apply(void 0, swcHelpers.toConsumableArray(anys)), pli.apply(void 0, [
     reads,
     writes,
     writes
-]));
+]);
diff --git a/crates/swc/tests/tsc-references/keyofAndIndexedAccess_es5.1.normal.js b/crates/swc/tests/tsc-references/keyofAndIndexedAccess_es5.1.normal.js
index b75cabe0e7..7060d003e7 100644
--- a/crates/swc/tests/tsc-references/keyofAndIndexedAccess_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/keyofAndIndexedAccess_es5.1.normal.js
@@ -391,11 +391,10 @@ function f1(thing) {
     var x2 = path(thing, "a", "y"); // string
     var x3 = path(thing, "b"); // boolean
     var x4 = path.apply(void 0, [
-        thing
-    ].concat(swcHelpers.toConsumableArray([
+        thing,
         "a",
         "x"
-    ]))); // any
+    ]); // any
 }
 // Repro from comment in #12114
 var assignTo2 = function(object, key1, key2) {
diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_spread.rs b/crates/swc_ecma_transforms_compat/tests/es2015_spread.rs
index f049d653a4..5e877a0b21 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2015_spread.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2015_spread.rs
@@ -886,3 +886,101 @@ foo("foo", "bar");
 
 "#
 );
+
+test!(
+    syntax(),
+    |_| tr(),
+    spread_string_literial,
+    "
+    String.raw({ raw: 'abcd' }, ...'___');
+    ",
+    r#"
+var _String;
+(_String = String).raw.apply(_String, [
+    {
+        raw: 'abcd'
+    }
+].concat(Array.from('___')));
+"#
+);
+
+test!(
+    syntax(),
+    |_| tr(),
+    spread_string_literial_2,
+    "
+    f({ x: 0 }, ...[1, 2], [3], ...'456');
+    ",
+    r#"
+f.apply(void 0, [
+    {
+        x: 0
+    }
+].concat(_toConsumableArray([
+    1,
+    2
+]), [
+    [
+        3
+    ]
+], Array.from('456')));
+"#
+);
+
+test!(
+    syntax(),
+    |_| tr(),
+    spread_literial,
+    r#"
+    f(1, ...[2, 3], ...[...[4, 5]], ...[6, ...[7]]);
+    f(1, ..."123", ...[..."456", ..."789"]);
+    "#,
+    r#"
+f.apply(void 0, [
+    1
+].concat(_toConsumableArray([
+    2,
+    3
+]), _toConsumableArray(_toConsumableArray([
+    4,
+    5
+])), _toConsumableArray([
+    6
+].concat(_toConsumableArray([
+    7
+])))));
+f.apply(void 0, [
+    1
+].concat(Array.from("123"), _toConsumableArray(Array.from("456").concat(Array.from("789")))));
+"#
+);
+
+test!(
+    syntax(),
+    |_| tr(),
+    spread_literial_init_hole,
+    r#"
+    f(1, ...[2, , 3], ...[...[4, ,]]);
+    f(...[2, , 3], ...[...[4, ,]]);
+    "#,
+    "
+    f.apply(void 0, [
+        1
+    ].concat(_toConsumableArray([
+        2,
+        ,
+        3
+    ]), _toConsumableArray(_toConsumableArray([
+        4,
+        ,
+    ]))));
+    f.apply(void 0, _toConsumableArray([
+        2,
+        ,
+        3
+    ]).concat(_toConsumableArray(_toConsumableArray([
+        4,
+        ,
+    ]))));
+    "
+);