Repository: swc-project/swc (rust)

Issue:
Can't resolve 'core-js/modules/es.string.at.js'
### Describe the bug

The output code contains `require("core-js/modules/es.string.at.js");`, but the path is not valid for `core-js` `3.21.1`

### Input code

```typescript
console.log([].at(0))
```


### Config

```json
{
  "$schema": "https://json.schemastore.org/swcrc.json",
  "jsc": {
    "parser": {
      "syntax": "typescript"
    }
  },
  "module": {
    "type": "commonjs"
  },
  "env": {
    "targets": "chrome >= 88",
    "mode": "usage",
    "coreJs": 3
  }
}
```


### Playground link

https://play.swc.rs/?version=1.2.160&code=H4sIAAAAAAAAA0vOzyvOz0nVy8lP14iO1Uss0TDQ1AQAUVJaSBUAAAA%3D&config=H4sIAAAAAAAAAyXOwQrDMAgG4FcpsmNpDruUwPYAe4vgpF1ZYlC7tZS8%2B5Lupn6%2F4gEXxZliAA%2BzWVbv3KKchv9UjYUGlsnpFwWHRtDDogj%2BgBxESVqle7Kw1Ru2Z1KUVzYopYfIz%2FVNLdGgOnKMnBaFipQ%2BpwSZyLThLBypu9%2B6cYRzua2sGiaqLdZXHjV2LeUH7dPf07YAAAA%3D

### Expected behavior

should polyfill using the path `esnext.string.at.js`

### Actual behavior

Using `es.string.at.js` which does not exist

### Version

1.2.160

### Additional context

_No response_

Pull request description:
fix(es/preset-env): do not import new features when using old core-js
<!-- 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 #4223 

Patch:
diff --git a/crates/swc_ecma_preset_env/src/corejs3/data.rs b/crates/swc_ecma_preset_env/src/corejs3/data.rs
index 9c3c44e9a7..bb4ccceb2f 100644
--- a/crates/swc_ecma_preset_env/src/corejs3/data.rs
+++ b/crates/swc_ecma_preset_env/src/corejs3/data.rs
@@ -1,3 +1,7 @@
+use once_cell::sync::Lazy;
+use preset_env_base::version::Version;
+use swc_common::collections::AHashMap;
+
 use crate::util::{DataMap, FeatureMap};
 
 pub static COMMON_ITERATORS: &[&str] = &[
@@ -468,7 +472,8 @@ pub static BUILTINS: DataMap<&[&str]> = data_map!(Map {
 });
 
 pub static INSTANCE_PROPERTIES: DataMap<&[&str]> = data_map!(Map {
-    at: ["es.string.at"],
+    // TODO: check type of variable
+    at: ["es.string.at-alternative", "es.array.at"],
     anchor: ["es.string.anchor"],
     big: ["es.string.big"],
     bind: ["es.function.bind"],
@@ -924,3 +929,17 @@ pub static STATIC_PROPERTIES: DataMap<DataMap<&[&str]>> = data_map!(Map {
 //];
 
 pub static POSSIBLE_GLOBAL_OBJECTS: &[&str] = &["global", "globalThis", "self", "window"];
+
+pub static MODULES_BY_VERSION: Lazy<AHashMap<&'static str, Version>> = Lazy::new(|| {
+    serde_json::from_str::<AHashMap<_, _>>(include_str!(
+        "../../data/core-js-compat/modules-by-versions.json"
+    ))
+    .expect("failed to parse modules-by-versions.json")
+    .into_iter()
+    .flat_map(|(k, v): (Version, Vec<String>)| {
+        v.into_iter()
+            .map(|s: String| (&*Box::leak(s.into_boxed_str()), k))
+            .collect::<Vec<_>>()
+    })
+    .collect()
+});
diff --git a/crates/swc_ecma_preset_env/src/corejs3/entry.rs b/crates/swc_ecma_preset_env/src/corejs3/entry.rs
index 66ec9a5c51..5ab6adc99c 100644
--- a/crates/swc_ecma_preset_env/src/corejs3/entry.rs
+++ b/crates/swc_ecma_preset_env/src/corejs3/entry.rs
@@ -9,7 +9,7 @@ use swc_common::{collections::AHashMap, DUMMY_SP};
 use swc_ecma_ast::*;
 use swc_ecma_visit::{Fold, FoldWith};
 
-use super::compat::DATA as CORE_JS_COMPAT_DATA;
+use super::{compat::DATA as CORE_JS_COMPAT_DATA, data::MODULES_BY_VERSION};
 
 static ENTRIES: Lazy<AHashMap<String, Vec<&'static str>>> = Lazy::new(|| {
     serde_json::from_str::<AHashMap<String, Vec<String>>>(include_str!(
@@ -28,23 +28,6 @@ static ENTRIES: Lazy<AHashMap<String, Vec<&'static str>>> = Lazy::new(|| {
     .collect()
 });
 
-static MODULES_BY_VERSION: Lazy<AHashMap<Version, Vec<&'static str>>> = Lazy::new(|| {
-    serde_json::from_str::<AHashMap<_, _>>(include_str!(
-        "../../data/core-js-compat/modules-by-versions.json"
-    ))
-    .expect("failed to parse modules-by-versions.json")
-    .into_iter()
-    .map(|(k, v): (Version, Vec<String>)| {
-        (
-            k,
-            v.into_iter()
-                .map(|s: String| &*Box::leak(s.into_boxed_str()))
-                .collect::<Vec<_>>(),
-        )
-    })
-    .collect()
-});
-
 #[derive(Debug)]
 pub struct Entry {
     is_any_target: bool,
@@ -94,15 +77,8 @@ impl Entry {
                     }
                 }
 
-                //                println!("{} -> {}", src, f);
-
-                for (_, features) in MODULES_BY_VERSION
-                    .iter()
-                    .filter(|(version, _features)| *corejs_version < **version)
-                {
-                    if features.contains(*f) {
-                        return false;
-                    }
+                if let Some(version) = MODULES_BY_VERSION.get(**f) {
+                    return version <= corejs_version;
                 }
 
                 true
diff --git a/crates/swc_ecma_preset_env/src/corejs3/usage.rs b/crates/swc_ecma_preset_env/src/corejs3/usage.rs
index 209e7687ee..129737d9f7 100644
--- a/crates/swc_ecma_preset_env/src/corejs3/usage.rs
+++ b/crates/swc_ecma_preset_env/src/corejs3/usage.rs
@@ -1,11 +1,11 @@
 use indexmap::IndexSet;
-use preset_env_base::version::should_enable;
+use preset_env_base::version::{should_enable, Version};
 use swc_atoms::{js_word, JsWord};
 use swc_common::DUMMY_SP;
 use swc_ecma_ast::*;
 use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
 
-use super::data::BUILTINS;
+use super::data::{BUILTINS, MODULES_BY_VERSION};
 use crate::{
     corejs3::{
         compat::DATA as CORE_JS_COMPAT_DATA,
@@ -22,11 +22,12 @@ pub(crate) struct UsageVisitor {
     shipped_proposals: bool,
     is_any_target: bool,
     target: Versions,
+    corejs_version: Version,
     pub required: IndexSet<&'static str, ahash::RandomState>,
 }
 
 impl UsageVisitor {
-    pub fn new(target: Versions, shipped_proposals: bool) -> Self {
+    pub fn new(target: Versions, shipped_proposals: bool, corejs_version: Version) -> Self {
         //        let mut v = Self { required: vec![] };
         //
         //
@@ -50,6 +51,7 @@ impl UsageVisitor {
             shipped_proposals,
             is_any_target: target.is_any_target(),
             target,
+            corejs_version,
             required: Default::default(),
         }
     }
@@ -60,6 +62,7 @@ impl UsageVisitor {
             shipped_proposals,
             is_any_target,
             target,
+            corejs_version,
             ..
         } = self;
 
@@ -78,6 +81,10 @@ impl UsageVisitor {
                 }
             }
 
+            if let Some(version) = MODULES_BY_VERSION.get(**f) {
+                return version <= corejs_version;
+            }
+
             true
         }));
     }
diff --git a/crates/swc_ecma_preset_env/src/lib.rs b/crates/swc_ecma_preset_env/src/lib.rs
index 97b06a8d02..05d2c782b8 100644
--- a/crates/swc_ecma_preset_env/src/lib.rs
+++ b/crates/swc_ecma_preset_env/src/lib.rs
@@ -339,8 +339,11 @@ impl Fold for Polyfills {
                         v.required
                     }
                     Version { major: 3, .. } => {
-                        let mut v =
-                            corejs3::UsageVisitor::new(self.targets, self.shipped_proposals);
+                        let mut v = corejs3::UsageVisitor::new(
+                            self.targets,
+                            self.shipped_proposals,
+                            self.corejs,
+                        );
                         m.visit_with(&mut v);
                         v.required
                     }


Test patch:
diff --git a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-all/output.mjs b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-all/output.mjs
index 731e0b6700..2d47968f4b 100644
--- a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-all/output.mjs
+++ b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-all/output.mjs
@@ -1,6 +1,5 @@
 import "core-js/modules/es.array.from.js";
 import "core-js/modules/es.array.iterator.js";
-import "core-js/modules/es.global-this.js";
 import "core-js/modules/es.map.js";
 import "core-js/modules/es.object.to-string.js";
 import "core-js/modules/es.promise.js";
diff --git a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/options.json b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/options.json
index c7f8e1de84..36e62227d7 100644
--- a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/options.json
+++ b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/options.json
@@ -4,7 +4,7 @@
       "../../../../lib",
       {
         "useBuiltIns": "usage",
-        "corejs": 3,
+        "corejs": "3.21",
         "modules": false
       }
     ]
diff --git a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/output.mjs b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/output.mjs
index ed6926d8fd..a5ca435553 100644
--- a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/output.mjs
+++ b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-finished-proposal/output.mjs
@@ -1,9 +1,10 @@
+import "core-js/modules/es.array.at.js";
 import "core-js/modules/es.array.iterator.js";
 import "core-js/modules/es.global-this.js";
 import "core-js/modules/es.object.to-string.js";
 import "core-js/modules/es.promise.all-settled.js";
 import "core-js/modules/es.promise.js";
-import "core-js/modules/es.string.at.js";
+import "core-js/modules/es.string.at-alternative.js";
 import "core-js/modules/es.string.iterator.js";
 import "core-js/modules/es.string.replace-all.js";
 import "core-js/modules/web.dom-collections.iterator.js";
diff --git a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/input.mjs b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/input.mjs
new file mode 100644
index 0000000000..53164c5ae5
--- /dev/null
+++ b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/input.mjs
@@ -0,0 +1,1 @@
+"asad".at();
diff --git a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/options.json b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/options.json
new file mode 100644
index 0000000000..3ded63324c
--- /dev/null
+++ b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/options.json
@@ -0,0 +1,12 @@
+{
+  "presets": [
+    [
+      "../../../../lib",
+      {
+        "useBuiltIns": "usage",
+        "corejs": "3.16",
+        "modules": false
+      }
+    ]
+  ]
+}
diff --git a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/output.mjs b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/output.mjs
new file mode 100644
index 0000000000..53164c5ae5
--- /dev/null
+++ b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-old-core-js/output.mjs
@@ -0,0 +1,1 @@
+"asad".at();
diff --git a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/output.mjs b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/output.mjs
index 38d5c81ae8..9c7b28f2c8 100644
--- a/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/output.mjs
+++ b/crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/output.mjs
@@ -1,7 +1,5 @@
 import "core-js/modules/es.regexp.constructor.js";
-import "core-js/modules/es.regexp.dot-all.js";
 import "core-js/modules/es.regexp.exec.js";
-import "core-js/modules/es.regexp.sticky.js";
 import "core-js/modules/es.regexp.to-string.js";
 var a = RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "u");
 var b = RegExp(".", "s");