parcel_resolver/
lib.rs

1//! parcel-resolver implements the Node.js module resolution algorithm.
2//! It supports both CommonJS and ES module resolution, along with many
3//! additional features supported by various tools in the JavaScript ecosystem,
4//! such as TypeScript's tsconfig paths and extension rewriting, the "alias"
5//! and "browser" fields used by bundlers, absolute and tilde paths, and more.
6//! These can be individually turned on or off using feature flags.
7//!
8//! For a full description of all supported resolution features, see [Parcel's documentation](https://parceljs.org/features/dependency-resolution/).
9//!
10//! # Example
11//!
12//! To create a resolver, first create a [Cache]. This stores information about the files
13//! in a [FileSystem], and can be reused between multiple resolvers. A fresh cache
14//! should generally be created once per build to ensure information is up to date.
15//!
16//! Next, create a [Resolver] using one of the constructors. For example, `Resolver::node`
17//! creates a Node.js compatible CommonJS resolver, `Resolver::node_esm` creates an ESM resolver,
18//! and `Resolver::parcel` creates a Parcel-compatible resolver. From there you can customize individual
19//! features such as extensions or index files by setting properties on the resolver.
20//!
21//! Finally, call `resolver.resolve` to resolve a specifier. This returns a result, along with [Invalidations]
22//! describing the files that should invalidate any resolution caches.
23//!
24//! ```
25//! use parcel_resolver::{Cache, Resolver, SpecifierType, ResolutionAndQuery};
26//! use std::path::Path;
27//!
28//! let cache = Cache::default();
29//! let resolver = Resolver::node_esm(Path::new("/path/to/project-root"), &cache);
30//!
31//! let res = resolver.resolve(
32//!   "lodash",
33//!   Path::new("/path/to/project-root/index.js"),
34//!   SpecifierType::Esm
35//! );
36//!
37//! if let Ok(ResolutionAndQuery { resolution, query }) = res.result {
38//!   // Do something with the resolution!
39//! }
40//! ```
41
42use std::{
43  borrow::Cow,
44  cell::OnceCell,
45  collections::HashMap,
46  path::{is_separator, Path, PathBuf},
47  sync::Arc,
48};
49
50use bitflags::bitflags;
51
52use cache::private::CacheCow;
53pub use cache::Cache;
54use cache::CachedPath;
55pub use error::ResolverError;
56#[cfg(not(target_arch = "wasm32"))]
57pub use fs::OsFileSystem;
58pub use fs::{FileKind, FileSystem};
59pub use invalidations::*;
60use package_json::{AliasValue, ExportsResolution, PackageJson};
61pub use package_json::{ExportsCondition, Fields, ModuleType, PackageJsonError};
62use specifier::{parse_package_specifier, parse_scheme};
63pub use specifier::{Specifier, SpecifierError, SpecifierType};
64use tsconfig::TsConfigWrapper;
65
66mod builtins;
67mod cache;
68mod error;
69mod fs;
70mod invalidations;
71mod json_comments_rs;
72mod package_json;
73mod specifier;
74mod tsconfig;
75mod url_to_path;
76
77bitflags! {
78  /// Resolution features to enable.
79  pub struct Flags: u16 {
80    /// Parcel-style absolute paths resolved relative to project root.
81    const ABSOLUTE_SPECIFIERS = 1 << 0;
82    /// Parcel-style tilde specifiers resolved relative to nearest module root.
83    const TILDE_SPECIFIERS = 1 << 1;
84    /// The `npm:` scheme.
85    const NPM_SCHEME = 1 << 2;
86    /// The "alias" field in package.json.
87    const ALIASES = 1 << 3;
88    /// The settings in tsconfig.json.
89    const TSCONFIG = 1 << 4;
90    /// The "exports" and "imports" fields in package.json.
91    const EXPORTS = 1 << 5;
92    /// Directory index files, e.g. index.js.
93    const DIR_INDEX = 1 << 6;
94    /// Optional extensions in specifiers, using the `extensions` setting.
95    const OPTIONAL_EXTENSIONS = 1 << 7;
96    /// Whether extensions are replaced in specifiers, e.g. `./foo.js` -> `./foo.ts`.
97    /// This also allows omitting the `.ts` and `.tsx` extensions when outside node_modules.
98    const TYPESCRIPT_EXTENSIONS = 1 << 8;
99    /// Whether to allow omitting the extension when resolving the same file type.
100    const PARENT_EXTENSION = 1 << 9;
101    /// Whether to allow optional extensions in the "exports" field.
102    const EXPORTS_OPTIONAL_EXTENSIONS = 1 << 10;
103
104    /// Default Node settings for CommonJS.
105    const NODE_CJS = Self::EXPORTS.bits | Self::DIR_INDEX.bits | Self::OPTIONAL_EXTENSIONS.bits;
106    /// Default Node settings for ESM.
107    const NODE_ESM = Self::EXPORTS.bits;
108    /// Default TypeScript settings.
109    const TYPESCRIPT = Self::TSCONFIG.bits | Self::EXPORTS.bits | Self::DIR_INDEX.bits | Self::OPTIONAL_EXTENSIONS.bits | Self::TYPESCRIPT_EXTENSIONS.bits | Self::EXPORTS_OPTIONAL_EXTENSIONS.bits;
110  }
111}
112
113/// Describes which modules in node_modules should be resolved.
114#[derive(Clone)]
115pub enum IncludeNodeModules {
116  /// Whether or not to include all node_modules.
117  Bool(bool),
118  /// An array of node_modules to include.
119  Array(Vec<String>),
120  /// A mapping of node_modules and whether to include them.
121  Map(HashMap<String, bool>),
122}
123
124impl Default for IncludeNodeModules {
125  fn default() -> Self {
126    IncludeNodeModules::Bool(true)
127  }
128}
129
130type ResolveModuleDir = dyn Fn(&str, &Path) -> Result<PathBuf, ResolverError> + Send + Sync;
131
132/// Implements the Node.js module resolution algorithm.
133pub struct Resolver<'a> {
134  /// The root path of the project.
135  pub project_root: CachedPath,
136  /// A list of file extensions to try when resolving.
137  pub extensions: Extensions<'a>,
138  /// A file name (without extension) for the index file of a directory.
139  pub index_file: &'a str,
140  /// package.json entry fields to try.
141  pub entries: Fields,
142  /// Resolution features to enable.
143  pub flags: Flags,
144  /// Configures which node_modules should be resolved.
145  pub include_node_modules: Cow<'a, IncludeNodeModules>,
146  /// package.json "exports" conditions to enable.
147  pub conditions: ExportsCondition,
148  /// A custom module directory resolution function, e.g. Yarn PnP.
149  pub module_dir_resolver: Option<Arc<ResolveModuleDir>>,
150  cache: CacheCow<'a>,
151}
152
153/// A list of file extensions to try when resolving.
154pub enum Extensions<'a> {
155  Borrowed(&'a [&'a str]),
156  Owned(Vec<String>),
157}
158
159impl<'a> Extensions<'a> {
160  fn iter(&self) -> impl Iterator<Item = &str> {
161    match self {
162      Extensions::Borrowed(v) => itertools::Either::Left(v.iter().copied()),
163      Extensions::Owned(v) => itertools::Either::Right(v.iter().map(|s| s.as_str())),
164    }
165  }
166}
167
168/// Options for individual resolution requests.
169#[derive(Default, Debug)]
170pub struct ResolveOptions {
171  /// Known condition flags.
172  pub conditions: ExportsCondition,
173  /// Custom conditions.
174  pub custom_conditions: Vec<String>,
175}
176
177/// Describes the result of a resolution request.
178#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
179#[serde(tag = "type", content = "value")]
180pub enum Resolution {
181  /// Resolved to a file path.
182  Path(PathBuf),
183  /// Resolved to a runtime builtin module.
184  Builtin(String),
185  /// Resolved to an external module that should not be bundled.
186  External,
187  /// Resolved to an empty module (e.g. `false` in the package.json#browser field).
188  Empty,
189  /// Resolved to a global variable.
190  Global(String),
191}
192
193/// The resolved path and query string from the original specifier, if any.
194#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
195pub struct ResolutionAndQuery {
196  /// The result of the resolution request.
197  pub resolution: Resolution,
198  /// The query string from the original specifier, if any.
199  pub query: Option<String>,
200}
201
202/// The result of a resolution request, and list of files that should invalidate the cache.
203pub struct ResolveResult {
204  /// The resolution result.
205  pub result: Result<ResolutionAndQuery, ResolverError>,
206  /// List of files that should invalidate the cache.
207  pub invalidations: Invalidations,
208}
209
210impl<'a> Resolver<'a> {
211  /// Creates a resolver with Node.js CommonJS settings.
212  pub fn node<C: Into<CacheCow<'a>>>(project_root: &Path, cache: C) -> Self {
213    let cache: CacheCow = cache.into();
214    Self {
215      project_root: cache.get(&project_root),
216      extensions: Extensions::Borrowed(&["js", "json", "node"]),
217      index_file: "index",
218      entries: Fields::MAIN,
219      flags: Flags::NODE_CJS,
220      cache,
221      include_node_modules: Cow::Owned(IncludeNodeModules::default()),
222      conditions: ExportsCondition::NODE,
223      module_dir_resolver: None,
224    }
225  }
226
227  /// Creates a resolver with Node.js ESM settings.
228  pub fn node_esm<C: Into<CacheCow<'a>>>(project_root: &Path, cache: C) -> Self {
229    let cache: CacheCow = cache.into();
230    Self {
231      project_root: cache.get(&project_root),
232      extensions: Extensions::Borrowed(&[]),
233      index_file: "index",
234      entries: Fields::MAIN,
235      flags: Flags::NODE_ESM,
236      cache,
237      include_node_modules: Cow::Owned(IncludeNodeModules::default()),
238      conditions: ExportsCondition::NODE,
239      module_dir_resolver: None,
240    }
241  }
242
243  /// Creates a resolver with Parcel settings.
244  pub fn parcel<C: Into<CacheCow<'a>>>(project_root: &Path, cache: C) -> Self {
245    let cache: CacheCow = cache.into();
246    Self {
247      project_root: cache.get(&project_root),
248      extensions: Extensions::Borrowed(&["mjs", "js", "jsx", "cjs", "json"]),
249      index_file: "index",
250      entries: Fields::MAIN | Fields::SOURCE | Fields::BROWSER | Fields::MODULE,
251      flags: Flags::all(),
252      cache,
253      include_node_modules: Cow::Owned(IncludeNodeModules::default()),
254      conditions: ExportsCondition::empty(),
255      module_dir_resolver: None,
256    }
257  }
258
259  /// Resolves a specifier relative to the given path, with default options.
260  pub fn resolve(
261    &self,
262    specifier: &str,
263    from: &Path,
264    specifier_type: SpecifierType,
265  ) -> ResolveResult {
266    self.resolve_with_options(specifier, from, specifier_type, Default::default())
267  }
268
269  /// Resolves a specifier relative to the given path, with custom options.
270  pub fn resolve_with_options(
271    &self,
272    specifier: &str,
273    from: &Path,
274    specifier_type: SpecifierType,
275    options: ResolveOptions,
276  ) -> ResolveResult {
277    let invalidations = Invalidations::default();
278    let result =
279      self.resolve_with_invalidations(specifier, from, specifier_type, &invalidations, options);
280
281    ResolveResult {
282      result,
283      invalidations,
284    }
285  }
286
287  /// Resolves a specifier with pre-existing Invalidations.
288  pub fn resolve_with_invalidations(
289    &self,
290    specifier: &str,
291    from: &Path,
292    specifier_type: SpecifierType,
293    invalidations: &Invalidations,
294    options: ResolveOptions,
295  ) -> Result<ResolutionAndQuery, ResolverError> {
296    let (specifier, query) = match Specifier::parse(specifier, specifier_type, self.flags) {
297      Ok(s) => s,
298      Err(e) => return Err(e.into()),
299    };
300    let from = self.cache.get(from);
301    let mut request = ResolveRequest::new(self, &specifier, specifier_type, &from, invalidations);
302    if !options.conditions.is_empty() || !options.custom_conditions.is_empty() {
303      // If custom conditions are defined, these override the default conditions inferred from the specifier type.
304      request.conditions = self.conditions | options.conditions;
305      request.custom_conditions = options.custom_conditions.as_slice();
306    }
307
308    match request.resolve() {
309      Ok(r) => Ok(ResolutionAndQuery {
310        resolution: r,
311        query: query.map(|q| q.to_owned()),
312      }),
313      Err(r) => Err(r),
314    }
315  }
316
317  /// Returns whether the given path has side effects, according to its parent package.json.
318  pub fn resolve_side_effects(
319    &self,
320    path: &Path,
321    invalidations: &Invalidations,
322  ) -> Result<bool, ResolverError> {
323    if let Some(package) = self.find_package(&self.cache.get(path.parent().unwrap()), invalidations)
324    {
325      Ok(unwrap_arc(&package)?.has_side_effects(path))
326    } else {
327      Ok(true)
328    }
329  }
330
331  /// Returns the module type (CommonJS, ESM, or JSON) of the given path,
332  /// according to either its extension or the package.json `type` field.
333  pub fn resolve_module_type(
334    &self,
335    path: &Path,
336    invalidations: &Invalidations,
337  ) -> Result<ModuleType, ResolverError> {
338    if let Some(ext) = path.extension() {
339      if ext == "mjs" {
340        return Ok(ModuleType::Module);
341      }
342
343      if ext == "cjs" || ext == "node" {
344        return Ok(ModuleType::CommonJs);
345      }
346
347      if ext == "json" {
348        return Ok(ModuleType::Json);
349      }
350
351      if ext == "js" {
352        if let Some(package) =
353          self.find_package(&self.cache.get(path.parent().unwrap()), invalidations)
354        {
355          return Ok(unwrap_arc(&package)?.module_type);
356        }
357      }
358    }
359
360    Ok(ModuleType::CommonJs)
361  }
362
363  fn find_package(
364    &self,
365    from: &CachedPath,
366    invalidations: &Invalidations,
367  ) -> Option<Arc<Result<PackageJson, ResolverError>>> {
368    if let Some(path) = self.find_ancestor_file(from, "package.json", invalidations) {
369      let package = path.package_json(&self.cache);
370      return Some(package);
371    }
372
373    None
374  }
375
376  fn find_ancestor_file(
377    &self,
378    from: &CachedPath,
379    filename: &str,
380    invalidations: &Invalidations,
381  ) -> Option<CachedPath> {
382    let mut first = true;
383    for dir in from.ancestors() {
384      if dir.is_node_modules() {
385        break;
386      }
387
388      let file = dir.join(filename, &self.cache);
389      if file.is_file(&*self.cache.fs) {
390        invalidations.invalidate_on_file_change(file.clone());
391        return Some(file);
392      }
393
394      if *dir == self.project_root {
395        break;
396      }
397
398      if first {
399        invalidations.invalidate_on_file_create_above(filename, from.clone());
400      }
401
402      first = false;
403    }
404
405    None
406  }
407
408  /// Returns the resolution cache.
409  pub fn cache(&self) -> &Cache {
410    &self.cache
411  }
412}
413
414struct ResolveRequest<'a> {
415  resolver: &'a Resolver<'a>,
416  specifier: &'a Specifier<'a>,
417  specifier_type: SpecifierType,
418  from: &'a CachedPath,
419  flags: RequestFlags,
420  tsconfig: OnceCell<Option<Arc<Result<TsConfigWrapper, ResolverError>>>>,
421  root_package: OnceCell<Option<Arc<Result<PackageJson, ResolverError>>>>,
422  invalidations: &'a Invalidations,
423  conditions: ExportsCondition,
424  custom_conditions: &'a [String],
425  priority_extension: Option<&'a str>,
426}
427
428bitflags! {
429  struct RequestFlags: u8 {
430    const IN_TS_FILE = 1 << 0;
431    const IN_JS_FILE = 1 << 1;
432    const IN_NODE_MODULES = 1 << 2;
433  }
434}
435
436impl<'a> ResolveRequest<'a> {
437  fn new(
438    resolver: &'a Resolver<'a>,
439    specifier: &'a Specifier<'a>,
440    mut specifier_type: SpecifierType,
441    from: &'a CachedPath,
442    invalidations: &'a Invalidations,
443  ) -> Self {
444    let mut flags = RequestFlags::empty();
445    let ext = from.extension();
446    if let Some(ext) = ext {
447      if ext == "ts" || ext == "tsx" || ext == "mts" || ext == "cts" {
448        flags |= RequestFlags::IN_TS_FILE;
449      } else if ext == "js" || ext == "jsx" || ext == "mjs" || ext == "cjs" {
450        flags |= RequestFlags::IN_JS_FILE;
451      }
452    }
453
454    if from.in_node_modules() {
455      flags |= RequestFlags::IN_NODE_MODULES;
456    }
457
458    // Replace the specifier type for `npm:` URLs so we resolve it like a module.
459    if specifier_type == SpecifierType::Url && matches!(specifier, Specifier::Package(..)) {
460      specifier_type = SpecifierType::Esm;
461    }
462
463    // Add "import" or "require" condition to global conditions based on specifier type.
464    // Also add the "module" condition if the "module" entry field is enabled.
465    let mut conditions = resolver.conditions;
466    let module_condition = if resolver.entries.contains(Fields::MODULE) {
467      ExportsCondition::MODULE
468    } else {
469      ExportsCondition::empty()
470    };
471    match specifier_type {
472      SpecifierType::Esm => conditions |= ExportsCondition::IMPORT | module_condition,
473      SpecifierType::Cjs => conditions |= ExportsCondition::REQUIRE | module_condition,
474      _ => {}
475    }
476
477    // Store the parent file extension so we can prioritize it even in sub-requests.
478    let priority_extension = if resolver.flags.contains(Flags::PARENT_EXTENSION) {
479      ext.and_then(|ext| ext.to_str())
480    } else {
481      None
482    };
483
484    Self {
485      resolver,
486      specifier,
487      specifier_type,
488      from,
489      flags,
490      tsconfig: OnceCell::new(),
491      root_package: OnceCell::new(),
492      invalidations,
493      conditions,
494      custom_conditions: &[],
495      priority_extension,
496    }
497  }
498
499  fn resolve_aliases(
500    &self,
501    package: &PackageJson,
502    specifier: &Specifier,
503    fields: Fields,
504  ) -> Result<Option<Resolution>, ResolverError> {
505    // Don't resolve alias if it came from the package.json itself (i.e. another alias).
506    if *self.from == package.path {
507      return Ok(None);
508    }
509
510    match package.resolve_aliases(specifier, fields) {
511      Some(alias) => match alias.as_ref() {
512        AliasValue::Specifier(specifier) => {
513          let mut req = ResolveRequest::new(
514            self.resolver,
515            specifier,
516            SpecifierType::Cjs,
517            &package.path,
518            self.invalidations,
519          );
520          req.priority_extension = self.priority_extension;
521          req.conditions = self.conditions;
522          req.custom_conditions = self.custom_conditions;
523          let resolved = req.resolve()?;
524          Ok(Some(resolved))
525        }
526        AliasValue::Bool(false) => Ok(Some(Resolution::Empty)),
527        AliasValue::Bool(true) => Ok(None),
528        AliasValue::Global { global } => Ok(Some(Resolution::Global((*global).to_owned()))),
529      },
530      None => Ok(None),
531    }
532  }
533
534  fn root_package(&self) -> &Option<Arc<Result<PackageJson, ResolverError>>> {
535    self
536      .root_package
537      .get_or_init(|| self.find_package(&self.resolver.project_root))
538  }
539
540  fn resolve(&self) -> Result<Resolution, ResolverError> {
541    match &self.specifier {
542      Specifier::Relative(specifier) => {
543        // Relative path
544        self.resolve_relative(specifier, self.from)
545      }
546      Specifier::Tilde(specifier) if self.resolver.flags.contains(Flags::TILDE_SPECIFIERS) => {
547        // Tilde path. Resolve relative to nearest node_modules directory,
548        // the nearest directory with package.json or the project root - whichever comes first.
549        if let Some(p) = self.find_ancestor_file(self.from, "package.json") {
550          return self.resolve_relative(specifier, &p);
551        }
552
553        Err(ResolverError::PackageJsonNotFound {
554          from: self.from.as_path().to_owned(),
555        })
556      }
557      Specifier::Absolute(specifier) => {
558        // In Parcel mode, absolute paths are actually relative to the project root.
559        if self.resolver.flags.contains(Flags::ABSOLUTE_SPECIFIERS) {
560          self.resolve_relative(
561            specifier.strip_prefix("/").unwrap(),
562            &self
563              .resolver
564              .project_root
565              .join("index", &self.resolver.cache),
566          )
567        } else if let Some(res) = self.load_path(&self.resolver.cache.get(&specifier), None)? {
568          Ok(res)
569        } else {
570          Err(ResolverError::FileNotFound {
571            relative: specifier.as_ref().to_owned(),
572            from: PathBuf::from("/"),
573          })
574        }
575      }
576      Specifier::Hash(hash) => {
577        if self.specifier_type == SpecifierType::Url {
578          // An ID-only URL, e.g. `url(#clip-path)` for CSS rules. Ignore.
579          Ok(Resolution::External)
580        } else if self.specifier_type == SpecifierType::Esm
581          && self.resolver.flags.contains(Flags::EXPORTS)
582        {
583          // An internal package #import specifier.
584          let package = self.find_package(self.from.parent().unwrap_or_else(|| self.from));
585          if let Some(package) = package {
586            let package = unwrap_arc(&package)?;
587            let res = package
588              .resolve_package_imports(
589                hash,
590                self.conditions,
591                self.custom_conditions,
592                &self.resolver.cache,
593              )
594              .map_err(|error| ResolverError::PackageJsonError {
595                error,
596                module: package.name.to_owned(),
597                path: package.path.as_path().into(),
598              })?;
599            match res {
600              ExportsResolution::Path(path) => {
601                // Extensionless specifiers are not supported in the imports field.
602                if let Some(res) = self.try_file_without_aliases(&path)? {
603                  return Ok(res);
604                }
605              }
606              ExportsResolution::Package(specifier) => {
607                let (module, subpath) = parse_package_specifier(&specifier)?;
608                // TODO: should this follow aliases??
609                return self.resolve_bare(module, subpath);
610              }
611              _ => {}
612            }
613          }
614
615          Err(ResolverError::PackageJsonNotFound {
616            from: self.from.as_path().to_owned(),
617          })
618        } else {
619          Err(ResolverError::UnknownError)
620        }
621      }
622      Specifier::Package(module, subpath) => {
623        // Bare specifier.
624        self.resolve_bare(module, subpath)
625      }
626      Specifier::Builtin(builtin) => {
627        if let Some(res) = self.resolve_package_aliases_and_tsconfig_paths(self.specifier)? {
628          return Ok(res);
629        }
630        Ok(Resolution::Builtin(builtin.as_ref().to_owned()))
631      }
632      Specifier::Url(url) => {
633        if self.specifier_type == SpecifierType::Url {
634          Ok(Resolution::External)
635        } else {
636          let (scheme, _) = parse_scheme(url)?;
637          Err(ResolverError::UnknownScheme {
638            scheme: scheme.into_owned(),
639          })
640        }
641      }
642      _ => Err(ResolverError::UnknownError),
643    }
644  }
645
646  fn find_ancestor_file(&self, from: &CachedPath, filename: &str) -> Option<CachedPath> {
647    let from = from.parent().unwrap();
648    self
649      .resolver
650      .find_ancestor_file(&from, filename, self.invalidations)
651  }
652
653  fn find_package(&self, from: &CachedPath) -> Option<Arc<Result<PackageJson, ResolverError>>> {
654    self.resolver.find_package(from, self.invalidations)
655  }
656
657  fn resolve_relative(
658    &self,
659    specifier: &Path,
660    from: &CachedPath,
661  ) -> Result<Resolution, ResolverError> {
662    // Resolve aliases from the nearest package.json.
663    let path = from.resolve(specifier, &self.resolver.cache);
664    let package = if self.resolver.flags.contains(Flags::ALIASES) {
665      self.find_package(&path.parent().unwrap())
666    } else {
667      None
668    };
669
670    let package = match &package {
671      Some(pkg) => Some(unwrap_arc(pkg)?),
672      None => None,
673    };
674
675    if let Some(res) = self.load_path(&path, package)? {
676      return Ok(res);
677    }
678
679    Err(ResolverError::FileNotFound {
680      relative: specifier.to_owned(),
681      from: from.as_path().to_owned(),
682    })
683  }
684
685  fn resolve_bare(&self, module: &str, subpath: &str) -> Result<Resolution, ResolverError> {
686    let include = match self.resolver.include_node_modules.as_ref() {
687      IncludeNodeModules::Bool(b) => *b,
688      IncludeNodeModules::Array(a) => a.iter().any(|v| v == module),
689      IncludeNodeModules::Map(m) => *m.get(module).unwrap_or(&true),
690    };
691
692    if !include {
693      return Ok(Resolution::External);
694    }
695
696    // Try aliases and tsconfig paths first.
697    let specifier = Specifier::Package(Cow::Borrowed(module), Cow::Borrowed(subpath));
698    if let Some(res) = self.resolve_package_aliases_and_tsconfig_paths(&specifier)? {
699      return Ok(res);
700    }
701
702    self.resolve_node_module(module, subpath)
703  }
704
705  fn resolve_package_aliases_and_tsconfig_paths(
706    &self,
707    specifier: &Specifier,
708  ) -> Result<Option<Resolution>, ResolverError> {
709    if self.resolver.flags.contains(Flags::ALIASES) {
710      // First, check for an alias in the root package.json.
711      if let Some(package) = self.root_package() {
712        if let Some(res) = self.resolve_aliases(unwrap_arc(package)?, specifier, Fields::ALIAS)? {
713          return Ok(Some(res));
714        }
715      }
716
717      // Next, try the local package.json.
718      if let Some(package) = self.find_package(self.from.parent().unwrap_or_else(|| self.from)) {
719        let mut fields = Fields::ALIAS;
720        if self.resolver.entries.contains(Fields::BROWSER) {
721          fields |= Fields::BROWSER;
722        }
723        if let Some(res) = self.resolve_aliases(unwrap_arc(&package)?, specifier, fields)? {
724          return Ok(Some(res));
725        }
726      }
727    }
728
729    // Next, check tsconfig.json for the paths and baseUrl options.
730    self.resolve_tsconfig_paths()
731  }
732
733  fn resolve_node_module(&self, module: &str, subpath: &str) -> Result<Resolution, ResolverError> {
734    // If there is a custom module directory resolver (e.g. Yarn PnP), use that.
735    if let Some(module_dir_resolver) = &self.resolver.module_dir_resolver {
736      let package_dir = module_dir_resolver(module, self.from.as_path())?;
737      return self.resolve_package(self.resolver.cache.get(&package_dir), module, subpath);
738    } else {
739      let mut file_name = String::with_capacity(module.len() + 13);
740      file_name.push_str("node_modules/");
741      file_name.push_str(module);
742      self.invalidations.invalidate_on_file_create_above(
743        file_name,
744        self
745          .from
746          .parent()
747          .cloned()
748          .unwrap_or_else(|| self.from.clone()),
749      );
750
751      for dir in self.from.ancestors() {
752        // Skip over node_modules directories
753        if dir.is_node_modules() {
754          continue;
755        }
756
757        let package_dir = dir.join_module(module, &self.resolver.cache);
758        if package_dir.is_dir(&*self.resolver.cache.fs) {
759          return self.resolve_package(package_dir, module, subpath);
760        }
761      }
762    }
763
764    // NODE_PATH??
765
766    Err(ResolverError::ModuleNotFound {
767      module: module.to_owned(),
768    })
769  }
770
771  fn resolve_package(
772    &self,
773    package_dir: CachedPath,
774    module: &str,
775    subpath: &str,
776  ) -> Result<Resolution, ResolverError> {
777    let package_path = package_dir.join("package.json", &self.resolver.cache);
778    let package = self.invalidations.read(&package_path, || {
779      package_path.package_json(&self.resolver.cache)
780    });
781
782    let package = match &*package {
783      Ok(package) => package,
784      Err(ResolverError::IOError(_)) => {
785        // No package.json in node_modules is probably invalid but we have tests for it...
786        if self.resolver.flags.contains(Flags::DIR_INDEX) {
787          if let Some(res) = self.load_file(
788            &package_dir.join(self.resolver.index_file, &self.resolver.cache),
789            None,
790          )? {
791            return Ok(res);
792          }
793        }
794
795        return Err(ResolverError::ModuleNotFound {
796          module: module.to_owned(),
797        });
798      }
799      Err(err) => return Err(err.clone()),
800    };
801
802    // Try the "source" field first, if present.
803    if self.resolver.entries.contains(Fields::SOURCE) && subpath.is_empty() {
804      if let Some(source) = package.source(&self.resolver.cache) {
805        if let Some(res) = self.load_path(&source, Some(&*package))? {
806          return Ok(res);
807        }
808      }
809    }
810
811    // If the exports field is present, use the Node ESM algorithm.
812    // Otherwise, fall back to classic CJS resolution.
813    if self.resolver.flags.contains(Flags::EXPORTS) && package.has_exports() {
814      let path = package
815        .resolve_package_exports(
816          subpath,
817          self.conditions,
818          self.custom_conditions,
819          &self.resolver.cache,
820        )
821        .map_err(|e| ResolverError::PackageJsonError {
822          module: package.name.to_owned(),
823          path: package.path.as_path().to_path_buf(),
824          error: e,
825        })?;
826
827      // Extensionless specifiers are not supported in the exports field
828      // according to the Node spec (for both ESM and CJS). However, webpack
829      // didn't follow this, so there are many packages that rely on it (e.g. underscore).
830      if self
831        .resolver
832        .flags
833        .contains(Flags::EXPORTS_OPTIONAL_EXTENSIONS)
834      {
835        if let Some(res) = self.load_file(&path, Some(&*package))? {
836          return Ok(res);
837        }
838      } else if let Some(res) = self.try_file_without_aliases(&path)? {
839        return Ok(res);
840      }
841
842      // TODO: track location of resolved field
843      Err(ResolverError::ModuleSubpathNotFound {
844        module: module.to_owned(),
845        path: path.as_path().to_path_buf(),
846        package_path: package.path.as_path().to_path_buf(),
847      })
848    } else if !subpath.is_empty() {
849      let package_dir = package_dir.join(subpath, &self.resolver.cache);
850      if let Some(res) = self.load_path(&package_dir, Some(&*package))? {
851        return Ok(res);
852      }
853
854      Err(ResolverError::ModuleSubpathNotFound {
855        module: module.to_owned(),
856        path: package_dir.as_path().to_owned(),
857        package_path: package.path.as_path().to_path_buf(),
858      })
859    } else {
860      let res = self.try_package_entries(&*package);
861      if let Ok(Some(res)) = res {
862        return Ok(res);
863      }
864
865      // Node ESM doesn't allow directory imports.
866      if self.resolver.flags.contains(Flags::DIR_INDEX) {
867        if let Some(res) = self.load_file(
868          &package_dir.join(self.resolver.index_file, &self.resolver.cache),
869          Some(&*package),
870        )? {
871          return Ok(res);
872        }
873      }
874
875      res?;
876
877      Err(ResolverError::ModuleSubpathNotFound {
878        module: module.to_owned(),
879        path: package_dir.as_path().join(self.resolver.index_file),
880        package_path: package.path.as_path().to_path_buf(),
881      })
882    }
883  }
884
885  fn try_package_entries(
886    &self,
887    package: &PackageJson,
888  ) -> Result<Option<Resolution>, ResolverError> {
889    // Try all entry fields.
890    if let Some((entry, field)) = package
891      .entries(self.resolver.entries, &self.resolver.cache)
892      .next()
893    {
894      if let Some(res) = self.load_path(&entry, Some(package))? {
895        return Ok(Some(res));
896      } else {
897        return Err(ResolverError::ModuleEntryNotFound {
898          module: package.name.to_owned(),
899          entry_path: entry.as_path().to_path_buf(),
900          package_path: package.path.as_path().to_path_buf(),
901          field,
902        });
903      }
904    }
905
906    Ok(None)
907  }
908
909  fn load_path(
910    &self,
911    path: &CachedPath,
912    package: Option<&PackageJson>,
913  ) -> Result<Option<Resolution>, ResolverError> {
914    // Urls and Node ESM do not resolve directory index files.
915    let can_load_directory =
916      self.resolver.flags.contains(Flags::DIR_INDEX) && self.specifier_type != SpecifierType::Url;
917
918    // If path ends with / only try loading as a directory.
919    let is_directory = can_load_directory
920      && path
921        .as_path()
922        .as_os_str()
923        .to_str()
924        .map(|s| s.ends_with(is_separator))
925        .unwrap_or(false);
926
927    if !is_directory {
928      if let Some(res) = self.load_file(path, package)? {
929        return Ok(Some(res));
930      }
931    }
932
933    // Urls and Node ESM do not resolve directory index files.
934    if can_load_directory {
935      return self.load_directory(path, package);
936    }
937
938    Ok(None)
939  }
940
941  fn load_file(
942    &self,
943    path: &CachedPath,
944    package: Option<&PackageJson>,
945  ) -> Result<Option<Resolution>, ResolverError> {
946    // First try the path as is.
947    // TypeScript only supports resolving specifiers ending with `.ts` or `.tsx`
948    // in a certain mode, but we always allow it.
949    // If there is no extension in the original specifier, only check aliases
950    // here and delay checking for an extensionless file until later (since this is unlikely).
951    if let Some(res) = self.try_suffixes(path, "", package, path.extension().is_none())? {
952      return Ok(Some(res));
953    }
954
955    // TypeScript allows a specifier like "./foo.js" to resolve to "./foo.ts".
956    // TSC does this before trying to append an extension. We match this
957    // rather than matching "./foo.js.ts", which seems more unlikely.
958    // However, if "./foo.js" exists we will resolve to it (above), unlike TSC.
959    // This is to match Node and other bundlers.
960    if self.resolver.flags.contains(Flags::TYPESCRIPT_EXTENSIONS)
961      && self.flags.contains(RequestFlags::IN_TS_FILE)
962      && !self.flags.contains(RequestFlags::IN_NODE_MODULES)
963      && self.specifier_type != SpecifierType::Url
964    {
965      if let Some(ext) = path.extension() {
966        // TODO: would be nice if there was a way to do this without cloning
967        // but OsStr doesn't let you create a slice.
968        let without_extension = &path.as_path().with_extension("");
969        let extensions: Option<&[&str]> = if ext == "js" || ext == "jsx" {
970          // TSC always prioritizes .ts over .tsx, even when the original extension was .jsx.
971          Some(&["ts", "tsx"])
972        } else if ext == "mjs" {
973          Some(&["mts"])
974        } else if ext == "cjs" {
975          Some(&["cts"])
976        } else {
977          None
978        };
979
980        let res = if let Some(extensions) = extensions {
981          self.try_extensions(
982            &self.resolver.cache.get(without_extension),
983            package,
984            &Extensions::Borrowed(extensions),
985            false,
986          )?
987        } else {
988          None
989        };
990
991        if res.is_some() {
992          return Ok(res);
993        }
994      }
995    }
996
997    // Try adding the same extension as in the parent file first.
998    if let Some(ext) = self.priority_extension {
999      // Use try_suffixes here to skip the specifier_type check.
1000      // This is reproducing a bug in the old version of the Parcel resolver
1001      // where URL dependencies could omit the extension if it was the same as the parent.
1002      // TODO: Revert this in the next major version.
1003      if let Some(res) = self.try_suffixes(path, ext, package, false)? {
1004        return Ok(Some(res));
1005      }
1006    }
1007
1008    // Try adding typescript extensions if outside node_modules.
1009    if self
1010      .resolver
1011      .flags
1012      .contains(Flags::TYPESCRIPT_EXTENSIONS | Flags::OPTIONAL_EXTENSIONS)
1013      && !self.flags.contains(RequestFlags::IN_NODE_MODULES)
1014    {
1015      if let Some(res) =
1016        self.try_extensions(path, package, &Extensions::Borrowed(&["ts", "tsx"]), true)?
1017      {
1018        return Ok(Some(res));
1019      }
1020    }
1021
1022    // Try appending the configured extensions.
1023    if let Some(res) = self.try_extensions(path, package, &self.resolver.extensions, true)? {
1024      return Ok(Some(res));
1025    }
1026
1027    // If there is no extension in the specifier, try an extensionless file as a last resort.
1028    if path.extension().is_none() {
1029      if let Some(res) = self.try_suffixes(path, "", package, false)? {
1030        return Ok(Some(res));
1031      }
1032    }
1033
1034    Ok(None)
1035  }
1036
1037  fn try_extensions(
1038    &self,
1039    path: &CachedPath,
1040    package: Option<&PackageJson>,
1041    extensions: &Extensions,
1042    skip_parent: bool,
1043  ) -> Result<Option<Resolution>, ResolverError> {
1044    if self.resolver.flags.contains(Flags::OPTIONAL_EXTENSIONS)
1045      && self.specifier_type != SpecifierType::Url
1046    {
1047      // Try appending each extension.
1048      for ext in extensions.iter() {
1049        // Skip parent extension if we already tried it.
1050        if skip_parent
1051          && self.resolver.flags.contains(Flags::PARENT_EXTENSION)
1052          && matches!(self.from.extension(), Some(e) if e == ext)
1053        {
1054          continue;
1055        }
1056
1057        if let Some(res) = self.try_suffixes(path, ext, package, false)? {
1058          return Ok(Some(res));
1059        }
1060      }
1061    }
1062
1063    Ok(None)
1064  }
1065
1066  fn try_suffixes(
1067    &self,
1068    path: &CachedPath,
1069    ext: &str,
1070    package: Option<&PackageJson>,
1071    alias_only: bool,
1072  ) -> Result<Option<Resolution>, ResolverError> {
1073    // TypeScript supports a moduleSuffixes option in tsconfig.json which allows suffixes
1074    // such as ".ios" to be appended just before the last extension.
1075    let tsconfig = self.tsconfig();
1076    let default_module_suffixes = [String::new()];
1077    let mut module_suffixes = default_module_suffixes.as_slice();
1078    if let Some(tsconfig) = &tsconfig {
1079      let tsconfig = unwrap_arc(tsconfig)?;
1080      if let Some(suffixes) = &tsconfig.compiler_options.module_suffixes {
1081        module_suffixes = suffixes.as_slice();
1082      }
1083    }
1084
1085    for suffix in module_suffixes {
1086      let mut p = if !suffix.is_empty() {
1087        // The suffix is placed before the _last_ extension. If we will be appending
1088        // another extension later, then we only need to append the suffix first.
1089        // Otherwise, we need to remove the original extension so we can add the suffix.
1090        // TODO: TypeScript only removes certain extensions here...
1091        let original_ext = path.extension();
1092        let mut s = if ext.is_empty() && original_ext.is_some() {
1093          path.as_path().with_extension("").into_os_string()
1094        } else {
1095          path.as_path().into()
1096        };
1097
1098        // Append the suffix (this is not necessarily an extension).
1099        s.push(suffix);
1100
1101        // Re-add the original extension if we removed it earlier.
1102        if ext.is_empty() {
1103          if let Some(original_ext) = original_ext {
1104            s.push(".");
1105            s.push(original_ext);
1106          }
1107        }
1108
1109        Cow::Owned(self.resolver.cache.get(Path::new(&s)))
1110      } else {
1111        Cow::Borrowed(path)
1112      };
1113
1114      if !ext.is_empty() {
1115        // Append the extension.
1116        p = Cow::Owned(p.into_owned().add_extension(ext, &self.resolver.cache));
1117      }
1118
1119      if let Some(res) = self.try_file(&p, package, alias_only)? {
1120        return Ok(Some(res));
1121      }
1122    }
1123
1124    Ok(None)
1125  }
1126
1127  fn try_file(
1128    &self,
1129    path: &CachedPath,
1130    package: Option<&PackageJson>,
1131    alias_only: bool,
1132  ) -> Result<Option<Resolution>, ResolverError> {
1133    if self.resolver.flags.contains(Flags::ALIASES) {
1134      // Check the project root package.json first.
1135      if let Some(package) = self.root_package() {
1136        let package = unwrap_arc(package)?;
1137        if let Ok(s) = path
1138          .as_path()
1139          .strip_prefix(package.path.parent().unwrap().as_path())
1140        {
1141          let specifier = Specifier::Relative(Cow::Borrowed(s));
1142          if let Some(res) = self.resolve_aliases(&*package, &specifier, Fields::ALIAS)? {
1143            return Ok(Some(res));
1144          }
1145        }
1146      }
1147
1148      // Next try the local package.json.
1149      if let Some(package) = package {
1150        if let Ok(s) = path
1151          .as_path()
1152          .strip_prefix(package.path.parent().unwrap().as_path())
1153        {
1154          let specifier = Specifier::Relative(Cow::Borrowed(s));
1155          let mut fields = Fields::ALIAS;
1156          if self.resolver.entries.contains(Fields::BROWSER) {
1157            fields |= Fields::BROWSER;
1158          }
1159          if let Some(res) = self.resolve_aliases(package, &specifier, fields)? {
1160            return Ok(Some(res));
1161          }
1162        }
1163      }
1164    }
1165
1166    if alias_only {
1167      return Ok(None);
1168    }
1169
1170    self.try_file_without_aliases(path)
1171  }
1172
1173  fn try_file_without_aliases(
1174    &self,
1175    path: &CachedPath,
1176  ) -> Result<Option<Resolution>, ResolverError> {
1177    if path.is_file(&*self.resolver.cache.fs) {
1178      Ok(Some(Resolution::Path(
1179        path
1180          .canonicalize(&self.resolver.cache)?
1181          .as_path()
1182          .to_owned(),
1183      )))
1184    } else {
1185      self.invalidations.invalidate_on_file_create(path.clone());
1186      Ok(None)
1187    }
1188  }
1189
1190  fn load_directory(
1191    &self,
1192    dir: &CachedPath,
1193    parent_package: Option<&PackageJson>,
1194  ) -> Result<Option<Resolution>, ResolverError> {
1195    // Check if there is a package.json in this directory, and if so, use its entries.
1196    // Note that the "exports" field is NOT used here - only in resolve_node_module.
1197    let path = dir.join("package.json", &self.resolver.cache);
1198    let mut res = Ok(None);
1199    let pkg = self
1200      .invalidations
1201      .read(&path, || path.package_json(&self.resolver.cache));
1202    let package = if let Ok(package) = &*pkg {
1203      res = self.try_package_entries(&*package);
1204      if matches!(res, Ok(Some(_))) {
1205        return res;
1206      }
1207      Some(package)
1208    } else {
1209      None
1210    };
1211
1212    // If no package.json, or no entries, try an index file with all possible extensions.
1213    if self.resolver.flags.contains(Flags::DIR_INDEX) && dir.is_dir(&*self.resolver.cache.fs) {
1214      return self.load_file(
1215        &dir.join(self.resolver.index_file, &self.resolver.cache),
1216        package.as_deref().or(parent_package),
1217      );
1218    }
1219
1220    res
1221  }
1222
1223  fn resolve_tsconfig_paths(&self) -> Result<Option<Resolution>, ResolverError> {
1224    if let Some(tsconfig) = self.tsconfig() {
1225      for path in unwrap_arc(tsconfig)?
1226        .compiler_options
1227        .paths(self.specifier, &self.resolver.cache)
1228      {
1229        // TODO: should aliases apply to tsconfig paths??
1230        if let Some(res) = self.load_path(&path, None)? {
1231          return Ok(Some(res));
1232        }
1233      }
1234    }
1235
1236    Ok(None)
1237  }
1238
1239  fn tsconfig(&self) -> &Option<Arc<Result<TsConfigWrapper, ResolverError>>> {
1240    if self.resolver.flags.contains(Flags::TSCONFIG)
1241      && self
1242        .flags
1243        .intersects(RequestFlags::IN_TS_FILE | RequestFlags::IN_JS_FILE)
1244      && !self.flags.contains(RequestFlags::IN_NODE_MODULES)
1245    {
1246      self.tsconfig.get_or_init(|| {
1247        if let Some(path) = self.find_ancestor_file(self.from, "tsconfig.json") {
1248          return Some(self.read_tsconfig(path));
1249        }
1250
1251        None
1252      })
1253    } else {
1254      &None
1255    }
1256  }
1257
1258  fn read_tsconfig(&self, path: CachedPath) -> Arc<Result<TsConfigWrapper, ResolverError>> {
1259    self.invalidations.read(&path, || {
1260      path.tsconfig(&self.resolver.cache, |tsconfig| {
1261        for i in 0..tsconfig.extends.len() {
1262          let path = match &tsconfig.extends[i] {
1263            Specifier::Absolute(path) => self.resolver.cache.get(path),
1264            Specifier::Relative(path) => {
1265              let mut absolute_path = tsconfig
1266                .compiler_options
1267                .path
1268                .resolve(path, &self.resolver.cache);
1269
1270              // TypeScript allows "." and ".." to implicitly refer to a tsconfig.json file.
1271              if path == Path::new(".") || path == Path::new("..") {
1272                absolute_path = absolute_path.join("tsconfig.json", &self.resolver.cache);
1273              }
1274
1275              let mut exists = absolute_path.is_file(&*self.resolver.cache.fs);
1276
1277              // If the file doesn't exist, and doesn't end with `.json`, try appending the extension.
1278              if !exists {
1279                let try_extension = match absolute_path.extension() {
1280                  None => true,
1281                  Some(ext) => ext != "json",
1282                };
1283
1284                if try_extension {
1285                  absolute_path = absolute_path.add_extension("json", &self.resolver.cache);
1286                  exists = absolute_path.is_file(&*self.resolver.cache.fs);
1287                }
1288              }
1289
1290              if !exists {
1291                return Err(ResolverError::TsConfigExtendsNotFound {
1292                  tsconfig: tsconfig.compiler_options.path.as_path().to_path_buf(),
1293                  error: Box::new(ResolverError::FileNotFound {
1294                    relative: path.to_path_buf(),
1295                    from: tsconfig.compiler_options.path.as_path().to_path_buf(),
1296                  }),
1297                });
1298              }
1299
1300              absolute_path
1301            }
1302            specifier @ Specifier::Package(..) => {
1303              let resolver = Resolver {
1304                project_root: self.resolver.project_root.clone(),
1305                extensions: Extensions::Borrowed(&["json"]),
1306                index_file: "tsconfig.json",
1307                entries: Fields::TSCONFIG,
1308                flags: Flags::NODE_CJS,
1309                cache: CacheCow::Borrowed(&self.resolver.cache),
1310                include_node_modules: Cow::Owned(IncludeNodeModules::default()),
1311                conditions: ExportsCondition::TYPES,
1312                module_dir_resolver: self.resolver.module_dir_resolver.clone(),
1313              };
1314
1315              let req = ResolveRequest::new(
1316                &resolver,
1317                specifier,
1318                SpecifierType::Cjs,
1319                &tsconfig.compiler_options.path,
1320                self.invalidations,
1321              );
1322
1323              let res = req
1324                .resolve()
1325                .map_err(|err| ResolverError::TsConfigExtendsNotFound {
1326                  tsconfig: tsconfig.compiler_options.path.as_path().to_path_buf(),
1327                  error: Box::new(err),
1328                })?;
1329
1330              if let Resolution::Path(res) = res {
1331                self.resolver.cache.get(&res)
1332              } else {
1333                return Err(ResolverError::TsConfigExtendsNotFound {
1334                  tsconfig: tsconfig.compiler_options.path.as_path().to_path_buf(),
1335                  error: Box::new(ResolverError::UnknownError),
1336                });
1337              }
1338            }
1339            _ => return Ok(()),
1340          };
1341
1342          let extended = self.read_tsconfig(path);
1343          match &*extended {
1344            Ok(extended) => {
1345              tsconfig.compiler_options.extend(&extended.compiler_options);
1346            }
1347            Err(e) => return Err(e.clone()),
1348          }
1349        }
1350
1351        Ok(())
1352      })
1353    })
1354  }
1355}
1356
1357fn unwrap_arc<T, E: Clone>(arc: &Arc<Result<T, E>>) -> Result<&T, E> {
1358  match &**arc {
1359    Ok(v) => Ok(v),
1360    Err(e) => Err(e.clone()),
1361  }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366  use std::collections::{HashMap, HashSet};
1367
1368  use super::*;
1369
1370  #[derive(PartialEq, Eq, Hash, Debug, Clone)]
1371  pub enum UncachedFileCreateInvalidation {
1372    Path(PathBuf),
1373    FileName { file_name: String, above: PathBuf },
1374    Glob(String),
1375  }
1376
1377  impl From<FileCreateInvalidation> for UncachedFileCreateInvalidation {
1378    fn from(value: FileCreateInvalidation) -> Self {
1379      match value {
1380        FileCreateInvalidation::Path(cached_path) => {
1381          UncachedFileCreateInvalidation::Path(cached_path.as_path().to_owned())
1382        }
1383        FileCreateInvalidation::FileName { file_name, above } => {
1384          UncachedFileCreateInvalidation::FileName {
1385            file_name,
1386            above: above.as_path().to_owned(),
1387          }
1388        }
1389        FileCreateInvalidation::Glob(glob) => UncachedFileCreateInvalidation::Glob(glob),
1390      }
1391    }
1392  }
1393
1394  fn root() -> PathBuf {
1395    Path::new(env!("CARGO_MANIFEST_DIR"))
1396      .parent()
1397      .unwrap()
1398      .parent()
1399      .unwrap()
1400      .join("packages/utils/node-resolver-core/test/fixture")
1401  }
1402
1403  fn test_resolver<'a>() -> Resolver<'a> {
1404    Resolver::parcel(&root(), Cache::default())
1405  }
1406
1407  fn node_resolver<'a>() -> Resolver<'a> {
1408    Resolver::node(&root(), Cache::default())
1409  }
1410
1411  #[test]
1412  fn relative() {
1413    assert_eq!(
1414      test_resolver()
1415        .resolve("./bar.js", &root().join("foo.js"), SpecifierType::Esm)
1416        .result
1417        .unwrap()
1418        .resolution,
1419      Resolution::Path(root().join("bar.js"))
1420    );
1421    assert_eq!(
1422      test_resolver()
1423        .resolve(".///bar.js", &root().join("foo.js"), SpecifierType::Esm)
1424        .result
1425        .unwrap()
1426        .resolution,
1427      Resolution::Path(root().join("bar.js"))
1428    );
1429    assert_eq!(
1430      test_resolver()
1431        .resolve("./bar", &root().join("foo.js"), SpecifierType::Esm)
1432        .result
1433        .unwrap()
1434        .resolution,
1435      Resolution::Path(root().join("bar.js"))
1436    );
1437    assert_eq!(
1438      test_resolver()
1439        .resolve("~/bar", &root().join("nested/test.js"), SpecifierType::Esm)
1440        .result
1441        .unwrap()
1442        .resolution,
1443      Resolution::Path(root().join("bar.js"))
1444    );
1445    assert_eq!(
1446      test_resolver()
1447        .resolve("~bar", &root().join("nested/test.js"), SpecifierType::Esm)
1448        .result
1449        .unwrap()
1450        .resolution,
1451      Resolution::Path(root().join("bar.js"))
1452    );
1453    assert_eq!(
1454      test_resolver()
1455        .resolve(
1456          "~/bar",
1457          &root().join("node_modules/foo/nested/baz.js"),
1458          SpecifierType::Esm
1459        )
1460        .result
1461        .unwrap()
1462        .resolution,
1463      Resolution::Path(root().join("node_modules/foo/bar.js"))
1464    );
1465    assert_eq!(
1466      test_resolver()
1467        .resolve("./nested", &root().join("foo.js"), SpecifierType::Esm)
1468        .result
1469        .unwrap()
1470        .resolution,
1471      Resolution::Path(root().join("nested/index.js"))
1472    );
1473    assert_eq!(
1474      test_resolver()
1475        .resolve("./bar?foo=2", &root().join("foo.js"), SpecifierType::Esm)
1476        .result
1477        .unwrap()
1478        .resolution,
1479      Resolution::Path(root().join("bar.js"))
1480    );
1481    assert_eq!(
1482      test_resolver()
1483        .resolve("./bar?foo=2", &root().join("foo.js"), SpecifierType::Cjs)
1484        .result
1485        .unwrap_err(),
1486      ResolverError::FileNotFound {
1487        relative: "bar?foo=2".into(),
1488        from: root().join("foo.js")
1489      },
1490    );
1491    assert_eq!(
1492      test_resolver()
1493        .resolve(
1494          "./foo",
1495          &root().join("priority/index.js"),
1496          SpecifierType::Esm
1497        )
1498        .result
1499        .unwrap()
1500        .resolution,
1501      Resolution::Path(root().join("priority/foo.js"))
1502    );
1503
1504    let invalidations = test_resolver()
1505      .resolve("./bar", &root().join("foo.js"), SpecifierType::Esm)
1506      .invalidations;
1507    assert_eq!(
1508      invalidations
1509        .invalidate_on_file_create
1510        .borrow()
1511        .iter()
1512        .collect::<HashSet<_>>(),
1513      HashSet::new()
1514    );
1515    assert_eq!(
1516      invalidations
1517        .invalidate_on_file_change
1518        .borrow()
1519        .iter()
1520        .map(|p| p.as_path().to_owned())
1521        .collect::<HashSet<_>>(),
1522      HashSet::from([root().join("package.json"), root().join("tsconfig.json")])
1523    );
1524  }
1525
1526  #[test]
1527  fn test_absolute() {
1528    assert_eq!(
1529      test_resolver()
1530        .resolve("/bar", &root().join("nested/test.js"), SpecifierType::Esm)
1531        .result
1532        .unwrap()
1533        .resolution,
1534      Resolution::Path(root().join("bar.js"))
1535    );
1536    assert_eq!(
1537      test_resolver()
1538        .resolve(
1539          "/bar",
1540          &root().join("node_modules/foo/index.js"),
1541          SpecifierType::Esm
1542        )
1543        .result
1544        .unwrap()
1545        .resolution,
1546      Resolution::Path(root().join("bar.js"))
1547    );
1548
1549    #[cfg(not(windows))]
1550    {
1551      assert_eq!(
1552        test_resolver()
1553          .resolve(
1554            "file:///bar",
1555            &root().join("nested/test.js"),
1556            SpecifierType::Esm
1557          )
1558          .result
1559          .unwrap()
1560          .resolution,
1561        Resolution::Path(root().join("bar.js"))
1562      );
1563      assert_eq!(
1564        node_resolver()
1565          .resolve(
1566            root().join("foo.js").to_str().unwrap(),
1567            &root().join("nested/test.js"),
1568            SpecifierType::Esm
1569          )
1570          .result
1571          .unwrap()
1572          .resolution,
1573        Resolution::Path(root().join("foo.js"))
1574      );
1575      assert_eq!(
1576        node_resolver()
1577          .resolve(
1578            &format!("file://{}", root().join("foo.js").to_str().unwrap()),
1579            &root().join("nested/test.js"),
1580            SpecifierType::Esm
1581          )
1582          .result
1583          .unwrap()
1584          .resolution,
1585        Resolution::Path(root().join("foo.js"))
1586      );
1587    }
1588  }
1589
1590  #[test]
1591  fn node_modules() {
1592    assert_eq!(
1593      test_resolver()
1594        .resolve("foo", &root().join("foo.js"), SpecifierType::Esm)
1595        .result
1596        .unwrap()
1597        .resolution,
1598      Resolution::Path(root().join("node_modules/foo/index.js"))
1599    );
1600    assert_eq!(
1601      test_resolver()
1602        .resolve("package-main", &root().join("foo.js"), SpecifierType::Esm)
1603        .result
1604        .unwrap()
1605        .resolution,
1606      Resolution::Path(root().join("node_modules/package-main/main.js"))
1607    );
1608    assert_eq!(
1609      test_resolver()
1610        .resolve("package-module", &root().join("foo.js"), SpecifierType::Esm)
1611        .result
1612        .unwrap()
1613        .resolution,
1614      Resolution::Path(root().join("node_modules/package-module/module.js"))
1615    );
1616    assert_eq!(
1617      test_resolver()
1618        .resolve(
1619          "package-browser",
1620          &root().join("foo.js"),
1621          SpecifierType::Esm
1622        )
1623        .result
1624        .unwrap()
1625        .resolution,
1626      Resolution::Path(root().join("node_modules/package-browser/browser.js"))
1627    );
1628    assert_eq!(
1629      test_resolver()
1630        .resolve(
1631          "package-fallback",
1632          &root().join("foo.js"),
1633          SpecifierType::Esm
1634        )
1635        .result
1636        .unwrap()
1637        .resolution,
1638      Resolution::Path(root().join("node_modules/package-fallback/index.js"))
1639    );
1640    assert_eq!(
1641      test_resolver()
1642        .resolve(
1643          "package-main-directory",
1644          &root().join("foo.js"),
1645          SpecifierType::Esm
1646        )
1647        .result
1648        .unwrap()
1649        .resolution,
1650      Resolution::Path(root().join("node_modules/package-main-directory/nested/index.js"))
1651    );
1652    assert_eq!(
1653      test_resolver()
1654        .resolve("foo/nested/baz", &root().join("foo.js"), SpecifierType::Esm)
1655        .result
1656        .unwrap()
1657        .resolution,
1658      Resolution::Path(root().join("node_modules/foo/nested/baz.js"))
1659    );
1660    assert_eq!(
1661      test_resolver()
1662        .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm)
1663        .result
1664        .unwrap()
1665        .resolution,
1666      Resolution::Path(root().join("node_modules/@scope/pkg/index.js"))
1667    );
1668    assert_eq!(
1669      test_resolver()
1670        .resolve(
1671          "@scope/pkg/foo/bar",
1672          &root().join("foo.js"),
1673          SpecifierType::Esm
1674        )
1675        .result
1676        .unwrap()
1677        .resolution,
1678      Resolution::Path(root().join("node_modules/@scope/pkg/foo/bar.js"))
1679    );
1680    assert_eq!(
1681      test_resolver()
1682        .resolve(
1683          "foo/with space.mjs",
1684          &root().join("foo.js"),
1685          SpecifierType::Esm
1686        )
1687        .result
1688        .unwrap()
1689        .resolution,
1690      Resolution::Path(root().join("node_modules/foo/with space.mjs"))
1691    );
1692    assert_eq!(
1693      test_resolver()
1694        .resolve(
1695          "foo/with%20space.mjs",
1696          &root().join("foo.js"),
1697          SpecifierType::Esm
1698        )
1699        .result
1700        .unwrap()
1701        .resolution,
1702      Resolution::Path(root().join("node_modules/foo/with space.mjs"))
1703    );
1704    assert_eq!(
1705      test_resolver()
1706        .resolve(
1707          "foo/with space.mjs",
1708          &root().join("foo.js"),
1709          SpecifierType::Cjs
1710        )
1711        .result
1712        .unwrap()
1713        .resolution,
1714      Resolution::Path(root().join("node_modules/foo/with space.mjs"))
1715    );
1716    assert_eq!(
1717      test_resolver()
1718        .resolve(
1719          "foo/with%20space.mjs",
1720          &root().join("foo.js"),
1721          SpecifierType::Cjs
1722        )
1723        .result
1724        .unwrap_err(),
1725      ResolverError::ModuleSubpathNotFound {
1726        module: "foo".into(),
1727        path: root().join("node_modules/foo/with%20space.mjs"),
1728        package_path: root().join("node_modules/foo/package.json")
1729      },
1730    );
1731    assert_eq!(
1732      test_resolver()
1733        .resolve(
1734          "@scope/pkg?foo=2",
1735          &root().join("foo.js"),
1736          SpecifierType::Esm
1737        )
1738        .result
1739        .unwrap()
1740        .resolution,
1741      Resolution::Path(root().join("node_modules/@scope/pkg/index.js"))
1742    );
1743    assert_eq!(
1744      test_resolver()
1745        .resolve(
1746          "@scope/pkg?foo=2",
1747          &root().join("foo.js"),
1748          SpecifierType::Cjs
1749        )
1750        .result
1751        .unwrap_err(),
1752      ResolverError::ModuleNotFound {
1753        module: "@scope/pkg?foo=2".into()
1754      },
1755    );
1756
1757    let invalidations = test_resolver()
1758      .resolve("foo", &root().join("foo.js"), SpecifierType::Esm)
1759      .invalidations;
1760    assert_eq!(
1761      invalidations
1762        .invalidate_on_file_create
1763        .borrow()
1764        .iter()
1765        .map(|p| p.clone().into())
1766        .collect::<HashSet<_>>(),
1767      HashSet::from([UncachedFileCreateInvalidation::FileName {
1768        file_name: "node_modules/foo".into(),
1769        above: root()
1770      },])
1771    );
1772    assert_eq!(
1773      invalidations
1774        .invalidate_on_file_change
1775        .borrow()
1776        .iter()
1777        .map(|p| p.as_path().to_owned())
1778        .collect::<HashSet<_>>(),
1779      HashSet::from([
1780        root().join("node_modules/foo/package.json"),
1781        root().join("package.json"),
1782        root().join("tsconfig.json")
1783      ])
1784    );
1785  }
1786
1787  #[test]
1788  fn browser_field() {
1789    assert_eq!(
1790      test_resolver()
1791        .resolve(
1792          "package-browser-alias",
1793          &root().join("foo.js"),
1794          SpecifierType::Esm
1795        )
1796        .result
1797        .unwrap()
1798        .resolution,
1799      Resolution::Path(root().join("node_modules/package-browser-alias/browser.js"))
1800    );
1801    assert_eq!(
1802      test_resolver()
1803        .resolve(
1804          "package-browser-alias/foo",
1805          &root().join("foo.js"),
1806          SpecifierType::Esm
1807        )
1808        .result
1809        .unwrap()
1810        .resolution,
1811      Resolution::Path(root().join("node_modules/package-browser-alias/bar.js"))
1812    );
1813    assert_eq!(
1814      test_resolver()
1815        .resolve(
1816          "./foo",
1817          &root().join("node_modules/package-browser-alias/browser.js"),
1818          SpecifierType::Esm
1819        )
1820        .result
1821        .unwrap()
1822        .resolution,
1823      Resolution::Path(root().join("node_modules/package-browser-alias/bar.js"))
1824    );
1825    assert_eq!(
1826      test_resolver()
1827        .resolve(
1828          "./nested",
1829          &root().join("node_modules/package-browser-alias/browser.js"),
1830          SpecifierType::Esm
1831        )
1832        .result
1833        .unwrap()
1834        .resolution,
1835      Resolution::Path(
1836        root().join("node_modules/package-browser-alias/subfolder1/subfolder2/subfile.js")
1837      )
1838    );
1839  }
1840
1841  #[test]
1842  fn local_aliases() {
1843    assert_eq!(
1844      test_resolver()
1845        .resolve(
1846          "package-alias/foo",
1847          &root().join("foo.js"),
1848          SpecifierType::Esm
1849        )
1850        .result
1851        .unwrap()
1852        .resolution,
1853      Resolution::Path(root().join("node_modules/package-alias/bar.js"))
1854    );
1855    assert_eq!(
1856      test_resolver()
1857        .resolve(
1858          "./foo",
1859          &root().join("node_modules/package-alias/browser.js"),
1860          SpecifierType::Esm
1861        )
1862        .result
1863        .unwrap()
1864        .resolution,
1865      Resolution::Path(root().join("node_modules/package-alias/bar.js"))
1866    );
1867    assert_eq!(
1868      test_resolver()
1869        .resolve(
1870          "./lib/test",
1871          &root().join("node_modules/package-alias-glob/browser.js"),
1872          SpecifierType::Esm
1873        )
1874        .result
1875        .unwrap()
1876        .resolution,
1877      Resolution::Path(root().join("node_modules/package-alias-glob/src/test.js"))
1878    );
1879    assert_eq!(
1880      test_resolver()
1881        .resolve(
1882          "package-browser-exclude",
1883          &root().join("foo.js"),
1884          SpecifierType::Esm
1885        )
1886        .result
1887        .unwrap()
1888        .resolution,
1889      Resolution::Empty
1890    );
1891    assert_eq!(
1892      test_resolver()
1893        .resolve(
1894          "./lib/test",
1895          &root().join("node_modules/package-alias-glob/index.js"),
1896          SpecifierType::Esm
1897        )
1898        .result
1899        .unwrap()
1900        .resolution,
1901      Resolution::Path(root().join("node_modules/package-alias-glob/src/test.js"))
1902    );
1903
1904    let invalidations = test_resolver()
1905      .resolve(
1906        "package-alias/foo",
1907        &root().join("foo.js"),
1908        SpecifierType::Esm,
1909      )
1910      .invalidations;
1911    assert_eq!(
1912      invalidations
1913        .invalidate_on_file_create
1914        .borrow()
1915        .iter()
1916        .map(|p| p.clone().into())
1917        .collect::<HashSet<_>>(),
1918      HashSet::from([UncachedFileCreateInvalidation::FileName {
1919        file_name: "node_modules/package-alias".into(),
1920        above: root()
1921      },])
1922    );
1923    assert_eq!(
1924      invalidations
1925        .invalidate_on_file_change
1926        .borrow()
1927        .iter()
1928        .map(|p| p.as_path().to_owned())
1929        .collect::<HashSet<_>>(),
1930      HashSet::from([
1931        root().join("node_modules/package-alias/package.json"),
1932        root().join("package.json"),
1933        root().join("tsconfig.json")
1934      ])
1935    );
1936  }
1937
1938  #[test]
1939  fn global_aliases() {
1940    assert_eq!(
1941      test_resolver()
1942        .resolve("aliased", &root().join("foo.js"), SpecifierType::Esm)
1943        .result
1944        .unwrap()
1945        .resolution,
1946      Resolution::Path(root().join("node_modules/foo/index.js"))
1947    );
1948    assert_eq!(
1949      test_resolver()
1950        .resolve(
1951          "aliased",
1952          &root().join("node_modules/package-alias/foo.js"),
1953          SpecifierType::Esm
1954        )
1955        .result
1956        .unwrap()
1957        .resolution,
1958      Resolution::Path(root().join("node_modules/foo/index.js"))
1959    );
1960    assert_eq!(
1961      test_resolver()
1962        .resolve(
1963          "aliased/bar",
1964          &root().join("node_modules/package-alias/foo.js"),
1965          SpecifierType::Esm
1966        )
1967        .result
1968        .unwrap()
1969        .resolution,
1970      Resolution::Path(root().join("node_modules/foo/bar.js"))
1971    );
1972    assert_eq!(
1973      test_resolver()
1974        .resolve("aliased-file", &root().join("foo.js"), SpecifierType::Esm)
1975        .result
1976        .unwrap()
1977        .resolution,
1978      Resolution::Path(root().join("bar.js"))
1979    );
1980    assert_eq!(
1981      test_resolver()
1982        .resolve(
1983          "aliased-file",
1984          &root().join("node_modules/package-alias/foo.js"),
1985          SpecifierType::Esm
1986        )
1987        .result
1988        .unwrap()
1989        .resolution,
1990      Resolution::Path(root().join("bar.js"))
1991    );
1992    assert_eq!(
1993      test_resolver()
1994        .resolve(
1995          "aliasedfolder/test.js",
1996          &root().join("foo.js"),
1997          SpecifierType::Esm
1998        )
1999        .result
2000        .unwrap()
2001        .resolution,
2002      Resolution::Path(root().join("nested/test.js"))
2003    );
2004    assert_eq!(
2005      test_resolver()
2006        .resolve("aliasedfolder", &root().join("foo.js"), SpecifierType::Esm)
2007        .result
2008        .unwrap()
2009        .resolution,
2010      Resolution::Path(root().join("nested/index.js"))
2011    );
2012    assert_eq!(
2013      test_resolver()
2014        .resolve(
2015          "aliasedabsolute/test.js",
2016          &root().join("foo.js"),
2017          SpecifierType::Esm
2018        )
2019        .result
2020        .unwrap()
2021        .resolution,
2022      Resolution::Path(root().join("nested/test.js"))
2023    );
2024    assert_eq!(
2025      test_resolver()
2026        .resolve(
2027          "aliasedabsolute",
2028          &root().join("foo.js"),
2029          SpecifierType::Esm
2030        )
2031        .result
2032        .unwrap()
2033        .resolution,
2034      Resolution::Path(root().join("nested/index.js"))
2035    );
2036    assert_eq!(
2037      test_resolver()
2038        .resolve("foo/bar", &root().join("foo.js"), SpecifierType::Esm)
2039        .result
2040        .unwrap()
2041        .resolution,
2042      Resolution::Path(root().join("bar.js"))
2043    );
2044    assert_eq!(
2045      test_resolver()
2046        .resolve("glob/bar/test", &root().join("foo.js"), SpecifierType::Esm)
2047        .result
2048        .unwrap()
2049        .resolution,
2050      Resolution::Path(root().join("nested/test.js"))
2051    );
2052    assert_eq!(
2053      test_resolver()
2054        .resolve("something", &root().join("foo.js"), SpecifierType::Esm)
2055        .result
2056        .unwrap()
2057        .resolution,
2058      Resolution::Path(root().join("nested/test.js"))
2059    );
2060    assert_eq!(
2061      test_resolver()
2062        .resolve(
2063          "something",
2064          &root().join("node_modules/package-alias/foo.js"),
2065          SpecifierType::Esm
2066        )
2067        .result
2068        .unwrap()
2069        .resolution,
2070      Resolution::Path(root().join("nested/test.js"))
2071    );
2072    assert_eq!(
2073      test_resolver()
2074        .resolve(
2075          "package-alias-exclude",
2076          &root().join("foo.js"),
2077          SpecifierType::Esm
2078        )
2079        .result
2080        .unwrap()
2081        .resolution,
2082      Resolution::Empty
2083    );
2084    assert_eq!(
2085      test_resolver()
2086        .resolve("./baz", &root().join("foo.js"), SpecifierType::Esm)
2087        .result
2088        .unwrap()
2089        .resolution,
2090      Resolution::Path(root().join("bar.js"))
2091    );
2092    assert_eq!(
2093      test_resolver()
2094        .resolve("../baz", &root().join("x/foo.js"), SpecifierType::Esm)
2095        .result
2096        .unwrap()
2097        .resolution,
2098      Resolution::Path(root().join("bar.js"))
2099    );
2100    assert_eq!(
2101      test_resolver()
2102        .resolve("~/baz", &root().join("x/foo.js"), SpecifierType::Esm)
2103        .result
2104        .unwrap()
2105        .resolution,
2106      Resolution::Path(root().join("bar.js"))
2107    );
2108    assert_eq!(
2109      test_resolver()
2110        .resolve(
2111          "./baz",
2112          &root().join("node_modules/foo/bar.js"),
2113          SpecifierType::Esm
2114        )
2115        .result
2116        .unwrap()
2117        .resolution,
2118      Resolution::Path(root().join("node_modules/foo/baz.js"))
2119    );
2120    assert_eq!(
2121      test_resolver()
2122        .resolve(
2123          "~/baz",
2124          &root().join("node_modules/foo/bar.js"),
2125          SpecifierType::Esm
2126        )
2127        .result
2128        .unwrap()
2129        .resolution,
2130      Resolution::Path(root().join("node_modules/foo/baz.js"))
2131    );
2132    assert_eq!(
2133      test_resolver()
2134        .resolve(
2135          "/baz",
2136          &root().join("node_modules/foo/bar.js"),
2137          SpecifierType::Esm
2138        )
2139        .result
2140        .unwrap()
2141        .resolution,
2142      Resolution::Path(root().join("bar.js"))
2143    );
2144    assert_eq!(
2145      test_resolver()
2146        .resolve("url", &root().join("foo.js"), SpecifierType::Esm)
2147        .result
2148        .unwrap()
2149        .resolution,
2150      Resolution::Empty
2151    );
2152  }
2153
2154  #[test]
2155  fn test_urls() {
2156    assert_eq!(
2157      test_resolver()
2158        .resolve(
2159          "http://example.com/foo.png",
2160          &root().join("foo.js"),
2161          SpecifierType::Url
2162        )
2163        .result
2164        .unwrap()
2165        .resolution,
2166      Resolution::External
2167    );
2168    assert_eq!(
2169      test_resolver()
2170        .resolve(
2171          "//example.com/foo.png",
2172          &root().join("foo.js"),
2173          SpecifierType::Url
2174        )
2175        .result
2176        .unwrap()
2177        .resolution,
2178      Resolution::External
2179    );
2180    assert_eq!(
2181      test_resolver()
2182        .resolve("#hash", &root().join("foo.js"), SpecifierType::Url)
2183        .result
2184        .unwrap()
2185        .resolution,
2186      Resolution::External
2187    );
2188    assert_eq!(
2189      test_resolver()
2190        .resolve(
2191          "http://example.com/foo.png",
2192          &root().join("foo.js"),
2193          SpecifierType::Esm
2194        )
2195        .result
2196        .unwrap_err(),
2197      ResolverError::UnknownScheme {
2198        scheme: "http".into()
2199      },
2200    );
2201    assert_eq!(
2202      test_resolver()
2203        .resolve("bar.js", &root().join("foo.js"), SpecifierType::Url)
2204        .result
2205        .unwrap()
2206        .resolution,
2207      Resolution::Path(root().join("bar.js"))
2208    );
2209    // Reproduce bug for now
2210    // assert_eq!(
2211    //   test_resolver()
2212    //     .resolve("bar", &root().join("foo.js"), SpecifierType::Url)
2213    //     .result
2214    //     .unwrap_err(),
2215    //   ResolverError::FileNotFound {
2216    //     relative: "bar".into(),
2217    //     from: root().join("foo.js")
2218    //   }
2219    // );
2220    assert_eq!(
2221      test_resolver()
2222        .resolve("bar", &root().join("foo.js"), SpecifierType::Url)
2223        .result
2224        .unwrap()
2225        .resolution,
2226      Resolution::Path(root().join("bar.js"))
2227    );
2228    assert_eq!(
2229      test_resolver()
2230        .resolve("npm:foo", &root().join("foo.js"), SpecifierType::Url)
2231        .result
2232        .unwrap()
2233        .resolution,
2234      Resolution::Path(root().join("node_modules/foo/index.js"))
2235    );
2236    assert_eq!(
2237      test_resolver()
2238        .resolve("npm:@scope/pkg", &root().join("foo.js"), SpecifierType::Url)
2239        .result
2240        .unwrap()
2241        .resolution,
2242      Resolution::Path(root().join("node_modules/@scope/pkg/index.js"))
2243    );
2244  }
2245
2246  #[test]
2247  fn test_exports() {
2248    assert_eq!(
2249      test_resolver()
2250        .resolve(
2251          "package-exports",
2252          &root().join("foo.js"),
2253          SpecifierType::Esm
2254        )
2255        .result
2256        .unwrap()
2257        .resolution,
2258      Resolution::Path(root().join("node_modules/package-exports/main.mjs"))
2259    );
2260    assert_eq!(
2261      test_resolver()
2262        .resolve(
2263          "package-exports/foo",
2264          &root().join("foo.js"),
2265          SpecifierType::Esm
2266        )
2267        .result
2268        .unwrap()
2269        .resolution,
2270      // "browser" field is NOT used.
2271      Resolution::Path(root().join("node_modules/package-exports/foo.mjs"))
2272    );
2273    assert_eq!(
2274      test_resolver()
2275        .resolve(
2276          "package-exports/features/test",
2277          &root().join("foo.js"),
2278          SpecifierType::Esm
2279        )
2280        .result
2281        .unwrap()
2282        .resolution,
2283      Resolution::Path(root().join("node_modules/package-exports/features/test.mjs"))
2284    );
2285    assert_eq!(
2286      test_resolver()
2287        .resolve(
2288          "package-exports/extensionless-features/test",
2289          &root().join("foo.js"),
2290          SpecifierType::Esm
2291        )
2292        .result
2293        .unwrap()
2294        .resolution,
2295      Resolution::Path(root().join("node_modules/package-exports/features/test.mjs"))
2296    );
2297    assert_eq!(
2298      test_resolver()
2299        .resolve(
2300          "package-exports/extensionless-features/test.mjs",
2301          &root().join("foo.js"),
2302          SpecifierType::Esm
2303        )
2304        .result
2305        .unwrap()
2306        .resolution,
2307      Resolution::Path(root().join("node_modules/package-exports/features/test.mjs"))
2308    );
2309    assert_eq!(
2310      node_resolver()
2311        .resolve(
2312          "package-exports/extensionless-features/test",
2313          &root().join("foo.js"),
2314          SpecifierType::Esm
2315        )
2316        .result
2317        .unwrap_err(),
2318      ResolverError::ModuleSubpathNotFound {
2319        module: "package-exports".into(),
2320        package_path: root().join("node_modules/package-exports/package.json"),
2321        path: root().join("node_modules/package-exports/features/test"),
2322      },
2323    );
2324    assert_eq!(
2325      node_resolver()
2326        .resolve(
2327          "package-exports/extensionless-features/test",
2328          &root().join("foo.js"),
2329          SpecifierType::Cjs
2330        )
2331        .result
2332        .unwrap_err(),
2333      ResolverError::ModuleSubpathNotFound {
2334        module: "package-exports".into(),
2335        package_path: root().join("node_modules/package-exports/package.json"),
2336        path: root().join("node_modules/package-exports/features/test"),
2337      },
2338    );
2339    assert_eq!(
2340      node_resolver()
2341        .resolve(
2342          "package-exports/extensionless-features/test.mjs",
2343          &root().join("foo.js"),
2344          SpecifierType::Esm
2345        )
2346        .result
2347        .unwrap()
2348        .resolution,
2349      Resolution::Path(root().join("node_modules/package-exports/features/test.mjs"))
2350    );
2351    assert_eq!(
2352      test_resolver()
2353        .resolve(
2354          "package-exports/space",
2355          &root().join("foo.js"),
2356          SpecifierType::Esm
2357        )
2358        .result
2359        .unwrap()
2360        .resolution,
2361      Resolution::Path(root().join("node_modules/package-exports/with space.mjs"))
2362    );
2363    // assert_eq!(
2364    //   test_resolver().resolve("package-exports/with%20space", &root().join("foo.js"), SpecifierType::Esm).unwrap().resolution,
2365    //   Resolution::Path(root().join("node_modules/package-exports/with space.mjs"))
2366    // );
2367    assert_eq!(
2368      test_resolver()
2369        .resolve(
2370          "package-exports/with space",
2371          &root().join("foo.js"),
2372          SpecifierType::Esm
2373        )
2374        .result
2375        .unwrap_err(),
2376      ResolverError::PackageJsonError {
2377        module: "package-exports".into(),
2378        path: root().join("node_modules/package-exports/package.json"),
2379        error: PackageJsonError::PackagePathNotExported
2380      },
2381    );
2382    assert_eq!(
2383      test_resolver()
2384        .resolve(
2385          "package-exports/internal",
2386          &root().join("foo.js"),
2387          SpecifierType::Esm
2388        )
2389        .result
2390        .unwrap_err(),
2391      ResolverError::PackageJsonError {
2392        module: "package-exports".into(),
2393        path: root().join("node_modules/package-exports/package.json"),
2394        error: PackageJsonError::PackagePathNotExported
2395      },
2396    );
2397    assert_eq!(
2398      test_resolver()
2399        .resolve(
2400          "package-exports/internal.mjs",
2401          &root().join("foo.js"),
2402          SpecifierType::Esm
2403        )
2404        .result
2405        .unwrap_err(),
2406      ResolverError::PackageJsonError {
2407        module: "package-exports".into(),
2408        path: root().join("node_modules/package-exports/package.json"),
2409        error: PackageJsonError::PackagePathNotExported
2410      },
2411    );
2412    assert_eq!(
2413      test_resolver()
2414        .resolve(
2415          "package-exports/invalid",
2416          &root().join("foo.js"),
2417          SpecifierType::Esm
2418        )
2419        .result
2420        .unwrap_err(),
2421      ResolverError::PackageJsonError {
2422        module: "package-exports".into(),
2423        path: root().join("node_modules/package-exports/package.json"),
2424        error: PackageJsonError::InvalidPackageTarget
2425      }
2426    );
2427  }
2428
2429  #[test]
2430  fn test_self_reference() {
2431    assert_eq!(
2432      test_resolver()
2433        .resolve(
2434          "package-exports",
2435          &root().join("node_modules/package-exports/foo.js"),
2436          SpecifierType::Esm
2437        )
2438        .result
2439        .unwrap()
2440        .resolution,
2441      Resolution::Path(root().join("node_modules/package-exports/main.mjs"))
2442    );
2443    assert_eq!(
2444      test_resolver()
2445        .resolve(
2446          "package-exports/foo",
2447          &root().join("node_modules/package-exports/foo.js"),
2448          SpecifierType::Esm
2449        )
2450        .result
2451        .unwrap()
2452        .resolution,
2453      Resolution::Path(root().join("node_modules/package-exports/foo.mjs"))
2454    );
2455  }
2456
2457  #[test]
2458  fn test_imports() {
2459    assert_eq!(
2460      test_resolver()
2461        .resolve(
2462          "#internal",
2463          &root().join("node_modules/package-exports/main.mjs"),
2464          SpecifierType::Esm
2465        )
2466        .result
2467        .unwrap()
2468        .resolution,
2469      Resolution::Path(root().join("node_modules/package-exports/internal.mjs"))
2470    );
2471    assert_eq!(
2472      test_resolver()
2473        .resolve(
2474          "#foo",
2475          &root().join("node_modules/package-exports/main.mjs"),
2476          SpecifierType::Esm
2477        )
2478        .result
2479        .unwrap()
2480        .resolution,
2481      Resolution::Path(root().join("node_modules/foo/index.js"))
2482    );
2483  }
2484
2485  #[test]
2486  fn test_builtins() {
2487    assert_eq!(
2488      test_resolver()
2489        .resolve("zlib", &root().join("foo.js"), SpecifierType::Esm)
2490        .result
2491        .unwrap()
2492        .resolution,
2493      Resolution::Builtin("zlib".into())
2494    );
2495    assert_eq!(
2496      test_resolver()
2497        .resolve("node:zlib", &root().join("foo.js"), SpecifierType::Esm)
2498        .result
2499        .unwrap()
2500        .resolution,
2501      Resolution::Builtin("zlib".into())
2502    );
2503    assert_eq!(
2504      test_resolver()
2505        .resolve(
2506          "node:fs/promises",
2507          &root().join("foo.js"),
2508          SpecifierType::Cjs
2509        )
2510        .result
2511        .unwrap()
2512        .resolution,
2513      Resolution::Builtin("fs/promises".into())
2514    );
2515  }
2516
2517  #[test]
2518  fn test_tsconfig() {
2519    assert_eq!(
2520      test_resolver()
2521        .resolve("ts-path", &root().join("foo.js"), SpecifierType::Esm)
2522        .result
2523        .unwrap()
2524        .resolution,
2525      Resolution::Path(root().join("foo.js"))
2526    );
2527    assert_eq!(
2528      test_resolver()
2529        .resolve(
2530          "ts-path",
2531          &root().join("nested/index.js"),
2532          SpecifierType::Esm
2533        )
2534        .result
2535        .unwrap()
2536        .resolution,
2537      Resolution::Path(root().join("nested/test.js"))
2538    );
2539    assert_eq!(
2540      test_resolver()
2541        .resolve(
2542          "foo",
2543          &root().join("tsconfig/index/index.js"),
2544          SpecifierType::Esm
2545        )
2546        .result
2547        .unwrap()
2548        .resolution,
2549      Resolution::Path(root().join("node_modules/tsconfig-index/foo.js"))
2550    );
2551    assert_eq!(
2552      test_resolver()
2553        .resolve(
2554          "foo",
2555          &root().join("tsconfig/field/index.js"),
2556          SpecifierType::Esm
2557        )
2558        .result
2559        .unwrap()
2560        .resolution,
2561      Resolution::Path(root().join("node_modules/tsconfig-field/foo.js"))
2562    );
2563    assert_eq!(
2564      test_resolver()
2565        .resolve(
2566          "foo",
2567          &root().join("tsconfig/exports/index.js"),
2568          SpecifierType::Esm
2569        )
2570        .result
2571        .unwrap()
2572        .resolution,
2573      Resolution::Path(root().join("node_modules/tsconfig-exports/foo.js"))
2574    );
2575    assert_eq!(
2576      test_resolver()
2577        .resolve(
2578          "foo",
2579          &root().join("tsconfig/extends-extension/index.js"),
2580          SpecifierType::Esm
2581        )
2582        .result
2583        .unwrap()
2584        .resolution,
2585      Resolution::Path(root().join("tsconfig/extends-extension/foo.js"))
2586    );
2587
2588    let mut extends_node_module_resolver = test_resolver();
2589    extends_node_module_resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Bool(false));
2590    assert_eq!(
2591      extends_node_module_resolver
2592        .resolve(
2593          "./bar",
2594          &root().join("tsconfig/extends-node-module/index.js"),
2595          SpecifierType::Esm
2596        )
2597        .result
2598        .unwrap()
2599        .resolution,
2600      Resolution::Path(root().join("tsconfig/extends-node-module/bar.ts"))
2601    );
2602
2603    assert_eq!(
2604      test_resolver()
2605        .resolve(
2606          "ts-path",
2607          &root().join("node_modules/tsconfig-not-used/index.js"),
2608          SpecifierType::Esm
2609        )
2610        .result
2611        .unwrap_err(),
2612      ResolverError::ModuleNotFound {
2613        module: "ts-path".into()
2614      },
2615    );
2616    assert_eq!(
2617      test_resolver()
2618        .resolve("ts-path", &root().join("foo.css"), SpecifierType::Esm)
2619        .result
2620        .unwrap_err(),
2621      ResolverError::ModuleNotFound {
2622        module: "ts-path".into()
2623      },
2624    );
2625    assert_eq!(
2626      test_resolver()
2627        .resolve(
2628          "zlib",
2629          &root().join("tsconfig/builtins/thing.js"),
2630          SpecifierType::Cjs
2631        )
2632        .result
2633        .unwrap()
2634        .resolution,
2635      Resolution::Builtin("zlib".into())
2636    );
2637
2638    let invalidations = test_resolver()
2639      .resolve("ts-path", &root().join("foo.js"), SpecifierType::Esm)
2640      .invalidations;
2641    assert_eq!(
2642      invalidations
2643        .invalidate_on_file_create
2644        .borrow()
2645        .iter()
2646        .collect::<HashSet<_>>(),
2647      HashSet::new()
2648    );
2649    assert_eq!(
2650      invalidations
2651        .invalidate_on_file_change
2652        .borrow()
2653        .iter()
2654        .map(|p| p.as_path().to_owned())
2655        .collect::<HashSet<_>>(),
2656      HashSet::from([root().join("package.json"), root().join("tsconfig.json")])
2657    );
2658  }
2659
2660  #[test]
2661  fn test_module_suffixes() {
2662    assert_eq!(
2663      test_resolver()
2664        .resolve(
2665          "./a",
2666          &root().join("tsconfig/suffixes/index.ts"),
2667          SpecifierType::Esm
2668        )
2669        .result
2670        .unwrap()
2671        .resolution,
2672      Resolution::Path(root().join("tsconfig/suffixes/a.ios.ts"))
2673    );
2674    assert_eq!(
2675      test_resolver()
2676        .resolve(
2677          "./a.ts",
2678          &root().join("tsconfig/suffixes/index.ts"),
2679          SpecifierType::Esm
2680        )
2681        .result
2682        .unwrap()
2683        .resolution,
2684      Resolution::Path(root().join("tsconfig/suffixes/a.ios.ts"))
2685    );
2686    assert_eq!(
2687      test_resolver()
2688        .resolve(
2689          "./b",
2690          &root().join("tsconfig/suffixes/index.ts"),
2691          SpecifierType::Esm
2692        )
2693        .result
2694        .unwrap()
2695        .resolution,
2696      Resolution::Path(root().join("tsconfig/suffixes/b.ts"))
2697    );
2698    assert_eq!(
2699      test_resolver()
2700        .resolve(
2701          "./b.ts",
2702          &root().join("tsconfig/suffixes/index.ts"),
2703          SpecifierType::Esm
2704        )
2705        .result
2706        .unwrap()
2707        .resolution,
2708      Resolution::Path(root().join("tsconfig/suffixes/b.ts"))
2709    );
2710    assert_eq!(
2711      test_resolver()
2712        .resolve(
2713          "./c",
2714          &root().join("tsconfig/suffixes/index.ts"),
2715          SpecifierType::Esm
2716        )
2717        .result
2718        .unwrap()
2719        .resolution,
2720      Resolution::Path(root().join("tsconfig/suffixes/c-test.ts"))
2721    );
2722    assert_eq!(
2723      test_resolver()
2724        .resolve(
2725          "./c.ts",
2726          &root().join("tsconfig/suffixes/index.ts"),
2727          SpecifierType::Esm
2728        )
2729        .result
2730        .unwrap()
2731        .resolution,
2732      Resolution::Path(root().join("tsconfig/suffixes/c-test.ts"))
2733    );
2734  }
2735
2736  #[test]
2737  fn test_tsconfig_parsing() {
2738    assert_eq!(
2739      test_resolver()
2740        .resolve(
2741          "foo",
2742          &root().join("tsconfig/trailing-comma/index.js"),
2743          SpecifierType::Esm
2744        )
2745        .result
2746        .unwrap()
2747        .resolution,
2748      Resolution::Path(root().join("tsconfig/trailing-comma/bar.js"))
2749    );
2750  }
2751
2752  #[test]
2753  fn test_ts_extensions() {
2754    assert_eq!(
2755      test_resolver()
2756        .resolve(
2757          "./a.js",
2758          &root().join("ts-extensions/index.ts"),
2759          SpecifierType::Esm
2760        )
2761        .result
2762        .unwrap()
2763        .resolution,
2764      Resolution::Path(root().join("ts-extensions/a.ts"))
2765    );
2766    assert_eq!(
2767      test_resolver()
2768        .resolve(
2769          "./a.jsx",
2770          &root().join("ts-extensions/index.ts"),
2771          SpecifierType::Esm
2772        )
2773        .result
2774        .unwrap()
2775        .resolution,
2776      // TSC always prioritizes .ts over .tsx
2777      Resolution::Path(root().join("ts-extensions/a.ts"))
2778    );
2779    assert_eq!(
2780      test_resolver()
2781        .resolve(
2782          "./a.mjs",
2783          &root().join("ts-extensions/index.ts"),
2784          SpecifierType::Esm
2785        )
2786        .result
2787        .unwrap()
2788        .resolution,
2789      Resolution::Path(root().join("ts-extensions/a.mts"))
2790    );
2791    assert_eq!(
2792      test_resolver()
2793        .resolve(
2794          "./a.cjs",
2795          &root().join("ts-extensions/index.ts"),
2796          SpecifierType::Esm
2797        )
2798        .result
2799        .unwrap()
2800        .resolution,
2801      Resolution::Path(root().join("ts-extensions/a.cts"))
2802    );
2803    assert_eq!(
2804      test_resolver()
2805        .resolve(
2806          "./b.js",
2807          &root().join("ts-extensions/index.ts"),
2808          SpecifierType::Esm
2809        )
2810        .result
2811        .unwrap()
2812        .resolution,
2813      // We deviate from TSC here to match Node/bundlers.
2814      Resolution::Path(root().join("ts-extensions/b.js"))
2815    );
2816    assert_eq!(
2817      test_resolver()
2818        .resolve(
2819          "./c.js",
2820          &root().join("ts-extensions/index.ts"),
2821          SpecifierType::Esm
2822        )
2823        .result
2824        .unwrap()
2825        .resolution,
2826      // This matches TSC. c.js.ts seems kinda unlikely?
2827      Resolution::Path(root().join("ts-extensions/c.ts"))
2828    );
2829    assert_eq!(
2830      test_resolver()
2831        .resolve(
2832          "./a.js",
2833          &root().join("ts-extensions/index.js"),
2834          SpecifierType::Esm
2835        )
2836        .result
2837        .unwrap_err(),
2838      ResolverError::FileNotFound {
2839        relative: "a.js".into(),
2840        from: root().join("ts-extensions/index.js")
2841      },
2842    );
2843
2844    let invalidations = test_resolver()
2845      .resolve(
2846        "./a.js",
2847        &root().join("ts-extensions/index.ts"),
2848        SpecifierType::Esm,
2849      )
2850      .invalidations;
2851    assert_eq!(
2852      invalidations
2853        .invalidate_on_file_create
2854        .borrow()
2855        .iter()
2856        .map(|p| p.clone().into())
2857        .collect::<HashSet<_>>(),
2858      HashSet::from([
2859        UncachedFileCreateInvalidation::Path(root().join("ts-extensions/a.js")),
2860        UncachedFileCreateInvalidation::FileName {
2861          file_name: "package.json".into(),
2862          above: root().join("ts-extensions")
2863        },
2864        UncachedFileCreateInvalidation::FileName {
2865          file_name: "tsconfig.json".into(),
2866          above: root().join("ts-extensions")
2867        },
2868      ])
2869    );
2870    assert_eq!(
2871      invalidations
2872        .invalidate_on_file_change
2873        .borrow()
2874        .iter()
2875        .map(|p| p.as_path().to_owned())
2876        .collect::<HashSet<_>>(),
2877      HashSet::from([root().join("package.json"), root().join("tsconfig.json")])
2878    );
2879  }
2880
2881  fn resolve_side_effects(specifier: &str, from: &Path) -> bool {
2882    let resolver = test_resolver();
2883    let resolved = resolver
2884      .resolve(specifier, from, SpecifierType::Esm)
2885      .result
2886      .unwrap()
2887      .resolution;
2888
2889    if let Resolution::Path(path) = resolved {
2890      resolver
2891        .resolve_side_effects(&path, &Invalidations::default())
2892        .unwrap()
2893    } else {
2894      unreachable!()
2895    }
2896  }
2897
2898  #[test]
2899  fn test_side_effects() {
2900    assert!(!resolve_side_effects(
2901      "side-effects-false/src/index.js",
2902      &root().join("foo.js")
2903    ));
2904    assert!(!resolve_side_effects(
2905      "side-effects-false/src/index",
2906      &root().join("foo.js")
2907    ));
2908    assert!(!resolve_side_effects(
2909      "side-effects-false/src/",
2910      &root().join("foo.js")
2911    ));
2912    assert!(!resolve_side_effects(
2913      "side-effects-false",
2914      &root().join("foo.js")
2915    ));
2916    assert!(!resolve_side_effects(
2917      "side-effects-package-redirect-up/foo/bar",
2918      &root().join("foo.js")
2919    ));
2920    assert!(!resolve_side_effects(
2921      "side-effects-package-redirect-down/foo/bar",
2922      &root().join("foo.js")
2923    ));
2924    assert!(resolve_side_effects(
2925      "side-effects-false-glob/a/index",
2926      &root().join("foo.js")
2927    ));
2928    assert!(!resolve_side_effects(
2929      "side-effects-false-glob/b/index.js",
2930      &root().join("foo.js")
2931    ));
2932    assert!(!resolve_side_effects(
2933      "side-effects-false-glob/sub/a/index.js",
2934      &root().join("foo.js")
2935    ));
2936    assert!(resolve_side_effects(
2937      "side-effects-false-glob/sub/index.json",
2938      &root().join("foo.js")
2939    ));
2940  }
2941
2942  #[test]
2943  fn test_include_node_modules() {
2944    let mut resolver = test_resolver();
2945    resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Bool(false));
2946
2947    assert_eq!(
2948      resolver
2949        .resolve("foo", &root().join("foo.js"), SpecifierType::Esm)
2950        .result
2951        .unwrap()
2952        .resolution,
2953      Resolution::External
2954    );
2955    assert_eq!(
2956      resolver
2957        .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm)
2958        .result
2959        .unwrap()
2960        .resolution,
2961      Resolution::External
2962    );
2963
2964    resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Array(vec!["foo".into()]));
2965    assert_eq!(
2966      resolver
2967        .resolve("foo", &root().join("foo.js"), SpecifierType::Esm)
2968        .result
2969        .unwrap()
2970        .resolution,
2971      Resolution::Path(root().join("node_modules/foo/index.js"))
2972    );
2973    assert_eq!(
2974      resolver
2975        .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm)
2976        .result
2977        .unwrap()
2978        .resolution,
2979      Resolution::External
2980    );
2981
2982    resolver.include_node_modules = Cow::Owned(IncludeNodeModules::Map(HashMap::from([
2983      ("foo".into(), false),
2984      ("@scope/pkg".into(), true),
2985    ])));
2986    assert_eq!(
2987      resolver
2988        .resolve("foo", &root().join("foo.js"), SpecifierType::Esm)
2989        .result
2990        .unwrap()
2991        .resolution,
2992      Resolution::External
2993    );
2994    assert_eq!(
2995      resolver
2996        .resolve("@scope/pkg", &root().join("foo.js"), SpecifierType::Esm)
2997        .result
2998        .unwrap()
2999        .resolution,
3000      Resolution::Path(root().join("node_modules/@scope/pkg/index.js"))
3001    );
3002  }
3003
3004  // #[test]
3005  // fn test_visitor() {
3006  //   let resolved = test_resolver().resolve("unified", &root(), SpecifierType::Esm).unwrap();
3007  //   println!("{:?}", resolved);
3008  //   if let Resolution::Path(p) = resolved {
3009  //     let res = build_esm_graph(
3010  //       &p,
3011  //       root()
3012  //     ).unwrap();
3013  //     println!("{:?}", res);
3014  //   }
3015  // }
3016}