Repository: swc-project/swc (rust)

Issue:
The minify method of @swc/core changes the order in which statements are executed
### Describe the bug

Using `minify`'s `compress` default configuration produces the wrong code order. Take the following example code
```js
import { minify } from '@swc/core'

async function testOrder() {
  const { code } = await minify(
    `
    function toFixed(value, maxDecimals, roundingFunction, optionals) {
      var splitValue = value.toString().split('.'),
          minDecimals = maxDecimals - (optionals || 0),
          
          optionalsRegExp,
          power,
          output;
      var boundedPrecisions;
      // var unused = 'xxxx';

      // Use the smallest precision value possible to avoid errors from floating point representation
      if (splitValue.length === 2) {
        boundedPrecisions = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals);
      } else {
        boundedPrecisions = minDecimals;
      }

      power = Math.pow(10, boundedPrecisions);

      // Multiply up by precision, round accurately, then divide and use native toFixed():
      output = (roundingFunction(value + 'e+' + boundedPrecisions) / power).toFixed(boundedPrecisions);

      if (optionals > maxDecimals - boundedPrecisions) {
          optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecisions)) + '}$');
          output = output.replace(optionalsRegExp, '');
      }

      return output;
  }
  toFixed(1.2345, 2, Math.round, 1);
    `,
    {
      compress: true,
      mangle: false,
    }
  )

  console.log(code)
}

testOrder()
```
The following code is generated，the `boundedPrecisions` variable is used before assignment, resulting in inconsistency with the original code logic
```js
function toFixed(value, maxDecimals, roundingFunction, optionals) {
  var boundedPrecisions,
    optionalsRegExp,
    output,
    splitValue = value.toString().split('.'),
    minDecimals = maxDecimals - (optionals || 0)
  return (
    (output = (
      roundingFunction(value + 'e+' + boundedPrecisions) /
      Math.pow(
        10,
        (boundedPrecisions =
          2 === splitValue.length
            ? Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals)
            : minDecimals)
      )
    ).toFixed(boundedPrecisions)),
    optionals > maxDecimals - boundedPrecisions &&
      ((optionalsRegExp = RegExp(
        '.?0{1,' + (optionals - (maxDecimals - boundedPrecisions)) + '}$'
      )),
      (output = output.replace(optionalsRegExp, ''))),
    output
  )
}
toFixed(1.2345, 2, Math.round, 1)
```
If both `defaults` and `collapse_vars` are set to `false`, the result is correct. But only `collapse_vars` is set to `false`, the generated code in the wrong order, which is a bit odd.
```js
compress: {
        defaults: false,
        arrows: true,
        booleans: true,
        // 会改变代码执行顺序
        collapse_vars: false,
        comparisons: true,
        computed_props: true,
        conditionals: true,
        dead_code: true,
        directives: true,
        drop_debugger: true,
        evaluate: true,
        hoist_props: true,
        if_return: true,
        join_vars: true,
        keep_fargs: true,
        loops: true,
        negate_iife: true,
        properties: true,
        side_effects: true,
        switches: true,
        typeofs: true,
        unused: true,
        const_to_let: true,
        pristine_globals: true
      },
      mangle: false,
    }
```
Using @swc/cli command line does not seem to have this problem and playground doesn't seem to reproduce the problem either.
Upgrade from @swc/core@1.3.93 to @swc/core@1.3.101 to find this problem, 1.3.93 is correct.



### Input code

_No response_

### Config

_No response_

### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.3.101&code=H4sIAAAAAAAAA4VTTY%2FTMBC991fMASmO1qRNgQtV4QJ7WwmB4MJycJNpa8m1I39kW3X3vzPOd1st5JI4M%2FPmzZvnbdCFl0aDN%2FfyiCWrhQrI4SCOX7CQB6EcB2uCLqXe3XfJHEwV3xRM4TyD5qmFBVcp6X9FBFhDg5R588NbqmVp1kRZkiUp72ric5C670RFk77wFtjQB56fYXFRt4mksPxmKd1FUpPYUPYdd1%2BP1TRUmSe0F7nBV8GvZt2v%2BRx%2BOgS%2FR3DEQqHzUPU92pkIwzm5UZRlQNRGloDWGutga80BtsoITxNTmtQeLFK5Q%2B1F5NR1kVtgo1iZQr3ze1iv17AcFb2dkQR6EH6fkWas%2FRDHCc7v%2FE8HxaeyphfrTFcd%2FAugokn%2F2W2CMpT1SjVK9ozowPIFv8FIp8I%2BBOVlpU4QKticRlk7h4EoimCFR3XicQEaSlnLEkFQLBBVTRLWOFg1%2FTibrpCosGuntnaGO0jwLqHXDT2Yt3OkWY%2F6%2BgRxaaMhP1159Rb6%2FLojiavGJ2gPLHl8zD4vzjmPFCct6AL8p0caR3t5kwxLvZCj%2FcjIgUoUyK5vBSRj3bBViz5YPdyKGIFB8Txbvnv%2FgcOSt1tv5OaQp6u%2FxAQRq0gEAAA%3D&config=H4sIAAAAAAAAA32US3LbMAyG9zmFR%2Bts20UPkF3PwKFJUKbDh4YAHWsyvnshSrLdGNJOwocfIAEQ32%2BHQ3dG0%2F05fPMn%2Fwy6IJT7P1twTKSvbOnARI2m%2BIG695WecUJOB4Rmus2kI116oKbCX4t7F3JGWN0XW%2FTJu%2FE5oclxKID4ZGMrx6sREuH%2F%2BoWV%2FDUBKvXZfsw5gE47RGlUPhH0UKTAJoegBwR10UWIMp1UF49ZSjHBSmDVUPIg8mQ9%2BZw45yu1oK0y2YKAfAFD%2FgKSjHOxLCFfT7hPwxaOte9bk3%2Bo4aJD1STkhGtrCZ9WiHrKHkm5mqQSznCjBjNcivtT6Z0qQLWkV905%2B7TRk08ArkDQiElHkOI2D8fztKV2u0qfHI8sjQLn%2BZZumaDnoirvnVDZqTJQyEvdLGCrgamyRjrOgjfKh96CAud4VoTQ%2BOXJnKSkNA6QnQC4v9pJUzUDdX%2BFG3x6EDv4g29J8oAtHlHTaZviGI857CSIQKdsdxy4FZS3ceEtcR22eU0WeDTAii4VG3hdAvwAKKvQluXLbPDz4IiqD%2Fn4WBOLw%2B2%2BhKNO%2FeO9z3v4bXHoYra1wWXDT%2F2d9%2FLv7uG0buH1BJ3Hv6uw5bz9A1EN%2Bh8sBgAA

### SWC Info output

_No response_

### Expected behavior

The code logic remains unchanged after compression

### Actual behavior

_No response_

### Version

1.3.101

### Additional context

_No response_

Pull request:
fix(es): Apply `paren_remover` for minify
<!-- 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):**
Closes #8437

Patch:
diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs
index c70276c530..0c3c701656 100644
--- a/crates/swc/src/lib.rs
+++ b/crates/swc/src/lib.rs
@@ -147,6 +147,7 @@ use swc_ecma_transforms::{
     pass::noop,
     resolver,
 };
+use swc_ecma_transforms_base::fixer::paren_remover;
 use swc_ecma_visit::{FoldWith, VisitMutWith, VisitWith};
 pub use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
 pub use swc_node_comments::SwcComments;
@@ -865,6 +866,8 @@ impl Compiler {
             let is_mangler_enabled = min_opts.mangle.is_some();
 
             let module = self.run_transform(handler, false, || {
+                let module = module.fold_with(&mut paren_remover(Some(&comments)));
+
                 let module =
                     module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false));
 


Test patch:
diff --git a/node-swc/__tests__/minify/issue_8437_test.mjs b/node-swc/__tests__/minify/issue_8437_test.mjs
new file mode 100644
index 0000000000..98a7ad12df
--- /dev/null
+++ b/node-swc/__tests__/minify/issue_8437_test.mjs
@@ -0,0 +1,61 @@
+import swc from "../../..";
+
+it("should output same result", async () => {
+    const origin = `
+function toFixed(value, maxDecimals, roundingFunction, optionals) {
+    var splitValue = value.toString().split('.'),
+        minDecimals = maxDecimals - (optionals || 0),
+          
+        optionalsRegExp,
+        power,
+        output;
+    var boundedPrecisions;
+    // var unused = 'xxxx';
+
+    // Use the smallest precision value possible to avoid errors from floating point representation
+    if (splitValue.length === 2) {
+      boundedPrecisions = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals);
+    } else {
+      boundedPrecisions = minDecimals;
+    }
+
+    power = Math.pow(10, boundedPrecisions);
+
+    // Multiply up by precision, round accurately, then divide and use native toFixed():
+    output = (roundingFunction(value + 'e+' + boundedPrecisions) / power).toFixed(boundedPrecisions);
+
+    if (optionals > maxDecimals - boundedPrecisions) {
+        optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecisions)) + '}$');
+        output = output.replace(optionalsRegExp, '');
+    }
+
+    return output;
+}
+toFixed(1.2345, 2, Math.round, 1);
+    `
+
+    async function minify() {
+        const { code } = await swc.minify(origin, {
+            compress: true,
+            mangle: false
+          });
+        return code;
+    }
+
+    async function transform() {
+        const { code } = await swc.transform(origin, {
+          jsc: {
+            minify: {
+                compress: true,
+                mangle: false
+            },
+          },
+          isModule: false,
+          minify: true
+        });
+        return code;
+    }
+
+    const [minifyResult, transformResult] = await Promise.all([minify(), transform()]);
+    expect(minifyResult).toEqual(transformResult);
+});
\ No newline at end of file