Skip to main content

oxc_resolver/
options.rs

1use std::{
2    fmt,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use crate::node_path::NodePath;
8
9/// Module Resolution Options
10///
11/// Options are directly ported from [enhanced-resolve](https://github.com/webpack/enhanced-resolve#resolver-options).
12///
13/// See [webpack resolve](https://webpack.js.org/configuration/resolve/) for information and examples
14#[expect(clippy::struct_excessive_bools)]
15#[derive(Debug, Clone)]
16pub struct ResolveOptions {
17    /// Current working directory, used for testing purposes.
18    pub cwd: Option<PathBuf>,
19
20    /// Discover tsconfig automatically or use the specified tsconfig.json path.
21    ///
22    /// Default `None`
23    pub tsconfig: Option<TsconfigDiscovery>,
24
25    /// Create aliases to import or require certain modules more easily.
26    ///
27    /// An alias is used to replace a whole path or part of a path.
28    /// For example, to alias a commonly used `src/` folders: `vec![("@/src"), vec![AliasValue::Path("/path/to/src")]]`
29    ///
30    /// A trailing $ can also be added to the given object's keys to signify an exact match.
31    ///
32    /// See [webpack's `resolve.alias` documentation](https://webpack.js.org/configuration/resolve/#resolvealias) for a list of use cases.
33    pub alias: Alias,
34
35    /// A list of alias fields in description files.
36    ///
37    /// Specify a field, such as `browser`, to be parsed according to [this specification](https://github.com/defunctzombie/package-browser-field-spec).
38    /// Can be a path to json object such as `["path", "to", "exports"]`.
39    ///
40    /// Default `[]`
41    pub alias_fields: Vec<Vec<String>>,
42
43    /// Condition names for exports field which defines entry points of a package.
44    ///
45    /// The key order in the exports field is significant. During condition matching, earlier entries have higher priority and take precedence over later entries.
46    ///
47    /// Default `[]`
48    pub condition_names: Vec<String>,
49
50    /// Set to [EnforceExtension::Enabled] for [ESM Mandatory file extensions](https://nodejs.org/api/esm.html#mandatory-file-extensions).
51    ///
52    /// If `enforce_extension` is set to [EnforceExtension::Enabled], resolution will not allow extension-less files.
53    /// This means `require('./foo.js')` will resolve, while `require('./foo')` will not.
54    ///
55    /// The default value for `enforce_extension` is [EnforceExtension::Auto], which is changed upon initialization.
56    ///
57    /// It changes to [EnforceExtension::Enabled] if [ResolveOptions::extensions] contains an empty string;
58    /// otherwise, this value changes to [EnforceExtension::Disabled].
59    ///
60    /// Explicitly set the value to [EnforceExtension::Disabled] to disable this automatic behavior.
61    ///
62    /// For reference, this behavior is aligned with `enhanced-resolve`. See <https://github.com/webpack/enhanced-resolve/pull/285>.
63    pub enforce_extension: EnforceExtension,
64
65    /// A list of exports fields in description files.
66    ///
67    /// Can be a path to a JSON object such as `["path", "to", "exports"]`.
68    ///
69    /// Default `[["exports"]]`.
70    pub exports_fields: Vec<Vec<String>>,
71
72    /// Fields from `package.json` which are used to provide the internal requests of a package
73    /// (requests starting with # are considered internal).
74    ///
75    /// Can be a path to a JSON object such as `["path", "to", "imports"]`.
76    ///
77    /// Default `[["imports"]]`.
78    pub imports_fields: Vec<Vec<String>>,
79
80    /// An object which maps extension to extension aliases.
81    ///
82    /// Default `{}`
83    pub extension_alias: Vec<(String, Vec<String>)>,
84
85    /// Attempt to resolve these extensions in order.
86    ///
87    /// If multiple files share the same name but have different extensions,
88    /// will resolve the one with the extension listed first in the array and skip the rest.
89    ///
90    /// All extensions must have a leading dot.
91    ///
92    /// Default `[".js", ".json", ".node"]`
93    pub extensions: Vec<String>,
94
95    /// Redirect module requests when normal resolving fails.
96    ///
97    /// Default `[]`
98    pub fallback: Alias,
99
100    /// Request passed to resolve is already fully specified and extensions or main files are not resolved for it (they are still resolved for internal requests).
101    ///
102    /// See also webpack configuration [resolve.fullySpecified](https://webpack.js.org/configuration/module/#resolvefullyspecified)
103    ///
104    /// Default `false`
105    pub fully_specified: bool,
106
107    /// A list of main fields in description files
108    ///
109    /// Default `["main"]`.
110    pub main_fields: Vec<String>,
111
112    /// The filename to be used while resolving directories.
113    ///
114    /// Default `["index"]`
115    pub main_files: Vec<String>,
116
117    /// A list of directories to resolve modules from, can be absolute path or folder name.
118    ///
119    /// Default `["node_modules"]`.
120    ///
121    /// When `NODE_PATH` is set, absolute entries from `NODE_PATH` are appended during option
122    /// sanitization.
123    pub modules: Vec<String>,
124
125    /// Resolve to a context instead of a file.
126    ///
127    /// Default `false`
128    pub resolve_to_context: bool,
129
130    /// Prefer to resolve module requests as relative requests instead of using modules from node_modules directories.
131    ///
132    /// Default `false`
133    pub prefer_relative: bool,
134
135    /// Prefer to resolve server-relative urls as absolute paths before falling back to resolve in ResolveOptions::roots.
136    ///
137    /// Default `false`
138    pub prefer_absolute: bool,
139
140    /// A list of resolve restrictions to restrict the paths that a request can be resolved on.
141    ///
142    /// Default `[]`
143    pub restrictions: Vec<Restriction>,
144
145    /// A list of directories where requests of server-relative URLs (starting with '/') are resolved.
146    /// On non-Windows systems these requests are resolved as an absolute path first.
147    ///
148    /// Default `[]`
149    pub roots: Vec<PathBuf>,
150
151    /// Whether to resolve symlinks to their symlinked location, if possible.
152    /// When enabled, symlinked resources are resolved to their real path, not their symlinked location.
153    /// Note that this may cause module resolution to fail when using tools that symlink packages (like `npm link`).
154    ///
155    /// Even if this option has been enabled, the resolver may decide not to follow the symlinks if the target cannot be
156    /// represented as a valid path for `require` or `import` statements in NodeJS. Specifically, we won't follow the symlink if:
157    /// 1. On Windows, the symlink is a [Volume mount point](https://learn.microsoft.com/en-us/windows/win32/fileio/volume-mount-points)
158    ///    to a Volume that does not have a drive letter.
159    ///    See: How to [mount a drive in a folder](https://learn.microsoft.com/en-us/windows-server/storage/disk-management/assign-a-mount-point-folder-path-to-a-drive).
160    /// 2. On Windows, the symlink points to a [DOS device path](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#dos-device-paths)
161    ///    that cannot be reduced into a [traditional DOS path](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#traditional-dos-paths).
162    ///    For example, all of the following symlink targets _will not_ be followed:
163    ///    * `\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\folder\` (Volume GUID)
164    ///    * `\\.\BootPartition\folder\file.ts` (Drive name)
165    ///
166    ///    DOS device path either pointing to a drive with drive letter, or a UNC path, will be simplified and followed, such as
167    ///    * `\\.\D:\path\to\file`: reduced to `D:\path\to\file`;
168    ///    * `\\.\UNC\server\share\path\to\file`: reduced to `\\server\share\path\to\file`.
169    ///
170    /// Default `true`
171    pub symlinks: bool,
172
173    /// Whether to read the `NODE_PATH` environment variable and append its entries to
174    /// [`modules`](ResolveOptions::modules).
175    ///
176    /// `NODE_PATH` is a deprecated Node.js feature that is not part of ESM resolution.
177    /// Set this to `false` to disable the behavior.
178    ///
179    /// See <https://nodejs.org/api/modules.html#loading-from-the-global-folders>
180    ///
181    /// Default `true`
182    pub node_path: bool,
183
184    /// Whether to parse [module.builtinModules](https://nodejs.org/api/module.html#modulebuiltinmodules) or not.
185    /// For example, "zlib" will throw [crate::ResolveError::Builtin] when set to true.
186    ///
187    /// Default `false`
188    pub builtin_modules: bool,
189
190    /// Resolve [crate::Resolution::module_type].
191    ///
192    /// Default: `false`
193    pub module_type: bool,
194
195    /// Allow `exports` field in `require('../directory')`.
196    ///
197    /// This is not part of the spec but some vite projects rely on this behavior.
198    /// See
199    /// * <https://github.com/vitejs/vite/pull/20252>
200    /// * <https://github.com/nodejs/node/issues/58827>
201    ///
202    /// Default: `false`
203    pub allow_package_exports_in_directory_resolve: bool,
204
205    /// Enable Yarn Plug'n'Play?.
206    ///
207    /// Pass in `!!process.versions.pnp` if called from node.js.
208    ///
209    /// Default: when env var `OXC_RESOLVER_YARN_PNP` is set.
210    #[cfg(feature = "yarn_pnp")]
211    pub yarn_pnp: bool,
212}
213
214impl ResolveOptions {
215    /// ## Examples
216    ///
217    /// ```
218    /// use oxc_resolver::ResolveOptions;
219    ///
220    /// let options = ResolveOptions::default().with_condition_names(&["bar"]);
221    /// assert_eq!(options.condition_names, vec!["bar".to_string()])
222    /// ```
223    #[must_use]
224    pub fn with_condition_names(mut self, names: &[&str]) -> Self {
225        self.condition_names = names.iter().map(ToString::to_string).collect::<Vec<String>>();
226        self
227    }
228
229    /// ## Examples
230    ///
231    /// ```
232    /// use oxc_resolver::ResolveOptions;
233    ///
234    /// let options = ResolveOptions::default().with_node_path(false);
235    /// assert_eq!(options.node_path, false)
236    /// ```
237    #[must_use]
238    pub const fn with_node_path(mut self, flag: bool) -> Self {
239        self.node_path = flag;
240        self
241    }
242
243    /// ## Examples
244    ///
245    /// ```
246    /// use oxc_resolver::ResolveOptions;
247    ///
248    /// let options = ResolveOptions::default().with_builtin_modules(false);
249    /// assert_eq!(options.builtin_modules, false)
250    /// ```
251    #[must_use]
252    pub const fn with_builtin_modules(mut self, flag: bool) -> Self {
253        self.builtin_modules = flag;
254        self
255    }
256
257    /// Adds a single root to the options
258    ///
259    /// ## Examples
260    ///
261    /// ```
262    /// use oxc_resolver::ResolveOptions;
263    /// use std::path::{Path, PathBuf};
264    ///
265    /// let options = ResolveOptions::default().with_root("foo");
266    /// assert_eq!(options.roots, vec![PathBuf::from("foo")])
267    /// ```
268    #[must_use]
269    pub fn with_root<P: AsRef<Path>>(mut self, root: P) -> Self {
270        self.roots.push(root.as_ref().to_path_buf());
271        self
272    }
273
274    /// Adds a single extension to the list of extensions. Extension must start with a `.`
275    ///
276    /// ## Examples
277    ///
278    /// ```
279    /// use oxc_resolver::ResolveOptions;
280    /// use std::path::{Path, PathBuf};
281    ///
282    /// let options = ResolveOptions::default().with_extension(".jsonc");
283    /// assert!(options.extensions.contains(&".jsonc".to_string()));
284    /// ```
285    #[must_use]
286    pub fn with_extension<S: Into<String>>(mut self, extension: S) -> Self {
287        self.extensions.push(extension.into());
288        self
289    }
290
291    /// Adds a single main field to the list of fields
292    ///
293    /// ## Examples
294    ///
295    /// ```
296    /// use oxc_resolver::ResolveOptions;
297    /// use std::path::{Path, PathBuf};
298    ///
299    /// let options = ResolveOptions::default().with_main_field("something");
300    /// assert!(options.main_fields.contains(&"something".to_string()));
301    /// ```
302    #[must_use]
303    pub fn with_main_field<S: Into<String>>(mut self, field: S) -> Self {
304        self.main_fields.push(field.into());
305        self
306    }
307
308    /// Changes how the extension should be treated
309    ///
310    /// ## Examples
311    ///
312    /// ```
313    /// use oxc_resolver::{ResolveOptions, EnforceExtension};
314    /// use std::path::{Path, PathBuf};
315    ///
316    /// let options = ResolveOptions::default().with_force_extension(EnforceExtension::Enabled);
317    /// assert_eq!(options.enforce_extension, EnforceExtension::Enabled);
318    /// ```
319    #[must_use]
320    pub const fn with_force_extension(mut self, enforce_extension: EnforceExtension) -> Self {
321        self.enforce_extension = enforce_extension;
322        self
323    }
324
325    /// Sets the value for [ResolveOptions::fully_specified]
326    ///
327    /// ## Examples
328    ///
329    /// ```
330    /// use oxc_resolver::{ResolveOptions};
331    /// use std::path::{Path, PathBuf};
332    ///
333    /// let options = ResolveOptions::default().with_fully_specified(true);
334    /// assert_eq!(options.fully_specified, true);
335    /// ```
336    #[must_use]
337    pub const fn with_fully_specified(mut self, fully_specified: bool) -> Self {
338        self.fully_specified = fully_specified;
339        self
340    }
341
342    /// Sets the value for [ResolveOptions::prefer_relative]
343    ///
344    /// ## Examples
345    ///
346    /// ```
347    /// use oxc_resolver::{ResolveOptions};
348    /// use std::path::{Path, PathBuf};
349    ///
350    /// let options = ResolveOptions::default().with_prefer_relative(true);
351    /// assert_eq!(options.prefer_relative, true);
352    /// ```
353    #[must_use]
354    pub const fn with_prefer_relative(mut self, flag: bool) -> Self {
355        self.prefer_relative = flag;
356        self
357    }
358
359    /// Sets the value for [ResolveOptions::prefer_absolute]
360    ///
361    /// ## Examples
362    ///
363    /// ```
364    /// use oxc_resolver::{ResolveOptions};
365    /// use std::path::{Path, PathBuf};
366    ///
367    /// let options = ResolveOptions::default().with_prefer_absolute(true);
368    /// assert_eq!(options.prefer_absolute, true);
369    /// ```
370    #[must_use]
371    pub const fn with_prefer_absolute(mut self, flag: bool) -> Self {
372        self.prefer_absolute = flag;
373        self
374    }
375
376    /// Changes the value of [ResolveOptions::symlinks]
377    ///
378    /// ## Examples
379    ///
380    /// ```
381    /// use oxc_resolver::{ResolveOptions};
382    ///
383    /// let options = ResolveOptions::default().with_symbolic_link(false);
384    /// assert_eq!(options.symlinks, false);
385    /// ```
386    #[must_use]
387    pub const fn with_symbolic_link(mut self, flag: bool) -> Self {
388        self.symlinks = flag;
389        self
390    }
391
392    /// Adds a module to [ResolveOptions::modules]
393    ///
394    /// ## Examples
395    ///
396    /// ```
397    /// use oxc_resolver::{ResolveOptions};
398    ///
399    /// let options = ResolveOptions::default().with_module("module");
400    /// assert!(options.modules.contains(&"module".to_string()));
401    /// ```
402    #[must_use]
403    pub fn with_module<M: Into<String>>(mut self, module: M) -> Self {
404        self.modules.push(module.into());
405        self
406    }
407
408    /// Adds a main file to [ResolveOptions::main_files]
409    ///
410    /// ## Examples
411    ///
412    /// ```
413    /// use oxc_resolver::{ResolveOptions};
414    ///
415    /// let options = ResolveOptions::default().with_main_file("foo");
416    /// assert!(options.main_files.contains(&"foo".to_string()));
417    /// ```
418    #[must_use]
419    pub fn with_main_file<M: Into<String>>(mut self, module: M) -> Self {
420        self.main_files.push(module.into());
421        self
422    }
423
424    pub(crate) fn sanitize(mut self) -> Self {
425        debug_assert!(
426            self.extensions.iter().filter(|e| !e.is_empty()).all(|e| e.starts_with('.')),
427            "All extensions must start with a leading dot"
428        );
429        // Set `enforceExtension` to `true` when [ResolveOptions::extensions] contains an empty string.
430        // See <https://github.com/webpack/enhanced-resolve/pull/285>
431        if self.enforce_extension == EnforceExtension::Auto {
432            if !self.extensions.is_empty() && self.extensions.iter().any(String::is_empty) {
433                self.enforce_extension = EnforceExtension::Enabled;
434            } else {
435                self.enforce_extension = EnforceExtension::Disabled;
436            }
437        }
438
439        if self.node_path {
440            self.modules.extend_from_slice(NodePath::build());
441        }
442
443        self
444    }
445}
446
447/// Value for [ResolveOptions::enforce_extension]
448#[derive(Debug, Clone, Copy, Eq, PartialEq)]
449pub enum EnforceExtension {
450    Auto,
451    Enabled,
452    Disabled,
453}
454
455impl EnforceExtension {
456    #[must_use]
457    pub const fn is_auto(self) -> bool {
458        matches!(self, Self::Auto)
459    }
460
461    #[must_use]
462    pub const fn is_enabled(self) -> bool {
463        matches!(self, Self::Enabled)
464    }
465
466    #[must_use]
467    pub const fn is_disabled(self) -> bool {
468        matches!(self, Self::Disabled)
469    }
470}
471
472/// Alias for [ResolveOptions::alias] and [ResolveOptions::fallback]
473pub type Alias = Vec<(String, Vec<AliasValue>)>;
474
475/// Alias Value for [ResolveOptions::alias] and [ResolveOptions::fallback]
476#[derive(Debug, Clone, Hash, PartialEq, Eq)]
477pub enum AliasValue {
478    /// The path value
479    Path(String),
480
481    /// The `false` value
482    Ignore,
483}
484
485impl<S> From<S> for AliasValue
486where
487    S: Into<String>,
488{
489    fn from(value: S) -> Self {
490        Self::Path(value.into())
491    }
492}
493
494/// Value for [ResolveOptions::restrictions]
495#[derive(Clone)]
496pub enum Restriction {
497    Path(PathBuf),
498    Fn(Arc<dyn Fn(&Path) -> bool + Sync + Send>),
499}
500
501impl std::fmt::Debug for Restriction {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        match self {
504            Self::Path(path) => write!(f, "Path(\"{}\")", path.display()),
505            Self::Fn(_) => write!(f, "Fn(<function>)"),
506        }
507    }
508}
509
510#[derive(Debug, Clone)]
511pub enum TsconfigDiscovery {
512    Auto,
513    Manual(TsconfigOptions),
514}
515
516/// Tsconfig Options for [ResolveOptions::tsconfig]
517///
518/// Derived from [tsconfig-paths-webpack-plugin](https://github.com/dividab/tsconfig-paths-webpack-plugin#options)
519#[derive(Debug, Clone)]
520pub struct TsconfigOptions {
521    /// Allows you to specify where to find the TypeScript configuration file.
522    /// You may provide
523    /// * a relative path to the configuration file. It will be resolved relative to cwd.
524    /// * an absolute path to the configuration file.
525    pub config_file: PathBuf,
526
527    /// Support for Typescript Project References.
528    pub references: TsconfigReferences,
529}
530
531/// Configuration for [TsconfigOptions::references]
532#[derive(Debug, Clone, Copy)]
533pub enum TsconfigReferences {
534    Disabled,
535    /// Use the `references` field from tsconfig of `config_file`.
536    Auto,
537}
538
539impl Default for ResolveOptions {
540    fn default() -> Self {
541        Self {
542            cwd: None,
543            tsconfig: None,
544            alias: vec![],
545            alias_fields: vec![],
546            condition_names: vec![],
547            enforce_extension: EnforceExtension::Auto,
548            extension_alias: vec![],
549            exports_fields: vec![vec!["exports".into()]],
550            imports_fields: vec![vec!["imports".into()]],
551            extensions: vec![".js".into(), ".json".into(), ".node".into()],
552            fallback: vec![],
553            fully_specified: false,
554            main_fields: vec!["main".into()],
555            main_files: vec!["index".into()],
556            modules: vec!["node_modules".into()],
557            resolve_to_context: false,
558            prefer_relative: false,
559            prefer_absolute: false,
560            restrictions: vec![],
561            roots: vec![],
562            symlinks: true,
563            node_path: true,
564            builtin_modules: false,
565            module_type: false,
566            allow_package_exports_in_directory_resolve: false,
567            #[cfg(feature = "yarn_pnp")]
568            yarn_pnp: std::env::var("OXC_RESOLVER_YARN_PNP").is_ok(),
569        }
570    }
571}
572
573// For tracing
574impl fmt::Display for ResolveOptions {
575    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
576        if let Some(tsconfig) = &self.tsconfig {
577            write!(f, "tsconfig:{tsconfig:?},")?;
578        }
579        if !self.alias.is_empty() {
580            write!(f, "alias:{:?},", self.alias)?;
581        }
582        if !self.alias_fields.is_empty() {
583            write!(f, "alias_fields:{:?},", self.alias_fields)?;
584        }
585        if !self.condition_names.is_empty() {
586            write!(f, "condition_names:{:?},", self.condition_names)?;
587        }
588        if self.enforce_extension.is_enabled() {
589            write!(f, "enforce_extension:{:?},", self.enforce_extension)?;
590        }
591        if !self.exports_fields.is_empty() {
592            write!(f, "exports_fields:{:?},", self.exports_fields)?;
593        }
594        if !self.imports_fields.is_empty() {
595            write!(f, "imports_fields:{:?},", self.imports_fields)?;
596        }
597        if !self.extension_alias.is_empty() {
598            write!(f, "extension_alias:{:?},", self.extension_alias)?;
599        }
600        if !self.extensions.is_empty() {
601            write!(f, "extensions:{:?},", self.extensions)?;
602        }
603        if !self.fallback.is_empty() {
604            write!(f, "fallback:{:?},", self.fallback)?;
605        }
606        if self.fully_specified {
607            write!(f, "fully_specified:{:?},", self.fully_specified)?;
608        }
609        if !self.main_fields.is_empty() {
610            write!(f, "main_fields:{:?},", self.main_fields)?;
611        }
612        if !self.main_files.is_empty() {
613            write!(f, "main_files:{:?},", self.main_files)?;
614        }
615        if !self.modules.is_empty() {
616            write!(f, "modules:{:?},", self.modules)?;
617        }
618        if self.resolve_to_context {
619            write!(f, "resolve_to_context:{:?},", self.resolve_to_context)?;
620        }
621        if self.prefer_relative {
622            write!(f, "prefer_relative:{:?},", self.prefer_relative)?;
623        }
624        if self.prefer_absolute {
625            write!(f, "prefer_absolute:{:?},", self.prefer_absolute)?;
626        }
627        if !self.restrictions.is_empty() {
628            write!(f, "restrictions:{:?},", self.restrictions)?;
629        }
630        if !self.roots.is_empty() {
631            write!(f, "roots:{:?},", self.roots)?;
632        }
633        if self.symlinks {
634            write!(f, "symlinks:{:?},", self.symlinks)?;
635        }
636        if !self.node_path {
637            write!(f, "node_path:{:?},", self.node_path)?;
638        }
639        if self.builtin_modules {
640            write!(f, "builtin_modules:{:?},", self.builtin_modules)?;
641        }
642        if self.allow_package_exports_in_directory_resolve {
643            write!(
644                f,
645                "allow_package_exports_in_directory_resolve:{:?},",
646                self.allow_package_exports_in_directory_resolve
647            )?;
648        }
649        Ok(())
650    }
651}
652
653#[cfg(test)]
654mod test {
655    use std::path::PathBuf;
656
657    use super::{
658        AliasValue, EnforceExtension, ResolveOptions, Restriction, TsconfigDiscovery,
659        TsconfigOptions, TsconfigReferences,
660    };
661
662    #[test]
663    fn enforce_extension() {
664        assert!(EnforceExtension::Auto.is_auto());
665        assert!(!EnforceExtension::Enabled.is_auto());
666        assert!(!EnforceExtension::Disabled.is_auto());
667
668        assert!(!EnforceExtension::Auto.is_enabled());
669        assert!(EnforceExtension::Enabled.is_enabled());
670        assert!(!EnforceExtension::Disabled.is_enabled());
671
672        assert!(!EnforceExtension::Auto.is_disabled());
673        assert!(!EnforceExtension::Enabled.is_disabled());
674        assert!(EnforceExtension::Disabled.is_disabled());
675    }
676
677    #[test]
678    fn display() {
679        let options = ResolveOptions {
680            tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions {
681                config_file: PathBuf::from("tsconfig.json"),
682                references: TsconfigReferences::Auto,
683            })),
684            alias: vec![("a".into(), vec![AliasValue::Ignore])],
685            alias_fields: vec![vec!["browser".into()]],
686            condition_names: vec!["require".into()],
687            enforce_extension: EnforceExtension::Enabled,
688            extension_alias: vec![(".js".into(), vec![".ts".into()])],
689            exports_fields: vec![vec!["exports".into()]],
690            imports_fields: vec![vec!["imports".into()]],
691            fallback: vec![("fallback".into(), vec![AliasValue::Ignore])],
692            fully_specified: true,
693            resolve_to_context: true,
694            prefer_relative: true,
695            prefer_absolute: true,
696            restrictions: vec![Restriction::Path(PathBuf::from("restrictions"))],
697            roots: vec![PathBuf::from("roots")],
698            builtin_modules: true,
699            allow_package_exports_in_directory_resolve: true,
700            ..ResolveOptions::default()
701        };
702
703        let expected = r#"tsconfig:Manual(TsconfigOptions { config_file: "tsconfig.json", references: Auto }),alias:[("a", [Ignore])],alias_fields:[["browser"]],condition_names:["require"],enforce_extension:Enabled,exports_fields:[["exports"]],imports_fields:[["imports"]],extension_alias:[(".js", [".ts"])],extensions:[".js", ".json", ".node"],fallback:[("fallback", [Ignore])],fully_specified:true,main_fields:["main"],main_files:["index"],modules:["node_modules"],resolve_to_context:true,prefer_relative:true,prefer_absolute:true,restrictions:[Path("restrictions")],roots:["roots"],symlinks:true,builtin_modules:true,allow_package_exports_in_directory_resolve:true,"#;
704        assert_eq!(format!("{options}"), expected);
705
706        let options = ResolveOptions {
707            cwd: None,
708            alias: vec![],
709            alias_fields: vec![],
710            node_path: true,
711            builtin_modules: false,
712            condition_names: vec![],
713            enforce_extension: EnforceExtension::Disabled,
714            exports_fields: vec![],
715            extension_alias: vec![],
716            extensions: vec![],
717            fallback: vec![],
718            fully_specified: false,
719            imports_fields: vec![],
720            main_fields: vec![],
721            main_files: vec![],
722            modules: vec![],
723            #[cfg(feature = "yarn_pnp")]
724            yarn_pnp: false,
725            prefer_absolute: false,
726            prefer_relative: false,
727            resolve_to_context: false,
728            restrictions: vec![],
729            roots: vec![],
730            symlinks: false,
731            tsconfig: None,
732            module_type: false,
733            allow_package_exports_in_directory_resolve: false,
734        };
735
736        assert_eq!(format!("{options}"), "");
737    }
738}