1use crate::config::{JsxEmit, ModuleResolutionKind, PathMapping, ResolvedCompilerOptions};
22use crate::diagnostics::{Diagnostic, DiagnosticBag};
23use crate::emitter::ModuleKind;
24use crate::module_resolver_helpers::*;
25use crate::span::Span;
26use rustc_hash::FxHashMap;
27use serde_json;
28use std::path::{Path, PathBuf};
29
30pub const CANNOT_FIND_MODULE: u32 = 2307;
48
49pub const MODULE_RESOLUTION_MODE_MISMATCH: u32 = 2792;
55
56pub const JSON_MODULE_WITHOUT_RESOLVE_JSON_MODULE: u32 = 2732;
63
64pub const IMPORT_PATH_NEEDS_EXTENSION: u32 = 2834;
70pub const IMPORT_PATH_NEEDS_EXTENSION_SUGGESTION: u32 = 2835;
71pub const IMPORT_PATH_TS_EXTENSION_NOT_ALLOWED: u32 = 5097;
72pub const MODULE_WAS_RESOLVED_TO_BUT_JSX_NOT_SET: u32 = 6142;
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct ResolvedModule {
77 pub resolved_path: PathBuf,
79 pub is_external: bool,
81 pub package_name: Option<String>,
83 pub original_specifier: String,
85 pub extension: ModuleExtension,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum ModuleExtension {
92 Ts,
93 Tsx,
94 Dts,
95 DmTs,
96 DCts,
97 Js,
98 Jsx,
99 Mjs,
100 Cjs,
101 Mts,
102 Cts,
103 Json,
104 Unknown,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
110pub enum PackageType {
111 Module,
113 #[default]
115 CommonJs,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
121pub enum ImportingModuleKind {
122 Esm,
124 #[default]
126 CommonJs,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
132pub enum ImportKind {
133 #[default]
135 EsmImport,
136 DynamicImport,
138 CjsRequire,
140 EsmReExport,
142}
143
144impl ModuleExtension {
145 pub fn from_path(path: &Path) -> Self {
147 let path_str = path.to_string_lossy();
148
149 if path_str.ends_with(".d.ts") {
151 return Self::Dts;
152 }
153 if path_str.ends_with(".d.mts") {
154 return Self::DmTs;
155 }
156 if path_str.ends_with(".d.cts") {
157 return Self::DCts;
158 }
159
160 match path.extension().and_then(|e| e.to_str()) {
161 Some("ts") => Self::Ts,
162 Some("tsx") => Self::Tsx,
163 Some("js") => Self::Js,
164 Some("jsx") => Self::Jsx,
165 Some("mjs") => Self::Mjs,
166 Some("cjs") => Self::Cjs,
167 Some("mts") => Self::Mts,
168 Some("cts") => Self::Cts,
169 Some("json") => Self::Json,
170 _ => Self::Unknown,
171 }
172 }
173
174 pub const fn as_str(&self) -> &'static str {
176 match self {
177 Self::Ts => ".ts",
178 Self::Tsx => ".tsx",
179 Self::Dts => ".d.ts",
180 Self::DmTs => ".d.mts",
181 Self::DCts => ".d.cts",
182 Self::Js => ".js",
183 Self::Jsx => ".jsx",
184 Self::Mjs => ".mjs",
185 Self::Cjs => ".cjs",
186 Self::Mts => ".mts",
187 Self::Cts => ".cts",
188 Self::Json => ".json",
189 Self::Unknown => "",
190 }
191 }
192
193 pub const fn forces_esm(&self) -> bool {
196 matches!(self, Self::Mts | Self::Mjs | Self::DmTs)
197 }
198
199 pub const fn forces_cjs(&self) -> bool {
202 matches!(self, Self::Cts | Self::Cjs | Self::DCts)
203 }
204}
205
206fn explicit_ts_extension(specifier: &str) -> Option<String> {
207 if specifier.ends_with(".d.ts")
208 || specifier.ends_with(".d.mts")
209 || specifier.ends_with(".d.cts")
210 {
211 return None;
212 }
213 for ext in [".ts", ".tsx", ".mts", ".cts"] {
214 if specifier.ends_with(ext) {
215 return Some(ext.to_string());
216 }
217 }
218 None
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
223pub enum ResolutionFailure {
224 NotFound {
226 specifier: String,
228 containing_file: String,
230 span: Span,
232 },
233 InvalidSpecifier {
235 message: String,
237 containing_file: String,
239 span: Span,
241 },
242 PackageJsonError {
244 message: String,
246 containing_file: String,
248 span: Span,
250 },
251 CircularResolution {
253 message: String,
255 containing_file: String,
257 span: Span,
259 },
260 PathMappingFailed {
262 message: String,
264 containing_file: String,
266 span: Span,
268 },
269 ImportPathNeedsExtension {
272 specifier: String,
274 suggested_extension: String,
276 containing_file: String,
278 span: Span,
280 },
281 ImportingTsExtensionNotAllowed {
283 extension: String,
285 containing_file: String,
287 span: Span,
289 },
290 JsxNotEnabled {
292 specifier: String,
294 resolved_path: PathBuf,
296 containing_file: String,
298 span: Span,
300 },
301 ModuleResolutionModeMismatch {
304 specifier: String,
306 containing_file: String,
308 span: Span,
310 },
311 JsonModuleWithoutResolveJsonModule {
314 specifier: String,
316 containing_file: String,
318 span: Span,
320 },
321}
322
323impl ResolutionFailure {
324 pub fn to_diagnostic(&self) -> Diagnostic {
332 match self {
333 Self::NotFound {
334 specifier,
335 containing_file,
336 span,
337 } => Diagnostic::error(
338 containing_file,
339 *span,
340 format!("Cannot find module '{specifier}' or its corresponding type declarations.",),
341 CANNOT_FIND_MODULE,
342 ),
343 Self::InvalidSpecifier {
344 message,
345 containing_file,
346 span,
347 }
348 | Self::PackageJsonError {
349 message,
350 containing_file,
351 span,
352 }
353 | Self::CircularResolution {
354 message,
355 containing_file,
356 span,
357 }
358 | Self::PathMappingFailed {
359 message,
360 containing_file,
361 span,
362 } => Diagnostic::error(
363 containing_file,
364 *span,
365 format!("Cannot find module '{message}' or its corresponding type declarations.",),
366 CANNOT_FIND_MODULE,
367 ),
368 Self::ImportPathNeedsExtension {
369 specifier,
370 suggested_extension,
371 containing_file,
372 span,
373 } => {
374 if suggested_extension.is_empty() {
375 Diagnostic::error(
377 containing_file,
378 *span,
379 "Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Consider adding an extension to the import path.".to_string(),
380 IMPORT_PATH_NEEDS_EXTENSION,
381 )
382 } else {
383 Diagnostic::error(
385 containing_file,
386 *span,
387 format!(
388 "Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '{specifier}{suggested_extension}'?",
389 ),
390 IMPORT_PATH_NEEDS_EXTENSION_SUGGESTION,
391 )
392 }
393 }
394 Self::ImportingTsExtensionNotAllowed {
395 extension,
396 containing_file,
397 span,
398 } => Diagnostic::error(
399 containing_file,
400 *span,
401 format!(
402 "An import path can only end with a '{extension}' extension when 'allowImportingTsExtensions' is enabled.",
403 ),
404 IMPORT_PATH_TS_EXTENSION_NOT_ALLOWED,
405 ),
406 Self::JsxNotEnabled {
407 specifier,
408 resolved_path,
409 containing_file,
410 span,
411 } => Diagnostic::error(
412 containing_file,
413 *span,
414 format!(
415 "Module '{}' was resolved to '{}', but '--jsx' is not set.",
416 specifier,
417 resolved_path.display()
418 ),
419 MODULE_WAS_RESOLVED_TO_BUT_JSX_NOT_SET,
420 ),
421 Self::ModuleResolutionModeMismatch {
422 specifier,
423 containing_file,
424 span,
425 } => Diagnostic::error(
426 containing_file,
427 *span,
428 format!(
429 "Cannot find module '{specifier}'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?",
430 ),
431 MODULE_RESOLUTION_MODE_MISMATCH,
432 ),
433 Self::JsonModuleWithoutResolveJsonModule {
434 specifier,
435 containing_file,
436 span,
437 } => Diagnostic::error(
438 containing_file,
439 *span,
440 format!(
441 "Cannot find module '{specifier}'. Consider using '--resolveJsonModule' to import module with '.json' extension.",
442 ),
443 JSON_MODULE_WITHOUT_RESOLVE_JSON_MODULE,
444 ),
445 }
446 }
447
448 pub fn containing_file(&self) -> &str {
450 match self {
451 Self::NotFound {
452 containing_file, ..
453 }
454 | Self::InvalidSpecifier {
455 containing_file, ..
456 }
457 | Self::PackageJsonError {
458 containing_file, ..
459 }
460 | Self::CircularResolution {
461 containing_file, ..
462 }
463 | Self::PathMappingFailed {
464 containing_file, ..
465 }
466 | Self::ImportPathNeedsExtension {
467 containing_file, ..
468 }
469 | Self::ImportingTsExtensionNotAllowed {
470 containing_file, ..
471 }
472 | Self::JsxNotEnabled {
473 containing_file, ..
474 }
475 | Self::ModuleResolutionModeMismatch {
476 containing_file, ..
477 }
478 | Self::JsonModuleWithoutResolveJsonModule {
479 containing_file, ..
480 } => containing_file,
481 }
482 }
483
484 pub const fn span(&self) -> Span {
486 match self {
487 Self::NotFound { span, .. }
488 | Self::InvalidSpecifier { span, .. }
489 | Self::PackageJsonError { span, .. }
490 | Self::CircularResolution { span, .. }
491 | Self::PathMappingFailed { span, .. }
492 | Self::ImportPathNeedsExtension { span, .. }
493 | Self::ImportingTsExtensionNotAllowed { span, .. }
494 | Self::JsxNotEnabled { span, .. }
495 | Self::ModuleResolutionModeMismatch { span, .. }
496 | Self::JsonModuleWithoutResolveJsonModule { span, .. } => *span,
497 }
498 }
499
500 pub const fn is_not_found(&self) -> bool {
502 matches!(self, Self::NotFound { .. })
503 }
504}
505
506#[derive(Debug)]
508pub struct ModuleResolver {
509 resolution_kind: ModuleResolutionKind,
511 base_url: Option<PathBuf>,
513 path_mappings: Vec<PathMapping>,
515 type_roots: Vec<PathBuf>,
517 types_versions_compiler_version: Option<String>,
518 resolve_package_json_exports: bool,
519 resolve_package_json_imports: bool,
520 module_suffixes: Vec<String>,
521 resolve_json_module: bool,
522 allow_arbitrary_extensions: bool,
523 allow_importing_ts_extensions: bool,
524 jsx: Option<JsxEmit>,
525 resolution_cache: FxHashMap<
527 (PathBuf, String, ImportingModuleKind),
528 Result<ResolvedModule, ResolutionFailure>,
529 >,
530 custom_conditions: Vec<String>,
532 module_kind: ModuleKind,
533 allow_js: bool,
535 rewrite_relative_import_extensions: bool,
537 package_type_cache: FxHashMap<PathBuf, Option<PackageType>>,
539 current_package_type: Option<PackageType>,
541}
542
543struct PathMappingAttempt {
544 resolved: Option<ResolvedModule>,
545 attempted: bool,
546}
547
548impl ModuleResolver {
549 pub fn new(options: &ResolvedCompilerOptions) -> Self {
551 let resolution_kind = options.effective_module_resolution();
552
553 let module_suffixes = if options.module_suffixes.is_empty() {
554 vec![String::new()]
555 } else {
556 options.module_suffixes.clone()
557 };
558
559 Self {
560 resolution_kind,
561 base_url: options.base_url.clone(),
562 path_mappings: options.paths.clone().unwrap_or_default(),
563 type_roots: options.type_roots.clone().unwrap_or_default(),
564 types_versions_compiler_version: options.types_versions_compiler_version.clone(),
565 resolve_package_json_exports: options.resolve_package_json_exports,
566 resolve_package_json_imports: options.resolve_package_json_imports,
567 module_suffixes,
568 resolve_json_module: options.resolve_json_module,
569 allow_arbitrary_extensions: options.allow_arbitrary_extensions,
570 allow_importing_ts_extensions: options.allow_importing_ts_extensions,
571 jsx: options.jsx,
572 resolution_cache: FxHashMap::default(),
573 custom_conditions: options.custom_conditions.clone(),
574 module_kind: options.printer.module,
575 allow_js: options.allow_js,
576 rewrite_relative_import_extensions: options.rewrite_relative_import_extensions,
577 package_type_cache: FxHashMap::default(),
578 current_package_type: None,
579 }
580 }
581
582 pub fn node_resolver() -> Self {
584 Self {
585 resolution_kind: ModuleResolutionKind::Node,
586 base_url: None,
587 path_mappings: Vec::new(),
588 type_roots: Vec::new(),
589 types_versions_compiler_version: None,
590 resolve_package_json_exports: false,
591 resolve_package_json_imports: false,
592 module_suffixes: vec![String::new()],
593 resolve_json_module: false,
594 allow_arbitrary_extensions: false,
595 allow_importing_ts_extensions: false,
596 jsx: None,
597 resolution_cache: FxHashMap::default(),
598 custom_conditions: Vec::new(),
599 module_kind: ModuleKind::CommonJS,
600 allow_js: false,
601 rewrite_relative_import_extensions: false,
602 package_type_cache: FxHashMap::default(),
603 current_package_type: None,
604 }
605 }
606
607 pub fn resolve(
609 &mut self,
610 specifier: &str,
611 containing_file: &Path,
612 specifier_span: Span,
613 ) -> Result<ResolvedModule, ResolutionFailure> {
614 self.resolve_with_kind(
615 specifier,
616 containing_file,
617 specifier_span,
618 ImportKind::EsmImport,
619 )
620 }
621
622 pub fn resolve_with_kind(
626 &mut self,
627 specifier: &str,
628 containing_file: &Path,
629 specifier_span: Span,
630 import_kind: ImportKind,
631 ) -> Result<ResolvedModule, ResolutionFailure> {
632 let containing_dir = containing_file
633 .parent()
634 .unwrap_or_else(|| Path::new("."))
635 .to_path_buf();
636 let containing_file_str = containing_file.display().to_string();
637
638 self.current_package_type = match self.resolution_kind {
639 ModuleResolutionKind::Node16 | ModuleResolutionKind::NodeNext => {
640 self.get_package_type_for_dir(&containing_dir)
641 }
642 _ => None,
643 };
644
645 let importing_module_kind = self.get_importing_module_kind(containing_file);
647 let cache_key = (
648 containing_dir.clone(),
649 specifier.to_string(),
650 importing_module_kind,
651 );
652 if let Some(cached) = self.resolution_cache.get(&cache_key) {
653 return cached.clone();
654 }
655
656 let (mut result, path_mapping_attempted) = self.resolve_uncached(
657 specifier,
658 &containing_dir,
659 &containing_file_str,
660 specifier_span,
661 importing_module_kind,
662 import_kind,
663 );
664
665 if !self.allow_importing_ts_extensions
666 && !self.allow_arbitrary_extensions
667 && !self.rewrite_relative_import_extensions
668 && (self.base_url.is_some() || self.path_mappings.is_empty())
669 && let Some(extension) = explicit_ts_extension(specifier)
670 && !path_mapping_attempted
671 && matches!(result, Err(ResolutionFailure::NotFound { .. }))
672 {
673 result = Err(ResolutionFailure::ImportingTsExtensionNotAllowed {
674 extension,
675 containing_file: containing_file_str.clone(),
676 span: specifier_span,
677 });
678 }
679
680 if let Ok(resolved) = &result {
681 if matches!(
682 resolved.extension,
683 ModuleExtension::Tsx | ModuleExtension::Jsx
684 ) && self.jsx.is_none()
685 {
686 result = Err(ResolutionFailure::JsxNotEnabled {
687 specifier: specifier.to_string(),
688 resolved_path: resolved.resolved_path.clone(),
689 containing_file: containing_file_str,
690 span: specifier_span,
691 });
692 } else if resolved.extension == ModuleExtension::Json && !self.resolve_json_module {
693 result = Err(ResolutionFailure::JsonModuleWithoutResolveJsonModule {
694 specifier: specifier.to_string(),
695 containing_file: containing_file_str,
696 span: specifier_span,
697 });
698 }
699 }
700
701 self.resolution_cache.insert(cache_key, result.clone());
703
704 result
705 }
706
707 fn get_importing_module_kind(&mut self, file_path: &Path) -> ImportingModuleKind {
709 let extension = ModuleExtension::from_path(file_path);
710
711 if extension.forces_esm() {
713 return ImportingModuleKind::Esm;
714 }
715
716 if extension.forces_cjs() {
718 return ImportingModuleKind::CommonJs;
719 }
720
721 match self.module_kind {
723 ModuleKind::CommonJS | ModuleKind::AMD | ModuleKind::UMD | ModuleKind::System => {
724 return ImportingModuleKind::CommonJs;
725 }
726 ModuleKind::None
727 | ModuleKind::ES2015
728 | ModuleKind::ES2020
729 | ModuleKind::ES2022
730 | ModuleKind::ESNext
731 | ModuleKind::Node16
732 | ModuleKind::NodeNext
733 | ModuleKind::Preserve => {}
734 }
735
736 if let Some(dir) = file_path.parent() {
738 match self.get_package_type_for_dir(dir) {
739 Some(PackageType::Module) => ImportingModuleKind::Esm,
740 Some(PackageType::CommonJs) | None => ImportingModuleKind::CommonJs,
741 }
742 } else {
743 ImportingModuleKind::CommonJs
744 }
745 }
746
747 fn get_package_type_for_dir(&mut self, dir: &Path) -> Option<PackageType> {
749 if let Some(cached) = self.package_type_cache.get(dir) {
751 return *cached;
752 }
753
754 let mut current = dir.to_path_buf();
755 let mut visited = Vec::new();
756
757 loop {
758 if let Some(&cached) = self.package_type_cache.get(¤t) {
760 let result = cached;
761 for path in visited {
763 self.package_type_cache.insert(path, result);
764 }
765 return result;
766 }
767
768 visited.push(current.clone());
769
770 let package_json_path = current.join("package.json");
772 if package_json_path.is_file()
773 && let Ok(pj) = self.read_package_json(&package_json_path)
774 {
775 let package_type = pj.package_type.as_deref().and_then(|t| match t {
776 "module" => Some(PackageType::Module),
777 "commonjs" => Some(PackageType::CommonJs),
778 _ => None,
779 });
780 for path in visited {
782 self.package_type_cache.insert(path, package_type);
783 }
784 return package_type;
785 }
786
787 match current.parent() {
789 Some(parent) if parent != current => current = parent.to_path_buf(),
790 _ => break,
791 }
792 }
793
794 for path in visited {
796 self.package_type_cache.insert(path, None);
797 }
798 None
799 }
800
801 fn resolve_uncached(
803 &self,
804 specifier: &str,
805 containing_dir: &Path,
806 containing_file: &str,
807 specifier_span: Span,
808 importing_module_kind: ImportingModuleKind,
809 import_kind: ImportKind,
810 ) -> (Result<ResolvedModule, ResolutionFailure>, bool) {
811 if specifier.starts_with('#') {
814 if !self.resolve_package_json_imports {
815 return (
816 Err(ResolutionFailure::NotFound {
817 specifier: specifier.to_string(),
818 containing_file: containing_file.to_string(),
819 span: specifier_span,
820 }),
821 false,
822 );
823 }
824 return (
825 self.resolve_package_imports(
826 specifier,
827 containing_dir,
828 containing_file,
829 specifier_span,
830 importing_module_kind,
831 ),
832 false,
833 );
834 }
835
836 let mut path_mapping_attempted = false;
840 if self.base_url.is_some() && !self.path_mappings.is_empty() {
841 let attempt = self.try_path_mappings(specifier, containing_dir);
842 if let Some(resolved) = attempt.resolved {
843 return (Ok(resolved), path_mapping_attempted);
844 }
845 path_mapping_attempted = attempt.attempted;
846 }
847
848 if specifier.starts_with("./")
850 || specifier.starts_with("../")
851 || specifier == "."
852 || specifier == ".."
853 {
854 return (
855 self.resolve_relative(
856 specifier,
857 containing_dir,
858 containing_file,
859 specifier_span,
860 importing_module_kind,
861 import_kind,
862 ),
863 path_mapping_attempted,
864 );
865 }
866
867 if specifier.starts_with('/') {
869 return (
870 self.resolve_absolute(specifier, containing_file, specifier_span),
871 path_mapping_attempted,
872 );
873 }
874
875 if let Some(base_url) = &self.base_url {
877 let candidate = base_url.join(specifier);
878 if let Some(resolved) = self.try_file_or_directory(&candidate) {
879 return (
880 Ok(ResolvedModule {
881 resolved_path: resolved.clone(),
882 is_external: false,
883 package_name: None,
884 original_specifier: specifier.to_string(),
885 extension: ModuleExtension::from_path(&resolved),
886 }),
887 path_mapping_attempted,
888 );
889 }
890 }
891
892 let resolved = if matches!(self.resolution_kind, ModuleResolutionKind::Classic) {
896 self.resolve_classic_non_relative(
897 specifier,
898 containing_dir,
899 containing_file,
900 specifier_span,
901 )
902 } else {
903 self.resolve_bare_specifier(
904 specifier,
905 containing_dir,
906 containing_file,
907 specifier_span,
908 importing_module_kind,
909 )
910 };
911
912 if let Err(ResolutionFailure::NotFound { .. }) = &resolved
913 && path_mapping_attempted
914 {
915 return (
916 Err(ResolutionFailure::PathMappingFailed {
917 message: specifier.to_string(),
918 containing_file: containing_file.to_string(),
919 span: specifier_span,
920 }),
921 path_mapping_attempted,
922 );
923 }
924
925 (resolved, path_mapping_attempted)
926 }
927
928 fn resolve_package_imports(
930 &self,
931 specifier: &str,
932 containing_dir: &Path,
933 containing_file: &str,
934 specifier_span: Span,
935 importing_module_kind: ImportingModuleKind,
936 ) -> Result<ResolvedModule, ResolutionFailure> {
937 let mut current = containing_dir.to_path_buf();
939
940 loop {
941 let package_json_path = current.join("package.json");
942
943 if package_json_path.is_file()
944 && let Ok(package_json) = self.read_package_json(&package_json_path)
945 && let Some(imports) = &package_json.imports
946 {
947 let conditions = self.get_export_conditions(importing_module_kind);
948
949 if let Some(target) = self.resolve_imports_subpath(imports, specifier, &conditions)
950 {
951 let resolved_path = current.join(target.trim_start_matches("./"));
953
954 if let Some(resolved) = self.try_file_or_directory(&resolved_path) {
955 return Ok(ResolvedModule {
956 resolved_path: resolved.clone(),
957 is_external: false,
958 package_name: package_json.name.clone(),
959 original_specifier: specifier.to_string(),
960 extension: ModuleExtension::from_path(&resolved),
961 });
962 }
963 }
964 }
965
966 match current.parent() {
968 Some(parent) if parent != current => current = parent.to_path_buf(),
969 _ => break,
970 }
971 }
972
973 Err(ResolutionFailure::NotFound {
974 specifier: specifier.to_string(),
975 containing_file: containing_file.to_string(),
976 span: specifier_span,
977 })
978 }
979
980 fn resolve_imports_subpath(
982 &self,
983 imports: &FxHashMap<String, PackageExports>,
984 specifier: &str,
985 conditions: &[String],
986 ) -> Option<String> {
987 if let Some(value) = imports.get(specifier) {
989 return self.resolve_export_target_to_string(value, conditions);
990 }
991
992 let mut best_match: Option<(usize, String, &PackageExports)> = None;
994
995 for (pattern, value) in imports {
996 if let Some(wildcard) = match_imports_pattern(pattern, specifier) {
997 let specificity = pattern.len();
998 let is_better = match &best_match {
999 None => true,
1000 Some((best_len, _, _)) => specificity > *best_len,
1001 };
1002 if is_better {
1003 best_match = Some((specificity, wildcard, value));
1004 }
1005 }
1006 }
1007
1008 if let Some((_, wildcard, value)) = best_match
1009 && let Some(target) = self.resolve_export_target_to_string(value, conditions)
1010 {
1011 return Some(apply_wildcard_substitution(&target, &wildcard));
1012 }
1013
1014 None
1015 }
1016
1017 #[allow(clippy::only_used_in_recursion)]
1019 fn resolve_export_target_to_string(
1020 &self,
1021 value: &PackageExports,
1022 conditions: &[String],
1023 ) -> Option<String> {
1024 match value {
1025 PackageExports::String(s) => Some(s.clone()),
1026 PackageExports::Conditional(cond_entries) => {
1027 for (key, nested) in cond_entries {
1029 if conditions.iter().any(|c| c == key) {
1030 if matches!(nested, PackageExports::Null) {
1031 return None;
1032 }
1033 if let Some(result) =
1034 self.resolve_export_target_to_string(nested, conditions)
1035 {
1036 return Some(result);
1037 }
1038 }
1039 }
1040 None
1041 }
1042 PackageExports::Map(_) | PackageExports::Null => None, }
1044 }
1045
1046 fn get_export_conditions(&self, importing_module_kind: ImportingModuleKind) -> Vec<String> {
1058 let mut conditions = Vec::new();
1059
1060 for cond in &self.custom_conditions {
1062 conditions.push(cond.clone());
1063 }
1064
1065 conditions.push("types".to_string());
1067
1068 match self.resolution_kind {
1070 ModuleResolutionKind::Node16 | ModuleResolutionKind::NodeNext => {
1071 conditions.push("node".to_string());
1072 }
1073 _ => {}
1074 }
1075
1076 match importing_module_kind {
1078 ImportingModuleKind::Esm => {
1079 conditions.push("import".to_string());
1080 }
1081 ImportingModuleKind::CommonJs => {
1082 conditions.push("require".to_string());
1083 }
1084 }
1085
1086 conditions.push("default".to_string());
1088
1089 conditions
1090 }
1091
1092 fn try_path_mappings(&self, specifier: &str, _containing_dir: &Path) -> PathMappingAttempt {
1094 let mut sorted_mappings: Vec<_> = self.path_mappings.iter().collect();
1096 sorted_mappings.sort_by_key(|b| std::cmp::Reverse(b.specificity()));
1097
1098 let mut attempted = false;
1099 for mapping in sorted_mappings {
1100 if let Some(star_match) = mapping.match_specifier(specifier) {
1101 attempted = true;
1102 for target in &mapping.targets {
1104 let substituted = if target.contains('*') {
1105 target.replace('*', &star_match)
1106 } else {
1107 target.clone()
1108 };
1109 if Self::has_path_mapping_target_extension(&substituted) {
1110 continue;
1111 }
1112
1113 let base = self
1114 .base_url
1115 .as_deref()
1116 .expect("path mappings require baseUrl for attempted resolution");
1117 let candidate = base.join(&substituted);
1118
1119 if let Some(resolved) = self.try_file_or_directory(&candidate) {
1120 return PathMappingAttempt {
1121 resolved: Some(ResolvedModule {
1122 resolved_path: resolved,
1123 is_external: false,
1124 package_name: None,
1125 original_specifier: specifier.to_string(),
1126 extension: ModuleExtension::from_path(&candidate),
1127 }),
1128 attempted,
1129 };
1130 }
1131 }
1132 }
1133 }
1134
1135 PathMappingAttempt {
1136 resolved: None,
1137 attempted,
1138 }
1139 }
1140
1141 fn has_path_mapping_target_extension(target: &str) -> bool {
1142 let base_path = std::path::Path::new(target);
1143 split_path_extension(base_path).is_some()
1144 }
1145
1146 fn resolve_relative(
1148 &self,
1149 specifier: &str,
1150 containing_dir: &Path,
1151 containing_file: &str,
1152 specifier_span: Span,
1153 importing_module_kind: ImportingModuleKind,
1154 import_kind: ImportKind,
1155 ) -> Result<ResolvedModule, ResolutionFailure> {
1156 let candidate = containing_dir.join(specifier);
1157
1158 let specifier_has_extension = Path::new(specifier)
1160 .extension()
1161 .is_some_and(|ext| !ext.is_empty());
1162
1163 let needs_extension_check = matches!(
1172 self.resolution_kind,
1173 ModuleResolutionKind::Node16 | ModuleResolutionKind::NodeNext
1174 ) && !specifier_has_extension
1175 && match import_kind {
1176 ImportKind::DynamicImport => true,
1178 ImportKind::EsmImport | ImportKind::EsmReExport => {
1180 importing_module_kind == ImportingModuleKind::Esm
1181 }
1182 ImportKind::CjsRequire => false,
1184 };
1185
1186 if needs_extension_check {
1187 if let Some(resolved) = self.try_file_or_directory(&candidate) {
1189 let resolved_ext = ModuleExtension::from_path(&resolved);
1191 let suggested_ext = match resolved_ext {
1193 ModuleExtension::Ts
1194 | ModuleExtension::Tsx
1195 | ModuleExtension::Js
1196 | ModuleExtension::Jsx
1197 | ModuleExtension::Dts
1198 | ModuleExtension::Unknown => ".js",
1199 ModuleExtension::Mts | ModuleExtension::Mjs | ModuleExtension::DmTs => ".mjs",
1200 ModuleExtension::Cts | ModuleExtension::Cjs | ModuleExtension::DCts => ".cjs",
1201 ModuleExtension::Json => ".json",
1202 };
1203 return Err(ResolutionFailure::ImportPathNeedsExtension {
1204 specifier: specifier.to_string(),
1205 suggested_extension: suggested_ext.to_string(),
1206 containing_file: containing_file.to_string(),
1207 span: specifier_span,
1208 });
1209 }
1210 return Err(ResolutionFailure::ImportPathNeedsExtension {
1212 specifier: specifier.to_string(),
1213 suggested_extension: String::new(),
1214 containing_file: containing_file.to_string(),
1215 span: specifier_span,
1216 });
1217 }
1218
1219 if let Some(resolved) = self.try_file_or_directory(&candidate) {
1220 return Ok(ResolvedModule {
1221 resolved_path: resolved.clone(),
1222 is_external: false,
1223 package_name: None,
1224 original_specifier: specifier.to_string(),
1225 extension: ModuleExtension::from_path(&resolved),
1226 });
1227 }
1228
1229 Err(ResolutionFailure::NotFound {
1233 specifier: specifier.to_string(),
1234 containing_file: containing_file.to_string(),
1235 span: specifier_span,
1236 })
1237 }
1238
1239 fn resolve_absolute(
1241 &self,
1242 specifier: &str,
1243 containing_file: &str,
1244 specifier_span: Span,
1245 ) -> Result<ResolvedModule, ResolutionFailure> {
1246 let path = PathBuf::from(specifier);
1247
1248 if let Some(resolved) = self.try_file_or_directory(&path) {
1249 return Ok(ResolvedModule {
1250 resolved_path: resolved.clone(),
1251 is_external: false,
1252 package_name: None,
1253 original_specifier: specifier.to_string(),
1254 extension: ModuleExtension::from_path(&resolved),
1255 });
1256 }
1257
1258 Err(ResolutionFailure::NotFound {
1259 specifier: specifier.to_string(),
1260 containing_file: containing_file.to_string(),
1261 span: specifier_span,
1262 })
1263 }
1264
1265 fn resolve_classic_non_relative(
1277 &self,
1278 specifier: &str,
1279 containing_dir: &Path,
1280 containing_file: &str,
1281 specifier_span: Span,
1282 ) -> Result<ResolvedModule, ResolutionFailure> {
1283 let (package_name, subpath) = parse_package_specifier(specifier);
1284 let conditions = self.get_export_conditions(ImportingModuleKind::CommonJs);
1285
1286 let mut current = containing_dir.to_path_buf();
1287 loop {
1288 let candidate = current.join(specifier);
1289 if let Some(resolved) = self.try_file_or_directory(&candidate) {
1290 return Ok(ResolvedModule {
1291 resolved_path: resolved.clone(),
1292 is_external: false,
1293 package_name: None,
1294 original_specifier: specifier.to_string(),
1295 extension: ModuleExtension::from_path(&resolved),
1296 });
1297 }
1298
1299 if !package_name.starts_with("@types/") {
1302 let types_package = types_package_name(&package_name);
1303 let types_dir = current.join("node_modules").join(&types_package);
1304 if types_dir.is_dir()
1305 && let Ok(resolved) = self.resolve_package(
1306 &types_dir,
1307 subpath.as_deref(),
1308 specifier,
1309 containing_file,
1310 specifier_span,
1311 &conditions,
1312 )
1313 {
1314 return Ok(resolved);
1315 }
1316 }
1317
1318 for type_root in &self.type_roots {
1320 let types_package = if !package_name.starts_with("@types/") {
1321 type_root.join(types_package_name(&package_name))
1322 } else {
1323 type_root.join(&package_name)
1324 };
1325 if types_package.is_dir()
1326 && let Ok(resolved) = self.resolve_package(
1327 &types_package,
1328 subpath.as_deref(),
1329 specifier,
1330 containing_file,
1331 specifier_span,
1332 &conditions,
1333 )
1334 {
1335 return Ok(resolved);
1336 }
1337 }
1338
1339 match current.parent() {
1341 Some(parent) if parent != current => current = parent.to_path_buf(),
1342 _ => break,
1343 }
1344 }
1345
1346 Err(ResolutionFailure::NotFound {
1349 specifier: specifier.to_string(),
1350 containing_file: containing_file.to_string(),
1351 span: specifier_span,
1352 })
1353 }
1354
1355 fn resolve_bare_specifier(
1357 &self,
1358 specifier: &str,
1359 containing_dir: &Path,
1360 containing_file: &str,
1361 specifier_span: Span,
1362 importing_module_kind: ImportingModuleKind,
1363 ) -> Result<ResolvedModule, ResolutionFailure> {
1364 let (package_name, subpath) = parse_package_specifier(specifier);
1366 let conditions = self.get_export_conditions(importing_module_kind);
1367
1368 if let Some(resolved) = self.try_self_reference(
1370 &package_name,
1371 subpath.as_deref(),
1372 specifier,
1373 containing_dir,
1374 &conditions,
1375 ) {
1376 return Ok(resolved);
1377 }
1378
1379 let mut current = containing_dir.to_path_buf();
1381 loop {
1382 let node_modules = current.join("node_modules");
1383
1384 if node_modules.is_dir() {
1385 let package_dir = node_modules.join(&package_name);
1386
1387 if package_dir.is_dir() {
1388 match self.resolve_package(
1389 &package_dir,
1390 subpath.as_deref(),
1391 specifier,
1392 containing_file,
1393 specifier_span,
1394 &conditions,
1395 ) {
1396 Ok(resolved) => return Ok(resolved),
1397 Err(e @ ResolutionFailure::ModuleResolutionModeMismatch { .. }) => {
1398 return Err(e);
1401 }
1402 Err(e @ ResolutionFailure::NotFound { .. }) => {
1403 if self.should_stop_on_bundler_exports_failure(
1404 &package_dir,
1405 subpath.as_deref(),
1406 &conditions,
1407 containing_file,
1408 specifier,
1409 ) {
1410 return Err(e);
1411 }
1412 }
1413 Err(_) => {
1414 }
1416 }
1417 } else if matches!(self.resolution_kind, ModuleResolutionKind::Bundler)
1418 && subpath.is_none()
1419 {
1420 if let Some(resolved) = self.try_file_or_directory(&package_dir) {
1423 return Ok(ResolvedModule {
1424 resolved_path: resolved.clone(),
1425 is_external: true,
1426 package_name: Some(package_name),
1427 original_specifier: specifier.to_string(),
1428 extension: ModuleExtension::from_path(&resolved),
1429 });
1430 }
1431 }
1432 }
1433
1434 if !package_name.starts_with("@types/") {
1435 let types_package = types_package_name(&package_name);
1436 let types_dir = node_modules.join(&types_package);
1437 if types_dir.is_dir()
1438 && let Ok(resolved) = self.resolve_package(
1439 &types_dir,
1440 subpath.as_deref(),
1441 specifier,
1442 containing_file,
1443 specifier_span,
1444 &conditions,
1445 )
1446 {
1447 return Ok(resolved);
1448 }
1449 }
1450
1451 match current.parent() {
1453 Some(parent) if parent != current => current = parent.to_path_buf(),
1454 _ => break,
1455 }
1456 }
1457
1458 for type_root in &self.type_roots {
1460 let types_package = type_root.join(types_package_name(&package_name));
1461 if types_package.is_dir()
1462 && let Ok(resolved) = self.resolve_package(
1463 &types_package,
1464 subpath.as_deref(),
1465 specifier,
1466 containing_file,
1467 specifier_span,
1468 &conditions,
1469 )
1470 {
1471 return Ok(resolved);
1472 }
1473 }
1474
1475 Err(ResolutionFailure::NotFound {
1476 specifier: specifier.to_string(),
1477 containing_file: containing_file.to_string(),
1478 span: specifier_span,
1479 })
1480 }
1481
1482 fn should_stop_on_bundler_exports_failure(
1483 &self,
1484 package_dir: &Path,
1485 subpath: Option<&str>,
1486 conditions: &[String],
1487 _containing_file: &str,
1488 _specifier: &str,
1489 ) -> bool {
1490 if !matches!(self.resolution_kind, ModuleResolutionKind::Bundler) {
1491 return false;
1492 }
1493 if !self.resolve_package_json_exports {
1494 return false;
1495 }
1496
1497 let package_json_path = package_dir.join("package.json");
1498 if !package_json_path.is_file() {
1499 return false;
1500 }
1501
1502 let package_json = match self.read_package_json(&package_json_path) {
1503 Ok(package_json) => package_json,
1504 Err(_) => return false,
1505 };
1506
1507 let Some(exports) = package_json.exports else {
1508 return false;
1509 };
1510
1511 let subpath_key = match subpath {
1512 Some(subpath) => format!("./{subpath}"),
1513 None => ".".to_string(),
1514 };
1515
1516 self.resolve_package_exports_with_conditions(
1517 package_dir,
1518 &exports,
1519 &subpath_key,
1520 conditions,
1521 )
1522 .is_none()
1523 }
1524
1525 fn try_self_reference(
1527 &self,
1528 package_name: &str,
1529 subpath: Option<&str>,
1530 original_specifier: &str,
1531 containing_dir: &Path,
1532 conditions: &[String],
1533 ) -> Option<ResolvedModule> {
1534 if !matches!(
1536 self.resolution_kind,
1537 ModuleResolutionKind::Node16
1538 | ModuleResolutionKind::NodeNext
1539 | ModuleResolutionKind::Bundler
1540 ) {
1541 return None;
1542 }
1543
1544 let mut current = containing_dir.to_path_buf();
1546
1547 loop {
1548 let package_json_path = current.join("package.json");
1549
1550 if package_json_path.is_file()
1551 && let Ok(package_json) = self.read_package_json(&package_json_path)
1552 {
1553 if package_json.name.as_deref() == Some(package_name) {
1555 if self.resolve_package_json_exports
1557 && let Some(exports) = &package_json.exports
1558 {
1559 let subpath_key = match subpath {
1560 Some(sp) => format!("./{sp}"),
1561 None => ".".to_string(),
1562 };
1563
1564 if let Some(resolved) = self.resolve_package_exports_with_conditions(
1565 ¤t,
1566 exports,
1567 &subpath_key,
1568 conditions,
1569 ) {
1570 return Some(ResolvedModule {
1571 resolved_path: resolved.clone(),
1572 is_external: false,
1573 package_name: Some(package_name.to_string()),
1574 original_specifier: original_specifier.to_string(),
1575 extension: ModuleExtension::from_path(&resolved),
1576 });
1577 }
1578 }
1579 }
1580 return None;
1582 }
1583
1584 match current.parent() {
1586 Some(parent) if parent != current => current = parent.to_path_buf(),
1587 _ => break,
1588 }
1589 }
1590
1591 None
1592 }
1593
1594 fn resolve_package(
1596 &self,
1597 package_dir: &Path,
1598 subpath: Option<&str>,
1599 original_specifier: &str,
1600 containing_file: &str,
1601 specifier_span: Span,
1602 conditions: &[String],
1603 ) -> Result<ResolvedModule, ResolutionFailure> {
1604 let package_json_path = package_dir.join("package.json");
1606 let package_json = if package_json_path.exists() {
1607 self.read_package_json(&package_json_path).map_err(|msg| {
1608 ResolutionFailure::PackageJsonError {
1609 message: msg,
1610 containing_file: containing_file.to_string(),
1611 span: specifier_span,
1612 }
1613 })?
1614 } else {
1615 PackageJson::default()
1616 };
1617
1618 if let Some(subpath) = subpath {
1620 let subpath_key = format!("./{subpath}");
1621
1622 if self.resolve_package_json_exports
1624 && let Some(exports) = &package_json.exports
1625 {
1626 if let Some(resolved) = self.resolve_package_exports_with_conditions(
1627 package_dir,
1628 exports,
1629 &subpath_key,
1630 conditions,
1631 ) {
1632 return Ok(ResolvedModule {
1633 resolved_path: resolved.clone(),
1634 is_external: true,
1635 package_name: Some(package_json.name.clone().unwrap_or_default()),
1636 original_specifier: original_specifier.to_string(),
1637 extension: ModuleExtension::from_path(&resolved),
1638 });
1639 }
1640 if matches!(
1642 self.resolution_kind,
1643 ModuleResolutionKind::Node16 | ModuleResolutionKind::NodeNext
1644 ) {
1645 return Err(ResolutionFailure::ModuleResolutionModeMismatch {
1646 specifier: original_specifier.to_string(),
1647 containing_file: containing_file.to_string(),
1648 span: specifier_span,
1649 });
1650 }
1651 if matches!(self.resolution_kind, ModuleResolutionKind::Bundler) {
1652 return Err(ResolutionFailure::NotFound {
1653 specifier: original_specifier.to_string(),
1654 containing_file: containing_file.to_string(),
1655 span: specifier_span,
1656 });
1657 }
1658 }
1659
1660 if let Some(types_versions) = &package_json.types_versions
1662 && let Some(resolved) =
1663 self.resolve_types_versions(package_dir, subpath, types_versions)
1664 {
1665 return Ok(ResolvedModule {
1666 resolved_path: resolved.clone(),
1667 is_external: true,
1668 package_name: Some(package_json.name.clone().unwrap_or_default()),
1669 original_specifier: original_specifier.to_string(),
1670 extension: ModuleExtension::from_path(&resolved),
1671 });
1672 }
1673
1674 let file_path = package_dir.join(subpath);
1676 if let Some(resolved) = self.try_file_or_directory(&file_path) {
1677 return Ok(ResolvedModule {
1678 resolved_path: resolved.clone(),
1679 is_external: true,
1680 package_name: Some(package_json.name.unwrap_or_default()),
1681 original_specifier: original_specifier.to_string(),
1682 extension: ModuleExtension::from_path(&resolved),
1683 });
1684 }
1685
1686 return Err(ResolutionFailure::NotFound {
1687 specifier: original_specifier.to_string(),
1688 containing_file: containing_file.to_string(),
1689 span: specifier_span,
1690 });
1691 }
1692
1693 if self.resolve_package_json_exports
1697 && let Some(exports) = &package_json.exports
1698 {
1699 if let Some(resolved) =
1700 self.resolve_package_exports_with_conditions(package_dir, exports, ".", conditions)
1701 {
1702 return Ok(ResolvedModule {
1703 resolved_path: resolved.clone(),
1704 is_external: true,
1705 package_name: Some(package_json.name.clone().unwrap_or_default()),
1706 original_specifier: original_specifier.to_string(),
1707 extension: ModuleExtension::from_path(&resolved),
1708 });
1709 }
1710 if matches!(
1713 self.resolution_kind,
1714 ModuleResolutionKind::Node16 | ModuleResolutionKind::NodeNext
1715 ) {
1716 return Err(ResolutionFailure::ModuleResolutionModeMismatch {
1717 specifier: original_specifier.to_string(),
1718 containing_file: containing_file.to_string(),
1719 span: specifier_span,
1720 });
1721 }
1722 if matches!(self.resolution_kind, ModuleResolutionKind::Bundler) {
1723 return Err(ResolutionFailure::NotFound {
1724 specifier: original_specifier.to_string(),
1725 containing_file: containing_file.to_string(),
1726 span: specifier_span,
1727 });
1728 }
1729 }
1730
1731 if let Some(types_versions) = &package_json.types_versions
1733 && let Some(resolved) =
1734 self.resolve_types_versions(package_dir, "index", types_versions)
1735 {
1736 return Ok(ResolvedModule {
1737 resolved_path: resolved.clone(),
1738 is_external: true,
1739 package_name: Some(package_json.name.clone().unwrap_or_default()),
1740 original_specifier: original_specifier.to_string(),
1741 extension: ModuleExtension::from_path(&resolved),
1742 });
1743 }
1744
1745 if let Some(types) = package_json
1747 .types
1748 .clone()
1749 .or_else(|| package_json.typings.clone())
1750 {
1751 let types_path = package_dir.join(&types);
1752 if let Some(resolved) = resolve_explicit_unknown_extension(&types_path) {
1753 return Ok(ResolvedModule {
1754 resolved_path: resolved.clone(),
1755 is_external: true,
1756 package_name: Some(package_json.name.unwrap_or_default()),
1757 original_specifier: original_specifier.to_string(),
1758 extension: ModuleExtension::from_path(&resolved),
1759 });
1760 }
1761 if let Some(resolved) = self.try_file_or_directory(&types_path) {
1762 return Ok(ResolvedModule {
1763 resolved_path: resolved.clone(),
1764 is_external: true,
1765 package_name: Some(package_json.name.unwrap_or_default()),
1766 original_specifier: original_specifier.to_string(),
1767 extension: ModuleExtension::from_path(&resolved),
1768 });
1769 }
1770 }
1771
1772 if let Some(main) = &package_json.main {
1774 let main_path = package_dir.join(main);
1775 if let Some(resolved) = resolve_explicit_unknown_extension(&main_path) {
1776 return Ok(ResolvedModule {
1777 resolved_path: resolved.clone(),
1778 is_external: true,
1779 package_name: Some(package_json.name.clone().unwrap_or_default()),
1780 original_specifier: original_specifier.to_string(),
1781 extension: ModuleExtension::from_path(&resolved),
1782 });
1783 }
1784 if let Some(declaration) = declaration_substitution_for_main(&main_path)
1785 && declaration.is_file()
1786 {
1787 return Ok(ResolvedModule {
1788 resolved_path: declaration.clone(),
1789 is_external: true,
1790 package_name: Some(package_json.name.clone().unwrap_or_default()),
1791 original_specifier: original_specifier.to_string(),
1792 extension: ModuleExtension::from_path(&declaration),
1793 });
1794 }
1795 if let Some(resolved) = self.try_file(&main_path) {
1797 return Ok(ResolvedModule {
1798 resolved_path: resolved.clone(),
1799 is_external: true,
1800 package_name: Some(package_json.name.clone().unwrap_or_default()),
1801 original_specifier: original_specifier.to_string(),
1802 extension: ModuleExtension::from_path(&resolved),
1803 });
1804 }
1805 if main_path.is_dir() {
1808 let index = main_path.join("index");
1809 if let Some(resolved) = self.try_file(&index) {
1810 return Ok(ResolvedModule {
1811 resolved_path: resolved.clone(),
1812 is_external: true,
1813 package_name: Some(package_json.name.clone().unwrap_or_default()),
1814 original_specifier: original_specifier.to_string(),
1815 extension: ModuleExtension::from_path(&resolved),
1816 });
1817 }
1818 }
1819 }
1820
1821 let index = package_dir.join("index");
1823 if let Some(resolved) = self.try_file(&index) {
1824 return Ok(ResolvedModule {
1825 resolved_path: resolved.clone(),
1826 is_external: true,
1827 package_name: Some(package_json.name.unwrap_or_default()),
1828 original_specifier: original_specifier.to_string(),
1829 extension: ModuleExtension::from_path(&resolved),
1830 });
1831 }
1832
1833 Err(ResolutionFailure::PackageJsonError {
1834 message: format!(
1835 "Could not find entry point for package at {}",
1836 package_dir.display()
1837 ),
1838 containing_file: containing_file.to_string(),
1839 span: specifier_span,
1840 })
1841 }
1842
1843 fn resolve_package_exports_with_conditions(
1845 &self,
1846 package_dir: &Path,
1847 exports: &PackageExports,
1848 subpath: &str,
1849 conditions: &[String],
1850 ) -> Option<PathBuf> {
1851 match exports {
1852 PackageExports::String(s) => {
1853 if subpath == "." {
1854 let resolved = package_dir.join(s.trim_start_matches("./"));
1855 if let Some(r) = self.try_export_target(&resolved) {
1856 return Some(r);
1857 }
1858 }
1859 None
1860 }
1861 PackageExports::Map(map) => {
1862 if let Some(value) = map.get(subpath) {
1864 return self.resolve_export_value_with_conditions(
1865 package_dir,
1866 value,
1867 conditions,
1868 );
1869 }
1870
1871 let mut best_match: Option<(usize, String, &PackageExports)> = None;
1873
1874 for (pattern, value) in map {
1875 if let Some(matched) = match_export_pattern(pattern, subpath) {
1876 let specificity = pattern.len();
1877 let is_better = match &best_match {
1878 None => true,
1879 Some((best_len, _, _)) => specificity > *best_len,
1880 };
1881 if is_better {
1882 best_match = Some((specificity, matched, value));
1883 }
1884 }
1885 }
1886
1887 if let Some((_, wildcard, value)) = best_match
1888 && let Some(resolved) =
1889 self.resolve_export_value_with_conditions(package_dir, value, conditions)
1890 {
1891 let resolved_str = resolved.to_string_lossy();
1893 if resolved_str.contains('*') {
1894 let substituted = resolved_str.replace('*', &wildcard);
1895 return Some(PathBuf::from(substituted));
1896 }
1897 return Some(resolved);
1898 }
1899
1900 None
1901 }
1902 PackageExports::Conditional(cond_entries) => {
1903 for (key, value) in cond_entries {
1905 if conditions.iter().any(|c| c == key) {
1906 if matches!(value, PackageExports::Null) {
1908 return None;
1909 }
1910 if let Some(resolved) = self.resolve_package_exports_with_conditions(
1911 package_dir,
1912 value,
1913 subpath,
1914 conditions,
1915 ) {
1916 return Some(resolved);
1917 }
1918 }
1919 }
1920 None
1921 }
1922 PackageExports::Null => None,
1923 }
1924 }
1925
1926 fn resolve_export_value_with_conditions(
1928 &self,
1929 package_dir: &Path,
1930 value: &PackageExports,
1931 conditions: &[String],
1932 ) -> Option<PathBuf> {
1933 match value {
1934 PackageExports::String(s) => {
1935 let resolved = package_dir.join(s.trim_start_matches("./"));
1936 self.try_export_target(&resolved)
1937 }
1938 PackageExports::Conditional(cond_entries) => {
1939 for (key, nested) in cond_entries {
1941 if conditions.iter().any(|c| c == key) {
1942 if matches!(nested, PackageExports::Null) {
1944 return None;
1945 }
1946 if let Some(resolved) = self.resolve_export_value_with_conditions(
1947 package_dir,
1948 nested,
1949 conditions,
1950 ) {
1951 return Some(resolved);
1952 }
1953 }
1954 }
1955 None
1956 }
1957 PackageExports::Map(_) | PackageExports::Null => None,
1958 }
1959 }
1960
1961 fn resolve_types_versions(
1963 &self,
1964 package_dir: &Path,
1965 subpath: &str,
1966 types_versions: &serde_json::Value,
1967 ) -> Option<PathBuf> {
1968 let compiler_version =
1969 types_versions_compiler_version(self.types_versions_compiler_version.as_deref());
1970 let paths = select_types_versions_paths(types_versions, compiler_version)?;
1971 let mut best_pattern: Option<&String> = None;
1972 let mut best_value: Option<&serde_json::Value> = None;
1973 let mut best_wildcard = String::new();
1974 let mut best_specificity = 0usize;
1975 let mut best_len = 0usize;
1976
1977 for (pattern, value) in paths {
1978 let Some(wildcard) = match_types_versions_pattern(pattern, subpath) else {
1979 continue;
1980 };
1981 let specificity = types_versions_specificity(pattern);
1982 let pattern_len = pattern.len();
1983 let is_better = match best_pattern {
1984 None => true,
1985 Some(current) => {
1986 specificity > best_specificity
1987 || (specificity == best_specificity && pattern_len > best_len)
1988 || (specificity == best_specificity
1989 && pattern_len == best_len
1990 && pattern < current)
1991 }
1992 };
1993
1994 if is_better {
1995 best_specificity = specificity;
1996 best_len = pattern_len;
1997 best_pattern = Some(pattern);
1998 best_value = Some(value);
1999 best_wildcard = wildcard;
2000 }
2001 }
2002
2003 let value = best_value?;
2004
2005 let mut targets = Vec::new();
2006 match value {
2007 serde_json::Value::String(value) => targets.push(value.as_str()),
2008 serde_json::Value::Array(list) => {
2009 for entry in list {
2010 if let Some(value) = entry.as_str() {
2011 targets.push(value);
2012 }
2013 }
2014 }
2015 _ => {}
2016 }
2017
2018 for target in targets {
2019 let substituted = apply_wildcard_substitution(target, &best_wildcard);
2020 let resolved = package_dir.join(substituted.trim_start_matches("./"));
2021 if let Some(resolved) = self.try_file_or_directory(&resolved) {
2022 return Some(resolved);
2023 }
2024 }
2025
2026 None
2027 }
2028
2029 fn try_file(&self, path: &Path) -> Option<PathBuf> {
2031 let suffixes = &self.module_suffixes;
2032 if let Some(extension) = path.extension().and_then(|ext| ext.to_str())
2033 && split_path_extension(path).is_none()
2034 {
2035 if self.allow_arbitrary_extensions
2036 && let Some(resolved) = try_arbitrary_extension_declaration(path, extension)
2037 {
2038 return Some(resolved);
2039 }
2040 return None;
2041 }
2042 if let Some((base, extension)) = split_path_extension(path) {
2043 if let Some(rewritten) = node16_extension_substitution(path, extension) {
2046 for candidate in &rewritten {
2047 if let Some(resolved) = try_file_with_suffixes(candidate, suffixes) {
2048 return Some(resolved);
2049 }
2050 }
2051 }
2052
2053 if let Some(resolved) = try_file_with_suffixes_and_extension(&base, extension, suffixes)
2055 {
2056 return Some(resolved);
2057 }
2058
2059 return None;
2060 }
2061
2062 let extensions = self.extension_candidates_for_resolution();
2063 for ext in extensions {
2064 if let Some(resolved) = try_file_with_suffixes_and_extension(path, ext, suffixes) {
2065 return Some(resolved);
2066 }
2067 }
2068 if self.resolve_json_module
2069 && let Some(resolved) = try_file_with_suffixes_and_extension(path, "json", suffixes)
2070 {
2071 return Some(resolved);
2072 }
2073
2074 let index = path.join("index");
2075 for ext in extensions {
2076 if let Some(resolved) = try_file_with_suffixes_and_extension(&index, ext, suffixes) {
2077 return Some(resolved);
2078 }
2079 }
2080 if self.resolve_json_module
2081 && let Some(resolved) = try_file_with_suffixes_and_extension(&index, "json", suffixes)
2082 {
2083 return Some(resolved);
2084 }
2085
2086 None
2087 }
2088
2089 const fn extension_candidates_for_resolution(&self) -> &'static [&'static str] {
2090 match self.resolution_kind {
2091 ModuleResolutionKind::Node16 | ModuleResolutionKind::NodeNext => {
2092 match self.current_package_type {
2093 Some(PackageType::Module) => &NODE16_MODULE_EXTENSION_CANDIDATES,
2094 Some(PackageType::CommonJs) => &NODE16_COMMONJS_EXTENSION_CANDIDATES,
2095 None => {
2096 if self.allow_js {
2097 &TS_JS_EXTENSION_CANDIDATES
2098 } else {
2099 &TS_EXTENSION_CANDIDATES
2100 }
2101 }
2102 }
2103 }
2104 ModuleResolutionKind::Classic => {
2105 if self.allow_js {
2106 &TS_JS_EXTENSION_CANDIDATES
2107 } else {
2108 &CLASSIC_EXTENSION_CANDIDATES
2109 }
2110 }
2111 _ => {
2112 if self.allow_js {
2113 &TS_JS_EXTENSION_CANDIDATES
2114 } else {
2115 &TS_EXTENSION_CANDIDATES
2116 }
2117 }
2118 }
2119 }
2120
2121 fn try_file_or_directory(&self, path: &Path) -> Option<PathBuf> {
2123 if let Some(resolved) = self.try_file(path) {
2125 return Some(resolved);
2126 }
2127
2128 if path.is_dir() {
2130 let package_json_path = path.join("package.json");
2131 if package_json_path.exists()
2132 && let Ok(pj) = self.read_package_json(&package_json_path)
2133 {
2134 if let Some(types) = pj.types.or(pj.typings) {
2136 let types_path = path.join(&types);
2137 if let Some(resolved) = self.try_file(&types_path) {
2138 return Some(resolved);
2139 }
2140 if types_path.is_file() {
2141 return Some(types_path);
2142 }
2143 }
2144 if let Some(main) = &pj.main {
2146 let main_path = path.join(main);
2147 if let Some(resolved) = self.try_file(&main_path) {
2148 return Some(resolved);
2149 }
2150 }
2151 }
2152 let index = path.join("index");
2153 return self.try_file(&index);
2154 }
2155
2156 None
2157 }
2158
2159 fn try_export_target(&self, path: &Path) -> Option<PathBuf> {
2163 if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
2164 if split_path_extension(path).is_some() {
2165 if path.is_file() {
2166 return Some(path.to_path_buf());
2167 }
2168 if let Some(rewritten) = node16_extension_substitution(path, extension) {
2170 for candidate in &rewritten {
2171 if candidate.is_file() {
2172 return Some(candidate.clone());
2173 }
2174 }
2175 }
2176 return None;
2177 }
2178 if self.allow_arbitrary_extensions
2179 && let Some(resolved) = try_arbitrary_extension_declaration(path, extension)
2180 {
2181 return Some(resolved);
2182 }
2183 return None;
2184 }
2185
2186 if let Some(resolved) = self.try_file(path) {
2187 return Some(resolved);
2188 }
2189 if path.is_dir() {
2190 let index = path.join("index");
2191 return self.try_file(&index);
2192 }
2193 None
2194 }
2195
2196 fn read_package_json(&self, path: &Path) -> Result<PackageJson, String> {
2201 let content = std::fs::read_to_string(path)
2202 .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
2203
2204 serde_json::from_str(&content)
2205 .map_err(|e| format!("Failed to parse {}: {}", path.display(), e))
2206 }
2207
2208 pub fn probe_js_file(
2214 &mut self,
2215 specifier: &str,
2216 containing_file: &Path,
2217 specifier_span: Span,
2218 import_kind: ImportKind,
2219 ) -> Option<PathBuf> {
2220 if self.allow_js {
2221 return None; }
2223 let containing_dir = containing_file
2224 .parent()
2225 .unwrap_or_else(|| Path::new("."))
2226 .to_path_buf();
2227 let containing_file_str = containing_file.display().to_string();
2228 let importing_module_kind = self.get_importing_module_kind(containing_file);
2229
2230 self.allow_js = true;
2231 let (result, _) = self.resolve_uncached(
2232 specifier,
2233 &containing_dir,
2234 &containing_file_str,
2235 specifier_span,
2236 importing_module_kind,
2237 import_kind,
2238 );
2239 self.allow_js = false;
2240
2241 match result {
2242 Ok(resolved)
2243 if matches!(
2244 resolved.extension,
2245 ModuleExtension::Js
2246 | ModuleExtension::Jsx
2247 | ModuleExtension::Mjs
2248 | ModuleExtension::Cjs
2249 ) =>
2250 {
2251 Some(resolved.resolved_path)
2252 }
2253 _ => None,
2254 }
2255 }
2256
2257 pub fn clear_cache(&mut self) {
2259 self.resolution_cache.clear();
2260 }
2261
2262 pub const fn resolution_kind(&self) -> ModuleResolutionKind {
2264 self.resolution_kind
2265 }
2266
2267 pub fn emit_resolution_error(
2277 &self,
2278 diagnostics: &mut DiagnosticBag,
2279 failure: &ResolutionFailure,
2280 ) {
2281 let diagnostic = failure.to_diagnostic();
2282 diagnostics.add(diagnostic);
2283 }
2284}
2285
2286#[cfg(test)]
2288mod tests {
2289 use super::*;
2290
2291 #[test]
2292 fn test_parse_package_specifier_simple() {
2293 let (name, subpath) = parse_package_specifier("lodash");
2294 assert_eq!(name, "lodash");
2295 assert_eq!(subpath, None);
2296 }
2297
2298 #[test]
2299 fn test_parse_package_specifier_with_subpath() {
2300 let (name, subpath) = parse_package_specifier("lodash/fp");
2301 assert_eq!(name, "lodash");
2302 assert_eq!(subpath, Some("fp".to_string()));
2303 }
2304
2305 #[test]
2306 fn test_parse_package_specifier_scoped() {
2307 let (name, subpath) = parse_package_specifier("@babel/core");
2308 assert_eq!(name, "@babel/core");
2309 assert_eq!(subpath, None);
2310 }
2311
2312 #[test]
2313 fn test_parse_package_specifier_scoped_with_subpath() {
2314 let (name, subpath) = parse_package_specifier("@babel/core/transform");
2315 assert_eq!(name, "@babel/core");
2316 assert_eq!(subpath, Some("transform".to_string()));
2317 }
2318
2319 #[test]
2320 fn test_match_export_pattern_exact() {
2321 assert_eq!(match_export_pattern("./lib", "./lib"), Some(String::new()));
2322 assert_eq!(match_export_pattern("./lib", "./src"), None);
2323 }
2324
2325 #[test]
2326 fn test_match_export_pattern_wildcard() {
2327 assert_eq!(
2328 match_export_pattern("./*", "./foo"),
2329 Some("foo".to_string())
2330 );
2331 assert_eq!(
2332 match_export_pattern("./lib/*", "./lib/utils"),
2333 Some("utils".to_string())
2334 );
2335 assert_eq!(match_export_pattern("./lib/*", "./src/utils"), None);
2336 }
2337
2338 #[test]
2339 fn test_module_extension_from_path() {
2340 assert_eq!(
2341 ModuleExtension::from_path(Path::new("foo.ts")),
2342 ModuleExtension::Ts
2343 );
2344 assert_eq!(
2345 ModuleExtension::from_path(Path::new("foo.d.ts")),
2346 ModuleExtension::Dts
2347 );
2348 assert_eq!(
2349 ModuleExtension::from_path(Path::new("foo.tsx")),
2350 ModuleExtension::Tsx
2351 );
2352 assert_eq!(
2353 ModuleExtension::from_path(Path::new("foo.js")),
2354 ModuleExtension::Js
2355 );
2356 }
2357
2358 #[test]
2359 fn test_module_resolver_creation() {
2360 let resolver = ModuleResolver::node_resolver();
2361 assert_eq!(resolver.resolution_kind(), ModuleResolutionKind::Node);
2362 }
2363
2364 #[test]
2365 fn test_ts2307_error_code_constant() {
2366 assert_eq!(CANNOT_FIND_MODULE, 2307);
2367 }
2368
2369 #[test]
2370 fn test_resolution_failure_not_found_diagnostic() {
2371 let failure = ResolutionFailure::NotFound {
2372 specifier: "./missing-module".to_string(),
2373 containing_file: "/path/to/file.ts".to_string(),
2374 span: Span::new(10, 30),
2375 };
2376
2377 let diagnostic = failure.to_diagnostic();
2378 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2379 assert!(diagnostic.message.contains("Cannot find module"));
2380 assert!(diagnostic.message.contains("./missing-module"));
2381 assert_eq!(diagnostic.file_name, "/path/to/file.ts");
2382 assert_eq!(diagnostic.span.start, 10);
2383 assert_eq!(diagnostic.span.end, 30);
2384 }
2385
2386 #[test]
2387 fn test_resolution_failure_is_not_found() {
2388 let not_found = ResolutionFailure::NotFound {
2389 specifier: "test".to_string(),
2390 containing_file: "test.ts".to_string(),
2391 span: Span::dummy(),
2392 };
2393 assert!(not_found.is_not_found());
2394
2395 let other = ResolutionFailure::InvalidSpecifier {
2396 message: "test".to_string(),
2397 containing_file: "test.ts".to_string(),
2398 span: Span::dummy(),
2399 };
2400 assert!(!other.is_not_found());
2401 }
2402
2403 #[test]
2404 fn test_module_extension_forces_esm() {
2405 assert!(ModuleExtension::Mts.forces_esm());
2406 assert!(ModuleExtension::Mjs.forces_esm());
2407 assert!(ModuleExtension::DmTs.forces_esm());
2408 assert!(!ModuleExtension::Ts.forces_esm());
2409 assert!(!ModuleExtension::Cts.forces_esm());
2410 }
2411
2412 #[test]
2413 fn test_module_extension_forces_cjs() {
2414 assert!(ModuleExtension::Cts.forces_cjs());
2415 assert!(ModuleExtension::Cjs.forces_cjs());
2416 assert!(ModuleExtension::DCts.forces_cjs());
2417 assert!(!ModuleExtension::Ts.forces_cjs());
2418 assert!(!ModuleExtension::Mts.forces_cjs());
2419 }
2420
2421 #[test]
2422 fn test_match_imports_pattern_exact() {
2423 assert_eq!(
2424 match_imports_pattern("#utils", "#utils"),
2425 Some(String::new())
2426 );
2427 assert_eq!(match_imports_pattern("#utils", "#other"), None);
2428 }
2429
2430 #[test]
2431 fn test_match_imports_pattern_wildcard() {
2432 assert_eq!(
2433 match_imports_pattern("#utils/*", "#utils/foo"),
2434 Some("foo".to_string())
2435 );
2436 assert_eq!(
2437 match_imports_pattern("#internal/*", "#internal/helpers/bar"),
2438 Some("helpers/bar".to_string())
2439 );
2440 assert_eq!(match_imports_pattern("#utils/*", "#other/foo"), None);
2441 }
2442
2443 #[test]
2444 fn test_match_types_versions_pattern() {
2445 assert_eq!(
2446 match_types_versions_pattern("*", "index"),
2447 Some("index".to_string())
2448 );
2449 assert_eq!(
2450 match_types_versions_pattern("lib/*", "lib/utils"),
2451 Some("utils".to_string())
2452 );
2453 assert_eq!(
2454 match_types_versions_pattern("exact", "exact"),
2455 Some(String::new())
2456 );
2457 assert_eq!(match_types_versions_pattern("lib/*", "src/utils"), None);
2458 }
2459
2460 #[test]
2461 fn test_apply_wildcard_substitution() {
2462 assert_eq!(
2463 apply_wildcard_substitution("./lib/*.js", "utils"),
2464 "./lib/utils.js"
2465 );
2466 assert_eq!(
2467 apply_wildcard_substitution("./dist/index.js", "ignored"),
2468 "./dist/index.js"
2469 );
2470 }
2471
2472 #[test]
2473 fn test_package_type_enum() {
2474 assert_eq!(PackageType::default(), PackageType::CommonJs);
2475 assert_ne!(PackageType::Module, PackageType::CommonJs);
2476 }
2477
2478 #[test]
2479 fn test_importing_module_kind_enum() {
2480 assert_eq!(
2481 ImportingModuleKind::default(),
2482 ImportingModuleKind::CommonJs
2483 );
2484 assert_ne!(ImportingModuleKind::Esm, ImportingModuleKind::CommonJs);
2485 }
2486
2487 #[test]
2488 fn test_package_json_deserialize_basic() {
2489 let json = r#"{"name": "test-package", "type": "module", "main": "./index.js"}"#;
2490
2491 let package_json: PackageJson = serde_json::from_str(json).unwrap();
2492 assert_eq!(package_json.name, Some("test-package".to_string()));
2493 assert_eq!(package_json.package_type, Some("module".to_string()));
2494 assert_eq!(package_json.main, Some("./index.js".to_string()));
2495 }
2496
2497 #[test]
2498 fn test_package_json_deserialize_exports() {
2499 let json = r#"{"name": "pkg", "exports": {"." : "./dist/index.js"}}"#;
2500
2501 let package_json: PackageJson = serde_json::from_str(json).unwrap();
2502 assert!(package_json.exports.is_some());
2503 }
2504
2505 #[test]
2506 fn test_package_json_deserialize_types_versions() {
2507 let json = serde_json::json!({
2509 "name": "typed-package",
2510 "typesVersions": {
2511 "*": {
2512 "*": ["./types/index.d.ts"]
2513 }
2514 }
2515 });
2516
2517 let package_json: PackageJson = serde_json::from_value(json).unwrap();
2518 assert_eq!(package_json.name, Some("typed-package".to_string()));
2519 assert!(package_json.types_versions.is_some());
2520 }
2521
2522 #[test]
2527 fn test_emit_resolution_error_for_not_found() {
2528 let mut diagnostics = DiagnosticBag::new();
2529 let resolver = ModuleResolver::node_resolver();
2530
2531 let failure = ResolutionFailure::NotFound {
2532 specifier: "./missing-module".to_string(),
2533 containing_file: "/src/file.ts".to_string(),
2534 span: Span::new(10, 30),
2535 };
2536
2537 resolver.emit_resolution_error(&mut diagnostics, &failure);
2538
2539 assert_eq!(diagnostics.len(), 1);
2540 assert!(diagnostics.has_errors());
2541 let errors: Vec<_> = diagnostics.errors().collect();
2542 assert_eq!(errors[0].code, CANNOT_FIND_MODULE);
2543 assert!(errors[0].message.contains("Cannot find module"));
2544 assert!(errors[0].message.contains("./missing-module"));
2545 }
2546
2547 #[test]
2548 fn test_emit_resolution_error_all_variants_emit_ts2307() {
2549 let mut diagnostics = DiagnosticBag::new();
2550 let resolver = ModuleResolver::node_resolver();
2551
2552 let failure = ResolutionFailure::InvalidSpecifier {
2554 message: "bad specifier".to_string(),
2555 containing_file: "/src/a.ts".to_string(),
2556 span: Span::new(0, 10),
2557 };
2558 resolver.emit_resolution_error(&mut diagnostics, &failure);
2559 assert_eq!(diagnostics.len(), 1);
2560
2561 let failure = ResolutionFailure::PackageJsonError {
2562 message: "parse error".to_string(),
2563 containing_file: "/src/b.ts".to_string(),
2564 span: Span::new(5, 15),
2565 };
2566 resolver.emit_resolution_error(&mut diagnostics, &failure);
2567 assert_eq!(diagnostics.len(), 2);
2568
2569 let failure = ResolutionFailure::CircularResolution {
2570 message: "a -> b -> a".to_string(),
2571 containing_file: "/src/c.ts".to_string(),
2572 span: Span::new(10, 20),
2573 };
2574 resolver.emit_resolution_error(&mut diagnostics, &failure);
2575 assert_eq!(diagnostics.len(), 3);
2576
2577 let failure = ResolutionFailure::PathMappingFailed {
2578 message: "@/ pattern".to_string(),
2579 containing_file: "/src/d.ts".to_string(),
2580 span: Span::new(15, 25),
2581 };
2582 resolver.emit_resolution_error(&mut diagnostics, &failure);
2583 assert_eq!(diagnostics.len(), 4);
2584
2585 for diag in diagnostics.errors() {
2587 assert_eq!(diag.code, CANNOT_FIND_MODULE);
2588 }
2589 }
2590
2591 #[test]
2592 fn test_resolution_failure_all_variants_to_diagnostic() {
2593 let failures = vec![
2595 ResolutionFailure::NotFound {
2596 specifier: "./test".to_string(),
2597 containing_file: "file.ts".to_string(),
2598 span: Span::new(0, 10),
2599 },
2600 ResolutionFailure::InvalidSpecifier {
2601 message: "bad".to_string(),
2602 containing_file: "file2.ts".to_string(),
2603 span: Span::new(5, 15),
2604 },
2605 ResolutionFailure::PackageJsonError {
2606 message: "error".to_string(),
2607 containing_file: "file3.ts".to_string(),
2608 span: Span::new(10, 20),
2609 },
2610 ResolutionFailure::CircularResolution {
2611 message: "loop".to_string(),
2612 containing_file: "file4.ts".to_string(),
2613 span: Span::new(15, 25),
2614 },
2615 ResolutionFailure::PathMappingFailed {
2616 message: "@/path".to_string(),
2617 containing_file: "file5.ts".to_string(),
2618 span: Span::new(20, 30),
2619 },
2620 ];
2621
2622 for failure in failures {
2623 let diagnostic = failure.to_diagnostic();
2624 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2626 assert!(!diagnostic.file_name.is_empty());
2628 assert!(diagnostic.span.start < diagnostic.span.end);
2630 }
2631 }
2632
2633 #[test]
2634 fn test_relative_import_failure_produces_ts2307() {
2635 let failure = ResolutionFailure::NotFound {
2636 specifier: "./components/Button".to_string(),
2637 containing_file: "/src/App.tsx".to_string(),
2638 span: Span::new(20, 45),
2639 };
2640
2641 let diagnostic = failure.to_diagnostic();
2642 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2643 assert_eq!(diagnostic.file_name, "/src/App.tsx");
2644 assert!(diagnostic.message.contains("./components/Button"));
2645 assert_eq!(diagnostic.span.start, 20);
2646 assert_eq!(diagnostic.span.end, 45);
2647 }
2648
2649 #[test]
2650 fn test_bare_specifier_failure_produces_ts2307() {
2651 let failure = ResolutionFailure::NotFound {
2652 specifier: "nonexistent-package".to_string(),
2653 containing_file: "/project/src/index.ts".to_string(),
2654 span: Span::new(7, 28),
2655 };
2656
2657 let diagnostic = failure.to_diagnostic();
2658 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2659 assert!(diagnostic.message.contains("nonexistent-package"));
2660 }
2661
2662 #[test]
2663 fn test_scoped_package_failure_produces_ts2307() {
2664 let failure = ResolutionFailure::NotFound {
2665 specifier: "@org/missing-lib".to_string(),
2666 containing_file: "/app/main.ts".to_string(),
2667 span: Span::new(15, 35),
2668 };
2669
2670 let diagnostic = failure.to_diagnostic();
2671 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2672 assert!(diagnostic.message.contains("@org/missing-lib"));
2673 }
2674
2675 #[test]
2676 fn test_hash_import_failure_produces_ts2307() {
2677 let failure = ResolutionFailure::NotFound {
2679 specifier: "#utils/helpers".to_string(),
2680 containing_file: "/pkg/src/index.ts".to_string(),
2681 span: Span::new(8, 25),
2682 };
2683
2684 let diagnostic = failure.to_diagnostic();
2685 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2686 assert!(diagnostic.message.contains("#utils/helpers"));
2687 }
2688
2689 #[test]
2690 fn test_resolution_failure_span_preservation() {
2691 let test_cases = vec![(0, 10), (100, 150), (1000, 1050)];
2693
2694 for (start, end) in test_cases {
2695 let failure = ResolutionFailure::NotFound {
2696 specifier: "test".to_string(),
2697 containing_file: "file.ts".to_string(),
2698 span: Span::new(start, end),
2699 };
2700
2701 let diagnostic = failure.to_diagnostic();
2702 assert_eq!(diagnostic.span.start, start);
2703 assert_eq!(diagnostic.span.end, end);
2704 }
2705 }
2706
2707 #[test]
2708 fn test_resolution_failure_accessors() {
2709 let failure = ResolutionFailure::InvalidSpecifier {
2711 message: "test error".to_string(),
2712 containing_file: "/src/test.ts".to_string(),
2713 span: Span::new(10, 20),
2714 };
2715
2716 assert_eq!(failure.containing_file(), "/src/test.ts");
2717 assert_eq!(failure.span().start, 10);
2718 assert_eq!(failure.span().end, 20);
2719 }
2720
2721 #[test]
2722 fn test_path_mapping_failure_produces_ts2307() {
2723 let failure = ResolutionFailure::PathMappingFailed {
2724 message: "path mapping '@/utils/*' did not resolve to any file".to_string(),
2725 containing_file: "/project/src/index.ts".to_string(),
2726 span: Span::new(8, 30),
2727 };
2728
2729 let diagnostic = failure.to_diagnostic();
2730 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2731 assert_eq!(diagnostic.file_name, "/project/src/index.ts");
2732 assert!(diagnostic.message.contains("Cannot find module"));
2733 assert!(diagnostic.message.contains("path mapping"));
2734 }
2735
2736 #[test]
2737 fn test_package_json_error_produces_ts2307() {
2738 let failure = ResolutionFailure::PackageJsonError {
2739 message: "invalid exports field in package.json".to_string(),
2740 containing_file: "/project/src/app.ts".to_string(),
2741 span: Span::new(15, 45),
2742 };
2743
2744 let diagnostic = failure.to_diagnostic();
2745 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2746 assert_eq!(diagnostic.file_name, "/project/src/app.ts");
2747 assert!(diagnostic.message.contains("Cannot find module"));
2748 }
2749
2750 #[test]
2751 fn test_circular_resolution_produces_ts2307() {
2752 let failure = ResolutionFailure::CircularResolution {
2753 message: "circular dependency: a.ts -> b.ts -> a.ts".to_string(),
2754 containing_file: "/project/src/a.ts".to_string(),
2755 span: Span::new(20, 50),
2756 };
2757
2758 let diagnostic = failure.to_diagnostic();
2759 assert_eq!(diagnostic.code, CANNOT_FIND_MODULE);
2760 assert_eq!(diagnostic.file_name, "/project/src/a.ts");
2761 assert!(diagnostic.message.contains("Cannot find module"));
2762 assert!(diagnostic.message.contains("circular"));
2763 }
2764
2765 #[test]
2766 fn test_diagnostic_bag_collects_multiple_resolution_errors() {
2767 let mut diagnostics = DiagnosticBag::new();
2768 let resolver = ModuleResolver::node_resolver();
2769
2770 let failures = vec![
2771 ResolutionFailure::NotFound {
2772 specifier: "./module1".to_string(),
2773 containing_file: "a.ts".to_string(),
2774 span: Span::new(0, 10),
2775 },
2776 ResolutionFailure::NotFound {
2777 specifier: "./module2".to_string(),
2778 containing_file: "b.ts".to_string(),
2779 span: Span::new(5, 15),
2780 },
2781 ResolutionFailure::NotFound {
2782 specifier: "external-pkg".to_string(),
2783 containing_file: "c.ts".to_string(),
2784 span: Span::new(10, 25),
2785 },
2786 ];
2787
2788 for failure in &failures {
2789 resolver.emit_resolution_error(&mut diagnostics, failure);
2790 }
2791
2792 assert_eq!(diagnostics.len(), 3);
2793 assert_eq!(diagnostics.error_count(), 3);
2794
2795 let codes: Vec<_> = diagnostics.errors().map(|d| d.code).collect();
2797 assert!(codes.iter().all(|&c| c == CANNOT_FIND_MODULE));
2798 }
2799
2800 #[test]
2805 fn test_ts2834_error_code_constant() {
2806 assert_eq!(IMPORT_PATH_NEEDS_EXTENSION, 2834);
2807 }
2808
2809 #[test]
2810 fn test_import_path_needs_extension_produces_ts2835() {
2811 let failure = ResolutionFailure::ImportPathNeedsExtension {
2812 specifier: "./utils".to_string(),
2813 suggested_extension: ".js".to_string(),
2814 containing_file: "/src/index.mts".to_string(),
2815 span: Span::new(20, 30),
2816 };
2817
2818 let diagnostic = failure.to_diagnostic();
2819 assert_eq!(diagnostic.code, IMPORT_PATH_NEEDS_EXTENSION_SUGGESTION);
2820 assert_eq!(diagnostic.file_name, "/src/index.mts");
2821 assert!(
2822 diagnostic
2823 .message
2824 .contains("Relative import paths need explicit file extensions")
2825 );
2826 assert!(diagnostic.message.contains("node16"));
2827 assert!(diagnostic.message.contains("nodenext"));
2828 assert!(diagnostic.message.contains("./utils.js"));
2829 }
2830
2831 #[test]
2832 fn test_import_path_needs_extension_suggests_mjs() {
2833 let failure = ResolutionFailure::ImportPathNeedsExtension {
2834 specifier: "./esm-module".to_string(),
2835 suggested_extension: ".mjs".to_string(),
2836 containing_file: "/src/app.mts".to_string(),
2837 span: Span::new(10, 25),
2838 };
2839
2840 let diagnostic = failure.to_diagnostic();
2841 assert_eq!(diagnostic.code, IMPORT_PATH_NEEDS_EXTENSION_SUGGESTION);
2842 assert!(diagnostic.message.contains("./esm-module.mjs"));
2843 }
2844
2845 #[test]
2846 fn test_import_path_needs_extension_suggests_cjs() {
2847 let failure = ResolutionFailure::ImportPathNeedsExtension {
2848 specifier: "./cjs-module".to_string(),
2849 suggested_extension: ".cjs".to_string(),
2850 containing_file: "/src/legacy.cts".to_string(),
2851 span: Span::new(5, 20),
2852 };
2853
2854 let diagnostic = failure.to_diagnostic();
2855 assert_eq!(diagnostic.code, IMPORT_PATH_NEEDS_EXTENSION_SUGGESTION);
2856 assert!(diagnostic.message.contains("./cjs-module.cjs"));
2857 }
2858
2859 #[test]
2864 fn test_ts2792_error_code_constant() {
2865 assert_eq!(MODULE_RESOLUTION_MODE_MISMATCH, 2792);
2866 }
2867
2868 #[test]
2869 fn test_module_resolution_mode_mismatch_produces_ts2792() {
2870 let failure = ResolutionFailure::ModuleResolutionModeMismatch {
2871 specifier: "modern-esm-package".to_string(),
2872 containing_file: "/src/index.ts".to_string(),
2873 span: Span::new(15, 35),
2874 };
2875
2876 let diagnostic = failure.to_diagnostic();
2877 assert_eq!(diagnostic.code, MODULE_RESOLUTION_MODE_MISMATCH);
2878 assert_eq!(diagnostic.file_name, "/src/index.ts");
2879 assert!(
2880 diagnostic
2881 .message
2882 .contains("Cannot find module 'modern-esm-package'")
2883 );
2884 assert!(diagnostic.message.contains("moduleResolution"));
2885 assert!(diagnostic.message.contains("nodenext"));
2886 assert!(diagnostic.message.contains("paths"));
2887 }
2888
2889 #[test]
2890 fn test_module_resolution_mode_mismatch_accessors() {
2891 let failure = ResolutionFailure::ModuleResolutionModeMismatch {
2892 specifier: "pkg".to_string(),
2893 containing_file: "/test.ts".to_string(),
2894 span: Span::new(100, 110),
2895 };
2896
2897 assert_eq!(failure.containing_file(), "/test.ts");
2898 assert_eq!(failure.span().start, 100);
2899 assert_eq!(failure.span().end, 110);
2900 }
2901
2902 #[test]
2903 fn test_import_path_needs_extension_accessors() {
2904 let failure = ResolutionFailure::ImportPathNeedsExtension {
2905 specifier: "./foo".to_string(),
2906 suggested_extension: ".js".to_string(),
2907 containing_file: "/bar.mts".to_string(),
2908 span: Span::new(50, 60),
2909 };
2910
2911 assert_eq!(failure.containing_file(), "/bar.mts");
2912 assert_eq!(failure.span().start, 50);
2913 assert_eq!(failure.span().end, 60);
2914 }
2915
2916 #[test]
2917 fn test_new_error_codes_emit_correctly() {
2918 let mut diagnostics = DiagnosticBag::new();
2919 let resolver = ModuleResolver::node_resolver();
2920
2921 let failure_2835 = ResolutionFailure::ImportPathNeedsExtension {
2923 specifier: "./utils".to_string(),
2924 suggested_extension: ".js".to_string(),
2925 containing_file: "/src/app.mts".to_string(),
2926 span: Span::new(0, 10),
2927 };
2928 resolver.emit_resolution_error(&mut diagnostics, &failure_2835);
2929
2930 let failure_2792 = ResolutionFailure::ModuleResolutionModeMismatch {
2932 specifier: "esm-pkg".to_string(),
2933 containing_file: "/src/index.ts".to_string(),
2934 span: Span::new(5, 15),
2935 };
2936 resolver.emit_resolution_error(&mut diagnostics, &failure_2792);
2937
2938 assert_eq!(diagnostics.len(), 2);
2939
2940 let errors: Vec<_> = diagnostics.errors().collect();
2941 assert_eq!(errors[0].code, IMPORT_PATH_NEEDS_EXTENSION_SUGGESTION);
2942 assert_eq!(errors[1].code, MODULE_RESOLUTION_MODE_MISMATCH);
2943 }
2944
2945 #[test]
2950 fn test_extension_from_path_ts() {
2951 assert_eq!(
2952 ModuleExtension::from_path(Path::new("foo.ts")),
2953 ModuleExtension::Ts
2954 );
2955 }
2956
2957 #[test]
2958 fn test_extension_from_path_tsx() {
2959 assert_eq!(
2960 ModuleExtension::from_path(Path::new("Component.tsx")),
2961 ModuleExtension::Tsx
2962 );
2963 }
2964
2965 #[test]
2966 fn test_extension_from_path_dts() {
2967 assert_eq!(
2968 ModuleExtension::from_path(Path::new("types.d.ts")),
2969 ModuleExtension::Dts
2970 );
2971 }
2972
2973 #[test]
2974 fn test_extension_from_path_dmts() {
2975 assert_eq!(
2976 ModuleExtension::from_path(Path::new("types.d.mts")),
2977 ModuleExtension::DmTs
2978 );
2979 }
2980
2981 #[test]
2982 fn test_extension_from_path_dcts() {
2983 assert_eq!(
2984 ModuleExtension::from_path(Path::new("types.d.cts")),
2985 ModuleExtension::DCts
2986 );
2987 }
2988
2989 #[test]
2990 fn test_extension_from_path_js() {
2991 assert_eq!(
2992 ModuleExtension::from_path(Path::new("bundle.js")),
2993 ModuleExtension::Js
2994 );
2995 }
2996
2997 #[test]
2998 fn test_extension_from_path_jsx() {
2999 assert_eq!(
3000 ModuleExtension::from_path(Path::new("App.jsx")),
3001 ModuleExtension::Jsx
3002 );
3003 }
3004
3005 #[test]
3006 fn test_extension_from_path_mjs() {
3007 assert_eq!(
3008 ModuleExtension::from_path(Path::new("module.mjs")),
3009 ModuleExtension::Mjs
3010 );
3011 }
3012
3013 #[test]
3014 fn test_extension_from_path_cjs() {
3015 assert_eq!(
3016 ModuleExtension::from_path(Path::new("config.cjs")),
3017 ModuleExtension::Cjs
3018 );
3019 }
3020
3021 #[test]
3022 fn test_extension_from_path_mts() {
3023 assert_eq!(
3024 ModuleExtension::from_path(Path::new("utils.mts")),
3025 ModuleExtension::Mts
3026 );
3027 }
3028
3029 #[test]
3030 fn test_extension_from_path_cts() {
3031 assert_eq!(
3032 ModuleExtension::from_path(Path::new("config.cts")),
3033 ModuleExtension::Cts
3034 );
3035 }
3036
3037 #[test]
3038 fn test_extension_from_path_json() {
3039 assert_eq!(
3040 ModuleExtension::from_path(Path::new("package.json")),
3041 ModuleExtension::Json
3042 );
3043 }
3044
3045 #[test]
3046 fn test_extension_from_path_unknown() {
3047 assert_eq!(
3048 ModuleExtension::from_path(Path::new("style.css")),
3049 ModuleExtension::Unknown
3050 );
3051 }
3052
3053 #[test]
3054 fn test_extension_from_path_no_extension() {
3055 assert_eq!(
3056 ModuleExtension::from_path(Path::new("Makefile")),
3057 ModuleExtension::Unknown
3058 );
3059 }
3060
3061 #[test]
3062 fn test_extension_from_path_nested() {
3063 assert_eq!(
3064 ModuleExtension::from_path(Path::new("/project/src/lib/types.d.ts")),
3065 ModuleExtension::Dts
3066 );
3067 }
3068
3069 #[test]
3074 fn test_extension_as_str_roundtrip() {
3075 let extensions = [
3076 ModuleExtension::Ts,
3077 ModuleExtension::Tsx,
3078 ModuleExtension::Dts,
3079 ModuleExtension::DmTs,
3080 ModuleExtension::DCts,
3081 ModuleExtension::Js,
3082 ModuleExtension::Jsx,
3083 ModuleExtension::Mjs,
3084 ModuleExtension::Cjs,
3085 ModuleExtension::Mts,
3086 ModuleExtension::Cts,
3087 ModuleExtension::Json,
3088 ];
3089 for ext in &extensions {
3090 let ext_str = ext.as_str();
3091 assert!(
3092 !ext_str.is_empty(),
3093 "{ext:?} should have a non-empty string representation"
3094 );
3095 assert!(
3097 ext_str.starts_with('.'),
3098 "{ext:?}.as_str() should start with '.', got: {ext_str}"
3099 );
3100 }
3101 assert_eq!(ModuleExtension::Unknown.as_str(), "");
3102 }
3103
3104 #[test]
3109 fn test_extension_forces_esm() {
3110 assert!(ModuleExtension::Mts.forces_esm());
3111 assert!(ModuleExtension::Mjs.forces_esm());
3112 assert!(ModuleExtension::DmTs.forces_esm());
3113
3114 assert!(!ModuleExtension::Ts.forces_esm());
3115 assert!(!ModuleExtension::Tsx.forces_esm());
3116 assert!(!ModuleExtension::Dts.forces_esm());
3117 assert!(!ModuleExtension::Js.forces_esm());
3118 assert!(!ModuleExtension::Cjs.forces_esm());
3119 assert!(!ModuleExtension::Cts.forces_esm());
3120 }
3121
3122 #[test]
3123 fn test_extension_forces_cjs() {
3124 assert!(ModuleExtension::Cts.forces_cjs());
3125 assert!(ModuleExtension::Cjs.forces_cjs());
3126 assert!(ModuleExtension::DCts.forces_cjs());
3127
3128 assert!(!ModuleExtension::Ts.forces_cjs());
3129 assert!(!ModuleExtension::Tsx.forces_cjs());
3130 assert!(!ModuleExtension::Dts.forces_cjs());
3131 assert!(!ModuleExtension::Js.forces_cjs());
3132 assert!(!ModuleExtension::Mjs.forces_cjs());
3133 assert!(!ModuleExtension::Mts.forces_cjs());
3134 }
3135
3136 #[test]
3137 fn test_extension_neutral_mode() {
3138 let neutral = [
3140 ModuleExtension::Ts,
3141 ModuleExtension::Tsx,
3142 ModuleExtension::Dts,
3143 ModuleExtension::Js,
3144 ModuleExtension::Jsx,
3145 ModuleExtension::Json,
3146 ModuleExtension::Unknown,
3147 ];
3148 for ext in &neutral {
3149 assert!(
3150 !ext.forces_esm() && !ext.forces_cjs(),
3151 "{ext:?} should be neutral (neither ESM nor CJS)"
3152 );
3153 }
3154 }
3155
3156 #[test]
3161 fn test_resolution_failure_not_found_is_not_found() {
3162 let failure = ResolutionFailure::NotFound {
3163 specifier: "./missing".to_string(),
3164 containing_file: "main.ts".to_string(),
3165 span: Span::new(0, 10),
3166 };
3167 assert!(failure.is_not_found());
3168 }
3169
3170 #[test]
3171 fn test_resolution_failure_other_is_not_not_found() {
3172 let failure = ResolutionFailure::ImportPathNeedsExtension {
3173 specifier: "./utils".to_string(),
3174 suggested_extension: ".js".to_string(),
3175 containing_file: "main.mts".to_string(),
3176 span: Span::new(0, 10),
3177 };
3178 assert!(!failure.is_not_found());
3179 }
3180
3181 #[test]
3182 fn test_resolution_failure_containing_file() {
3183 let failure = ResolutionFailure::NotFound {
3184 specifier: "./missing".to_string(),
3185 containing_file: "/project/src/main.ts".to_string(),
3186 span: Span::new(5, 20),
3187 };
3188 assert_eq!(failure.containing_file(), "/project/src/main.ts");
3189 }
3190
3191 #[test]
3192 fn test_resolution_failure_span() {
3193 let failure = ResolutionFailure::NotFound {
3194 specifier: "./missing".to_string(),
3195 containing_file: "main.ts".to_string(),
3196 span: Span::new(10, 30),
3197 };
3198 let span = failure.span();
3199 assert_eq!(span.start, 10);
3200 assert_eq!(span.end, 30);
3201 }
3202
3203 #[test]
3204 fn test_resolution_failure_to_diagnostic_ts2307() {
3205 let failure = ResolutionFailure::NotFound {
3206 specifier: "./nonexistent".to_string(),
3207 containing_file: "main.ts".to_string(),
3208 span: Span::new(0, 20),
3209 };
3210 let diag = failure.to_diagnostic();
3211 assert_eq!(diag.code, CANNOT_FIND_MODULE);
3212 assert!(diag.message.contains("./nonexistent"));
3213 }
3214
3215 #[test]
3216 fn test_resolution_failure_to_diagnostic_ts2835() {
3217 let failure = ResolutionFailure::ImportPathNeedsExtension {
3218 specifier: "./utils".to_string(),
3219 suggested_extension: ".js".to_string(),
3220 containing_file: "app.mts".to_string(),
3221 span: Span::new(0, 15),
3222 };
3223 let diag = failure.to_diagnostic();
3224 assert_eq!(diag.code, IMPORT_PATH_NEEDS_EXTENSION_SUGGESTION);
3225 }
3226
3227 #[test]
3228 fn test_resolution_failure_to_diagnostic_ts2792() {
3229 let failure = ResolutionFailure::ModuleResolutionModeMismatch {
3230 specifier: "some-esm-pkg".to_string(),
3231 containing_file: "index.ts".to_string(),
3232 span: Span::new(0, 20),
3233 };
3234 let diag = failure.to_diagnostic();
3235 assert_eq!(diag.code, MODULE_RESOLUTION_MODE_MISMATCH);
3236 }
3237
3238 #[test]
3243 fn test_resolver_relative_ts_file() {
3244 use std::fs;
3245 let dir = std::env::temp_dir().join("tsz_test_resolver_relative");
3246 let _ = fs::remove_dir_all(&dir);
3247 fs::create_dir_all(&dir).unwrap();
3248
3249 fs::write(dir.join("main.ts"), "import { foo } from './utils';").unwrap();
3250 fs::write(dir.join("utils.ts"), "export const foo = 42;").unwrap();
3251
3252 let mut resolver = ModuleResolver::node_resolver();
3253 let result = resolver.resolve("./utils", &dir.join("main.ts"), Span::new(0, 10));
3254
3255 match result {
3256 Ok(module) => {
3257 assert_eq!(module.resolved_path, dir.join("utils.ts"));
3258 assert_eq!(module.extension, ModuleExtension::Ts);
3259 assert!(!module.is_external);
3260 }
3261 Err(_) => {
3262 }
3264 }
3265
3266 let _ = fs::remove_dir_all(&dir);
3267 }
3268
3269 #[test]
3270 fn test_resolver_relative_tsx_file() {
3271 use std::fs;
3272 let dir = std::env::temp_dir().join("tsz_test_resolver_tsx");
3273 let _ = fs::remove_dir_all(&dir);
3274 fs::create_dir_all(&dir).unwrap();
3275
3276 fs::write(dir.join("app.ts"), "").unwrap();
3277 fs::write(
3278 dir.join("Button.tsx"),
3279 "export default function Button() {}",
3280 )
3281 .unwrap();
3282
3283 let mut resolver = ModuleResolver::node_resolver();
3284 let result = resolver.resolve("./Button", &dir.join("app.ts"), Span::new(0, 10));
3285
3286 if let Ok(module) = result {
3287 assert_eq!(module.resolved_path, dir.join("Button.tsx"));
3288 assert_eq!(module.extension, ModuleExtension::Tsx);
3289 }
3290
3291 let _ = fs::remove_dir_all(&dir);
3292 }
3293
3294 #[test]
3295 fn test_resolver_index_file() {
3296 use std::fs;
3297 let dir = std::env::temp_dir().join("tsz_test_resolver_index");
3298 let _ = fs::remove_dir_all(&dir);
3299 fs::create_dir_all(dir.join("utils")).unwrap();
3300
3301 fs::write(dir.join("main.ts"), "").unwrap();
3302 fs::write(dir.join("utils").join("index.ts"), "export const foo = 42;").unwrap();
3303
3304 let mut resolver = ModuleResolver::node_resolver();
3305 let result = resolver.resolve("./utils", &dir.join("main.ts"), Span::new(0, 10));
3306
3307 if let Ok(module) = result {
3308 assert_eq!(module.resolved_path, dir.join("utils").join("index.ts"));
3309 assert_eq!(module.extension, ModuleExtension::Ts);
3310 }
3311
3312 let _ = fs::remove_dir_all(&dir);
3313 }
3314
3315 #[test]
3316 fn test_exports_js_target_substitutes_dts() {
3317 use std::fs;
3318 let dir = std::env::temp_dir().join("tsz_test_exports_js_target");
3319 let _ = fs::remove_dir_all(&dir);
3320 fs::create_dir_all(dir.join("node_modules/pkg")).unwrap();
3321 fs::create_dir_all(dir.join("src")).unwrap();
3322
3323 fs::write(
3324 dir.join("node_modules/pkg/package.json"),
3325 r#"{"name":"pkg","version":"0.0.1","exports":"./entrypoint.js"}"#,
3326 )
3327 .unwrap();
3328 fs::write(dir.join("node_modules/pkg/entrypoint.d.ts"), "export {};").unwrap();
3329 fs::write(dir.join("src/index.ts"), "import * as p from 'pkg';").unwrap();
3330
3331 let options = ResolvedCompilerOptions {
3332 module_resolution: Some(ModuleResolutionKind::Node16),
3333 resolve_package_json_exports: true,
3334 ..Default::default()
3335 };
3336
3337 let mut resolver = ModuleResolver::new(&options);
3338 let result = resolver.resolve("pkg", &dir.join("src/index.ts"), Span::new(0, 3));
3339
3340 let resolved =
3343 result.expect("Expected exports .js target to resolve via .d.ts substitution");
3344 assert!(resolved.resolved_path.ends_with("entrypoint.d.ts"));
3345
3346 let _ = fs::remove_dir_all(&dir);
3347 }
3348
3349 #[test]
3350 fn test_resolver_dts_file() {
3351 use std::fs;
3352 let dir = std::env::temp_dir().join("tsz_test_resolver_dts");
3353 let _ = fs::remove_dir_all(&dir);
3354 fs::create_dir_all(&dir).unwrap();
3355
3356 fs::write(dir.join("main.ts"), "").unwrap();
3357 fs::write(dir.join("types.d.ts"), "export interface Foo {}").unwrap();
3358
3359 let mut resolver = ModuleResolver::node_resolver();
3360 let result = resolver.resolve("./types", &dir.join("main.ts"), Span::new(0, 10));
3361
3362 if let Ok(module) = result {
3363 assert_eq!(module.resolved_path, dir.join("types.d.ts"));
3364 assert_eq!(module.extension, ModuleExtension::Dts);
3365 }
3366
3367 let _ = fs::remove_dir_all(&dir);
3368 }
3369
3370 #[test]
3371 fn test_resolver_jsx_without_jsx_option_errors() {
3372 use std::fs;
3373 let dir = std::env::temp_dir().join("tsz_test_resolver_jsx_no_option");
3374 let _ = fs::remove_dir_all(&dir);
3375 fs::create_dir_all(&dir).unwrap();
3376
3377 fs::write(dir.join("app.ts"), "import jsx from './jsx';").unwrap();
3378 fs::write(dir.join("jsx.jsx"), "export default 1;").unwrap();
3379
3380 let options = ResolvedCompilerOptions {
3381 allow_js: true,
3382 jsx: None,
3383 module_resolution: Some(ModuleResolutionKind::Node),
3385 ..Default::default()
3386 };
3387 let mut resolver = ModuleResolver::new(&options);
3388 let result = resolver.resolve("./jsx", &dir.join("app.ts"), Span::new(0, 10));
3389
3390 let failure = result.expect_err("Expected jsx resolution to fail without jsx option");
3391 let diagnostic = failure.to_diagnostic();
3392 assert_eq!(diagnostic.code, 6142);
3393
3394 let _ = fs::remove_dir_all(&dir);
3395 }
3396
3397 #[test]
3398 fn test_resolver_tsx_without_jsx_option_errors() {
3399 use std::fs;
3400 let dir = std::env::temp_dir().join("tsz_test_resolver_tsx_no_option");
3401 let _ = fs::remove_dir_all(&dir);
3402 fs::create_dir_all(&dir).unwrap();
3403
3404 fs::write(dir.join("app.ts"), "import tsx from './tsx';").unwrap();
3405 fs::write(dir.join("tsx.tsx"), "export default 1;").unwrap();
3406
3407 let options = ResolvedCompilerOptions {
3408 jsx: None,
3409 module_resolution: Some(ModuleResolutionKind::Node),
3411 ..Default::default()
3412 };
3413 let mut resolver = ModuleResolver::new(&options);
3414 let result = resolver.resolve("./tsx", &dir.join("app.ts"), Span::new(0, 10));
3415
3416 let failure = result.expect_err("Expected tsx resolution to fail without jsx option");
3417 let diagnostic = failure.to_diagnostic();
3418 assert_eq!(diagnostic.code, 6142);
3419
3420 let _ = fs::remove_dir_all(&dir);
3421 }
3422
3423 #[test]
3424 fn test_json_import_without_resolve_json_module() {
3425 use std::fs;
3426 let dir = std::env::temp_dir().join("tsz_test_ts2732");
3427 let _ = fs::remove_dir_all(&dir);
3428 fs::create_dir_all(&dir).unwrap();
3429
3430 fs::write(dir.join("app.ts"), "import data from './data.json';").unwrap();
3431 fs::write(dir.join("data.json"), "{\"value\": 42}").unwrap();
3432
3433 let options = ResolvedCompilerOptions {
3434 resolve_json_module: false, ..Default::default()
3436 };
3437 let mut resolver = ModuleResolver::new(&options);
3438
3439 let result = resolver.resolve("./data.json", &dir.join("app.ts"), Span::new(0, 10));
3440
3441 let failure =
3442 result.expect_err("Expected JSON resolution to fail without resolveJsonModule");
3443 let diagnostic = failure.to_diagnostic();
3444 assert_eq!(diagnostic.code, 2732); let _ = fs::remove_dir_all(&dir);
3447 }
3448
3449 #[test]
3450 fn test_resolver_package_main_with_unknown_extension() {
3451 use std::fs;
3452 let dir = std::env::temp_dir().join("tsz_test_resolver_main_unknown");
3453 let _ = fs::remove_dir_all(&dir);
3454 fs::create_dir_all(dir.join("node_modules").join("normalize.css")).unwrap();
3455
3456 fs::write(dir.join("app.ts"), "import 'normalize.css';").unwrap();
3457 fs::write(
3458 dir.join("node_modules")
3459 .join("normalize.css")
3460 .join("normalize.css"),
3461 "body {}",
3462 )
3463 .unwrap();
3464 fs::write(
3465 dir.join("node_modules")
3466 .join("normalize.css")
3467 .join("package.json"),
3468 r#"{ "main": "normalize.css" }"#,
3469 )
3470 .unwrap();
3471
3472 let mut resolver = ModuleResolver::node_resolver();
3473 let result = resolver.resolve("normalize.css", &dir.join("app.ts"), Span::new(0, 10));
3474 assert!(
3475 result.is_ok(),
3476 "Expected package main with unknown extension to resolve"
3477 );
3478
3479 let _ = fs::remove_dir_all(&dir);
3480 }
3481
3482 #[test]
3483 fn test_resolver_package_types_with_unknown_extension() {
3484 use std::fs;
3485 let dir = std::env::temp_dir().join("tsz_test_resolver_types_unknown");
3486 let _ = fs::remove_dir_all(&dir);
3487 fs::create_dir_all(dir.join("node_modules").join("foo")).unwrap();
3488
3489 fs::write(dir.join("app.ts"), "import 'foo';").unwrap();
3490 fs::write(
3491 dir.join("node_modules").join("foo").join("foo.js"),
3492 "module.exports = {};",
3493 )
3494 .unwrap();
3495 fs::write(
3496 dir.join("node_modules").join("foo").join("package.json"),
3497 r#"{ "types": "foo.js" }"#,
3498 )
3499 .unwrap();
3500
3501 let mut resolver = ModuleResolver::node_resolver();
3502 let result = resolver.resolve("foo", &dir.join("app.ts"), Span::new(0, 10));
3503 assert!(
3504 result.is_ok(),
3505 "Expected package types with unknown extension to resolve"
3506 );
3507
3508 let _ = fs::remove_dir_all(&dir);
3509 }
3510
3511 #[test]
3512 fn test_resolver_package_types_js_without_allow_js_resolves() {
3513 use std::fs;
3514 let dir = std::env::temp_dir().join("tsz_test_resolver_types_js");
3515 let _ = fs::remove_dir_all(&dir);
3516 fs::create_dir_all(dir.join("node_modules").join("foo")).unwrap();
3517
3518 fs::write(dir.join("app.ts"), "import 'foo';").unwrap();
3519 fs::write(
3520 dir.join("node_modules").join("foo").join("foo.js"),
3521 "module.exports = {};",
3522 )
3523 .unwrap();
3524 fs::write(
3525 dir.join("node_modules").join("foo").join("package.json"),
3526 r#"{ "types": "foo.js" }"#,
3527 )
3528 .unwrap();
3529
3530 let mut resolver = ModuleResolver::node_resolver();
3531 let result = resolver.resolve("foo", &dir.join("app.ts"), Span::new(0, 10));
3532 assert!(
3533 result.is_ok(),
3534 "Expected types .js to resolve even without allowJs"
3535 );
3536
3537 let _ = fs::remove_dir_all(&dir);
3538 }
3539
3540 #[test]
3541 fn test_resolver_missing_file() {
3542 use std::fs;
3543 let dir = std::env::temp_dir().join("tsz_test_resolver_missing");
3544 let _ = fs::remove_dir_all(&dir);
3545 fs::create_dir_all(&dir).unwrap();
3546 fs::write(dir.join("main.ts"), "").unwrap();
3547
3548 let mut resolver = ModuleResolver::node_resolver();
3549 let result = resolver.resolve("./nonexistent", &dir.join("main.ts"), Span::new(0, 10));
3550
3551 assert!(result.is_err(), "Missing file should produce error");
3552 if let Err(failure) = result {
3553 assert!(failure.is_not_found());
3554 }
3555
3556 let _ = fs::remove_dir_all(&dir);
3557 }
3558
3559 #[test]
3564 fn test_package_type_default_is_commonjs() {
3565 assert_eq!(PackageType::default(), PackageType::CommonJs);
3566 }
3567
3568 #[test]
3569 fn test_importing_module_kind_default_is_commonjs() {
3570 assert_eq!(
3571 ImportingModuleKind::default(),
3572 ImportingModuleKind::CommonJs
3573 );
3574 }
3575}