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