Repository: swc-project/swc (rust)

Issue:
Minifying multiple references to variable pointing at hoisted function breaks
### Describe the bug

When a variable references a function before it is defined, and that variable is referenced more than once, the first reference is inlined and the rest reference the original function that no longer exists causing runtime errors. If the same variable is defined after the function instead of before, it works correctly. Seems related to function hoisting.

### Input code

```typescript
var x = someFunction;

function someFunction() {
    return 2;
}

console.log(x);
console.log(x);
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": true,
      "mangle": true
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": true,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.3.66&code=H4sIAAAAAAAAAytLLFKoULBVKM7PTXUrzUsuyczPs%2BbiSoMyUcQ1NBWquRSAoCi1pLQoT8HImquWiys5P684PydVLyc%2FXaNC0xqDDwAqOu8dYgAAAA%3D%3D&config=H4sIAAAAAAAAA42VS47bMAxA93OKwOsu2kWLogformcQFItylMqiIVKZBIPcvbRsZzJj2ugmiPn4ESmSens5HJoztc2vw5v8lY%2FBZoL8%2BBYJ3RLbq0gaaHtLbQ4DN18WeqYReRsJqug%2BkYZt7oCrFX2f1ZuISLCoz7I%2BpOBvzwFb7IcMRE8ykYq%2F0kNi%2Bmg%2Fs4yvI%2BBcnuVHxAg27RBjyYTE0EHWHLcYox0IzMVmxct4UpsDoRZihIXBmSHjoPLkAgdMEnNNHVhnWnSgoJCh5XABzUxiiVkiSU%2FJp2IHx9J19ZI%2FWcPFxmJZiQnXeiVyWsXrCQOx8SVpJZzgRg0mOBf3s2XwJgOXnNZ2Zwxp407%2BAkgFoiVKtgfNb9Xw0k9b1n7XMiQvLcs3hUt%2Fa1km6KSoJgSvVHasDGQO2m1mcKWFsbKtdpwZb5SPggMD3kuvKK7pNXB70oLybQD0CpD7tV7rqgmYxxRu8HEgdvBvyZL1Bps1esunbUq3%2FohxJ0APfEK3oyBXwbiNs2yJ67DNS3IgrQFOVSlUwXoJyAAwmliX5ao3ZDzEo%2BkiHt%2FXxKxwfyzh3qauzvvTvmQcIlwgbrXxf4zILpWzXcauXiZ7Nb3wU2tKK4MXvn398GZIKi%2FLb02q6dGVp4RqT05vyY%2FmXWl5OZaqNYH%2BLIa1Tvd%2FDWFcTeAGAAA%3D

### Expected behavior

```js
var x=function(){return 2};console.log(x),console.log(x);
```

It should reference the same function in both instances rather than inlining. This is the output that is produced when `var x = someFunction` is defined after `someFunction` rather than before.

### Actual behavior

```js
console.log(function(){return 2}),console.log(someFunction);
```

This inlines `someFunction` in the first reference, but still references `someFunction` in the second (which doesn't exist anymore).

### Version

1.3.66

### Additional context

_No response_

Pull request:
fix(es/minifier): Add usage to inlined ident eagerly
<!-- 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:**
Closes #7591 

**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):**


Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs
index c476858020..0494684f0a 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs
@@ -96,6 +96,8 @@ impl Optimizer<'_> {
 
             self.vars.inline_with_multi_replacer(init);
 
+            let id = ident.to_id();
+
             // We inline arrays partially if it's pure (all elements are literal), and not
             // modified.
             // We don't drop definition, but we just inline array accesses with numeric
@@ -243,7 +245,17 @@ impl Optimizer<'_> {
                     _ => false,
                 }
             {
-                self.mode.store(ident.to_id(), &*init);
+                self.mode.store(id.clone(), &*init);
+
+                let usage_count = usage.usage_count;
+                let mut inc_usage = || {
+                    if let Expr::Ident(i) = &*init {
+                        if let Some(u) = self.data.vars.get_mut(&i.to_id()) {
+                            u.ref_count += ref_count;
+                            u.usage_count += usage_count;
+                        }
+                    }
+                };
 
                 if self.options.inline != 0
                     && !should_preserve
@@ -264,7 +276,9 @@ impl Optimizer<'_> {
                     //     var.span = var.span.apply_mark(self.marks.non_top_level);
                     // }
 
-                    self.vars.lits.insert(ident.to_id(), init.take().into());
+                    inc_usage();
+
+                    self.vars.lits.insert(id.clone(), init.take().into());
 
                     ident.take();
                 } else if self.options.inline != 0 || self.options.reduce_vars {
@@ -274,12 +288,16 @@ impl Optimizer<'_> {
                         ident.span.ctxt
                     );
 
-                    self.mode.store(ident.to_id(), &*init);
+                    self.mode.store(id.clone(), &*init);
 
-                    self.vars.lits.insert(ident.to_id(), init.clone().into());
+                    inc_usage();
+
+                    self.vars.lits.insert(id.clone(), init.clone().into());
                 }
             }
 
+            let usage = self.data.vars.get(&id).unwrap();
+
             // Single use => inlined
             if !self.ctx.is_exported
                 && is_inline_enabled
@@ -760,13 +778,6 @@ impl Optimizer<'_> {
                     self.changed = true;
                     report_change!("inline: Replacing a variable `{}` with cheap expression", i);
 
-                    if let Expr::Ident(i) = &*value {
-                        if let Some(usage) = self.data.vars.get_mut(&i.to_id()) {
-                            usage.ref_count += 1;
-                            usage.usage_count += 1;
-                        }
-                    }
-
                     *e = *value;
                     return;
                 }


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7591/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/7591/input.js
new file mode 100644
index 0000000000..52537ed61a
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/7591/input.js
@@ -0,0 +1,8 @@
+var x = someFunction;
+
+function someFunction() {
+    return 2;
+}
+
+console.log(x);
+console.log(x);
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7591/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/7591/output.js
new file mode 100644
index 0000000000..6d5b64765d
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/7591/output.js
@@ -0,0 +1,4 @@
+function someFunction() {
+    return 2;
+}
+console.log(someFunction), console.log(someFunction);