1use 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 pub import_conditions_override: Option<Vec<Cow<'static, str>>>,
79 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
229struct MaybeTypesResolvedUrl(LocalUrlOrPath);
233
234enum 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 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 pub fn resolve(
348 &self,
349 specifier: &str,
350 referrer: &Url,
351 resolution_mode: ResolutionMode,
352 resolution_kind: NodeResolutionKind,
353 ) -> Result<NodeResolution, NodeResolveError> {
354 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 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 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 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 return Ok(UrlOrPath::Path(path));
521 } else {
522 (path, None)
523 }
524 }
525 };
526
527 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 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 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 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 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 Ok(package_folder.join(bin_entry))
726 }
727
728 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 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, };
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 ".",
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 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: ®ex::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: ®ex::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 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 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 if package_subpath.ends_with('/') {
1455 }
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 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 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 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 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 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 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 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 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 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 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 pub fn handle_if_in_node_modules(&self, specifier: &Url) -> Option<Url> {
2019 if specifier.scheme() == "file"
2021 && specifier.path().contains("/node_modules/")
2022 {
2023 let specifier = resolve_specifier_into_node_modules(&self.sys, specifier);
2026 return Some(specifier);
2027 }
2028
2029 None
2030 }
2031
2032 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
2156fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
2159 const NON_DECL_EXTS: &[&str] = &[
2160 "cjs", "js", "json", "jsx", "mjs", "tsx", "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 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
2270pub 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 .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
2333pub fn types_package_name(package_name: &str) -> String {
2335 debug_assert!(!package_name.starts_with("@types/"));
2336 format!(
2339 "@types/{}",
2340 package_name.trim_start_matches('@').replace('/', "__")
2341 )
2342}
2343
2344fn node_join_url(url: &Url, path: &str) -> Result<Url, url::ParseError> {
2347 if let Some(suffix) = path.strip_prefix(".//") {
2348 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 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 assert_eq!(
2439 resolve_bin_entry_value(&pkg_json, None).unwrap(),
2440 "./value3"
2441 );
2442
2443 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 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 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 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 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 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 {
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 {
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 {
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 {
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 {
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 {
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}