node_resolver/
resolution.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3use std::borrow::Cow;
4use std::fmt::Debug;
5use std::path::Path;
6use std::path::PathBuf;
7
8use anyhow::Error as AnyError;
9use anyhow::bail;
10use deno_media_type::MediaType;
11use deno_package_json::PackageJson;
12use deno_path_util::url_to_file_path;
13use deno_semver::Version;
14use deno_semver::VersionReq;
15use serde_json::Map;
16use serde_json::Value;
17use sys_traits::FileType;
18use sys_traits::FsCanonicalize;
19use sys_traits::FsMetadata;
20use sys_traits::FsRead;
21use url::Url;
22
23use crate::InNpmPackageChecker;
24use crate::IsBuiltInNodeModuleChecker;
25use crate::NpmPackageFolderResolver;
26use crate::PackageJsonResolverRc;
27use crate::PathClean;
28use crate::cache::NodeResolutionSys;
29use crate::errors;
30use crate::errors::DataUrlReferrerError;
31use crate::errors::FinalizeResolutionError;
32use crate::errors::InvalidModuleSpecifierError;
33use crate::errors::InvalidPackageTargetError;
34use crate::errors::LegacyResolveError;
35use crate::errors::ModuleNotFoundError;
36use crate::errors::NodeJsErrorCode;
37use crate::errors::NodeJsErrorCoded;
38use crate::errors::NodeResolveError;
39use crate::errors::NodeResolveRelativeJoinError;
40use crate::errors::PackageExportsResolveError;
41use crate::errors::PackageImportNotDefinedError;
42use crate::errors::PackageImportsResolveError;
43use crate::errors::PackageImportsResolveErrorKind;
44use crate::errors::PackagePathNotExportedError;
45use crate::errors::PackageResolveError;
46use crate::errors::PackageSubpathFromDenoModuleResolveError;
47use crate::errors::PackageSubpathResolveError;
48use crate::errors::PackageSubpathResolveErrorKind;
49use crate::errors::PackageTargetNotFoundError;
50use crate::errors::PackageTargetResolveError;
51use crate::errors::PackageTargetResolveErrorKind;
52use crate::errors::ResolveBinaryCommandsError;
53use crate::errors::ResolvePkgJsonBinExportError;
54use crate::errors::TypesNotFoundError;
55use crate::errors::TypesNotFoundErrorData;
56use crate::errors::UnknownBuiltInNodeModuleError;
57use crate::errors::UnsupportedDirImportError;
58use crate::errors::UnsupportedEsmUrlSchemeError;
59use crate::path::UrlOrPath;
60use crate::path::UrlOrPathRef;
61
62pub static IMPORT_CONDITIONS: &[Cow<'static, str>] = &[
63  Cow::Borrowed("deno"),
64  Cow::Borrowed("node"),
65  Cow::Borrowed("import"),
66];
67pub static REQUIRE_CONDITIONS: &[Cow<'static, str>] =
68  &[Cow::Borrowed("require"), Cow::Borrowed("node")];
69static TYPES_ONLY_CONDITIONS: &[Cow<'static, str>] = &[Cow::Borrowed("types")];
70
71#[derive(Debug, Default, Clone)]
72pub struct NodeConditionOptions {
73  pub conditions: Vec<Cow<'static, str>>,
74  /// Provide a value to override the default import conditions.
75  ///
76  /// Defaults to `["deno", "node", "import"]`
77  pub import_conditions_override: Option<Vec<Cow<'static, str>>>,
78  /// Provide a value to override the default require conditions.
79  ///
80  /// Defaults to `["require", "node"]`
81  pub require_conditions_override: Option<Vec<Cow<'static, str>>>,
82}
83
84#[derive(Debug, Clone)]
85struct ConditionResolver {
86  import_conditions: Cow<'static, [Cow<'static, str>]>,
87  require_conditions: Cow<'static, [Cow<'static, str>]>,
88}
89
90impl ConditionResolver {
91  pub fn new(options: NodeConditionOptions) -> Self {
92    fn combine_conditions(
93      user_conditions: Cow<'_, [Cow<'static, str>]>,
94      override_default: Option<Vec<Cow<'static, str>>>,
95      default_conditions: &'static [Cow<'static, str>],
96    ) -> Cow<'static, [Cow<'static, str>]> {
97      let default_conditions = override_default
98        .map(Cow::Owned)
99        .unwrap_or(Cow::Borrowed(default_conditions));
100      if user_conditions.is_empty() {
101        default_conditions
102      } else {
103        let mut new =
104          Vec::with_capacity(user_conditions.len() + default_conditions.len());
105        let mut append =
106          |conditions: Cow<'_, [Cow<'static, str>]>| match conditions {
107            Cow::Borrowed(conditions) => new.extend(conditions.iter().cloned()),
108            Cow::Owned(conditions) => new.extend(conditions),
109          };
110        append(user_conditions);
111        append(default_conditions);
112        Cow::Owned(new)
113      }
114    }
115
116    Self {
117      import_conditions: combine_conditions(
118        Cow::Borrowed(&options.conditions),
119        options.import_conditions_override,
120        IMPORT_CONDITIONS,
121      ),
122      require_conditions: combine_conditions(
123        Cow::Owned(options.conditions),
124        options.require_conditions_override,
125        REQUIRE_CONDITIONS,
126      ),
127    }
128  }
129
130  pub fn resolve(
131    &self,
132    resolution_mode: ResolutionMode,
133  ) -> &[Cow<'static, str>] {
134    match resolution_mode {
135      ResolutionMode::Import => &self.import_conditions,
136      ResolutionMode::Require => &self.require_conditions,
137    }
138  }
139
140  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
141    &self.require_conditions
142  }
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
146pub enum ResolutionMode {
147  Import,
148  Require,
149}
150
151impl ResolutionMode {
152  pub fn default_conditions(&self) -> &'static [Cow<'static, str>] {
153    match self {
154      ResolutionMode::Import => IMPORT_CONDITIONS,
155      ResolutionMode::Require => REQUIRE_CONDITIONS,
156    }
157  }
158
159  #[cfg(feature = "graph")]
160  pub fn from_deno_graph(mode: deno_graph::source::ResolutionMode) -> Self {
161    use deno_graph::source::ResolutionMode::*;
162    match mode {
163      Import => Self::Import,
164      Require => Self::Require,
165    }
166  }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
170pub enum NodeResolutionKind {
171  Execution,
172  Types,
173}
174
175impl NodeResolutionKind {
176  pub fn is_types(&self) -> bool {
177    matches!(self, NodeResolutionKind::Types)
178  }
179
180  #[cfg(feature = "graph")]
181  pub fn from_deno_graph(kind: deno_graph::source::ResolutionKind) -> Self {
182    use deno_graph::source::ResolutionKind::*;
183    match kind {
184      Execution => Self::Execution,
185      Types => Self::Types,
186    }
187  }
188}
189
190#[derive(Debug)]
191pub enum NodeResolution {
192  Module(UrlOrPath),
193  BuiltIn(String),
194}
195
196impl NodeResolution {
197  pub fn into_url(self) -> Result<Url, NodeResolveError> {
198    match self {
199      Self::Module(u) => Ok(u.into_url()?),
200      Self::BuiltIn(specifier) => Ok(if specifier.starts_with("node:") {
201        Url::parse(&specifier).unwrap()
202      } else {
203        Url::parse(&format!("node:{specifier}")).unwrap()
204      }),
205    }
206  }
207}
208
209struct LocalPath {
210  path: PathBuf,
211  known_exists: bool,
212}
213
214enum LocalUrlOrPath {
215  Url(Url),
216  Path(LocalPath),
217}
218
219impl LocalUrlOrPath {
220  pub fn into_url_or_path(self) -> UrlOrPath {
221    match self {
222      LocalUrlOrPath::Url(url) => UrlOrPath::Url(url),
223      LocalUrlOrPath::Path(local_path) => UrlOrPath::Path(local_path.path),
224    }
225  }
226}
227
228/// This struct helps ensure we remember to probe for
229/// declaration files and to prevent accidentally probing
230/// multiple times.
231struct MaybeTypesResolvedUrl(LocalUrlOrPath);
232
233/// Kind of method that resolution suceeded with.
234enum ResolvedMethod {
235  Url,
236  RelativeOrAbsolute,
237  PackageImports,
238  PackageExports,
239  PackageSubPath,
240}
241
242#[derive(Debug, Default, Clone)]
243pub struct NodeResolverOptions {
244  pub conditions: NodeConditionOptions,
245  pub is_browser_platform: bool,
246  pub bundle_mode: bool,
247  /// TypeScript version to use for typesVersions resolution and
248  /// `types@req` exports resolution.
249  pub typescript_version: Option<Version>,
250}
251
252#[derive(Debug)]
253struct ResolutionConfig {
254  pub bundle_mode: bool,
255  pub prefer_browser_field: bool,
256  pub typescript_version: Option<Version>,
257}
258
259#[sys_traits::auto_impl]
260pub trait NodeResolverSys: FsCanonicalize + FsMetadata + FsRead {}
261
262#[allow(clippy::disallowed_types)]
263pub type NodeResolverRc<
264  TInNpmPackageChecker,
265  TIsBuiltInNodeModuleChecker,
266  TNpmPackageFolderResolver,
267  TSys,
268> = deno_maybe_sync::MaybeArc<
269  NodeResolver<
270    TInNpmPackageChecker,
271    TIsBuiltInNodeModuleChecker,
272    TNpmPackageFolderResolver,
273    TSys,
274  >,
275>;
276
277#[derive(Debug)]
278pub struct NodeResolver<
279  TInNpmPackageChecker: InNpmPackageChecker,
280  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
281  TNpmPackageFolderResolver: NpmPackageFolderResolver,
282  TSys: NodeResolverSys,
283> {
284  in_npm_pkg_checker: TInNpmPackageChecker,
285  is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
286  npm_pkg_folder_resolver: TNpmPackageFolderResolver,
287  pkg_json_resolver: PackageJsonResolverRc<TSys>,
288  sys: NodeResolutionSys<TSys>,
289  condition_resolver: ConditionResolver,
290  resolution_config: ResolutionConfig,
291}
292
293impl<
294  TInNpmPackageChecker: InNpmPackageChecker,
295  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
296  TNpmPackageFolderResolver: NpmPackageFolderResolver,
297  TSys: NodeResolverSys,
298>
299  NodeResolver<
300    TInNpmPackageChecker,
301    TIsBuiltInNodeModuleChecker,
302    TNpmPackageFolderResolver,
303    TSys,
304  >
305{
306  pub fn new(
307    in_npm_pkg_checker: TInNpmPackageChecker,
308    is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
309    npm_pkg_folder_resolver: TNpmPackageFolderResolver,
310    pkg_json_resolver: PackageJsonResolverRc<TSys>,
311    sys: NodeResolutionSys<TSys>,
312    options: NodeResolverOptions,
313  ) -> Self {
314    Self {
315      in_npm_pkg_checker,
316      is_built_in_node_module_checker,
317      npm_pkg_folder_resolver,
318      pkg_json_resolver,
319      sys,
320      condition_resolver: ConditionResolver::new(NodeConditionOptions {
321        conditions: options.conditions.conditions,
322        import_conditions_override: options
323          .conditions
324          .import_conditions_override
325          .or_else(|| {
326            if options.is_browser_platform {
327              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("import")])
328            } else {
329              None
330            }
331          }),
332        require_conditions_override: options
333          .conditions
334          .require_conditions_override
335          .or_else(|| {
336            if options.is_browser_platform {
337              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("require")])
338            } else {
339              None
340            }
341          }),
342      }),
343      resolution_config: ResolutionConfig {
344        bundle_mode: options.bundle_mode,
345        prefer_browser_field: options.is_browser_platform,
346        typescript_version: options.typescript_version,
347      },
348    }
349  }
350
351  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
352    self.condition_resolver.require_conditions()
353  }
354
355  pub fn in_npm_package(&self, specifier: &Url) -> bool {
356    self.in_npm_pkg_checker.in_npm_package(specifier)
357  }
358
359  #[inline(always)]
360  pub fn is_builtin_node_module(&self, specifier: &str) -> bool {
361    self
362      .is_built_in_node_module_checker
363      .is_builtin_node_module(specifier)
364  }
365
366  /// This function is an implementation of `defaultResolve` in
367  /// `lib/internal/modules/esm/resolve.js` from Node.
368  pub fn resolve(
369    &self,
370    specifier: &str,
371    referrer: &Url,
372    resolution_mode: ResolutionMode,
373    resolution_kind: NodeResolutionKind,
374  ) -> Result<NodeResolution, NodeResolveError> {
375    // Note: if we are here, then the referrer is an esm module
376    // TODO(bartlomieju): skipped "policy" part as we don't plan to support it
377
378    if self.is_builtin_node_module(specifier) {
379      return Ok(NodeResolution::BuiltIn(specifier.to_string()));
380    }
381
382    if let Ok(url) = Url::parse(specifier) {
383      if url.scheme() == "data" {
384        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
385      }
386
387      if let Some(module_name) =
388        self.get_module_name_from_builtin_node_module_url(&url)?
389      {
390        return Ok(NodeResolution::BuiltIn(module_name.to_string()));
391      }
392
393      let protocol = url.scheme();
394
395      if protocol != "file" && protocol != "data" {
396        return Err(
397          UnsupportedEsmUrlSchemeError {
398            url_scheme: protocol.to_string(),
399          }
400          .into(),
401        );
402      }
403
404      // todo(dsherret): this seems wrong
405      if referrer.scheme() == "data" {
406        let url = referrer
407          .join(specifier)
408          .map_err(|source| DataUrlReferrerError { source })?;
409        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
410      }
411    }
412
413    let conditions = self.condition_resolver.resolve(resolution_mode);
414    let referrer = UrlOrPathRef::from_url(referrer);
415    let (url, resolved_kind) = self.module_resolve(
416      specifier,
417      &referrer,
418      resolution_mode,
419      conditions,
420      resolution_kind,
421    )?;
422
423    let url_or_path = self.finalize_resolution(
424      url,
425      resolved_kind,
426      resolution_mode,
427      conditions,
428      resolution_kind,
429      Some(&referrer),
430    )?;
431    let resolve_response = NodeResolution::Module(url_or_path);
432    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
433    // "preserveSymlinksMain"/"preserveSymlinks" options.
434    Ok(resolve_response)
435  }
436
437  fn module_resolve(
438    &self,
439    specifier: &str,
440    referrer: &UrlOrPathRef,
441    resolution_mode: ResolutionMode,
442    conditions: &[Cow<'static, str>],
443    resolution_kind: NodeResolutionKind,
444  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), NodeResolveError> {
445    if should_be_treated_as_relative_or_absolute_path(specifier) {
446      let referrer_url = referrer.url()?;
447      let url = node_join_url(referrer_url, specifier).map_err(|err| {
448        NodeResolveRelativeJoinError {
449          path: specifier.to_string(),
450          base: referrer_url.clone(),
451          source: err,
452        }
453      })?;
454      let url = self.maybe_resolve_types(
455        LocalUrlOrPath::Url(url),
456        Some(referrer),
457        resolution_mode,
458        conditions,
459        resolution_kind,
460      )?;
461      Ok((url, ResolvedMethod::RelativeOrAbsolute))
462    } else if specifier.starts_with('#') {
463      let pkg_config = self
464        .pkg_json_resolver
465        .get_closest_package_json(referrer.path()?)
466        .map_err(PackageImportsResolveErrorKind::PkgJsonLoad)
467        .map_err(|err| PackageImportsResolveError(Box::new(err)))?;
468      Ok((
469        self.package_imports_resolve_internal(
470          specifier,
471          Some(referrer),
472          resolution_mode,
473          pkg_config.as_deref(),
474          conditions,
475          resolution_kind,
476        )?,
477        ResolvedMethod::PackageImports,
478      ))
479    } else if let Ok(url) = Url::parse(specifier) {
480      let url_or_path = self.maybe_resolve_types(
481        LocalUrlOrPath::Url(url),
482        Some(referrer),
483        resolution_mode,
484        conditions,
485        resolution_kind,
486      )?;
487      Ok((url_or_path, ResolvedMethod::Url))
488    } else {
489      Ok(self.package_resolve(
490        specifier,
491        referrer,
492        resolution_mode,
493        conditions,
494        resolution_kind,
495      )?)
496    }
497  }
498
499  fn finalize_resolution(
500    &self,
501    resolved: MaybeTypesResolvedUrl,
502    resolved_method: ResolvedMethod,
503    resolution_mode: ResolutionMode,
504    conditions: &[Cow<'static, str>],
505    resolution_kind: NodeResolutionKind,
506    maybe_referrer: Option<&UrlOrPathRef>,
507  ) -> Result<UrlOrPath, FinalizeResolutionError> {
508    let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C");
509
510    let resolved = resolved.0;
511    let text = match &resolved {
512      LocalUrlOrPath::Url(url) => Cow::Borrowed(url.as_str()),
513      LocalUrlOrPath::Path(LocalPath { path, .. }) => path.to_string_lossy(),
514    };
515    if encoded_sep_re.is_match(&text) {
516      return Err(
517        errors::InvalidModuleSpecifierError {
518          request: text.into_owned(),
519          reason: Cow::Borrowed(
520            "must not include encoded \"/\" or \"\\\\\" characters",
521          ),
522          maybe_referrer: maybe_referrer.map(|r| match r.path() {
523            // in this case, prefer showing the path string
524            Ok(path) => path.display().to_string(),
525            Err(_) => r.display().to_string(),
526          }),
527        }
528        .into(),
529      );
530    }
531
532    let (path, maybe_url) = match resolved {
533      LocalUrlOrPath::Url(url) => {
534        if url.scheme() == "file" {
535          (url_to_file_path(&url)?, Some(url))
536        } else {
537          return Ok(UrlOrPath::Url(url));
538        }
539      }
540      LocalUrlOrPath::Path(LocalPath { path, known_exists }) => {
541        if known_exists {
542          // no need to do the finalization checks
543          return Ok(UrlOrPath::Path(path));
544        } else {
545          (path, None)
546        }
547      }
548    };
549
550    // TODO(bartlomieju): currently not supported
551    // if (getOptionValue('--experimental-specifier-resolution') === 'node') {
552    //   ...
553    // }
554
555    let p_str = path.to_str().unwrap();
556    let path = match p_str.strip_suffix('/') {
557      Some(s) => Cow::Borrowed(Path::new(s)),
558      None => Cow::Owned(path),
559    };
560
561    let maybe_file_type = self.sys.get_file_type(&path);
562    match maybe_file_type {
563      Ok(FileType::Dir) => {
564        if resolution_mode == ResolutionMode::Import
565          && !self.resolution_config.bundle_mode
566        {
567          let suggestion = self.directory_import_suggestion(&path);
568          Err(
569            UnsupportedDirImportError {
570              dir_url: UrlOrPath::Path(path.into_owned()),
571              maybe_referrer: maybe_referrer.map(|r| r.display()),
572              suggestion,
573            }
574            .into(),
575          )
576        } else {
577          // prefer the file over the directory
578          let path_with_ext = with_known_extension(&path, "js");
579          if self.sys.is_file(&path_with_ext) {
580            Ok(UrlOrPath::Path(path_with_ext))
581          } else {
582            let (resolved_url, resolved_method) = self
583              .resolve_package_dir_subpath(
584                &path,
585                ".",
586                maybe_referrer,
587                resolution_mode,
588                conditions,
589                resolution_kind,
590              )?;
591            self.finalize_resolution(
592              resolved_url,
593              resolved_method,
594              resolution_mode,
595              conditions,
596              resolution_kind,
597              maybe_referrer,
598            )
599          }
600        }
601      }
602      Ok(FileType::File) => {
603        // prefer returning the url to avoid re-allocating in the CLI crate
604        Ok(
605          maybe_url
606            .map(UrlOrPath::Url)
607            .unwrap_or(UrlOrPath::Path(path.into_owned())),
608        )
609      }
610      _ => {
611        if let Err(e) = maybe_file_type
612          && (resolution_mode == ResolutionMode::Require
613            || self.resolution_config.bundle_mode)
614          && e.kind() == std::io::ErrorKind::NotFound
615        {
616          let file_with_ext = with_known_extension(&path, "js");
617          if self.sys.is_file(&file_with_ext) {
618            return Ok(UrlOrPath::Path(file_with_ext));
619          }
620        }
621
622        Err(
623          ModuleNotFoundError {
624            suggested_ext: self
625              .module_not_found_ext_suggestion(&path, resolved_method),
626            specifier: UrlOrPath::Path(path.into_owned()),
627            maybe_referrer: maybe_referrer.map(|r| r.display()),
628          }
629          .into(),
630        )
631      }
632    }
633  }
634
635  fn module_not_found_ext_suggestion(
636    &self,
637    path: &Path,
638    resolved_method: ResolvedMethod,
639  ) -> Option<&'static str> {
640    fn should_probe(path: &Path, resolved_method: ResolvedMethod) -> bool {
641      if MediaType::from_path(path) != MediaType::Unknown {
642        return false;
643      }
644      match resolved_method {
645        ResolvedMethod::Url
646        | ResolvedMethod::RelativeOrAbsolute
647        | ResolvedMethod::PackageSubPath => true,
648        ResolvedMethod::PackageImports | ResolvedMethod::PackageExports => {
649          false
650        }
651      }
652    }
653
654    if should_probe(path, resolved_method) {
655      ["js", "mjs", "cjs"]
656        .into_iter()
657        .find(|ext| self.sys.is_file(&with_known_extension(path, ext)))
658    } else {
659      None
660    }
661  }
662
663  fn directory_import_suggestion(
664    &self,
665    dir_import_path: &Path,
666  ) -> Option<String> {
667    let dir_index_paths = ["index.mjs", "index.js", "index.cjs"]
668      .into_iter()
669      .map(|file_name| dir_import_path.join(file_name));
670    let file_paths = [
671      with_known_extension(dir_import_path, "js"),
672      with_known_extension(dir_import_path, "mjs"),
673      with_known_extension(dir_import_path, "cjs"),
674    ];
675    dir_index_paths
676      .chain(file_paths)
677      .chain(
678        std::iter::once_with(|| {
679          // check if this directory has a package.json
680          let package_json_path = dir_import_path.join("package.json");
681          let pkg_json = self
682            .pkg_json_resolver
683            .load_package_json(&package_json_path)
684            .ok()??;
685          let main = pkg_json.main.as_ref()?;
686          Some(dir_import_path.join(main))
687        })
688        .flatten(),
689      )
690      .map(|p| deno_path_util::normalize_path(Cow::Owned(p)))
691      .find(|p| self.sys.is_file(p))
692      .and_then(|suggested_file_path| {
693        let pkg_json = self
694          .pkg_json_resolver
695          .get_closest_package_jsons(&suggested_file_path)
696          .filter_map(|pkg_json| pkg_json.ok())
697          .find(|p| p.name.is_some())?;
698        let pkg_name = pkg_json.name.as_ref()?;
699        let sub_path = suggested_file_path
700          .strip_prefix(pkg_json.dir_path())
701          .ok()?
702          .to_string_lossy()
703          .replace("\\", "/");
704        Some(format!("{}/{}", pkg_name, sub_path))
705      })
706  }
707
708  pub fn resolve_package_subpath_from_deno_module(
709    &self,
710    package_dir: &Path,
711    package_subpath: Option<&str>,
712    maybe_referrer: Option<&Url>,
713    resolution_mode: ResolutionMode,
714    resolution_kind: NodeResolutionKind,
715  ) -> Result<UrlOrPath, PackageSubpathFromDenoModuleResolveError> {
716    // todo(dsherret): don't allocate a string here (maybe use an
717    // enum that says the subpath is not prefixed with a ./)
718    let package_subpath = package_subpath
719      .map(|s| Cow::Owned(format!("./{s}")))
720      .unwrap_or_else(|| Cow::Borrowed("."));
721    let maybe_referrer = maybe_referrer.map(UrlOrPathRef::from_url);
722    let conditions = self.condition_resolver.resolve(resolution_mode);
723    let (resolved_url, resolved_method) = self.resolve_package_dir_subpath(
724      package_dir,
725      &package_subpath,
726      maybe_referrer.as_ref(),
727      resolution_mode,
728      conditions,
729      resolution_kind,
730    )?;
731    let url_or_path = self.finalize_resolution(
732      resolved_url,
733      resolved_method,
734      resolution_mode,
735      conditions,
736      resolution_kind,
737      maybe_referrer.as_ref(),
738    )?;
739    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
740    // "preserveSymlinksMain"/"preserveSymlinks" options.
741    Ok(url_or_path)
742  }
743
744  pub fn resolve_binary_commands(
745    &self,
746    package_folder: &Path,
747  ) -> Result<Vec<String>, ResolveBinaryCommandsError> {
748    let pkg_json_path = package_folder.join("package.json");
749    let Some(package_json) =
750      self.pkg_json_resolver.load_package_json(&pkg_json_path)?
751    else {
752      return Ok(Vec::new());
753    };
754
755    Ok(match &package_json.bin {
756      Some(Value::String(_)) => {
757        let Some(name) = &package_json.name else {
758          return Err(ResolveBinaryCommandsError::MissingPkgJsonName {
759            pkg_json_path,
760          });
761        };
762        let name = name.split("/").last().unwrap();
763        vec![name.to_string()]
764      }
765      Some(Value::Object(o)) => {
766        o.iter().map(|(key, _)| key.clone()).collect::<Vec<_>>()
767      }
768      _ => Vec::new(),
769    })
770  }
771
772  pub fn resolve_binary_export(
773    &self,
774    package_folder: &Path,
775    sub_path: Option<&str>,
776  ) -> Result<PathBuf, ResolvePkgJsonBinExportError> {
777    let pkg_json_path = package_folder.join("package.json");
778    let Some(package_json) =
779      self.pkg_json_resolver.load_package_json(&pkg_json_path)?
780    else {
781      return Err(ResolvePkgJsonBinExportError::MissingPkgJson {
782        pkg_json_path,
783      });
784    };
785    let bin_entry =
786      resolve_bin_entry_value(&package_json, sub_path).map_err(|err| {
787        ResolvePkgJsonBinExportError::InvalidBinProperty {
788          message: err.to_string(),
789        }
790      })?;
791    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
792    // "preserveSymlinksMain"/"preserveSymlinks" options.
793    Ok(package_folder.join(bin_entry))
794  }
795
796  /// Resolves an npm package folder path from the specified referrer.
797  pub fn resolve_package_folder_from_package(
798    &self,
799    specifier: &str,
800    referrer: &UrlOrPathRef,
801  ) -> Result<PathBuf, errors::PackageFolderResolveError> {
802    self
803      .npm_pkg_folder_resolver
804      .resolve_package_folder_from_package(specifier, referrer)
805  }
806
807  fn maybe_resolve_types(
808    &self,
809    url: LocalUrlOrPath,
810    maybe_referrer: Option<&UrlOrPathRef>,
811    resolution_mode: ResolutionMode,
812    conditions: &[Cow<'static, str>],
813    resolution_kind: NodeResolutionKind,
814  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
815    if resolution_kind.is_types() {
816      let file_path = match url {
817        LocalUrlOrPath::Url(url) => {
818          match deno_path_util::url_to_file_path(&url) {
819            Ok(path) => LocalPath {
820              path,
821              known_exists: false,
822            },
823            Err(_) => {
824              return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
825            }
826          }
827        }
828        LocalUrlOrPath::Path(path) => path,
829      };
830      self.path_to_declaration_path(
831        file_path,
832        maybe_referrer,
833        resolution_mode,
834        conditions,
835      )
836    } else {
837      Ok(MaybeTypesResolvedUrl(url))
838    }
839  }
840
841  /// Checks if the resolved file has a corresponding declaration file.
842  fn path_to_declaration_path(
843    &self,
844    local_path: LocalPath,
845    maybe_referrer: Option<&UrlOrPathRef>,
846    resolution_mode: ResolutionMode,
847    conditions: &[Cow<'static, str>],
848  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
849    fn probe_extensions<TSys: FsMetadata>(
850      sys: &NodeResolutionSys<TSys>,
851      path: &Path,
852      media_type: MediaType,
853      resolution_mode: ResolutionMode,
854    ) -> Option<PathBuf> {
855      let mut searched_for_d_mts = false;
856      let mut searched_for_d_cts = false;
857      if media_type == MediaType::Mjs {
858        let d_mts_path = with_known_extension(path, "d.mts");
859        if sys.exists_(&d_mts_path) {
860          return Some(d_mts_path);
861        }
862        searched_for_d_mts = true;
863      } else if media_type == MediaType::Cjs {
864        let d_cts_path = with_known_extension(path, "d.cts");
865        if sys.exists_(&d_cts_path) {
866          return Some(d_cts_path);
867        }
868        searched_for_d_cts = true;
869      }
870
871      let dts_path = with_known_extension(path, "d.ts");
872      if sys.exists_(&dts_path) {
873        return Some(dts_path);
874      }
875
876      let specific_dts_path = match resolution_mode {
877        ResolutionMode::Require if !searched_for_d_cts => {
878          Some(with_known_extension(path, "d.cts"))
879        }
880        ResolutionMode::Import if !searched_for_d_mts => {
881          Some(with_known_extension(path, "d.mts"))
882        }
883        _ => None, // already searched above
884      };
885      if let Some(specific_dts_path) = specific_dts_path
886        && sys.exists_(&specific_dts_path)
887      {
888        return Some(specific_dts_path);
889      }
890      let ts_path = with_known_extension(path, "ts");
891      if sys.is_file(&ts_path) {
892        return Some(ts_path);
893      }
894      None
895    }
896
897    let media_type = MediaType::from_path(&local_path.path);
898    if media_type.is_declaration() {
899      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(local_path)));
900    }
901    if let Some(path) =
902      probe_extensions(&self.sys, &local_path.path, media_type, resolution_mode)
903    {
904      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
905        path,
906        known_exists: true,
907      })));
908    }
909    if self.sys.is_dir(&local_path.path) {
910      let resolution_result = self.resolve_package_dir_subpath(
911        &local_path.path,
912        /* sub path */ ".",
913        maybe_referrer,
914        resolution_mode,
915        conditions,
916        NodeResolutionKind::Types,
917      );
918      if let Ok((url_or_path, _)) = resolution_result {
919        return Ok(url_or_path);
920      }
921      let index_path = local_path.path.join("index.js");
922      if let Some(path) = probe_extensions(
923        &self.sys,
924        &index_path,
925        MediaType::from_path(&index_path),
926        resolution_mode,
927      ) {
928        return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
929          path,
930          known_exists: true,
931        })));
932      }
933    }
934    // allow resolving .ts-like or .css files for types resolution
935    if media_type.is_typed() || media_type == MediaType::Css {
936      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(local_path)));
937    }
938    Err(TypesNotFoundError(Box::new(TypesNotFoundErrorData {
939      code_specifier: UrlOrPathRef::from_path(&local_path.path).display(),
940      maybe_referrer: maybe_referrer.map(|r| r.display()),
941    })))
942  }
943
944  #[allow(clippy::too_many_arguments)]
945  pub fn resolve_package_import(
946    &self,
947    name: &str,
948    maybe_referrer: Option<&UrlOrPathRef>,
949    referrer_pkg_json: Option<&PackageJson>,
950    resolution_mode: ResolutionMode,
951    resolution_kind: NodeResolutionKind,
952  ) -> Result<UrlOrPath, PackageImportsResolveError> {
953    self
954      .package_imports_resolve_internal(
955        name,
956        maybe_referrer,
957        resolution_mode,
958        referrer_pkg_json,
959        self.condition_resolver.resolve(resolution_mode),
960        resolution_kind,
961      )
962      .map(|url| url.0.into_url_or_path())
963  }
964
965  #[allow(clippy::too_many_arguments)]
966  fn package_imports_resolve_internal(
967    &self,
968    name: &str,
969    maybe_referrer: Option<&UrlOrPathRef>,
970    resolution_mode: ResolutionMode,
971    referrer_pkg_json: Option<&PackageJson>,
972    conditions: &[Cow<'static, str>],
973    resolution_kind: NodeResolutionKind,
974  ) -> Result<MaybeTypesResolvedUrl, PackageImportsResolveError> {
975    if name == "#" || name.starts_with("#/") || name.ends_with('/') {
976      let reason = "is not a valid internal imports specifier name";
977      return Err(
978        errors::InvalidModuleSpecifierError {
979          request: name.to_string(),
980          reason: Cow::Borrowed(reason),
981          maybe_referrer: maybe_referrer.map(to_specifier_display_string),
982        }
983        .into(),
984      );
985    }
986
987    if let Some(pkg_json) = &referrer_pkg_json
988      && let Some(resolved_import) = resolve_pkg_json_import(pkg_json, name)
989    {
990      let maybe_resolved = self.resolve_package_target(
991        &pkg_json.path,
992        resolved_import.target,
993        resolved_import.sub_path,
994        resolved_import.package_sub_path,
995        maybe_referrer,
996        resolution_mode,
997        resolved_import.is_pattern,
998        true,
999        conditions,
1000        resolution_kind,
1001      )?;
1002      if let Some(resolved) = maybe_resolved {
1003        return Ok(resolved);
1004      }
1005    }
1006
1007    Err(
1008      PackageImportNotDefinedError {
1009        name: name.to_string(),
1010        package_json_path: referrer_pkg_json.map(|p| p.path.clone()),
1011        maybe_referrer: maybe_referrer.map(|r| r.display()),
1012      }
1013      .into(),
1014    )
1015  }
1016
1017  #[allow(clippy::too_many_arguments)]
1018  fn resolve_package_target_string(
1019    &self,
1020    target: &str,
1021    subpath: &str,
1022    match_: &str,
1023    package_json_path: &Path,
1024    maybe_referrer: Option<&UrlOrPathRef>,
1025    resolution_mode: ResolutionMode,
1026    pattern: bool,
1027    internal: bool,
1028    conditions: &[Cow<'static, str>],
1029    resolution_kind: NodeResolutionKind,
1030  ) -> Result<MaybeTypesResolvedUrl, PackageTargetResolveError> {
1031    if !subpath.is_empty() && !pattern && !target.ends_with('/') {
1032      return Err(
1033        InvalidPackageTargetError {
1034          pkg_json_path: package_json_path.to_path_buf(),
1035          sub_path: match_.to_string(),
1036          target: target.to_string(),
1037          is_import: internal,
1038          maybe_referrer: maybe_referrer.map(|r| r.display()),
1039        }
1040        .into(),
1041      );
1042    }
1043    let invalid_segment_re =
1044      lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)");
1045    let pattern_re = lazy_regex::regex!(r"\*");
1046    if !target.starts_with("./") {
1047      if internal && !target.starts_with("../") && !target.starts_with('/') {
1048        let target_url = Url::parse(target);
1049        match target_url {
1050          Ok(url) => {
1051            if self
1052              .get_module_name_from_builtin_node_module_url(&url)?
1053              .is_some()
1054            {
1055              return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
1056            }
1057          }
1058          Err(_) => {
1059            let export_target = if pattern {
1060              pattern_re.replace(target, |_caps: &regex::Captures| subpath)
1061            } else if subpath.is_empty() {
1062              Cow::Borrowed(target)
1063            } else {
1064              Cow::Owned(format!("{target}{subpath}"))
1065            };
1066            let result = match self.package_resolve(
1067              &export_target,
1068              &UrlOrPathRef::from_path(package_json_path),
1069              resolution_mode,
1070              conditions,
1071              resolution_kind,
1072            ) {
1073              Ok((url, _)) => Ok(url),
1074              Err(err) => match err.code() {
1075                NodeJsErrorCode::ERR_INVALID_FILE_URL_PATH
1076                | NodeJsErrorCode::ERR_INVALID_MODULE_SPECIFIER
1077                | NodeJsErrorCode::ERR_INVALID_PACKAGE_CONFIG
1078                | NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET
1079                | NodeJsErrorCode::ERR_PACKAGE_IMPORT_NOT_DEFINED
1080                | NodeJsErrorCode::ERR_PACKAGE_PATH_NOT_EXPORTED
1081                | NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
1082                | NodeJsErrorCode::ERR_UNSUPPORTED_DIR_IMPORT
1083                | NodeJsErrorCode::ERR_UNSUPPORTED_ESM_URL_SCHEME
1084                | NodeJsErrorCode::ERR_UNKNOWN_BUILTIN_MODULE
1085                | NodeJsErrorCode::ERR_TYPES_NOT_FOUND => {
1086                  Err(PackageTargetResolveErrorKind::PackageResolve(err).into())
1087                }
1088                NodeJsErrorCode::ERR_MODULE_NOT_FOUND => Err(
1089                  PackageTargetResolveErrorKind::NotFound(
1090                    PackageTargetNotFoundError {
1091                      pkg_json_path: package_json_path.to_path_buf(),
1092                      target: export_target.to_string(),
1093                      maybe_resolved: err
1094                        .maybe_specifier()
1095                        .map(|c| c.into_owned()),
1096                      maybe_referrer: maybe_referrer.map(|r| r.display()),
1097                      resolution_mode,
1098                      resolution_kind,
1099                    },
1100                  )
1101                  .into(),
1102                ),
1103              },
1104            };
1105
1106            return match result {
1107              Ok(url) => Ok(url),
1108              Err(err) => {
1109                if self
1110                  .is_built_in_node_module_checker
1111                  .is_builtin_node_module(target)
1112                {
1113                  Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(
1114                    Url::parse(&format!("node:{}", target)).unwrap(),
1115                  )))
1116                } else {
1117                  Err(err)
1118                }
1119              }
1120            };
1121          }
1122        }
1123      }
1124      return Err(
1125        InvalidPackageTargetError {
1126          pkg_json_path: package_json_path.to_path_buf(),
1127          sub_path: match_.to_string(),
1128          target: target.to_string(),
1129          is_import: internal,
1130          maybe_referrer: maybe_referrer.map(|r| r.display()),
1131        }
1132        .into(),
1133      );
1134    }
1135    if invalid_segment_re.is_match(&target[2..]) {
1136      return Err(
1137        InvalidPackageTargetError {
1138          pkg_json_path: package_json_path.to_path_buf(),
1139          sub_path: match_.to_string(),
1140          target: target.to_string(),
1141          is_import: internal,
1142          maybe_referrer: maybe_referrer.map(|r| r.display()),
1143        }
1144        .into(),
1145      );
1146    }
1147    let package_path = package_json_path.parent().unwrap();
1148    let resolved_path = package_path.join(target).clean();
1149    if !resolved_path.starts_with(package_path) {
1150      return Err(
1151        InvalidPackageTargetError {
1152          pkg_json_path: package_json_path.to_path_buf(),
1153          sub_path: match_.to_string(),
1154          target: target.to_string(),
1155          is_import: internal,
1156          maybe_referrer: maybe_referrer.map(|r| r.display()),
1157        }
1158        .into(),
1159      );
1160    }
1161    let path = if subpath.is_empty() {
1162      LocalPath {
1163        path: resolved_path,
1164        known_exists: false,
1165      }
1166    } else if invalid_segment_re.is_match(subpath) {
1167      let request = if pattern {
1168        match_.replace('*', subpath)
1169      } else {
1170        format!("{match_}{subpath}")
1171      };
1172      return Err(
1173        throw_invalid_subpath(
1174          request,
1175          package_json_path,
1176          internal,
1177          maybe_referrer,
1178        )
1179        .into(),
1180      );
1181    } else if pattern {
1182      let resolved_path_str = resolved_path.to_string_lossy();
1183      let replaced = pattern_re
1184        .replace(&resolved_path_str, |_caps: &regex::Captures| subpath);
1185      LocalPath {
1186        path: PathBuf::from(replaced.as_ref()),
1187        known_exists: false,
1188      }
1189    } else {
1190      LocalPath {
1191        path: resolved_path.join(subpath).clean(),
1192        known_exists: false,
1193      }
1194    };
1195    Ok(self.maybe_resolve_types(
1196      LocalUrlOrPath::Path(path),
1197      maybe_referrer,
1198      resolution_mode,
1199      conditions,
1200      resolution_kind,
1201    )?)
1202  }
1203
1204  #[allow(clippy::too_many_arguments)]
1205  fn resolve_package_target(
1206    &self,
1207    package_json_path: &Path,
1208    target: &Value,
1209    subpath: &str,
1210    package_subpath: &str,
1211    maybe_referrer: Option<&UrlOrPathRef>,
1212    resolution_mode: ResolutionMode,
1213    pattern: bool,
1214    internal: bool,
1215    conditions: &[Cow<'static, str>],
1216    resolution_kind: NodeResolutionKind,
1217  ) -> Result<Option<MaybeTypesResolvedUrl>, PackageTargetResolveError> {
1218    let result = self.resolve_package_target_inner(
1219      package_json_path,
1220      target,
1221      subpath,
1222      package_subpath,
1223      maybe_referrer,
1224      resolution_mode,
1225      pattern,
1226      internal,
1227      conditions,
1228      resolution_kind,
1229    );
1230    match result {
1231      Ok(maybe_resolved) => Ok(maybe_resolved),
1232      Err(err) => {
1233        if resolution_kind.is_types()
1234          && err.code() == NodeJsErrorCode::ERR_TYPES_NOT_FOUND
1235          && conditions != TYPES_ONLY_CONDITIONS
1236        {
1237          // try resolving with just "types" conditions for when someone misconfigures
1238          // and puts the "types" condition in the wrong place
1239          if let Ok(Some(resolved)) = self.resolve_package_target_inner(
1240            package_json_path,
1241            target,
1242            subpath,
1243            package_subpath,
1244            maybe_referrer,
1245            resolution_mode,
1246            pattern,
1247            internal,
1248            TYPES_ONLY_CONDITIONS,
1249            resolution_kind,
1250          ) {
1251            return Ok(Some(resolved));
1252          }
1253        }
1254
1255        Err(err)
1256      }
1257    }
1258  }
1259
1260  #[allow(clippy::too_many_arguments)]
1261  fn resolve_package_target_inner(
1262    &self,
1263    package_json_path: &Path,
1264    target: &Value,
1265    subpath: &str,
1266    package_subpath: &str,
1267    maybe_referrer: Option<&UrlOrPathRef>,
1268    resolution_mode: ResolutionMode,
1269    pattern: bool,
1270    internal: bool,
1271    conditions: &[Cow<'static, str>],
1272    resolution_kind: NodeResolutionKind,
1273  ) -> Result<Option<MaybeTypesResolvedUrl>, PackageTargetResolveError> {
1274    if let Some(target) = target.as_str() {
1275      let url_or_path = self.resolve_package_target_string(
1276        target,
1277        subpath,
1278        package_subpath,
1279        package_json_path,
1280        maybe_referrer,
1281        resolution_mode,
1282        pattern,
1283        internal,
1284        conditions,
1285        resolution_kind,
1286      )?;
1287      return Ok(Some(url_or_path));
1288    } else if let Some(target_arr) = target.as_array() {
1289      if target_arr.is_empty() {
1290        return Ok(None);
1291      }
1292
1293      let mut last_error = None;
1294      for target_item in target_arr {
1295        let resolved_result = self.resolve_package_target(
1296          package_json_path,
1297          target_item,
1298          subpath,
1299          package_subpath,
1300          maybe_referrer,
1301          resolution_mode,
1302          pattern,
1303          internal,
1304          conditions,
1305          resolution_kind,
1306        );
1307
1308        match resolved_result {
1309          Ok(Some(resolved)) => return Ok(Some(resolved)),
1310          Ok(None) => {
1311            last_error = None;
1312            continue;
1313          }
1314          Err(e) => {
1315            if e.code() == NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET {
1316              last_error = Some(e);
1317              continue;
1318            } else {
1319              return Err(e);
1320            }
1321          }
1322        }
1323      }
1324      if last_error.is_none() {
1325        return Ok(None);
1326      }
1327      return Err(last_error.unwrap());
1328    } else if let Some(target_obj) = target.as_object() {
1329      for (key, condition_target) in target_obj {
1330        // TODO(bartlomieju): verify that keys are not numeric
1331        // return Err(errors::err_invalid_package_config(
1332        //   to_file_path_string(package_json_url),
1333        //   Some(base.as_str().to_string()),
1334        //   Some("\"exports\" cannot contain numeric property keys.".to_string()),
1335        // ));
1336
1337        if key == "default"
1338          || conditions.contains(&Cow::Borrowed(key))
1339          || resolution_kind.is_types() && self.matches_types_key(key)
1340        {
1341          let resolved = self.resolve_package_target(
1342            package_json_path,
1343            condition_target,
1344            subpath,
1345            package_subpath,
1346            maybe_referrer,
1347            resolution_mode,
1348            pattern,
1349            internal,
1350            conditions,
1351            resolution_kind,
1352          )?;
1353          match resolved {
1354            Some(resolved) => return Ok(Some(resolved)),
1355            None => {
1356              continue;
1357            }
1358          }
1359        }
1360      }
1361    }
1362
1363    Ok(None)
1364  }
1365
1366  fn matches_types_key(&self, key: &str) -> bool {
1367    if key == "types" {
1368      return true;
1369    }
1370    let Some(ts_version) = &self.resolution_config.typescript_version else {
1371      return false;
1372    };
1373    let Some(constraint) = key.strip_prefix("types@") else {
1374      return false;
1375    };
1376    let Ok(version_req) = VersionReq::parse_from_npm(constraint) else {
1377      return false;
1378    };
1379    version_req.matches(ts_version)
1380  }
1381
1382  #[allow(clippy::too_many_arguments)]
1383  pub fn package_exports_resolve(
1384    &self,
1385    package_json_path: &Path,
1386    package_subpath: &str,
1387    package_exports: &Map<String, Value>,
1388    maybe_referrer: Option<&UrlOrPathRef>,
1389    resolution_mode: ResolutionMode,
1390    conditions: &[Cow<'static, str>],
1391    resolution_kind: NodeResolutionKind,
1392  ) -> Result<UrlOrPath, PackageExportsResolveError> {
1393    self
1394      .package_exports_resolve_internal(
1395        package_json_path,
1396        package_subpath,
1397        package_exports,
1398        maybe_referrer,
1399        resolution_mode,
1400        conditions,
1401        resolution_kind,
1402      )
1403      .map(|url| url.0.into_url_or_path())
1404  }
1405
1406  #[allow(clippy::too_many_arguments)]
1407  fn package_exports_resolve_internal(
1408    &self,
1409    package_json_path: &Path,
1410    package_subpath: &str,
1411    package_exports: &Map<String, Value>,
1412    maybe_referrer: Option<&UrlOrPathRef>,
1413    resolution_mode: ResolutionMode,
1414    conditions: &[Cow<'static, str>],
1415    resolution_kind: NodeResolutionKind,
1416  ) -> Result<MaybeTypesResolvedUrl, PackageExportsResolveError> {
1417    if let Some(target) = package_exports.get(package_subpath)
1418      && package_subpath.find('*').is_none()
1419      && !package_subpath.ends_with('/')
1420    {
1421      let resolved = self.resolve_package_target(
1422        package_json_path,
1423        target,
1424        "",
1425        package_subpath,
1426        maybe_referrer,
1427        resolution_mode,
1428        false,
1429        false,
1430        conditions,
1431        resolution_kind,
1432      )?;
1433      return match resolved {
1434        Some(resolved) => Ok(resolved),
1435        None => Err(
1436          PackagePathNotExportedError {
1437            pkg_json_path: package_json_path.to_path_buf(),
1438            subpath: package_subpath.to_string(),
1439            maybe_referrer: maybe_referrer.map(|r| r.display()),
1440            resolution_kind,
1441          }
1442          .into(),
1443        ),
1444      };
1445    }
1446
1447    let mut best_match = "";
1448    let mut best_match_data = None;
1449    for (key, target) in package_exports {
1450      let Some(pattern_index) = key.find('*') else {
1451        continue;
1452      };
1453      let key_sub = &key[0..pattern_index];
1454      if !package_subpath.starts_with(key_sub) {
1455        continue;
1456      }
1457
1458      // When this reaches EOL, this can throw at the top of the whole function:
1459      //
1460      // if (StringPrototypeEndsWith(packageSubpath, '/'))
1461      //   throwInvalidSubpath(packageSubpath)
1462      //
1463      // To match "imports" and the spec.
1464      if package_subpath.ends_with('/') {
1465        // TODO(bartlomieju):
1466        // emitTrailingSlashPatternDeprecation();
1467      }
1468      let pattern_trailer = &key[pattern_index + 1..];
1469      if package_subpath.len() >= key.len()
1470        && package_subpath.ends_with(&pattern_trailer)
1471        && pattern_key_compare(best_match, key) == 1
1472        && key.rfind('*') == Some(pattern_index)
1473      {
1474        best_match = key;
1475        best_match_data = Some((
1476          target,
1477          &package_subpath
1478            [pattern_index..(package_subpath.len() - pattern_trailer.len())],
1479        ));
1480      }
1481    }
1482
1483    if let Some((target, subpath)) = best_match_data {
1484      let maybe_resolved = self.resolve_package_target(
1485        package_json_path,
1486        target,
1487        subpath,
1488        best_match,
1489        maybe_referrer,
1490        resolution_mode,
1491        true,
1492        false,
1493        conditions,
1494        resolution_kind,
1495      )?;
1496      if let Some(resolved) = maybe_resolved {
1497        return Ok(resolved);
1498      } else {
1499        return Err(
1500          PackagePathNotExportedError {
1501            pkg_json_path: package_json_path.to_path_buf(),
1502            subpath: package_subpath.to_string(),
1503            maybe_referrer: maybe_referrer.map(|r| r.display()),
1504            resolution_kind,
1505          }
1506          .into(),
1507        );
1508      }
1509    }
1510
1511    Err(
1512      PackagePathNotExportedError {
1513        pkg_json_path: package_json_path.to_path_buf(),
1514        subpath: package_subpath.to_string(),
1515        maybe_referrer: maybe_referrer.map(|r| r.display()),
1516        resolution_kind,
1517      }
1518      .into(),
1519    )
1520  }
1521
1522  fn package_resolve(
1523    &self,
1524    specifier: &str,
1525    referrer: &UrlOrPathRef,
1526    resolution_mode: ResolutionMode,
1527    conditions: &[Cow<'static, str>],
1528    resolution_kind: NodeResolutionKind,
1529  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1530    let (package_name, package_subpath, _is_scoped) =
1531      parse_npm_pkg_name(specifier, referrer)?;
1532
1533    if let Some(package_config) = self
1534      .pkg_json_resolver
1535      .get_closest_package_json(referrer.path()?)?
1536    {
1537      // ResolveSelf
1538      if package_config.name.as_deref() == Some(package_name)
1539        && let Some(exports) = &package_config.exports
1540      {
1541        return self
1542          .package_exports_resolve_internal(
1543            &package_config.path,
1544            &package_subpath,
1545            exports,
1546            Some(referrer),
1547            resolution_mode,
1548            conditions,
1549            resolution_kind,
1550          )
1551          .map(|url| (url, ResolvedMethod::PackageExports))
1552          .map_err(|err| err.into());
1553      }
1554    }
1555
1556    self.resolve_package_subpath_for_package(
1557      package_name,
1558      &package_subpath,
1559      referrer,
1560      resolution_mode,
1561      conditions,
1562      resolution_kind,
1563    )
1564  }
1565
1566  #[allow(clippy::too_many_arguments)]
1567  fn resolve_package_subpath_for_package(
1568    &self,
1569    package_name: &str,
1570    package_subpath: &str,
1571    referrer: &UrlOrPathRef,
1572    resolution_mode: ResolutionMode,
1573    conditions: &[Cow<'static, str>],
1574    resolution_kind: NodeResolutionKind,
1575  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1576    let result = self.resolve_package_subpath_for_package_inner(
1577      package_name,
1578      package_subpath,
1579      referrer,
1580      resolution_mode,
1581      conditions,
1582      resolution_kind,
1583    );
1584    if resolution_kind.is_types() && result.is_err() {
1585      // try to resolve with the @types package
1586      let package_name = types_package_name(package_name);
1587      if let Ok(result) = self.resolve_package_subpath_for_package_inner(
1588        &package_name,
1589        package_subpath,
1590        referrer,
1591        resolution_mode,
1592        conditions,
1593        resolution_kind,
1594      ) {
1595        return Ok(result);
1596      }
1597    }
1598    result
1599  }
1600
1601  #[allow(clippy::too_many_arguments)]
1602  fn resolve_package_subpath_for_package_inner(
1603    &self,
1604    package_name: &str,
1605    package_subpath: &str,
1606    referrer: &UrlOrPathRef,
1607    resolution_mode: ResolutionMode,
1608    conditions: &[Cow<'static, str>],
1609    resolution_kind: NodeResolutionKind,
1610  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
1611    let package_dir_path = self
1612      .npm_pkg_folder_resolver
1613      .resolve_package_folder_from_package(package_name, referrer)?;
1614
1615    // todo: error with this instead when can't find package
1616    // Err(errors::err_module_not_found(
1617    //   &package_json_url
1618    //     .join(".")
1619    //     .unwrap()
1620    //     .to_file_path()
1621    //     .unwrap()
1622    //     .display()
1623    //     .to_string(),
1624    //   &to_file_path_string(referrer),
1625    //   "package",
1626    // ))
1627
1628    // Package match.
1629    self
1630      .resolve_package_dir_subpath(
1631        &package_dir_path,
1632        package_subpath,
1633        Some(referrer),
1634        resolution_mode,
1635        conditions,
1636        resolution_kind,
1637      )
1638      .map_err(|err| err.into())
1639  }
1640
1641  #[allow(clippy::too_many_arguments)]
1642  fn resolve_package_dir_subpath(
1643    &self,
1644    package_dir_path: &Path,
1645    package_subpath: &str,
1646    maybe_referrer: Option<&UrlOrPathRef>,
1647    resolution_mode: ResolutionMode,
1648    conditions: &[Cow<'static, str>],
1649    resolution_kind: NodeResolutionKind,
1650  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1651  {
1652    let package_json_path = package_dir_path.join("package.json");
1653    match self
1654      .pkg_json_resolver
1655      .load_package_json(&package_json_path)?
1656    {
1657      Some(pkg_json) => self.resolve_package_subpath(
1658        &pkg_json,
1659        package_subpath,
1660        maybe_referrer,
1661        resolution_mode,
1662        conditions,
1663        resolution_kind,
1664      ),
1665      None => self
1666        .resolve_package_subpath_no_pkg_json(
1667          package_dir_path,
1668          package_subpath,
1669          maybe_referrer,
1670          resolution_mode,
1671          conditions,
1672          resolution_kind,
1673        )
1674        .map(|url| (url, ResolvedMethod::PackageSubPath))
1675        .map_err(|err| {
1676          PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1677        }),
1678    }
1679  }
1680
1681  #[allow(clippy::too_many_arguments)]
1682  fn resolve_package_subpath(
1683    &self,
1684    package_json: &PackageJson,
1685    package_subpath: &str,
1686    referrer: Option<&UrlOrPathRef>,
1687    resolution_mode: ResolutionMode,
1688    conditions: &[Cow<'static, str>],
1689    resolution_kind: NodeResolutionKind,
1690  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
1691  {
1692    if let Some(exports) = &package_json.exports {
1693      let result = self.package_exports_resolve_internal(
1694        &package_json.path,
1695        package_subpath,
1696        exports,
1697        referrer,
1698        resolution_mode,
1699        conditions,
1700        resolution_kind,
1701      );
1702      match result {
1703        Ok(found) => return Ok((found, ResolvedMethod::PackageExports)),
1704        Err(exports_err) => {
1705          if resolution_kind.is_types() && package_subpath == "." {
1706            return self
1707              .legacy_main_resolve(
1708                package_json,
1709                referrer,
1710                resolution_mode,
1711                conditions,
1712                resolution_kind,
1713              )
1714              .map(|url| (url, ResolvedMethod::PackageSubPath))
1715              .map_err(|err| {
1716                PackageSubpathResolveErrorKind::LegacyResolve(err).into()
1717              });
1718          }
1719          return Err(
1720            PackageSubpathResolveErrorKind::Exports(exports_err).into(),
1721          );
1722        }
1723      }
1724    }
1725
1726    if package_subpath == "." {
1727      self
1728        .legacy_main_resolve(
1729          package_json,
1730          referrer,
1731          resolution_mode,
1732          conditions,
1733          resolution_kind,
1734        )
1735        .map(|url| (url, ResolvedMethod::PackageSubPath))
1736        .map_err(|err| {
1737          PackageSubpathResolveErrorKind::LegacyResolve(err).into_box()
1738        })
1739    } else {
1740      self
1741        .resolve_subpath_exact(
1742          package_json.path.parent().unwrap(),
1743          package_subpath,
1744          Some(package_json),
1745          referrer,
1746          resolution_mode,
1747          conditions,
1748          resolution_kind,
1749        )
1750        .map(|url| (url, ResolvedMethod::PackageSubPath))
1751        .map_err(|err| {
1752          PackageSubpathResolveErrorKind::LegacyResolve(err.into()).into_box()
1753        })
1754    }
1755  }
1756
1757  fn pkg_json_types_versions<'a>(
1758    &'a self,
1759    pkg_json: &'a PackageJson,
1760    resolution_kind: NodeResolutionKind,
1761  ) -> Option<TypesVersions<'a, TSys>> {
1762    if !resolution_kind.is_types() {
1763      return None;
1764    }
1765    pkg_json
1766      .types_versions
1767      .as_ref()
1768      .and_then(|entries| {
1769        let ts_version = self.resolution_config.typescript_version.as_ref()?;
1770        entries
1771          .iter()
1772          .filter_map(|(k, v)| {
1773            let version_req = VersionReq::parse_from_npm(k).ok()?;
1774            version_req.matches(ts_version).then_some(v)
1775          })
1776          .next()
1777      })
1778      .and_then(|value| value.as_object())
1779      .map(|value| TypesVersions {
1780        value,
1781        dir_path: pkg_json.dir_path(),
1782        sys: &self.sys,
1783      })
1784  }
1785
1786  #[allow(clippy::too_many_arguments)]
1787  fn resolve_subpath_exact(
1788    &self,
1789    directory: &Path,
1790    package_subpath: &str,
1791    package_json: Option<&PackageJson>,
1792    referrer: Option<&UrlOrPathRef>,
1793    resolution_mode: ResolutionMode,
1794    conditions: &[Cow<'static, str>],
1795    resolution_kind: NodeResolutionKind,
1796  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
1797    assert_ne!(package_subpath, ".");
1798    let types_versions = package_json.and_then(|pkg_json| {
1799      self.pkg_json_types_versions(pkg_json, resolution_kind)
1800    });
1801    let package_subpath = types_versions
1802      .and_then(|v| v.map(package_subpath))
1803      .unwrap_or(Cow::Borrowed(package_subpath));
1804    let file_path = directory.join(package_subpath.as_ref());
1805    self.maybe_resolve_types(
1806      LocalUrlOrPath::Path(LocalPath {
1807        path: file_path,
1808        known_exists: false,
1809      }),
1810      referrer,
1811      resolution_mode,
1812      conditions,
1813      resolution_kind,
1814    )
1815  }
1816
1817  fn resolve_package_subpath_no_pkg_json(
1818    &self,
1819    directory: &Path,
1820    package_subpath: &str,
1821    maybe_referrer: Option<&UrlOrPathRef>,
1822    resolution_mode: ResolutionMode,
1823    conditions: &[Cow<'static, str>],
1824    resolution_kind: NodeResolutionKind,
1825  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1826    if package_subpath == "." {
1827      self.legacy_index_resolve(
1828        directory,
1829        maybe_referrer,
1830        resolution_mode,
1831        resolution_kind,
1832      )
1833    } else {
1834      self
1835        .resolve_subpath_exact(
1836          directory,
1837          package_subpath,
1838          None,
1839          maybe_referrer,
1840          resolution_mode,
1841          conditions,
1842          resolution_kind,
1843        )
1844        .map_err(|err| err.into())
1845    }
1846  }
1847
1848  pub(crate) fn legacy_fallback_resolve<'a>(
1849    &self,
1850    package_json: &'a PackageJson,
1851  ) -> Option<&'a str> {
1852    fn filter_empty(value: Option<&str>) -> Option<&str> {
1853      value.map(|v| v.trim()).filter(|v| !v.is_empty())
1854    }
1855    if self.resolution_config.bundle_mode {
1856      let maybe_browser = if self.resolution_config.prefer_browser_field {
1857        filter_empty(package_json.browser.as_deref())
1858      } else {
1859        None
1860      };
1861      maybe_browser
1862        .or(filter_empty(package_json.module.as_deref()))
1863        .or(filter_empty(package_json.main.as_deref()))
1864    } else {
1865      filter_empty(package_json.main.as_deref())
1866    }
1867  }
1868
1869  fn legacy_main_resolve(
1870    &self,
1871    package_json: &PackageJson,
1872    maybe_referrer: Option<&UrlOrPathRef>,
1873    resolution_mode: ResolutionMode,
1874    conditions: &[Cow<'static, str>],
1875    resolution_kind: NodeResolutionKind,
1876  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1877    let maybe_main = if resolution_kind.is_types() {
1878      match package_json.types.as_ref() {
1879        Some(types) => {
1880          let types_versions =
1881            self.pkg_json_types_versions(package_json, resolution_kind);
1882          Some(
1883            types_versions
1884              .and_then(|v| v.map(types.as_ref()))
1885              .unwrap_or(Cow::Borrowed(types.as_str())),
1886          )
1887        }
1888        None => {
1889          // fallback to checking the main entrypoint for
1890          // a corresponding declaration file
1891          if let Some(main) = self.legacy_fallback_resolve(package_json) {
1892            let main = package_json.path.parent().unwrap().join(main).clean();
1893            let decl_path_result = self.path_to_declaration_path(
1894              LocalPath {
1895                path: main,
1896                known_exists: false,
1897              },
1898              maybe_referrer,
1899              resolution_mode,
1900              conditions,
1901            );
1902            // don't surface errors, fallback to checking the index now
1903            if let Ok(url_or_path) = decl_path_result {
1904              return Ok(url_or_path);
1905            }
1906          }
1907          None
1908        }
1909      }
1910    } else {
1911      self
1912        .legacy_fallback_resolve(package_json)
1913        .map(Cow::Borrowed)
1914    };
1915
1916    if let Some(main) = maybe_main.as_deref() {
1917      let guess = package_json.path.parent().unwrap().join(main).clean();
1918      if self.sys.is_file(&guess) {
1919        return Ok(self.maybe_resolve_types(
1920          LocalUrlOrPath::Path(LocalPath {
1921            path: guess,
1922            known_exists: true,
1923          }),
1924          maybe_referrer,
1925          resolution_mode,
1926          conditions,
1927          resolution_kind,
1928        )?);
1929      }
1930
1931      // todo(dsherret): investigate exactly how node and typescript handles this
1932      let endings = if resolution_kind.is_types() {
1933        match resolution_mode {
1934          ResolutionMode::Require => {
1935            vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"]
1936          }
1937          ResolutionMode::Import => vec![
1938            ".d.ts",
1939            ".d.mts",
1940            "/index.d.ts",
1941            "/index.d.mts",
1942            ".d.cts",
1943            "/index.d.cts",
1944          ],
1945        }
1946      } else {
1947        vec![".js", "/index.js"]
1948      };
1949      for ending in endings {
1950        let guess = package_json
1951          .path
1952          .parent()
1953          .unwrap()
1954          .join(format!("{main}{ending}"))
1955          .clean();
1956        if self.sys.is_file(&guess) {
1957          // TODO(bartlomieju): emitLegacyIndexDeprecation()
1958          return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
1959            path: guess,
1960            known_exists: true,
1961          })));
1962        }
1963      }
1964    }
1965
1966    self.legacy_index_resolve(
1967      package_json.path.parent().unwrap(),
1968      maybe_referrer,
1969      resolution_mode,
1970      resolution_kind,
1971    )
1972  }
1973
1974  fn legacy_index_resolve(
1975    &self,
1976    directory: &Path,
1977    maybe_referrer: Option<&UrlOrPathRef>,
1978    resolution_mode: ResolutionMode,
1979    resolution_kind: NodeResolutionKind,
1980  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
1981    let index_file_names = if resolution_kind.is_types() {
1982      // todo(dsherret): investigate exactly how typescript does this
1983      match resolution_mode {
1984        ResolutionMode::Require => vec!["index.d.ts", "index.d.cts"],
1985        ResolutionMode::Import => {
1986          vec!["index.d.ts", "index.d.mts", "index.d.cts"]
1987        }
1988      }
1989    } else {
1990      // Node only resolves index.js and not index.cjs/mjs
1991      vec!["index.js"]
1992    };
1993    for index_file_name in index_file_names {
1994      let guess = directory.join(index_file_name).clean();
1995      if self.sys.is_file(&guess) {
1996        // TODO(bartlomieju): emitLegacyIndexDeprecation()
1997        return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
1998          path: guess,
1999          known_exists: true,
2000        })));
2001      }
2002    }
2003
2004    if resolution_kind.is_types() {
2005      Err(
2006        TypesNotFoundError(Box::new(TypesNotFoundErrorData {
2007          code_specifier: UrlOrPathRef::from_path(&directory.join("index.js"))
2008            .display(),
2009          maybe_referrer: maybe_referrer.map(|r| r.display()),
2010        }))
2011        .into(),
2012      )
2013    } else {
2014      Err(
2015        ModuleNotFoundError {
2016          specifier: UrlOrPath::Path(directory.join("index.js")),
2017          maybe_referrer: maybe_referrer.map(|r| r.display()),
2018          suggested_ext: None,
2019        }
2020        .into(),
2021      )
2022    }
2023  }
2024
2025  /// Resolves a specifier that is pointing into a node_modules folder by canonicalizing it.
2026  ///
2027  /// Returns `None` when the specifier is not in a node_modules folder.
2028  pub fn handle_if_in_node_modules(&self, specifier: &Url) -> Option<Url> {
2029    // skip canonicalizing if we definitely know it's unnecessary
2030    if specifier.scheme() == "file"
2031      && specifier.path().contains("/node_modules/")
2032    {
2033      // Specifiers in the node_modules directory are canonicalized
2034      // so canoncalize then check if it's in the node_modules directory.
2035      let specifier = resolve_specifier_into_node_modules(&self.sys, specifier);
2036      return Some(specifier);
2037    }
2038
2039    None
2040  }
2041
2042  /// Ex. returns `fs` for `node:fs`
2043  fn get_module_name_from_builtin_node_module_url<'url>(
2044    &self,
2045    url: &'url Url,
2046  ) -> Result<Option<&'url str>, UnknownBuiltInNodeModuleError> {
2047    if url.scheme() != "node" {
2048      return Ok(None);
2049    }
2050
2051    let module_name = url.path();
2052
2053    if !self
2054      .is_built_in_node_module_checker
2055      .is_builtin_node_module(module_name)
2056    {
2057      return Err(UnknownBuiltInNodeModuleError {
2058        module_name: module_name.to_string(),
2059      });
2060    }
2061    Ok(Some(module_name))
2062  }
2063}
2064
2065struct ResolvedPkgJsonImport<'a> {
2066  pub target: &'a serde_json::Value,
2067  pub sub_path: &'a str,
2068  pub package_sub_path: &'a str,
2069  pub is_pattern: bool,
2070}
2071
2072fn resolve_pkg_json_import<'a>(
2073  pkg_json: &'a PackageJson,
2074  name: &'a str,
2075) -> Option<ResolvedPkgJsonImport<'a>> {
2076  let imports = pkg_json.imports.as_ref()?;
2077  if let Some((name, target)) =
2078    imports.get_key_value(name).filter(|_| !name.contains('*'))
2079  {
2080    Some(ResolvedPkgJsonImport {
2081      target,
2082      sub_path: "",
2083      package_sub_path: name,
2084      is_pattern: false,
2085    })
2086  } else {
2087    let mut best_match: &'a str = "";
2088    let mut best_match_subpath: &'a str = "";
2089    for key in imports.keys() {
2090      let pattern_index = key.find('*');
2091      if let Some(pattern_index) = pattern_index {
2092        let key_sub = &key[0..pattern_index];
2093        if name.starts_with(key_sub) {
2094          let pattern_trailer = &key[pattern_index + 1..];
2095          if name.len() > key.len()
2096            && name.ends_with(&pattern_trailer)
2097            && pattern_key_compare(best_match, key) == 1
2098            && key.rfind('*') == Some(pattern_index)
2099          {
2100            best_match = key;
2101            best_match_subpath =
2102              &name[pattern_index..(name.len() - pattern_trailer.len())];
2103          }
2104        }
2105      }
2106    }
2107
2108    if !best_match.is_empty() {
2109      let target = imports.get(best_match).unwrap();
2110      Some(ResolvedPkgJsonImport {
2111        target,
2112        sub_path: best_match_subpath,
2113        package_sub_path: best_match,
2114        is_pattern: true,
2115      })
2116    } else {
2117      None
2118    }
2119  }
2120}
2121
2122fn resolve_bin_entry_value<'a>(
2123  package_json: &'a PackageJson,
2124  bin_name: Option<&str>,
2125) -> Result<&'a str, AnyError> {
2126  let bin = match &package_json.bin {
2127    Some(bin) => bin,
2128    None => bail!(
2129      "'{}' did not have a bin property",
2130      package_json.path.display(),
2131    ),
2132  };
2133  let bin_entry = match bin {
2134    Value::String(_) => {
2135      if bin_name.is_some()
2136        && bin_name
2137          != package_json
2138            .name
2139            .as_deref()
2140            .map(|name| name.rsplit_once('/').map_or(name, |(_, name)| name))
2141      {
2142        None
2143      } else {
2144        Some(bin)
2145      }
2146    }
2147    Value::Object(o) => {
2148      if let Some(bin_name) = bin_name {
2149        o.get(bin_name)
2150      } else if o.len() == 1
2151        || o.len() > 1 && o.values().all(|v| v == o.values().next().unwrap())
2152      {
2153        o.values().next()
2154      } else {
2155        package_json.name.as_ref().and_then(|n| o.get(n))
2156      }
2157    }
2158    _ => bail!(
2159      "'{}' did not have a bin property with a string or object value",
2160      package_json.path.display()
2161    ),
2162  };
2163  let bin_entry = match bin_entry {
2164    Some(e) => e,
2165    None => {
2166      let prefix = package_json
2167        .name
2168        .as_ref()
2169        .map(|n| {
2170          let mut prefix = format!("npm:{}", n);
2171          if let Some(version) = &package_json.version {
2172            prefix.push('@');
2173            prefix.push_str(version);
2174          }
2175          prefix.push('/');
2176          prefix
2177        })
2178        .unwrap_or_default();
2179      let keys = bin
2180        .as_object()
2181        .map(|o| {
2182          o.keys()
2183            .map(|k| format!(" * {prefix}{k}"))
2184            .collect::<Vec<_>>()
2185        })
2186        .unwrap_or_default();
2187      bail!(
2188        "'{}' did not have a bin entry{}{}",
2189        package_json.path.display(),
2190        bin_name
2191          .or(package_json.name.as_deref())
2192          .map(|name| format!(" for '{}'", name))
2193          .unwrap_or_default(),
2194        if keys.is_empty() {
2195          "".to_string()
2196        } else {
2197          format!("\n\nPossibilities:\n{}", keys.join("\n"))
2198        }
2199      )
2200    }
2201  };
2202  match bin_entry {
2203    Value::String(s) => Ok(s),
2204    _ => bail!(
2205      "'{}' had a non-string sub property of bin",
2206      package_json.path.display(),
2207    ),
2208  }
2209}
2210
2211fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool {
2212  if specifier.is_empty() {
2213    return false;
2214  }
2215
2216  if specifier.starts_with('/') {
2217    return true;
2218  }
2219
2220  deno_path_util::is_relative_specifier(specifier)
2221}
2222
2223/// Alternate `PathBuf::with_extension` that will handle known extensions
2224/// more intelligently.
2225fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
2226  const NON_DECL_EXTS: &[&str] = &[
2227    "cjs", "js", "json", "jsx", "mjs", "tsx", /* ex. types.d */ "d",
2228  ];
2229  const DECL_EXTS: &[&str] = &["cts", "mts", "ts"];
2230
2231  let file_name = match path.file_name() {
2232    Some(value) => value.to_string_lossy(),
2233    None => return path.to_path_buf(),
2234  };
2235  let lowercase_file_name = file_name.to_lowercase();
2236  let period_index = lowercase_file_name.rfind('.').and_then(|period_index| {
2237    let ext = &lowercase_file_name[period_index + 1..];
2238    if DECL_EXTS.contains(&ext) {
2239      if let Some(next_period_index) =
2240        lowercase_file_name[..period_index].rfind('.')
2241      {
2242        if &lowercase_file_name[next_period_index + 1..period_index] == "d" {
2243          Some(next_period_index)
2244        } else {
2245          Some(period_index)
2246        }
2247      } else {
2248        Some(period_index)
2249      }
2250    } else if NON_DECL_EXTS.contains(&ext) {
2251      Some(period_index)
2252    } else {
2253      None
2254    }
2255  });
2256
2257  let file_name = match period_index {
2258    Some(period_index) => &file_name[..period_index],
2259    None => &file_name,
2260  };
2261  path.with_file_name(format!("{file_name}.{ext}"))
2262}
2263
2264fn to_specifier_display_string(url: &UrlOrPathRef) -> String {
2265  if let Ok(path) = url.path() {
2266    path.display().to_string()
2267  } else {
2268    url.display().to_string()
2269  }
2270}
2271
2272fn throw_invalid_subpath(
2273  subpath: String,
2274  package_json_path: &Path,
2275  internal: bool,
2276  maybe_referrer: Option<&UrlOrPathRef>,
2277) -> InvalidModuleSpecifierError {
2278  let ie = if internal { "imports" } else { "exports" };
2279  let reason = format!(
2280    "request is not a valid subpath for the \"{}\" resolution of {}",
2281    ie,
2282    package_json_path.display(),
2283  );
2284  InvalidModuleSpecifierError {
2285    request: subpath,
2286    reason: Cow::Owned(reason),
2287    maybe_referrer: maybe_referrer.map(to_specifier_display_string),
2288  }
2289}
2290
2291pub fn parse_npm_pkg_name<'a>(
2292  specifier: &'a str,
2293  referrer: &UrlOrPathRef,
2294) -> Result<(&'a str, Cow<'static, str>, bool), InvalidModuleSpecifierError> {
2295  let mut separator_index = specifier.find('/');
2296  let mut valid_package_name = true;
2297  let mut is_scoped = false;
2298  if specifier.is_empty() {
2299    valid_package_name = false;
2300  } else if specifier.starts_with('@') {
2301    is_scoped = true;
2302    if let Some(index) = separator_index {
2303      separator_index = specifier[index + 1..]
2304        .find('/')
2305        .map(|new_index| index + 1 + new_index);
2306    } else {
2307      valid_package_name = false;
2308    }
2309  }
2310
2311  let (package_name, subpath) = if let Some(index) = separator_index {
2312    let (package_name, subpath) = specifier.split_at(index);
2313    (package_name, Cow::Owned(format!(".{}", subpath)))
2314  } else {
2315    (specifier, Cow::Borrowed("."))
2316  };
2317
2318  // Package name cannot have leading . and cannot have percent-encoding or separators.
2319  for ch in package_name.chars() {
2320    if ch == '%' || ch == '\\' {
2321      valid_package_name = false;
2322      break;
2323    }
2324  }
2325
2326  if !valid_package_name {
2327    return Err(errors::InvalidModuleSpecifierError {
2328      request: specifier.to_string(),
2329      reason: Cow::Borrowed("is not a valid package name"),
2330      maybe_referrer: Some(to_specifier_display_string(referrer)),
2331    });
2332  }
2333
2334  Ok((package_name, subpath, is_scoped))
2335}
2336
2337/// Resolves a specifier that is pointing into a node_modules folder.
2338///
2339/// Note: This should be called whenever getting the specifier from
2340/// a Module::External(module) reference because that module might
2341/// not be fully resolved at the time deno_graph is analyzing it
2342/// because the node_modules folder might not exist at that time.
2343pub fn resolve_specifier_into_node_modules(
2344  sys: &impl FsCanonicalize,
2345  specifier: &Url,
2346) -> Url {
2347  deno_path_util::url_to_file_path(specifier)
2348    .ok()
2349    // this path might not exist at the time the graph is being created
2350    // because the node_modules folder might not yet exist
2351    .and_then(|path| {
2352      deno_path_util::fs::canonicalize_path_maybe_not_exists(sys, &path).ok()
2353    })
2354    .and_then(|path| deno_path_util::url_from_file_path(&path).ok())
2355    .unwrap_or_else(|| specifier.clone())
2356}
2357
2358fn pattern_key_compare(a: &str, b: &str) -> i32 {
2359  let a_pattern_index = a.find('*');
2360  let b_pattern_index = b.find('*');
2361
2362  let base_len_a = if let Some(index) = a_pattern_index {
2363    index + 1
2364  } else {
2365    a.len()
2366  };
2367  let base_len_b = if let Some(index) = b_pattern_index {
2368    index + 1
2369  } else {
2370    b.len()
2371  };
2372
2373  if base_len_a > base_len_b {
2374    return -1;
2375  }
2376
2377  if base_len_b > base_len_a {
2378    return 1;
2379  }
2380
2381  if a_pattern_index.is_none() {
2382    return 1;
2383  }
2384
2385  if b_pattern_index.is_none() {
2386    return -1;
2387  }
2388
2389  if a.len() > b.len() {
2390    return -1;
2391  }
2392
2393  if b.len() > a.len() {
2394    return 1;
2395  }
2396
2397  0
2398}
2399
2400/// Gets the corresponding @types package for the provided package name.
2401pub fn types_package_name(package_name: &str) -> String {
2402  debug_assert!(!package_name.starts_with("@types/"));
2403  // Scoped packages will get two underscores for each slash
2404  // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages
2405  format!(
2406    "@types/{}",
2407    package_name.trim_start_matches('@').replace('/', "__")
2408  )
2409}
2410
2411/// Node is more lenient joining paths than the url crate is,
2412/// so this function handles that.
2413fn node_join_url(url: &Url, path: &str) -> Result<Url, url::ParseError> {
2414  if let Some(suffix) = path.strip_prefix(".//") {
2415    // specifier had two leading slashes
2416    url.join(&format!("./{}", suffix))
2417  } else {
2418    url.join(path)
2419  }
2420}
2421
2422struct TypesVersions<'a, TSys: FsMetadata> {
2423  dir_path: &'a Path,
2424  value: &'a serde_json::Map<std::string::String, serde_json::Value>,
2425  sys: &'a NodeResolutionSys<TSys>,
2426}
2427
2428impl<'a, TSys: FsMetadata> TypesVersions<'a, TSys> {
2429  pub fn map(&self, search: &str) -> Option<Cow<'a, str>> {
2430    let mut search = search
2431      .strip_prefix("./")
2432      .unwrap_or(search)
2433      .trim_matches('/');
2434    for (key, value) in self.value {
2435      let key = key.strip_suffix("./").unwrap_or(key).trim_matches('/');
2436      let is_match = if key == "*" || key == search {
2437        true
2438      } else if let Some(key_prefix) = key.strip_suffix("/*") {
2439        if let Some(new_search) = search.strip_prefix(key_prefix) {
2440          search = new_search.trim_matches('/');
2441          true
2442        } else {
2443          false
2444        }
2445      } else {
2446        false
2447      };
2448      if !is_match {
2449        continue;
2450      }
2451      if let Some(values) = value.as_array() {
2452        for value in values.iter().filter_map(|s| s.as_str()) {
2453          let value = if let Some(asterisk_index) = value.find('*') {
2454            Cow::Owned(format!(
2455              "{}{}{}",
2456              &value[..asterisk_index],
2457              search,
2458              &value[asterisk_index + 1..]
2459            ))
2460          } else {
2461            Cow::Borrowed(value)
2462          };
2463          let path = self.dir_path.join(value.as_ref());
2464          if self.sys.is_file(&path) {
2465            return Some(value);
2466          }
2467        }
2468      }
2469    }
2470    None
2471  }
2472}
2473
2474#[cfg(test)]
2475mod tests {
2476  use serde_json::json;
2477  use sys_traits::FsCreateDirAll;
2478  use sys_traits::FsWrite;
2479  use sys_traits::impls::InMemorySys;
2480
2481  use super::*;
2482
2483  fn build_package_json(json: Value) -> PackageJson {
2484    PackageJson::load_from_value(PathBuf::from("/package.json"), json).unwrap()
2485  }
2486
2487  #[test]
2488  fn test_resolve_bin_entry_value() {
2489    // should resolve the specified value
2490    let pkg_json = build_package_json(json!({
2491      "name": "pkg",
2492      "version": "1.1.1",
2493      "bin": {
2494        "bin1": "./value1",
2495        "bin2": "./value2",
2496        "pkg": "./value3",
2497      }
2498    }));
2499    assert_eq!(
2500      resolve_bin_entry_value(&pkg_json, Some("bin1")).unwrap(),
2501      "./value1"
2502    );
2503
2504    // should resolve the value with the same name when not specified
2505    assert_eq!(
2506      resolve_bin_entry_value(&pkg_json, None).unwrap(),
2507      "./value3"
2508    );
2509
2510    // should not resolve when specified value does not exist
2511    assert_eq!(
2512      resolve_bin_entry_value(&pkg_json, Some("other"),)
2513        .err()
2514        .unwrap()
2515        .to_string(),
2516      concat!(
2517        "'/package.json' did not have a bin entry for 'other'\n",
2518        "\n",
2519        "Possibilities:\n",
2520        " * npm:pkg@1.1.1/bin1\n",
2521        " * npm:pkg@1.1.1/bin2\n",
2522        " * npm:pkg@1.1.1/pkg"
2523      )
2524    );
2525
2526    // should not resolve when default value can't be determined
2527    let pkg_json = build_package_json(json!({
2528      "name": "pkg",
2529      "version": "1.1.1",
2530      "bin": {
2531        "bin": "./value1",
2532        "bin2": "./value2",
2533      }
2534    }));
2535    assert_eq!(
2536      resolve_bin_entry_value(&pkg_json, None)
2537        .err()
2538        .unwrap()
2539        .to_string(),
2540      concat!(
2541        "'/package.json' did not have a bin entry for 'pkg'\n",
2542        "\n",
2543        "Possibilities:\n",
2544        " * npm:pkg@1.1.1/bin\n",
2545        " * npm:pkg@1.1.1/bin2",
2546      )
2547    );
2548
2549    // should resolve since all the values are the same
2550    let pkg_json = build_package_json(json!({
2551      "name": "pkg",
2552      "version": "1.2.3",
2553      "bin": {
2554        "bin1": "./value",
2555        "bin2": "./value",
2556      }
2557    }));
2558    assert_eq!(
2559      resolve_bin_entry_value(&pkg_json, None,).unwrap(),
2560      "./value"
2561    );
2562
2563    // should not resolve when specified and is a string
2564    let pkg_json = build_package_json(json!({
2565      "name": "pkg",
2566      "version": "1.2.3",
2567      "bin": "./value",
2568    }));
2569    assert_eq!(
2570      resolve_bin_entry_value(&pkg_json, Some("path"),)
2571        .err()
2572        .unwrap()
2573        .to_string(),
2574      "'/package.json' did not have a bin entry for 'path'"
2575    );
2576
2577    // no version in the package.json
2578    let pkg_json = build_package_json(json!({
2579      "name": "pkg",
2580      "bin": {
2581        "bin1": "./value1",
2582        "bin2": "./value2",
2583      }
2584    }));
2585    assert_eq!(
2586      resolve_bin_entry_value(&pkg_json, None)
2587        .err()
2588        .unwrap()
2589        .to_string(),
2590      concat!(
2591        "'/package.json' did not have a bin entry for 'pkg'\n",
2592        "\n",
2593        "Possibilities:\n",
2594        " * npm:pkg/bin1\n",
2595        " * npm:pkg/bin2",
2596      )
2597    );
2598
2599    // no name or version in the package.json
2600    let pkg_json = build_package_json(json!({
2601      "bin": {
2602        "bin1": "./value1",
2603        "bin2": "./value2",
2604      }
2605    }));
2606    assert_eq!(
2607      resolve_bin_entry_value(&pkg_json, None)
2608        .err()
2609        .unwrap()
2610        .to_string(),
2611      concat!(
2612        "'/package.json' did not have a bin entry\n",
2613        "\n",
2614        "Possibilities:\n",
2615        " * bin1\n",
2616        " * bin2",
2617      )
2618    );
2619  }
2620
2621  #[test]
2622  fn test_parse_npm_pkg_name() {
2623    let dummy_referrer = Url::parse("http://example.com").unwrap();
2624    let dummy_referrer = UrlOrPathRef::from_url(&dummy_referrer);
2625
2626    assert_eq!(
2627      parse_npm_pkg_name("fetch-blob", &dummy_referrer).unwrap(),
2628      ("fetch-blob", Cow::Borrowed("."), false)
2629    );
2630    assert_eq!(
2631      parse_npm_pkg_name("@vue/plugin-vue", &dummy_referrer).unwrap(),
2632      ("@vue/plugin-vue", Cow::Borrowed("."), true)
2633    );
2634    assert_eq!(
2635      parse_npm_pkg_name("@astrojs/prism/dist/highlighter", &dummy_referrer)
2636        .unwrap(),
2637      (
2638        "@astrojs/prism",
2639        Cow::Owned("./dist/highlighter".to_string()),
2640        true
2641      )
2642    );
2643  }
2644
2645  #[test]
2646  fn test_with_known_extension() {
2647    let cases = &[
2648      ("test", "d.ts", "test.d.ts"),
2649      ("test.d.ts", "ts", "test.ts"),
2650      ("test.worker", "d.ts", "test.worker.d.ts"),
2651      ("test.d.mts", "js", "test.js"),
2652    ];
2653    for (path, ext, expected) in cases {
2654      let actual = with_known_extension(Path::new(path), ext);
2655      assert_eq!(actual.to_string_lossy(), *expected);
2656    }
2657  }
2658
2659  #[test]
2660  fn test_types_package_name() {
2661    assert_eq!(types_package_name("name"), "@types/name");
2662    assert_eq!(
2663      types_package_name("@scoped/package"),
2664      "@types/scoped__package"
2665    );
2666  }
2667
2668  #[test]
2669  fn test_types_versions() {
2670    let dir_path = PathBuf::from("/dir");
2671    let sys = InMemorySys::default();
2672    sys.fs_create_dir_all(dir_path.join("ts3.1")).unwrap();
2673    sys.fs_write(dir_path.join("file.d.ts"), "").unwrap();
2674    sys.fs_write(dir_path.join("ts3.1/file.d.ts"), "").unwrap();
2675    sys.fs_write(dir_path.join("ts3.1/file2.d.ts"), "").unwrap();
2676    let node_resolution_sys = NodeResolutionSys::new(sys, None);
2677
2678    // asterisk key
2679    {
2680      let value = serde_json::json!({
2681        "*": ["ts3.1/*"]
2682      });
2683      let types_versions = TypesVersions {
2684        dir_path: &dir_path,
2685        value: value.as_object().unwrap(),
2686        sys: &node_resolution_sys,
2687      };
2688      assert_eq!(types_versions.map("file.d.ts").unwrap(), "ts3.1/file.d.ts");
2689      assert_eq!(
2690        types_versions.map("file2.d.ts").unwrap(),
2691        "ts3.1/file2.d.ts"
2692      );
2693      assert!(types_versions.map("non_existent/file.d.ts").is_none());
2694    }
2695    // specific file
2696    {
2697      let value = serde_json::json!({
2698        "types.d.ts": ["ts3.1/file.d.ts"]
2699      });
2700      let types_versions = TypesVersions {
2701        dir_path: &dir_path,
2702        value: value.as_object().unwrap(),
2703        sys: &node_resolution_sys,
2704      };
2705      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2706      assert!(types_versions.map("file2.d.ts").is_none());
2707    }
2708    // multiple specific files
2709    {
2710      let value = serde_json::json!({
2711        "types.d.ts": ["ts3.1/file.d.ts"],
2712        "other.d.ts": ["ts3.1/file2.d.ts"],
2713      });
2714      let types_versions = TypesVersions {
2715        dir_path: &dir_path,
2716        value: value.as_object().unwrap(),
2717        sys: &node_resolution_sys,
2718      };
2719      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
2720      assert_eq!(
2721        types_versions.map("other.d.ts").unwrap(),
2722        "ts3.1/file2.d.ts"
2723      );
2724      assert!(types_versions.map("file2.d.ts").is_none());
2725    }
2726    // existing fallback
2727    {
2728      let value = serde_json::json!({
2729        "*": ["ts3.1/*", "ts3.1/file2.d.ts"]
2730      });
2731      let types_versions = TypesVersions {
2732        dir_path: &dir_path,
2733        value: value.as_object().unwrap(),
2734        sys: &node_resolution_sys,
2735      };
2736      assert_eq!(
2737        types_versions.map("testing/types.d.ts").unwrap(),
2738        "ts3.1/file2.d.ts"
2739      );
2740    }
2741    // text then asterisk in key
2742    {
2743      let value = serde_json::json!({
2744        "sub/*": ["ts3.1/file.d.ts"]
2745      });
2746      let types_versions = TypesVersions {
2747        dir_path: &dir_path,
2748        value: value.as_object().unwrap(),
2749        sys: &node_resolution_sys,
2750      };
2751      assert_eq!(
2752        types_versions.map("sub/types.d.ts").unwrap(),
2753        "ts3.1/file.d.ts"
2754      );
2755    }
2756    // text then asterisk in key and asterisk in value
2757    {
2758      let value = serde_json::json!({
2759        "sub/*": ["ts3.1/*"]
2760      });
2761      let types_versions = TypesVersions {
2762        dir_path: &dir_path,
2763        value: value.as_object().unwrap(),
2764        sys: &node_resolution_sys,
2765      };
2766      assert_eq!(
2767        types_versions.map("sub/file.d.ts").unwrap(),
2768        "ts3.1/file.d.ts"
2769      );
2770    }
2771  }
2772}