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