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}