Repository: swc-project/swc (rust)

Issue:
Loose transpilation of spreads to ES5 is incorrect
### Describe the bug

Loose transpilation of spreads generates an `[].concat(...)` that is incorrect under some circumstances.

### Input code

```typescript
const sequence = [
    ...[['foo', 123 ]],
    ['bar', 456],
];
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": true,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```

### Playground link

https://play.swc.rs/?version=1.3.73&code=H4sIAAAAAAAAA0vOzysuUShOLSxNzUtOVbBViOZSAAI9Pb3oaPW0%2FHx1HQVDI2OF2FgdsHi0elJiEVDMxNQMKBJrDQBDgoERPwAAAA%3D%3D&config=H4sIAAAAAAAAA1WPPQ7DIAxGd06BPHdth94hh7CoExHxJ0ykoih3LyFA2w2%2Fz%2FYzu5ASVlbwlHt5liJgZIqjLoSzS%2FguBEhZZBV1SHDr6cpnNKNhqui4EkgYF0p1iu%2BtHYz3TIWluFFDVjs951%2Bf8jZEYu5rh8qiWwz920QzgvWvrYbtHykHuuwP%2BDZ12VgMmqc%2BeV4ljg%2F3WmjeEwEAAA%3D%3D

### Expected behavior

```js
> sequence
[ [ 'foo', 123 ], [ 'bar', 456 ] ]
```

### Actual behavior

```js
> sequence
[ [ 'foo', 123 ], 'bar', 456 ]
```

### Version

1.3.73

### Additional context

This is due to Array.prototype.concat behaving differently for Array or Array-like arguments than for all other arguments.  See the documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat#description.

The fix would then seem to be to wrap all such arguments to `concat` in an extra Array.

Pull request description:
fix(es/compat): Fix loose mode of the `spread` pass
As per the comments, we have to carefully nest non-spread arguments in arrays before passing them to Array.prototype.concat. This is the same approach babel takes in loose array spread transpilation.

Fixes #7736.

Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/es2015/spread.rs b/crates/swc_ecma_transforms_compat/src/es2015/spread.rs
index 3ea792b216..7f05f61907 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/spread.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/spread.rs
@@ -251,12 +251,45 @@ impl Spread {
             };
         }
 
-        // shorthand [].concat(arr1, arr2) shoule be used under loose mode
+        // Shorthand [].concat(arr1, arr2) should be used under loose mode.
+        // Array.prototype.concat has the lovely feature by which arrays passed
+        // to it are depth-1 flattened. This effectively implements the loose
+        // spread. But if any arrays that are not being spread are present, they
+        // will be incorrectly flattened. The solution is to wrap every
+        // contiguous slice of non-spread args in an array, which will protect
+        // array args from being flattened.
         if self.c.loose {
             let mut arg_list = vec![];
+            let mut current_elems = vec![];
             for arg in args.flatten() {
                 let expr = arg.expr;
-                arg_list.push(expr.as_arg());
+                match arg.spread {
+                    Some(_) => {
+                        if !current_elems.is_empty() {
+                            arg_list.push(
+                                ArrayLit {
+                                    span: DUMMY_SP,
+                                    elems: current_elems,
+                                }
+                                .as_arg(),
+                            );
+                            current_elems = vec![];
+                        }
+                        arg_list.push(expr.as_arg());
+                    }
+                    None => {
+                        current_elems.push(Some(expr.as_arg()));
+                    }
+                }
+            }
+            if !current_elems.is_empty() {
+                arg_list.push(
+                    ArrayLit {
+                        span: DUMMY_SP,
+                        elems: current_elems,
+                    }
+                    .as_arg(),
+                );
             }
 
             return Expr::Call(CallExpr {


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7736/case1/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7736/case1/input/.swcrc
new file mode 100644
index 0000000000..0777dc5844
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7736/case1/input/.swcrc
@@ -0,0 +1,11 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": false
+        },
+        "target": "es5",
+        "loose": true
+    },
+    "isModule": false
+}
diff --git a/crates/swc/tests/fixture/issues-7xxx/7736/case1/input/index.js b/crates/swc/tests/fixture/issues-7xxx/7736/case1/input/index.js
new file mode 100644
index 0000000000..37961fdfa7
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7736/case1/input/index.js
@@ -0,0 +1,11 @@
+[
+  1,
+  2,
+  ...['foo', 3],
+  4,
+  ...['bar', 5],
+  ['baz', 6],
+  7,
+  8,
+  9,
+]
diff --git a/crates/swc/tests/fixture/issues-7xxx/7736/case1/output/index.js b/crates/swc/tests/fixture/issues-7xxx/7736/case1/output/index.js
new file mode 100644
index 0000000000..46ecfe07e5
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7736/case1/output/index.js
@@ -0,0 +1,20 @@
+[].concat([
+    1,
+    2
+], [
+    "foo",
+    3
+], [
+    4
+], [
+    "bar",
+    5
+], [
+    [
+        "baz",
+        6
+    ],
+    7,
+    8,
+    9
+]);