Repository: swc-project/swc (rust)

Issue:
swc does not remove unused arguments during minification
### Describe the bug

with `keep_fargs = false, unused = true` during minifcation unused function parameters are not removed.


### Input code

```typescript
export function foo(cb) {
    cb();
}

foo((a, b) => true);
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es2015",
    "loose": false,
    "minify": {
      "compress": {
        "keep_fargs": false
      },
      "mangle": {
      }
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.3.100&code=H4sIAAAAAAAAA0utKMgvKlFIK81LLsnMz1NIy8%2FXSE7SVKjmUgCC5CQNTWuuWi4ukLBGoo4CUMbWTqGkqDQVKA4AqXoOfDwAAAA%3D&config=H4sIAAAAAAAAA1WOSw7CIBCG9z1FM2sXaqIL7%2BAZDKHThloeYSCxabi7U1oqbgjz%2FY%2BZpWlbGEnCo134y4MTntAfMxOaTRAfJoBSC5JeuQCnoo60Sr2YCDNKmwJB%2BAFDTtH1fLntCZisJSyJnWllVD%2FXO6XVziNRxZi%2BEd2r52L6L8hiNJGwYyH4iDtOx5lamGHCqi415c0m0LaLlQHC7HC7%2Fg4%2FUzl03ZGJomcJ5r3pC5WMvgRSAQAA

### SWC Info output

_No response_

### Expected behavior

SWC should output more minified code.

```js
// terser
export function foo(o){o()}foo((()=>!0));

// swc
export function foo(o){o()}foo((o,f)=>!0);
```

It's worth noting that when there is 1 arg - SWC output is sightly smaller:
```js
// terser
export function foo(o){o()}foo((()=>!0));

// swc
export function foo(o){o()}foo(o=>!0);
```

Note: I have no idea why terser wraps the arrow in parentheses.


### Actual behavior

_No response_

### Version

1.4.0

### Additional context

_No response_

Pull request:
feat(es/minifier): Remove unused parameters of arrow functions
**Description:**


**Related issue:**

 - Closes #8626

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
index 3226a31f81..f9e1a71469 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
@@ -1454,6 +1454,8 @@ impl VisitMut for Optimizer<'_> {
 
     #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
     fn visit_mut_arrow_expr(&mut self, n: &mut ArrowExpr) {
+        self.drop_unused_arrow_params(&mut n.params);
+
         let prepend = self.prepend_stmts.take();
 
         let ctx = self.ctx;
@@ -1685,9 +1687,7 @@ impl VisitMut for Optimizer<'_> {
         match n {
             DefaultDecl::Class(_) => {}
             DefaultDecl::Fn(f) => {
-                if !self.options.keep_fargs && self.options.unused {
-                    self.drop_unused_params(&mut f.function.params);
-                }
+                self.drop_unused_params(&mut f.function.params);
             }
             DefaultDecl::TsInterfaceDecl(_) => {}
         }
@@ -1708,9 +1708,7 @@ impl VisitMut for Optimizer<'_> {
     #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
     fn visit_mut_export_decl(&mut self, n: &mut ExportDecl) {
         if let Decl::Fn(f) = &mut n.decl {
-            if !self.options.keep_fargs && self.options.unused {
-                self.drop_unused_params(&mut f.function.params);
-            }
+            self.drop_unused_params(&mut f.function.params);
         }
 
         let ctx = Ctx {
@@ -2027,9 +2025,7 @@ impl VisitMut for Optimizer<'_> {
             .entry(f.ident.to_id())
             .or_insert_with(|| FnMetadata::from(&*f.function));
 
-        if !self.options.keep_fargs && self.options.unused {
-            self.drop_unused_params(&mut f.function.params);
-        }
+        self.drop_unused_params(&mut f.function.params);
 
         let ctx = Ctx {
             top_level: false,
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
index 19c5a952f0..575fbb414d 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs
@@ -132,13 +132,36 @@ impl Optimizer<'_> {
 
     #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
     pub(super) fn drop_unused_params(&mut self, params: &mut Vec<Param>) {
+        if self.options.keep_fargs || !self.options.unused {
+            return;
+        }
+
         for param in params.iter_mut().rev() {
             self.take_pat_if_unused(&mut param.pat, None, false);
 
             if !param.pat.is_invalid() {
-                return;
+                break;
+            }
+        }
+
+        params.retain(|p| !p.pat.is_invalid());
+    }
+
+    #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
+    pub(super) fn drop_unused_arrow_params(&mut self, params: &mut Vec<Pat>) {
+        if self.options.keep_fargs || !self.options.unused {
+            return;
+        }
+
+        for param in params.iter_mut().rev() {
+            self.take_pat_if_unused(param, None, false);
+
+            if !param.is_invalid() {
+                break;
             }
         }
+
+        params.retain(|p| !p.is_invalid());
     }
 
     #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/3126/1/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/3126/1/output.js
index 7c9c480ebb..86b77a9dcb 100644
--- a/crates/swc_ecma_minifier/tests/fixture/issues/3126/1/output.js
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/3126/1/output.js
@@ -1,12 +1,12 @@
 (()=>{
     var __webpack_modules__ = {
-        746: (__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__1)=>{
+        746: ()=>{
             Object.prototype.hasOwnProperty;
         }
     };
     __webpack_require__.m = __webpack_modules__;
     (()=>{
-        __webpack_require__.O = (result, chunkIds, fn, priority)=>{
+        __webpack_require__.O = (result, chunkIds)=>{
             for(var j = 0; j < chunkIds.length; j++)Object.keys(__webpack_require__.O).every((key)=>__webpack_require__.O[key](chunkIds[j]));
         };
     })();
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8626/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/8626/config.json
new file mode 100644
index 0000000000..a67b77ab4d
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8626/config.json
@@ -0,0 +1,4 @@
+{
+    "defaults": true,
+    "keep_fargs": false
+}
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8626/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8626/input.js
new file mode 100644
index 0000000000..996b5e1759
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8626/input.js
@@ -0,0 +1,5 @@
+export function foo(cb) {
+    cb();
+}
+
+foo((a, b) => true);
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8626/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8626/output.js
new file mode 100644
index 0000000000..91d6657c93
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8626/output.js
@@ -0,0 +1,4 @@
+export function foo(cb) {
+    cb();
+}
+foo(()=>!0);