Repository: oxc-project/oxc-resolver
Pull request #110 (2024-03-13 09:01:40)

feat: add more builder functions for options
## Changes

- moved `sanitize` in the first `impl` block, so we don't have two `impl` of the same struct
- we have doctects which didn't run in the CI, this PR changes the workflow to add `cargo t --doc`. By default `cargo test` doesn't run doctests
- added three new builder functions for `ResolverOptions`
- made functions of `EnforceExtension` const functions
- implemented `From` for `AliasValue` and updated its usage in the tests. Less typing :)

## Test plan

Added some doc tests, which pass locally. CI should pass

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/110?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project) Report
Attention: Patch coverage is `63.63636%` with `12 lines` in your changes are missing coverage. Please review.
> Project coverage is 94.82%. Comparing base [(`79d5896`)](https://app.codecov.io/gh/oxc-project/oxc-resolver/commit/79d58961e27d4e47cfca57c698e75fff6a248b7c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project) to head [(`f0acb20`)](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/110?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project).
> Report is 2 commits behind head on main.

> :exclamation: Current head f0acb20 differs from pull request most recent head ba841a9. Consider uploading reports for the commit ba841a9 to get more accurate results

| [Files](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/110?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project) | Patch % | Lines |
|---|---|---|
| [src/options.rs](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/110?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project#diff-c3JjL29wdGlvbnMucnM=) | 63.63% | [12 Missing :warning: ](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/110?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #110      +/-   ##
==========================================
- Coverage   95.34%   94.82%   -0.52%     
==========================================
  Files          11       11              
  Lines        2170     2185      +15     
==========================================
+ Hits         2069     2072       +3     
- Misses        101      113      +12     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/oxc-project/oxc-resolver/pull/110?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=oxc-project).


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8a00ec0..ac642fd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -217,4 +217,6 @@ jobs:
       - uses: actions/checkout@v4
       - uses: ./.github/actions/pnpm
       - uses: ./.github/actions/rustup
-      - run: cargo test --all-features
+      - run: |
+          cargo test --all-features
+          cargo test --doc
diff --git a/benches/resolver.rs b/benches/resolver.rs
index 73300d6..3d46efa 100644
--- a/benches/resolver.rs
+++ b/benches/resolver.rs
@@ -60,7 +60,7 @@ fn data() -> Vec<(PathBuf, &'static str)> {
 
 fn oxc_resolver() -> oxc_resolver::Resolver {
     use oxc_resolver::{AliasValue, ResolveOptions, Resolver};
-    let alias_value = AliasValue::Path("./".into());
+    let alias_value = AliasValue::from("./");
     Resolver::new(ResolveOptions {
         extensions: vec![".ts".into(), ".js".into()],
         condition_names: vec!["webpack".into()],
diff --git a/examples/resolver.rs b/examples/resolver.rs
index 1eef96f..454698a 100644
--- a/examples/resolver.rs
+++ b/examples/resolver.rs
@@ -13,7 +13,7 @@ fn main() {
 
     let options = ResolveOptions {
         alias_fields: vec![vec!["browser".into()]],
-        alias: vec![("asdf".into(), vec![AliasValue::Path("./test.js".into())])],
+        alias: vec![("asdf".into(), vec![AliasValue::from("./test.js")])],
         ..ResolveOptions::default()
     };
 
diff --git a/napi/src/lib.rs b/napi/src/lib.rs
index cad1b72..62e09a5 100644
--- a/napi/src/lib.rs
+++ b/napi/src/lib.rs
@@ -86,7 +86,7 @@ impl ResolverFactory {
                             let v = v
                                 .into_iter()
                                 .map(|item| match item {
-                                    Some(path) => oxc_resolver::AliasValue::Path(path),
+                                    Some(path) => oxc_resolver::AliasValue::from(path),
                                     None => oxc_resolver::AliasValue::Ignore,
                                 })
                                 .collect();
@@ -123,7 +123,7 @@ impl ResolverFactory {
                             let v = v
                                 .into_iter()
                                 .map(|item| match item {
-                                    Some(path) => oxc_resolver::AliasValue::Path(path),
+                                    Some(path) => oxc_resolver::AliasValue::from(path),
                                     None => oxc_resolver::AliasValue::Ignore,
                                 })
                                 .collect();
diff --git a/src/options.rs b/src/options.rs
index 2c4ef7b..6b39353 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -280,6 +280,67 @@ impl ResolveOptions {
         self.prefer_absolute = flag;
         self
     }
+
+    /// Changes the value of [ResolveOptions::symlinks]
+    ///
+    /// ## Examples
+    ///
+    /// ```
+    /// use oxc_resolver::{ResolveOptions};
+    ///
+    /// let options = ResolveOptions::default().with_symbolic_link(false);
+    /// assert_eq!(options.symlinks, false);
+    /// ```
+    #[must_use]
+    pub fn with_symbolic_link(mut self, flag: bool) -> Self {
+        self.symlinks = flag;
+        self
+    }
+
+    /// Adds a module to [ResolveOptions::modules]
+    ///
+    /// ## Examples
+    ///
+    /// ```
+    /// use oxc_resolver::{ResolveOptions};
+    ///
+    /// let options = ResolveOptions::default().with_module("module");
+    /// assert!(options.modules.contains(&"module".to_string()));
+    /// ```
+    #[must_use]
+    pub fn with_module<M: Into<String>>(mut self, module: M) -> Self {
+        self.modules.push(module.into());
+        self
+    }
+
+    /// Adds a main file to [ResolveOptions::main_files]
+    ///
+    /// ## Examples
+    ///
+    /// ```
+    /// use oxc_resolver::{ResolveOptions};
+    ///
+    /// let options = ResolveOptions::default().with_main_file("foo");
+    /// assert!(options.main_files.contains(&"foo".to_string()));
+    /// ```
+    #[must_use]
+    pub fn with_main_file<M: Into<String>>(mut self, module: M) -> Self {
+        self.main_files.push(module.into());
+        self
+    }
+
+    pub(crate) fn sanitize(mut self) -> Self {
+        // Set `enforceExtension` to `true` when [ResolveOptions::extensions] contains an empty string.
+        // See <https://github.com/webpack/enhanced-resolve/pull/285>
+        if self.enforce_extension == EnforceExtension::Auto {
+            if !self.extensions.is_empty() && self.extensions.iter().any(String::is_empty) {
+                self.enforce_extension = EnforceExtension::Enabled;
+            } else {
+                self.enforce_extension = EnforceExtension::Disabled;
+            }
+        }
+        self
+    }
 }
 
 /// Value for [ResolveOptions::enforce_extension]
@@ -291,16 +352,16 @@ pub enum EnforceExtension {
 }
 
 impl EnforceExtension {
-    pub fn is_auto(&self) -> bool {
-        *self == Self::Auto
+    pub const fn is_auto(&self) -> bool {
+        matches!(self, Self::Auto)
     }
 
-    pub fn is_enabled(&self) -> bool {
-        *self == Self::Enabled
+    pub const fn is_enabled(&self) -> bool {
+        matches!(self, Self::Enabled)
     }
 
-    pub fn is_disabled(&self) -> bool {
-        *self == Self::Disabled
+    pub const fn is_disabled(&self) -> bool {
+        matches!(self, Self::Disabled)
     }
 }
 
@@ -317,6 +378,15 @@ pub enum AliasValue {
     Ignore,
 }
 
+impl<S> From<S> for AliasValue
+where
+    S: Into<String>,
+{
+    fn from(value: S) -> Self {
+        Self::Path(value.into())
+    }
+}
+
 /// Value for [ResolveOptions::restrictions]
 #[derive(Debug, Clone)]
 pub enum Restriction {
@@ -377,21 +447,6 @@ impl Default for ResolveOptions {
     }
 }
 
-impl ResolveOptions {
-    pub(crate) fn sanitize(mut self) -> Self {
-        // Set `enforceExtension` to `true` when [ResolveOptions::extensions] contains an empty string.
-        // See <https://github.com/webpack/enhanced-resolve/pull/285>
-        if self.enforce_extension == EnforceExtension::Auto {
-            if !self.extensions.is_empty() && self.extensions.iter().any(String::is_empty) {
-                self.enforce_extension = EnforceExtension::Enabled;
-            } else {
-                self.enforce_extension = EnforceExtension::Disabled;
-            }
-        }
-        self
-    }
-}
-
 // For tracing
 impl fmt::Display for ResolveOptions {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {


Test patch:
diff --git a/src/tests/alias.rs b/src/tests/alias.rs
index 1cba1a8..c7f7129 100644
--- a/src/tests/alias.rs
+++ b/src/tests/alias.rs
@@ -35,30 +35,30 @@ fn alias() {
         file_system,
         ResolveOptions {
             alias: vec![
-                ("aliasA".into(), vec![AliasValue::Path("a".into())]),
-                ("b$".into(), vec![AliasValue::Path("a/index".into())]),
-                ("c$".into(), vec![AliasValue::Path("/a/index".into())]),
+                ("aliasA".into(), vec![AliasValue::from("a")]),
+                ("b$".into(), vec![AliasValue::from("a/index")]),
+                ("c$".into(), vec![AliasValue::from("/a/index")]),
                 (
                     "multiAlias".into(),
                     vec![
-                        AliasValue::Path("b".into()),
-                        AliasValue::Path("c".into()),
-                        AliasValue::Path("d".into()),
-                        AliasValue::Path("e".into()),
-                        AliasValue::Path("a".into()),
+                        AliasValue::from("b"),
+                        AliasValue::from("c"),
+                        AliasValue::from("d"),
+                        AliasValue::from("e"),
+                        AliasValue::from("a"),
                     ],
                 ),
-                ("recursive".into(), vec![AliasValue::Path("recursive/dir".into())]),
-                ("/d/dir".into(), vec![AliasValue::Path("/c/dir".into())]),
-                ("/d/index.js".into(), vec![AliasValue::Path("/c/index".into())]),
-                ("#".into(), vec![AliasValue::Path("/c/dir".into())]),
-                ("@".into(), vec![AliasValue::Path("/c/dir".into())]),
+                ("recursive".into(), vec![AliasValue::from("recursive/dir")]),
+                ("/d/dir".into(), vec![AliasValue::from("/c/dir")]),
+                ("/d/index.js".into(), vec![AliasValue::from("/c/index")]),
+                ("#".into(), vec![AliasValue::from("/c/dir")]),
+                ("@".into(), vec![AliasValue::from("/c/dir")]),
                 ("ignored".into(), vec![AliasValue::Ignore]),
                 // not part of enhanced-resolve, added to make sure query in alias value works
-                ("alias_query".into(), vec![AliasValue::Path("a?query_after".into())]),
-                ("alias_fragment".into(), vec![AliasValue::Path("a#fragment_after".into())]),
+                ("alias_query".into(), vec![AliasValue::from("a?query_after")]),
+                ("alias_fragment".into(), vec![AliasValue::from("a#fragment_after")]),
                 ("dash".into(), vec![AliasValue::Ignore]),
-                ("@scope/package-name/file$".into(), vec![AliasValue::Path("/c/dir".into())]),
+                ("@scope/package-name/file$".into(), vec![AliasValue::from("/c/dir")]),
             ],
             modules: vec!["/".into()],
             ..ResolveOptions::default()
@@ -126,8 +126,8 @@ fn infinite_recursion() {
     let f = super::fixture();
     let resolver = Resolver::new(ResolveOptions {
         alias: vec![
-            ("./a".into(), vec![AliasValue::Path("./b".into())]),
-            ("./b".into(), vec![AliasValue::Path("./a".into())]),
+            ("./a".into(), vec![AliasValue::from("./b")]),
+            ("./b".into(), vec![AliasValue::from("./a")]),
         ],
         ..ResolveOptions::default()
     });
@@ -165,10 +165,7 @@ fn absolute_path() {
 fn system_path() {
     let f = super::fixture();
     let resolver = Resolver::new(ResolveOptions {
-        alias: vec![(
-            "@app".into(),
-            vec![AliasValue::Path(f.join("alias").to_string_lossy().to_string())],
-        )],
+        alias: vec![("@app".into(), vec![AliasValue::from(f.join("alias").to_string_lossy())])],
         ..ResolveOptions::default()
     });
 
diff --git a/src/tests/browser_field.rs b/src/tests/browser_field.rs
index 82f785f..8855b58 100644
--- a/src/tests/browser_field.rs
+++ b/src/tests/browser_field.rs
@@ -122,7 +122,7 @@ fn crypto_js() {
         alias_fields: vec![vec!["browser".into()]],
         fallback: vec![(
             "crypto".into(),
-            vec![AliasValue::Path(f.join("lib.js").to_string_lossy().to_string())],
+            vec![AliasValue::from(f.join("lib.js").to_string_lossy())],
         )],
         ..ResolveOptions::default()
     });
diff --git a/src/tests/full_specified.rs b/src/tests/full_specified.rs
index bad57e8..a70c019 100644
--- a/src/tests/full_specified.rs
+++ b/src/tests/full_specified.rs
@@ -33,8 +33,8 @@ mod windows {
             file_system,
             ResolveOptions {
                 alias: vec![
-                    ("alias1".into(), vec![AliasValue::Path("/a/abc".into())]),
-                    ("alias2".into(), vec![AliasValue::Path("/a".into())]),
+                    ("alias1".into(), vec![AliasValue::from("/a/abc")]),
+                    ("alias2".into(), vec![AliasValue::from("/a")]),
                 ],
                 alias_fields: vec![vec!["browser".into()]],
                 fully_specified: true,
@@ -84,8 +84,8 @@ mod windows {
             file_system,
             ResolveOptions {
                 alias: vec![
-                    ("alias1".into(), vec![AliasValue::Path("/a/abc".into())]),
-                    ("alias2".into(), vec![AliasValue::Path("/a".into())]),
+                    ("alias1".into(), vec![AliasValue::from("/a/abc")]),
+                    ("alias2".into(), vec![AliasValue::from("/a")]),
                 ],
                 alias_fields: vec![vec!["browser".into()]],
                 fully_specified: true,
diff --git a/src/tests/roots.rs b/src/tests/roots.rs
index 460b2da..72f122f 100644
--- a/src/tests/roots.rs
+++ b/src/tests/roots.rs
@@ -14,7 +14,7 @@ fn roots() {
 
     let resolver = Resolver::new(ResolveOptions {
         extensions: vec![".js".into()],
-        alias: vec![("foo".into(), vec![AliasValue::Path("/fixtures".into())])],
+        alias: vec![("foo".into(), vec![AliasValue::from("/fixtures")])],
         roots: vec![dirname(), f.clone()],
         ..ResolveOptions::default()
     });
@@ -62,7 +62,7 @@ fn prefer_absolute() {
     let f = super::fixture();
     let resolver = Resolver::new(ResolveOptions {
         extensions: vec![".js".into()],
-        alias: vec![("foo".into(), vec![AliasValue::Path("/fixtures".into())])],
+        alias: vec![("foo".into(), vec![AliasValue::from("/fixtures")])],
         roots: vec![dirname(), f.clone()],
         prefer_absolute: true,
         ..ResolveOptions::default()