Repository: denoland/deno (language: rust)

Issue:
sloppy resolution across `compilerOptions.rootDirs` not working



Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5e41e795c57e75..cef572f6869505 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1579,9 +1579,9 @@ dependencies = [
 
 [[package]]
 name = "deno_config"
-version = "0.47.1"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f7883c48549bab8e446a58c64ee3d106a13052d2ff5e864de765a60260cb02b"
+checksum = "6c486df63f7fa0f2142c7eba286c7be87a3cd8c93f66f744fb5853a77cf4347b"
 dependencies = [
  "boxed_error",
  "capacity_builder 0.5.0",
@@ -2382,10 +2382,14 @@ dependencies = [
  "deno_semver",
  "deno_terminal 0.2.0",
  "futures",
+ "import_map",
+ "indexmap 2.3.0",
  "log",
  "node_resolver",
  "once_cell",
  "parking_lot",
+ "serde",
+ "serde_json",
  "sys_traits",
  "test_server",
  "thiserror 2.0.3",
@@ -8033,6 +8037,8 @@ dependencies = [
  "getrandom",
  "libc",
  "parking_lot",
+ "serde",
+ "serde_json",
  "windows-sys 0.59.0",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 5af9ac3a88980d..99a2e0812dfa9e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,7 +54,7 @@ deno_ast = { version = "=0.44.0", features = ["transpiling"] }
 deno_core = { version = "0.333.0" }
 
 deno_bench_util = { version = "0.183.0", path = "./bench_util" }
-deno_config = { version = "=0.47.1", features = ["workspace"] }
+deno_config = { version = "=0.48.0", features = ["workspace"] }
 deno_lockfile = "=0.24.0"
 deno_media_type = { version = "=0.2.5", features = ["module_specifier"] }
 deno_npm = "=0.27.2"
@@ -155,6 +155,7 @@ hyper = { version = "1.6.0", features = ["full"] }
 hyper-rustls = { version = "0.27.2", default-features = false, features = ["http1", "http2", "tls12", "ring"] }
 hyper-util = { version = "0.1.10", features = ["tokio", "client", "client-legacy", "server", "server-auto"] }
 hyper_v014 = { package = "hyper", version = "0.14.26", features = ["runtime", "http1"] }
+import_map = { version = "0.21.0", features = ["ext"] }
 indexmap = { version = "2", features = ["serde"] }
 ipnet = "2.3"
 jsonc-parser = { version = "=0.26.2", features = ["serde"] }
diff --git a/cli/factory.rs b/cli/factory.rs
index bd9b58073f654a..1ae4954f69ae30 100644
--- a/cli/factory.rs
+++ b/cli/factory.rs
@@ -9,7 +9,6 @@ use std::sync::Arc;
 use deno_cache_dir::npm::NpmCacheDir;
 use deno_config::workspace::Workspace;
 use deno_config::workspace::WorkspaceDirectory;
-use deno_config::workspace::WorkspaceResolver;
 use deno_core::anyhow::Context;
 use deno_core::error::AnyError;
 use deno_core::futures::FutureExt;
@@ -38,6 +37,7 @@ use deno_resolver::factory::ResolverFactoryOptions;
 use deno_resolver::factory::SpecifiedImportMapProvider;
 use deno_resolver::npm::managed::NpmResolutionCell;
 use deno_resolver::npm::DenoInNpmPackageChecker;
+use deno_resolver::workspace::WorkspaceResolver;
 use deno_runtime::deno_fs;
 use deno_runtime::deno_fs::RealFs;
 use deno_runtime::deno_permissions::Permissions;
@@ -97,7 +97,6 @@ use crate::resolver::CliDenoResolver;
 use crate::resolver::CliNpmGraphResolver;
 use crate::resolver::CliNpmReqResolver;
 use crate::resolver::CliResolver;
-use crate::resolver::CliSloppyImportsResolver;
 use crate::resolver::FoundPackageJsonDepFlag;
 use crate::standalone::binary::DenoCompileBinaryWriter;
 use crate::sys::CliSys;
@@ -160,7 +159,8 @@ struct CliSpecifiedImportMapProvider {
 impl SpecifiedImportMapProvider for CliSpecifiedImportMapProvider {
   async fn get(
     &self,
-  ) -> Result<Option<deno_config::workspace::SpecifiedImportMap>, AnyError> {
+  ) -> Result<Option<deno_resolver::workspace::SpecifiedImportMap>, AnyError>
+  {
     async fn resolve_import_map_value_from_specifier(
       specifier: &Url,
       file_fetcher: &CliFileFetcher,
@@ -189,7 +189,7 @@ impl SpecifiedImportMapProvider for CliSpecifiedImportMapProvider {
         .with_context(|| {
           format!("Unable to load '{}' import map", specifier)
         })?;
-        Ok(Some(deno_config::workspace::SpecifiedImportMap {
+        Ok(Some(deno_resolver::workspace::SpecifiedImportMap {
           base_url: specifier,
           value,
         }))
@@ -199,7 +199,7 @@ impl SpecifiedImportMapProvider for CliSpecifiedImportMapProvider {
           self.workspace_external_import_map_loader.get_or_load()?
         {
           let path_url = deno_path_util::url_from_file_path(&import_map.path)?;
-          Ok(Some(deno_config::workspace::SpecifiedImportMap {
+          Ok(Some(deno_resolver::workspace::SpecifiedImportMap {
             base_url: path_url,
             value: import_map.value.clone(),
           }))
@@ -646,7 +646,6 @@ impl CliFactory {
         ResolverFactoryOptions {
           conditions_from_resolution_mode: Default::default(),
           node_resolution_cache: Some(Arc::new(NodeResolutionThreadLocalCache)),
-          no_sloppy_imports_cache: false,
           npm_system_info: self.flags.subcommand.npm_system_info(),
           specified_import_map: Some(Box::new(CliSpecifiedImportMapProvider {
             cli_options: self.cli_options()?.clone(),
@@ -663,7 +662,7 @@ impl CliFactory {
             DenoSubcommand::Publish(_) => {
               // the node_modules directory is not published to jsr, so resolve
               // dependencies via the package.json rather than using node resolution
-              Some(deno_config::workspace::PackageJsonDepResolution::Enabled)
+              Some(deno_resolver::workspace::PackageJsonDepResolution::Enabled)
             }
             _ => None,
           },
@@ -672,12 +671,6 @@ impl CliFactory {
     })
   }
 
-  pub fn sloppy_imports_resolver(
-    &self,
-  ) -> Result<Option<&Arc<CliSloppyImportsResolver>>, AnyError> {
-    self.resolver_factory()?.sloppy_imports_resolver()
-  }
-
   pub fn workspace(&self) -> Result<&Arc<Workspace>, AnyError> {
     Ok(&self.workspace_directory()?.workspace)
   }
@@ -790,10 +783,9 @@ impl CliFactory {
   }
 
   pub async fn lint_rule_provider(&self) -> Result<LintRuleProvider, AnyError> {
-    Ok(LintRuleProvider::new(
-      self.sloppy_imports_resolver()?.cloned(),
-      Some(self.workspace_resolver().await?.clone()),
-    ))
+    Ok(LintRuleProvider::new(Some(
+      self.workspace_resolver().await?.clone(),
+    )))
   }
 
   pub async fn node_resolver(&self) -> Result<&Arc<CliNodeResolver>, AnyError> {
diff --git a/cli/graph_util.rs b/cli/graph_util.rs
index 6a640b7cb01f19..861fa664bff05f 100644
--- a/cli/graph_util.rs
+++ b/cli/graph_util.rs
@@ -34,8 +34,7 @@ use deno_graph::SpecifierError;
 use deno_graph::WorkspaceFastCheckOption;
 use deno_path_util::url_to_file_path;
 use deno_resolver::npm::DenoInNpmPackageChecker;
-use deno_resolver::sloppy_imports::SloppyImportsCachedFs;
-use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
+use deno_resolver::workspace::sloppy_imports_resolve;
 use deno_runtime::deno_node;
 use deno_runtime::deno_permissions::PermissionsContainer;
 use deno_semver::jsr::JsrDepPackageReq;
@@ -62,7 +61,6 @@ use crate::npm::CliNpmResolver;
 use crate::resolver::CliCjsTracker;
 use crate::resolver::CliNpmGraphResolver;
 use crate::resolver::CliResolver;
-use crate::resolver::CliSloppyImportsResolver;
 use crate::sys::CliSys;
 use crate::tools::check;
 use crate::tools::check::CheckError;
@@ -949,11 +947,14 @@ pub fn maybe_additional_sloppy_imports_message(
   sys: &CliSys,
   specifier: &ModuleSpecifier,
 ) -> Option<String> {
+  let (resolved, sloppy_reason) = sloppy_imports_resolve(
+    specifier,
+    deno_resolver::workspace::ResolutionKind::Execution,
+    sys.clone(),
+  )?;
   Some(format!(
     "{} {}",
-    CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(sys.clone()))
-      .resolve(specifier, SloppyImportsResolutionKind::Execution)?
-      .as_suggestion_message(),
+    sloppy_reason.suggestion_message_for_specifier(&resolved),
     RUN_WITH_SLOPPY_IMPORTS_MSG
   ))
 }
diff --git a/cli/lib/standalone/binary.rs b/cli/lib/standalone/binary.rs
index ae02197bf47a4a..516d120a2001a2 100644
--- a/cli/lib/standalone/binary.rs
+++ b/cli/lib/standalone/binary.rs
@@ -3,8 +3,8 @@
 use std::borrow::Cow;
 use std::collections::BTreeMap;
 
-use deno_config::workspace::PackageJsonDepResolution;
 use deno_media_type::MediaType;
+use deno_resolver::workspace::PackageJsonDepResolution;
 use deno_runtime::deno_permissions::PermissionsOptions;
 use deno_runtime::deno_telemetry::OtelConfig;
 use deno_semver::Version;
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 16a815e08ff7bb..6d62a68febe9c1 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -9,7 +9,6 @@ use std::path::Path;
 use deno_ast::SourceRange;
 use deno_ast::SourceRangedForSpanned;
 use deno_ast::SourceTextInfo;
-use deno_config::workspace::MappedResolution;
 use deno_core::error::AnyError;
 use deno_core::serde::Deserialize;
 use deno_core::serde::Serialize;
@@ -20,6 +19,7 @@ use deno_error::JsErrorBox;
 use deno_lint::diagnostic::LintDiagnosticRange;
 use deno_path_util::url_to_file_path;
 use deno_resolver::npm::managed::NpmResolutionCell;
+use deno_resolver::workspace::MappedResolution;
 use deno_runtime::deno_node::PathClean;
 use deno_semver::jsr::JsrPackageNvReference;
 use deno_semver::jsr::JsrPackageReqReference;
@@ -1348,11 +1348,10 @@ impl CodeActionCollection {
       let npm_ref = if let Ok(resolution) = workspace_resolver.resolve(
         &dep_key,
         document.specifier(),
-        deno_config::workspace::ResolutionKind::Execution,
+        deno_resolver::workspace::ResolutionKind::Execution,
       ) {
         let specifier = match resolution {
-          MappedResolution::Normal { specifier, .. }
-          | MappedResolution::ImportMap { specifier, .. } => specifier,
+          MappedResolution::Normal { specifier, .. } => specifier,
           _ => {
             return None;
           }
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index b76c54c7ed6b5f..2e02d839e4ad19 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -21,16 +21,12 @@ use deno_config::deno_json::TsConfig;
 use deno_config::deno_json::TsConfigWithIgnoredOptions;
 use deno_config::glob::FilePatterns;
 use deno_config::glob::PathOrPatternSet;
-use deno_config::workspace::CreateResolverOptions;
-use deno_config::workspace::PackageJsonDepResolution;
-use deno_config::workspace::SpecifiedImportMap;
 use deno_config::workspace::VendorEnablement;
 use deno_config::workspace::Workspace;
 use deno_config::workspace::WorkspaceCache;
 use deno_config::workspace::WorkspaceDirectory;
 use deno_config::workspace::WorkspaceDirectoryEmptyOptions;
 use deno_config::workspace::WorkspaceDiscoverOptions;
-use deno_config::workspace::WorkspaceResolver;
 use deno_core::anyhow::anyhow;
 use deno_core::error::AnyError;
 use deno_core::parking_lot::Mutex;
@@ -49,7 +45,12 @@ use deno_npm::npm_rc::ResolvedNpmRc;
 use deno_package_json::PackageJsonCache;
 use deno_path_util::url_to_file_path;
 use deno_resolver::npmrc::discover_npmrc_from_workspace;
-use deno_resolver::sloppy_imports::SloppyImportsCachedFs;
+use deno_resolver::workspace::CreateResolverOptions;
+use deno_resolver::workspace::FsCacheOptions;
+use deno_resolver::workspace::PackageJsonDepResolution;
+use deno_resolver::workspace::SloppyImportsOptions;
+use deno_resolver::workspace::SpecifiedImportMap;
+use deno_resolver::workspace::WorkspaceResolver;
 use deno_runtime::deno_node::PackageJson;
 use indexmap::IndexSet;
 use lsp_types::ClientCapabilities;
@@ -65,7 +66,6 @@ use crate::args::LintFlags;
 use crate::args::LintOptions;
 use crate::file_fetcher::CliFileFetcher;
 use crate::lsp::logging::lsp_warn;
-use crate::resolver::CliSloppyImportsResolver;
 use crate::sys::CliSys;
 use crate::tools::lint::CliLinter;
 use crate::tools::lint::CliLinterOptions;
@@ -1206,7 +1206,6 @@ pub struct ConfigData {
   pub lockfile: Option<Arc<CliLockfile>>,
   pub npmrc: Option<Arc<ResolvedNpmRc>>,
   pub resolver: Arc<WorkspaceResolver<CliSys>>,
-  pub sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
   pub import_map_from_settings: Option<ModuleSpecifier>,
   pub unstable: BTreeSet<String>,
   watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
@@ -1569,35 +1568,52 @@ impl ConfigData {
         None
       }
     };
-    let resolver = member_dir
+    let unstable = member_dir
       .workspace
-      .create_resolver(
-        CliSys::default(),
-        CreateResolverOptions {
-          pkg_json_dep_resolution,
-          specified_import_map,
+      .unstable_features()
+      .iter()
+      .chain(settings.unstable.as_deref())
+      .cloned()
+      .collect::<BTreeSet<_>>();
+    let unstable_sloppy_imports = std::env::var("DENO_UNSTABLE_SLOPPY_IMPORTS")
+      .is_ok()
+      || unstable.contains("sloppy-imports");
+    let resolver = WorkspaceResolver::from_workspace(
+      &member_dir.workspace,
+      CliSys::default(),
+      CreateResolverOptions {
+        pkg_json_dep_resolution,
+        specified_import_map,
+        sloppy_imports_options: if unstable_sloppy_imports {
+          SloppyImportsOptions::Enabled
+        } else {
+          SloppyImportsOptions::Disabled
         },
+        fs_cache_options: FsCacheOptions::Disabled,
+      },
+    )
+    .inspect_err(|err| {
+      lsp_warn!(
+        "  Failed to load resolver: {}",
+        err // will contain the specifier
+      );
+    })
+    .ok()
+    .unwrap_or_else(|| {
+      // create a dummy resolver
+      WorkspaceResolver::new_raw(
+        scope.clone(),
+        None,
+        member_dir.workspace.resolver_jsr_pkgs().collect(),
+        member_dir.workspace.package_jsons().cloned().collect(),
+        pkg_json_dep_resolution,
+        Default::default(),
+        Default::default(),
+        Default::default(),
+        Default::default(),
+        CliSys::default(),
       )
-      .inspect_err(|err| {
-        lsp_warn!(
-          "  Failed to load resolver: {}",
-          err // will contain the specifier
-        );
-      })
-      .ok()
-      .unwrap_or_else(|| {
-        // create a dummy resolver
-        WorkspaceResolver::new_raw(
-          scope.clone(),
-          None,
-          member_dir.workspace.resolver_jsr_pkgs().collect(),
-          member_dir.workspace.package_jsons().cloned().collect(),
-          pkg_json_dep_resolution,
-          Default::default(),
-          Default::default(),
-          CliSys::default(),
-        )
-      });
+    });
     if !resolver.diagnostics().is_empty() {
       lsp_warn!(
         "  Resolver diagnostics:\n{}",
@@ -1609,26 +1625,8 @@ impl ConfigData {
           .join("\n")
       );
     }
-    let unstable = member_dir
-      .workspace
-      .unstable_features()
-      .iter()
-      .chain(settings.unstable.as_deref())
-      .cloned()
-      .collect::<BTreeSet<_>>();
-    let unstable_sloppy_imports = std::env::var("DENO_UNSTABLE_SLOPPY_IMPORTS")
-      .is_ok()
-      || unstable.contains("sloppy-imports");
-    let sloppy_imports_resolver = unstable_sloppy_imports.then(|| {
-      Arc::new(CliSloppyImportsResolver::new(
-        SloppyImportsCachedFs::new_without_stat_cache(CliSys::default()),
-      ))
-    });
     let resolver = Arc::new(resolver);
-    let lint_rule_provider = LintRuleProvider::new(
-      sloppy_imports_resolver.clone(),
-      Some(resolver.clone()),
-    );
+    let lint_rule_provider = LintRuleProvider::new(Some(resolver.clone()));
 
     let lint_options = LintOptions::resolve(
       member_dir.dir_path(),
@@ -1676,7 +1674,6 @@ impl ConfigData {
       canonicalized_scope,
       member_dir,
       resolver,
-      sloppy_imports_resolver,
       fmt_config,
       lint_config,
       test_config,
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 7283f2cf8f684a..3adbefd88843ab 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -27,9 +27,7 @@ use deno_graph::Resolution;
 use deno_graph::ResolutionError;
 use deno_graph::SpecifierError;
 use deno_lint::linter::LintConfig as DenoLintConfig;
-use deno_resolver::sloppy_imports::SloppyImportsCachedFs;
-use deno_resolver::sloppy_imports::SloppyImportsResolution;
-use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
+use deno_resolver::workspace::sloppy_imports_resolve;
 use deno_runtime::deno_node;
 use deno_runtime::tokio_util::create_basic_runtime;
 use deno_semver::jsr::JsrPackageReqReference;
@@ -64,7 +62,6 @@ use crate::graph_util;
 use crate::graph_util::enhanced_resolution_error_message;
 use crate::lsp::logging::lsp_warn;
 use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
-use crate::resolver::CliSloppyImportsResolver;
 use crate::sys::CliSys;
 use crate::tools::lint::CliLinter;
 use crate::tools::lint::CliLinterOptions;
@@ -1013,7 +1010,7 @@ fn generate_lint_diagnostics(
           Arc::new(LintConfig::new_with_base(PathBuf::from("/"))),
           Arc::new(CliLinter::new(CliLinterOptions {
             configured_rules: {
-              let lint_rule_provider = LintRuleProvider::new(None, None);
+              let lint_rule_provider = LintRuleProvider::new(None);
               lint_rule_provider.resolve_lint_rules(Default::default(), None)
             },
             fix: false,
@@ -1443,14 +1440,14 @@ impl DenoDiagnostic {
   pub fn to_lsp_diagnostic(&self, range: &lsp::Range) -> lsp::Diagnostic {
     fn no_local_message(
       specifier: &ModuleSpecifier,
-      maybe_sloppy_resolution: Option<&SloppyImportsResolution>,
+      suggestion_message: Option<String>,
     ) -> String {
       let mut message = format!(
         "Unable to load a local module: {}\n",
         to_percent_decoded_str(specifier.as_ref())
       );
-      if let Some(res) = maybe_sloppy_resolution {
-        message.push_str(&res.as_suggestion_message());
+      if let Some(suggestion_message) = suggestion_message {
+        message.push_str(&suggestion_message);
         message.push('.');
       } else {
         message.push_str("Please check the file path.");
@@ -1467,17 +1464,15 @@ impl DenoDiagnostic {
       Self::NotInstalledJsr(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("JSR package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))),
       Self::NotInstalledNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("npm package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))),
       Self::NoLocal(specifier) => {
-        let maybe_sloppy_resolution = CliSloppyImportsResolver::new(
-          SloppyImportsCachedFs::new(CliSys::default())
-        ).resolve(specifier, SloppyImportsResolutionKind::Execution);
-        let data = maybe_sloppy_resolution.as_ref().map(|res| {
+        let sloppy_resolution = sloppy_imports_resolve(specifier, deno_resolver::workspace::ResolutionKind::Execution, CliSys::default());
+        let data = sloppy_resolution.as_ref().map(|(resolved, sloppy_reason)| {
           json!({
             "specifier": specifier,
-            "to": res.as_specifier(),
-            "message": res.as_quick_fix_message(),
+            "to": resolved,
+            "message": sloppy_reason.quick_fix_message_for_specifier(resolved),
           })
         });
-        (lsp::DiagnosticSeverity::ERROR, no_local_message(specifier, maybe_sloppy_resolution.as_ref()), data)
+        (lsp::DiagnosticSeverity::ERROR, no_local_message(specifier, sloppy_resolution.as_ref().map(|(resolved, sloppy_reason)| sloppy_reason.suggestion_message_for_specifier(resolved))), data)
       },
       Self::Redirect { from, to} => (lsp::DiagnosticSeverity::INFORMATION, format!("The import of \"{from}\" was redirected to \"{to}\"."), Some(json!({ "specifier": from, "redirect": to }))),
       Self::ResolutionError(err) => {
diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs
index 81673b693b8a20..074372aab32994 100644
--- a/cli/lsp/resolver.rs
+++ b/cli/lsp/resolver.rs
@@ -12,8 +12,6 @@ use deno_ast::MediaType;
 use deno_cache_dir::npm::NpmCacheDir;
 use deno_cache_dir::HttpCache;
 use deno_config::deno_json::JsxImportSourceConfig;
-use deno_config::workspace::PackageJsonDepResolution;
-use deno_config::workspace::WorkspaceResolver;
 use deno_core::parking_lot::Mutex;
 use deno_core::url::Url;
 use deno_graph::GraphImport;
@@ -29,6 +27,8 @@ use deno_resolver::npm::CreateInNpmPkgCheckerOptions;
 use deno_resolver::npm::DenoInNpmPackageChecker;
 use deno_resolver::npm::NpmReqResolverOptions;
 use deno_resolver::npmrc::create_default_npmrc;
+use deno_resolver::workspace::PackageJsonDepResolution;
+use deno_resolver::workspace::WorkspaceResolver;
 use deno_resolver::DenoResolverOptions;
 use deno_resolver::NodeAndNpmReqResolver;
 use deno_semver::jsr::JsrPackageReqReference;
@@ -844,9 +844,6 @@ impl<'a> ResolverFactory<'a> {
           }
           _ => None,
         },
-        sloppy_imports_resolver: self
-          .config_data
-          .and_then(|d| d.sloppy_imports_resolver.clone()),
         workspace_resolver: self
           .config_data
           .map(|d| d.resolver.clone())
@@ -860,6 +857,8 @@ impl<'a> ResolverFactory<'a> {
               PackageJsonDepResolution::Disabled,
               Default::default(),
               Default::default(),
+              Default::default(),
+              Default::default(),
               self.sys.clone(),
             ))
           }),
diff --git a/cli/resolver.rs b/cli/resolver.rs
index 5dcdadb7ff579d..b9ca0eb3069dd8 100644
--- a/cli/resolver.rs
+++ b/cli/resolver.rs
@@ -4,8 +4,6 @@ use std::sync::Arc;
 
 use async_trait::async_trait;
 use dashmap::DashSet;
-use deno_config::workspace::MappedResolutionDiagnostic;
-use deno_config::workspace::MappedResolutionError;
 use deno_core::ModuleSpecifier;
 use deno_error::JsErrorBox;
 use deno_graph::source::ResolveError;
@@ -14,8 +12,8 @@ use deno_graph::NpmLoadError;
 use deno_graph::NpmResolvePkgReqsResult;
 use deno_npm::resolution::NpmResolutionError;
 use deno_resolver::npm::DenoInNpmPackageChecker;
-use deno_resolver::sloppy_imports::SloppyImportsCachedFs;
-use deno_resolver::sloppy_imports::SloppyImportsResolver;
+use deno_resolver::workspace::MappedResolutionDiagnostic;
+use deno_resolver::workspace::MappedResolutionError;
 use deno_runtime::colors;
 use deno_runtime::deno_node::is_builtin_node_module;
 use deno_semver::package::PackageReq;
@@ -35,14 +33,10 @@ pub type CliCjsTracker =
   deno_resolver::cjs::CjsTracker<DenoInNpmPackageChecker, CliSys>;
 pub type CliIsCjsResolver =
   deno_resolver::cjs::IsCjsResolver<DenoInNpmPackageChecker, CliSys>;
-pub type CliSloppyImportsCachedFs = SloppyImportsCachedFs<CliSys>;
-pub type CliSloppyImportsResolver =
-  SloppyImportsResolver<CliSloppyImportsCachedFs>;
 pub type CliDenoResolver = deno_resolver::DenoResolver<
   DenoInNpmPackageChecker,
   DenoIsBuiltInNodeModuleChecker,
   CliNpmResolver,
-  CliSloppyImportsCachedFs,
   CliSys,
 >;
 pub type CliNpmReqResolver = deno_resolver::npm::NpmReqResolver<
diff --git a/cli/rt/run.rs b/cli/rt/run.rs
index 3087682ef9fc06..1eca838cba643b 100644
--- a/cli/rt/run.rs
+++ b/cli/rt/run.rs
@@ -7,9 +7,7 @@ use std::sync::Arc;
 use std::sync::OnceLock;
 
 use deno_cache_dir::npm::NpmCacheDir;
-use deno_config::workspace::MappedResolution;
 use deno_config::workspace::ResolverWorkspaceJsrPackage;
-use deno_config::workspace::WorkspaceResolver;
 use deno_core::error::AnyError;
 use deno_core::error::ModuleLoaderError;
 use deno_core::futures::future::LocalBoxFuture;
@@ -59,9 +57,9 @@ use deno_resolver::npm::NpmReqResolver;
 use deno_resolver::npm::NpmReqResolverOptions;
 use deno_resolver::npm::NpmResolver;
 use deno_resolver::npm::NpmResolverCreateOptions;
-use deno_resolver::sloppy_imports::SloppyImportsCachedFs;
-use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
-use deno_resolver::sloppy_imports::SloppyImportsResolver;
+use deno_resolver::workspace::MappedResolution;
+use deno_resolver::workspace::SloppyImportsOptions;
+use deno_resolver::workspace::WorkspaceResolver;
 use deno_runtime::code_cache::CodeCache;
 use deno_runtime::deno_fs::FileSystem;
 use deno_runtime::deno_node::create_host_defined_options;
@@ -107,8 +105,6 @@ struct SharedModuleLoaderState {
   npm_module_loader: Arc<DenoRtNpmModuleLoader>,
   npm_registry_permission_checker: NpmRegistryReadPermissionChecker<DenoRtSys>,
   npm_req_resolver: Arc<DenoRtNpmReqResolver>,
-  sloppy_imports_resolver:
-    Option<SloppyImportsResolver<SloppyImportsCachedFs<DenoRtSys>>>,
   vfs: Arc<FileBackedVfs>,
   workspace_resolver: WorkspaceResolver<DenoRtSys>,
 }
@@ -210,7 +206,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
     let mapped_resolution = self.shared.workspace_resolver.resolve(
       raw_specifier,
       &referrer,
-      deno_config::workspace::ResolutionKind::Execution,
+      deno_resolver::workspace::ResolutionKind::Execution,
     );
 
     match mapped_resolution {
@@ -289,8 +285,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
           )
         }
       },
-      Ok(MappedResolution::Normal { specifier, .. })
-      | Ok(MappedResolution::ImportMap { specifier, .. }) => {
+      Ok(MappedResolution::Normal { specifier, .. }) => {
         if let Ok(reference) =
           NpmPackageReqReference::from_specifier(&specifier)
         {
@@ -322,18 +317,6 @@ impl ModuleLoader for EmbeddedModuleLoader {
           }
         }
 
-        // do sloppy imports resolution if enabled
-        let specifier = if let Some(sloppy_imports_resolver) =
-          &self.shared.sloppy_imports_resolver
-        {
-          sloppy_imports_resolver
-            .resolve(&specifier, SloppyImportsResolutionKind::Execution)
-            .map(|s| s.into_specifier())
-            .unwrap_or(specifier)
-        } else {
-          specifier
-        };
-
         Ok(
           self
             .shared
@@ -832,10 +815,6 @@ pub async fn run(
     pkg_json_resolver.clone(),
     sys.clone(),
   ));
-  let sloppy_imports_resolver =
-    metadata.unstable_config.sloppy_imports.then(|| {
-      SloppyImportsResolver::new(SloppyImportsCachedFs::new(sys.clone()))
-    });
   let workspace_resolver = {
     let import_map = match metadata.workspace_resolver.import_map {
       Some(import_map) => Some(
@@ -883,6 +862,12 @@ pub async fn run(
         .collect(),
       pkg_jsons,
       metadata.workspace_resolver.pkg_json_resolution,
+      if metadata.unstable_config.sloppy_imports {
+        SloppyImportsOptions::Enabled
+      } else {
+        SloppyImportsOptions::Disabled
+      },
+      Default::default(),
       Default::default(),
       Default::default(),
       sys.clone(),
@@ -915,7 +900,6 @@ pub async fn run(
       )),
       npm_registry_permission_checker,
       npm_req_resolver,
-      sloppy_imports_resolver,
       vfs: vfs.clone(),
       workspace_resolver,
     }),
diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs
index 4f0ce5a3c00f63..ccd0b641852108 100644
--- a/cli/standalone/binary.rs
+++ b/cli/standalone/binary.rs
@@ -16,7 +16,6 @@ use capacity_builder::BytesAppendable;
 use deno_ast::MediaType;
 use deno_ast::ModuleKind;
 use deno_ast::ModuleSpecifier;
-use deno_config::workspace::WorkspaceResolver;
 use deno_core::anyhow::bail;
 use deno_core::anyhow::Context;
 use deno_core::error::AnyError;
@@ -49,6 +48,7 @@ use deno_npm::resolution::SerializedNpmResolutionSnapshot;
 use deno_npm::NpmSystemInfo;
 use deno_path_util::url_from_directory_path;
 use deno_path_util::url_to_file_path;
+use deno_resolver::workspace::WorkspaceResolver;
 use indexmap::IndexMap;
 use node_resolver::analyze::CjsAnalysis;
 use node_resolver::analyze::CjsCodeAnalyzer;
diff --git a/cli/tools/info.rs b/cli/tools/info.rs
index e6bf1baa36b5d2..f7368f90af224c 100644
--- a/cli/tools/info.rs
+++ b/cli/tools/info.rs
@@ -59,21 +59,18 @@ pub async fn info(
     let maybe_import_specifier = if let Ok(resolved) = resolver.resolve(
       &specifier,
       &cwd_url,
-      deno_config::workspace::ResolutionKind::Execution,
+      deno_resolver::workspace::ResolutionKind::Execution,
     ) {
       match resolved {
-        deno_config::workspace::MappedResolution::Normal {
-          specifier, ..
-        }
-        | deno_config::workspace::MappedResolution::ImportMap {
+        deno_resolver::workspace::MappedResolution::Normal {
           specifier,
           ..
         }
-        | deno_config::workspace::MappedResolution::WorkspaceJsrPackage {
+        | deno_resolver::workspace::MappedResolution::WorkspaceJsrPackage {
           specifier,
           ..
         } => Some(specifier),
-        deno_config::workspace::MappedResolution::WorkspaceNpmPackage {
+        deno_resolver::workspace::MappedResolution::WorkspaceNpmPackage {
           target_pkg_json,
           sub_path,
           ..
@@ -88,7 +85,7 @@ pub async fn info(
             )?
             .into_url()?,
         ),
-        deno_config::workspace::MappedResolution::PackageJson {
+        deno_resolver::workspace::MappedResolution::PackageJson {
           alias,
           sub_path,
           dep_result,
diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs
index 74c46d4c189112..04734151a67839 100644
--- a/cli/tools/lint/mod.rs
+++ b/cli/tools/lint/mod.rs
@@ -499,7 +499,7 @@ fn collect_lint_files(
 
 #[allow(clippy::print_stdout)]
 pub fn print_rules_list(json: bool, maybe_rules_tags: Option<Vec<String>>) {
-  let rule_provider = LintRuleProvider::new(None, None);
+  let rule_provider = LintRuleProvider::new(None);
   let mut all_rules = rule_provider.all_rules();
   let configured_rules = rule_provider.resolve_lint_rules(
     LintRulesConfig {
@@ -686,7 +686,7 @@ mod tests {
   }
 
   fn get_all_rules() -> Vec<String> {
-    let rule_provider = LintRuleProvider::new(None, None);
+    let rule_provider = LintRuleProvider::new(None);
     let configured_rules =
       rule_provider.resolve_lint_rules(Default::default(), None);
     let mut all_rules = configured_rules
diff --git a/cli/tools/lint/rules/mod.rs b/cli/tools/lint/rules/mod.rs
index 9f2cee24fa9b21..99ce3331d3ec78 100644
--- a/cli/tools/lint/rules/mod.rs
+++ b/cli/tools/lint/rules/mod.rs
@@ -7,15 +7,14 @@ use std::sync::Arc;
 use deno_ast::ModuleSpecifier;
 use deno_config::deno_json::ConfigFile;
 use deno_config::deno_json::LintRulesConfig;
-use deno_config::workspace::WorkspaceResolver;
 use deno_core::anyhow::bail;
 use deno_core::error::AnyError;
 use deno_graph::ModuleGraph;
 use deno_lint::diagnostic::LintDiagnostic;
 use deno_lint::rules::LintRule;
 use deno_lint::tags;
+use deno_resolver::workspace::WorkspaceResolver;
 
-use crate::resolver::CliSloppyImportsResolver;
 use crate::sys::CliSys;
 
 mod no_sloppy_imports;
@@ -141,19 +140,14 @@ impl ConfiguredRules {
 }
 
 pub struct LintRuleProvider {
-  sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
   workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
 }
 
 impl LintRuleProvider {
   pub fn new(
-    sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
     workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
   ) -> Self {
-    Self {
-      sloppy_imports_resolver,
-      workspace_resolver,
-    }
+    Self { workspace_resolver }
   }
 
   pub fn resolve_lint_rules_err_empty(
@@ -172,7 +166,6 @@ impl LintRuleProvider {
     let deno_lint_rules = deno_lint::rules::get_all_rules();
     let cli_lint_rules = vec![CliLintRule(CliLintRuleKind::Extended(
       Box::new(no_sloppy_imports::NoSloppyImportsRule::new(
-        self.sloppy_imports_resolver.clone(),
         self.workspace_resolver.clone(),
       )),
     ))];
@@ -274,7 +267,7 @@ mod test {
       include: None,
       tags: None,
     };
-    let rules_provider = LintRuleProvider::new(None, None);
+    let rules_provider = LintRuleProvider::new(None);
     let rules = rules_provider.resolve_lint_rules(rules_config, None);
     let mut rule_names = rules
       .rules
diff --git a/cli/tools/lint/rules/no_sloppy_imports.rs b/cli/tools/lint/rules/no_sloppy_imports.rs
index 34eeef521d95ae..48b0e5015f8807 100644
--- a/cli/tools/lint/rules/no_sloppy_imports.rs
+++ b/cli/tools/lint/rules/no_sloppy_imports.rs
@@ -6,7 +6,6 @@ use std::collections::HashMap;
 use std::sync::Arc;
 
 use deno_ast::SourceRange;
-use deno_config::workspace::WorkspaceResolver;
 use deno_error::JsErrorBox;
 use deno_graph::source::ResolutionKind;
 use deno_graph::source::ResolveError;
@@ -17,31 +16,25 @@ use deno_lint::diagnostic::LintFix;
 use deno_lint::diagnostic::LintFixChange;
 use deno_lint::rules::LintRule;
 use deno_lint::tags;
-use deno_resolver::sloppy_imports::SloppyImportsResolution;
-use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
+use deno_resolver::workspace::SloppyImportsResolutionReason;
+use deno_resolver::workspace::WorkspaceResolver;
 use text_lines::LineAndColumnIndex;
 
 use super::ExtendedLintRule;
 use crate::graph_util::CliJsrUrlProvider;
-use crate::resolver::CliSloppyImportsResolver;
 use crate::sys::CliSys;
 
 #[derive(Debug)]
 pub struct NoSloppyImportsRule {
-  sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
   // None for making printing out the lint rules easy
   workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
 }
 
 impl NoSloppyImportsRule {
   pub fn new(
-    sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
     workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
   ) -> Self {
-    NoSloppyImportsRule {
-      sloppy_imports_resolver,
-      workspace_resolver,
-    }
+    NoSloppyImportsRule { workspace_resolver }
   }
 }
 
@@ -54,7 +47,11 @@ impl ExtendedLintRule for NoSloppyImportsRule {
     // do sloppy import resolution because sloppy import
     // resolution requires knowing about the surrounding files
     // in addition to the current one
-    self.sloppy_imports_resolver.is_none() || self.workspace_resolver.is_none()
+    let Some(workspace_resolver) = &self.workspace_resolver else {
+      return true;
+    };
+    !workspace_resolver.sloppy_imports_enabled()
+      && !workspace_resolver.has_compiler_options_root_dirs()
   }
 
   fn help_docs_url(&self) -> Cow<'static, str> {
@@ -75,16 +72,12 @@ impl LintRule for NoSloppyImportsRule {
     let Some(workspace_resolver) = &self.workspace_resolver else {
       return;
     };
-    let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver else {
-      return;
-    };
     if context.specifier().scheme() != "file" {
       return;
     }
 
     let resolver = SloppyImportCaptureResolver {
       workspace_resolver,
-      sloppy_imports_resolver,
       captures: Default::default(),
     };
 
@@ -102,7 +95,9 @@ impl LintRule for NoSloppyImportsRule {
       maybe_npm_resolver: None,
     });
 
-    for (referrer, sloppy_import) in resolver.captures.borrow_mut().drain() {
+    for (referrer, (specifier, sloppy_reason)) in
+      resolver.captures.borrow_mut().drain()
+    {
       let start_range =
         context.text_info().loc_to_source_pos(LineAndColumnIndex {
           line_index: referrer.range.start.line,
@@ -126,10 +121,12 @@ impl LintRule for NoSloppyImportsRule {
           custom_docs_url: Some(DOCS_URL.to_string()),
           fixes: context
             .specifier()
-            .make_relative(sloppy_import.as_specifier())
+            .make_relative(&specifier)
             .map(|relative| {
               vec![LintFix {
-                description: Cow::Owned(sloppy_import.as_quick_fix_message()),
+                description: Cow::Owned(
+                  sloppy_reason.quick_fix_message_for_specifier(&specifier),
+                ),
                 changes: vec![LintFixChange {
                   new_text: Cow::Owned({
                     let relative = if relative.starts_with("../") {
@@ -176,8 +173,9 @@ impl LintRule for NoSloppyImportsRule {
 #[derive(Debug)]
 struct SloppyImportCaptureResolver<'a> {
   workspace_resolver: &'a WorkspaceResolver<CliSys>,
-  sloppy_imports_resolver: &'a CliSloppyImportsResolver,
-  captures: RefCell<HashMap<Range, SloppyImportsResolution>>,
+  captures: RefCell<
+    HashMap<Range, (deno_ast::ModuleSpecifier, SloppyImportsResolutionReason)>,
+  >,
 }
 
 impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
@@ -194,45 +192,37 @@ impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
         &referrer_range.specifier,
         match resolution_kind {
           ResolutionKind::Execution => {
-            deno_config::workspace::ResolutionKind::Execution
+            deno_resolver::workspace::ResolutionKind::Execution
           }
           ResolutionKind::Types => {
-            deno_config::workspace::ResolutionKind::Types
+            deno_resolver::workspace::ResolutionKind::Types
           }
         },
       )
       .map_err(|err| ResolveError::Other(JsErrorBox::from_err(err)))?;
 
     match resolution {
-      deno_config::workspace::MappedResolution::Normal {
-        specifier, ..
-      }
-      | deno_config::workspace::MappedResolution::ImportMap {
-        specifier, ..
-      } => match self.sloppy_imports_resolver.resolve(
-        &specifier,
-        match resolution_kind {
-          ResolutionKind::Execution => SloppyImportsResolutionKind::Execution,
-          ResolutionKind::Types => SloppyImportsResolutionKind::Types,
-        },
-      ) {
-        Some(res) => {
+      deno_resolver::workspace::MappedResolution::Normal {
+        specifier,
+        sloppy_reason,
+        ..
+      } => {
+        if let Some(sloppy_reason) = sloppy_reason {
           self
             .captures
             .borrow_mut()
             .entry(referrer_range.clone())
-            .or_insert_with(|| res.clone());
-          Ok(res.into_specifier())
+            .or_insert_with(|| (specifier.clone(), sloppy_reason));
         }
-        None => Ok(specifier),
-      },
-      deno_config::workspace::MappedResolution::WorkspaceJsrPackage {
+        Ok(specifier)
+      }
+      deno_resolver::workspace::MappedResolution::WorkspaceJsrPackage {
         ..
       }
-      | deno_config::workspace::MappedResolution::WorkspaceNpmPackage {
+      | deno_resolver::workspace::MappedResolution::WorkspaceNpmPackage {
         ..
       }
-      | deno_config::workspace::MappedResolution::PackageJson { .. } => {
+      | deno_resolver::workspace::MappedResolution::PackageJson { .. } => {
         // this error is ignored
         Err(ResolveError::Other(JsErrorBox::generic("")))
       }
diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs
index c2ed94e8473aee..1834774d0c4dad 100644
--- a/cli/tools/registry/mod.rs
+++ b/cli/tools/registry/mod.rs
@@ -120,7 +120,6 @@ pub async fn publish(
   }
 
   let specifier_unfurler = Arc::new(SpecifierUnfurler::new(
-    cli_factory.sloppy_imports_resolver()?.cloned(),
     cli_factory.workspace_resolver().await?.clone(),
     cli_options.unstable_bare_node_builtins(),
   ));
diff --git a/cli/tools/registry/unfurl.rs b/cli/tools/registry/unfurl.rs
index 469a19fdf4124f..526b4cbdcb71de 100644
--- a/cli/tools/registry/unfurl.rs
+++ b/cli/tools/registry/unfurl.rs
@@ -15,9 +15,6 @@ use deno_ast::ParsedSource;
 use deno_ast::SourceRange;
 use deno_ast::SourceTextInfo;
 use deno_ast::SourceTextProvider;
-use deno_config::workspace::MappedResolution;
-use deno_config::workspace::PackageJsonDepResolution;
-use deno_config::workspace::WorkspaceResolver;
 use deno_core::anyhow;
 use deno_core::ModuleSpecifier;
 use deno_graph::DependencyDescriptor;
@@ -27,12 +24,13 @@ use deno_graph::StaticDependencyKind;
 use deno_graph::TypeScriptReference;
 use deno_package_json::PackageJsonDepValue;
 use deno_package_json::PackageJsonDepWorkspaceReq;
-use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
+use deno_resolver::workspace::MappedResolution;
+use deno_resolver::workspace::PackageJsonDepResolution;
+use deno_resolver::workspace::WorkspaceResolver;
 use deno_runtime::deno_node::is_builtin_node_module;
 use deno_semver::Version;
 use deno_semver::VersionReq;
 
-use crate::resolver::CliSloppyImportsResolver;
 use crate::sys::CliSys;
 
 #[derive(Debug, Clone)]
@@ -190,14 +188,12 @@ enum UnfurlSpecifierError {
 }
 
 pub struct SpecifierUnfurler {
-  sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
   workspace_resolver: Arc<WorkspaceResolver<CliSys>>,
   bare_node_builtins: bool,
 }
 
 impl SpecifierUnfurler {
   pub fn new(
-    sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
     workspace_resolver: Arc<WorkspaceResolver<CliSys>>,
     bare_node_builtins: bool,
   ) -> Self {
@@ -206,7 +202,6 @@ impl SpecifierUnfurler {
       PackageJsonDepResolution::Enabled
     );
     Self {
-      sloppy_imports_resolver,
       workspace_resolver,
       bare_node_builtins,
     }
@@ -216,7 +211,7 @@ impl SpecifierUnfurler {
     &self,
     referrer: &ModuleSpecifier,
     specifier: &str,
-    resolution_kind: SloppyImportsResolutionKind,
+    resolution_kind: deno_resolver::workspace::ResolutionKind,
     text_info: &SourceTextInfo,
     range: &deno_graph::PositionRange,
     diagnostic_reporter: &mut dyn FnMut(SpecifierUnfurlerDiagnostic),
@@ -251,16 +246,15 @@ impl SpecifierUnfurler {
     &self,
     referrer: &ModuleSpecifier,
     specifier: &str,
-    resolution_kind: SloppyImportsResolutionKind,
+    resolution_kind: deno_resolver::workspace::ResolutionKind,
   ) -> Result<Option<String>, UnfurlSpecifierError> {
-    let resolved = if let Ok(resolved) = self.workspace_resolver.resolve(
-      specifier,
-      referrer,
-      resolution_kind.into(),
-    ) {
+    let resolved = if let Ok(resolved) =
+      self
+        .workspace_resolver
+        .resolve(specifier, referrer, resolution_kind)
+    {
       match resolved {
-        MappedResolution::Normal { specifier, .. }
-        | MappedResolution::ImportMap { specifier, .. } => Some(specifier),
+        MappedResolution::Normal { specifier, .. } => Some(specifier),
         MappedResolution::WorkspaceJsrPackage { pkg_req_ref, .. } => {
           Some(ModuleSpecifier::parse(&pkg_req_ref.to_string()).unwrap())
         }
@@ -398,15 +392,6 @@ impl SpecifierUnfurler {
     // } else {
     //   resolved
     // };
-    let resolved =
-      if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
-        sloppy_imports_resolver
-          .resolve(&resolved, resolution_kind)
-          .map(|res| res.into_specifier())
-          .unwrap_or(resolved)
-      } else {
-        resolved
-      };
     let relative_resolved = relative_url(&resolved, referrer);
     if relative_resolved == specifier {
       Ok(None) // nothing to unfurl
@@ -464,7 +449,7 @@ impl SpecifierUnfurler {
         let maybe_unfurled = self.unfurl_specifier_reporting_diagnostic(
           module_url,
           specifier,
-          SloppyImportsResolutionKind::Execution, // dynamic imports are always execution
+          deno_resolver::workspace::ResolutionKind::Execution, // dynamic imports are always execution
           text_info,
           &dep.argument_range,
           diagnostic_reporter,
@@ -492,7 +477,7 @@ impl SpecifierUnfurler {
           let unfurled = self.unfurl_specifier_reporting_diagnostic(
             module_url,
             specifier,
-            SloppyImportsResolutionKind::Execution, // dynamic imports are always execution
+            deno_resolver::workspace::ResolutionKind::Execution, // dynamic imports are always execution
             text_info,
             &dep.argument_range,
             diagnostic_reporter,
@@ -538,7 +523,7 @@ impl SpecifierUnfurler {
     let analyze_specifier =
       |specifier: &str,
        range: &deno_graph::PositionRange,
-       resolution_kind: SloppyImportsResolutionKind,
+       resolution_kind: deno_resolver::workspace::ResolutionKind,
        text_changes: &mut Vec<deno_ast::TextChange>,
        diagnostic_reporter: &mut dyn FnMut(SpecifierUnfurlerDiagnostic)| {
         if let Some(unfurled) = self.unfurl_specifier_reporting_diagnostic(
@@ -559,18 +544,18 @@ impl SpecifierUnfurler {
       match dep {
         DependencyDescriptor::Static(dep) => {
           let resolution_kind = if parsed_source.media_type().is_declaration() {
-            SloppyImportsResolutionKind::Types
+            deno_resolver::workspace::ResolutionKind::Types
           } else {
             match dep.kind {
               StaticDependencyKind::Export
               | StaticDependencyKind::Import
               | StaticDependencyKind::ExportEquals
               | StaticDependencyKind::ImportEquals => {
-                SloppyImportsResolutionKind::Execution
+                deno_resolver::workspace::ResolutionKind::Execution
               }
               StaticDependencyKind::ExportType
               | StaticDependencyKind::ImportType => {
-                SloppyImportsResolutionKind::Types
+                deno_resolver::workspace::ResolutionKind::Types
               }
             }
           };
@@ -616,7 +601,7 @@ impl SpecifierUnfurler {
       analyze_specifier(
         &specifier_with_range.text,
         &specifier_with_range.range,
-        SloppyImportsResolutionKind::Types,
+        deno_resolver::workspace::ResolutionKind::Types,
         &mut text_changes,
         diagnostic_reporter,
       );
@@ -625,7 +610,7 @@ impl SpecifierUnfurler {
       analyze_specifier(
         &jsdoc.specifier.text,
         &jsdoc.specifier.range,
-        SloppyImportsResolutionKind::Types,
+        deno_resolver::workspace::ResolutionKind::Types,
         &mut text_changes,
         diagnostic_reporter,
       );
@@ -634,7 +619,7 @@ impl SpecifierUnfurler {
       analyze_specifier(
         &specifier_with_range.text,
         &specifier_with_range.range,
-        SloppyImportsResolutionKind::Execution,
+        deno_resolver::workspace::ResolutionKind::Execution,
         &mut text_changes,
         diagnostic_reporter,
       );
@@ -643,7 +628,7 @@ impl SpecifierUnfurler {
       analyze_specifier(
         &specifier_with_range.text,
         &specifier_with_range.range,
-        SloppyImportsResolutionKind::Types,
+        deno_resolver::workspace::ResolutionKind::Types,
         &mut text_changes,
         diagnostic_reporter,
       );
@@ -700,7 +685,7 @@ mod tests {
   use deno_config::workspace::ResolverWorkspaceJsrPackage;
   use deno_core::serde_json::json;
   use deno_core::url::Url;
-  use deno_resolver::sloppy_imports::SloppyImportsCachedFs;
+  use deno_resolver::workspace::SloppyImportsOptions;
   use deno_runtime::deno_node::PackageJson;
   use deno_semver::Version;
   use import_map::ImportMapWithDiagnostics;
@@ -760,18 +745,14 @@ mod tests {
         exports: IndexMap::from([(".".to_string(), "mod.ts".to_string())]),
       }],
       vec![Arc::new(package_json)],
-      deno_config::workspace::PackageJsonDepResolution::Enabled,
+      deno_resolver::workspace::PackageJsonDepResolution::Enabled,
+      SloppyImportsOptions::Enabled,
+      Default::default(),
       Default::default(),
       Default::default(),
       CliSys::default(),
     );
-    let unfurler = SpecifierUnfurler::new(
-      Some(Arc::new(CliSloppyImportsResolver::new(
-        SloppyImportsCachedFs::new(CliSys::default()),
-      ))),
-      Arc::new(workspace_resolver),
-      true,
-    );
+    let unfurler = SpecifierUnfurler::new(Arc::new(workspace_resolver), true);
 
     // Unfurling TS file should apply changes.
     {
@@ -926,18 +907,14 @@ export type * from "./c.d.ts";
         Arc::new(pkg_json_subtract),
         Arc::new(pkg_json_publishing),
       ],
-      deno_config::workspace::PackageJsonDepResolution::Enabled,
+      deno_resolver::workspace::PackageJsonDepResolution::Enabled,
+      Default::default(),
+      Default::default(),
       Default::default(),
       Default::default(),
       sys.clone(),
     );
-    let unfurler = SpecifierUnfurler::new(
-      Some(Arc::new(CliSloppyImportsResolver::new(
-        SloppyImportsCachedFs::new(sys),
-      ))),
-      Arc::new(workspace_resolver),
-      true,
-    );
+    let unfurler = SpecifierUnfurler::new(Arc::new(workspace_resolver), true);
 
     {
       let source_code = r#"import add from "add";
diff --git a/resolvers/deno/Cargo.toml b/resolvers/deno/Cargo.toml
index b333ba19c53970..92f509a4cff6a7 100644
--- a/resolvers/deno/Cargo.toml
+++ b/resolvers/deno/Cargo.toml
@@ -33,13 +33,18 @@ deno_path_util.workspace = true
 deno_semver.workspace = true
 deno_terminal.workspace = true
 futures.workspace = true
+import_map.workspace = true
+indexmap.workspace = true
 log.workspace = true
 node_resolver.workspace = true
 once_cell.workspace = true
 parking_lot.workspace = true
+serde.workspace = true
+serde_json.workspace = true
 sys_traits.workspace = true
 thiserror.workspace = true
 url.workspace = true
 
 [dev-dependencies]
+sys_traits = { workspace = true, features = ["memory", "real", "serde_json"] }
 test_util.workspace = true
diff --git a/resolvers/deno/factory.rs b/resolvers/deno/factory.rs
index 57f78a546c021a..4e1a59788c6129 100644
--- a/resolvers/deno/factory.rs
+++ b/resolvers/deno/factory.rs
@@ -12,7 +12,6 @@ use deno_cache_dir::HttpCacheRc;
 use deno_cache_dir::LocalHttpCache;
 use deno_config::deno_json::NodeModulesDirMode;
 use deno_config::workspace::FolderConfigs;
-use deno_config::workspace::PackageJsonDepResolution;
 use deno_config::workspace::VendorEnablement;
 use deno_config::workspace::WorkspaceDirectory;
 use deno_config::workspace::WorkspaceDirectoryEmptyOptions;
@@ -61,12 +60,13 @@ use crate::npm::NpmResolverCreateOptions;
 use crate::npmrc::discover_npmrc_from_workspace;
 use crate::npmrc::NpmRcDiscoverError;
 use crate::npmrc::ResolvedNpmRcRc;
-use crate::sloppy_imports::SloppyImportsCachedFs;
-use crate::sloppy_imports::SloppyImportsResolver;
-use crate::sloppy_imports::SloppyImportsResolverRc;
 use crate::sync::new_rc;
 use crate::sync::MaybeSend;
 use crate::sync::MaybeSync;
+use crate::workspace::FsCacheOptions;
+use crate::workspace::PackageJsonDepResolution;
+use crate::workspace::SloppyImportsOptions;
+use crate::workspace::WorkspaceResolver;
 use crate::DefaultDenoResolverRc;
 use crate::DenoResolver;
 use crate::DenoResolverOptions;
@@ -133,7 +133,7 @@ pub trait SpecifiedImportMapProvider:
 {
   async fn get(
     &self,
-  ) -> Result<Option<deno_config::workspace::SpecifiedImportMap>, anyhow::Error>;
+  ) -> Result<Option<crate::workspace::SpecifiedImportMap>, anyhow::Error>;
 }
 
 #[derive(Debug, Clone)]
@@ -560,7 +560,6 @@ impl<TSys: WorkspaceFactorySys> WorkspaceFactory<TSys> {
 #[derive(Debug, Default)]
 pub struct ResolverFactoryOptions {
   pub conditions_from_resolution_mode: ConditionsFromResolutionMode,
-  pub no_sloppy_imports_cache: bool,
   pub npm_system_info: NpmSystemInfo,
   pub node_resolution_cache: Option<node_resolver::NodeResolutionCacheRc>,
   pub package_json_cache: Option<node_resolver::PackageJsonCacheRc>,
@@ -593,8 +592,6 @@ pub struct ResolverFactory<TSys: WorkspaceFactorySys> {
   npm_resolver: Deferred<NpmResolver<TSys>>,
   npm_resolution: NpmResolutionCellRc,
   pkg_json_resolver: Deferred<PackageJsonResolverRc<TSys>>,
-  sloppy_imports_resolver:
-    Deferred<Option<SloppyImportsResolverRc<SloppyImportsCachedFs<TSys>>>>,
   workspace_factory: WorkspaceFactoryRc<TSys>,
   workspace_resolver: async_once_cell::OnceCell<WorkspaceResolverRc<TSys>>,
 }
@@ -616,7 +613,6 @@ impl<TSys: WorkspaceFactorySys> ResolverFactory<TSys> {
       npm_resolution: Default::default(),
       npm_resolver: Default::default(),
       pkg_json_resolver: Default::default(),
-      sloppy_imports_resolver: Default::default(),
       workspace_factory,
       workspace_resolver: Default::default(),
       options,
@@ -646,7 +642,6 @@ impl<TSys: WorkspaceFactorySys> ResolverFactory<TSys> {
               .workspace_directory()?
               .workspace
               .vendor_dir_path(),
-            sloppy_imports_resolver: self.sloppy_imports_resolver()?.cloned(),
             workspace_resolver: self.workspace_resolver().await?.clone(),
           })))
         }
@@ -770,38 +765,6 @@ impl<TSys: WorkspaceFactorySys> ResolverFactory<TSys> {
     })
   }
 
-  pub fn sloppy_imports_resolver(
-    &self,
-  ) -> Result<
-    Option<&SloppyImportsResolverRc<SloppyImportsCachedFs<TSys>>>,
-    anyhow::Error,
-  > {
-    self
-      .sloppy_imports_resolver
-      .get_or_try_init(|| {
-        let enabled = self.options.unstable_sloppy_imports
-          || self
-            .workspace_factory
-            .workspace_directory()?
-            .workspace
-            .has_unstable("sloppy-imports");
-        if enabled {
-          Ok(Some(new_rc(SloppyImportsResolver::new(
-            if self.options.no_sloppy_imports_cache {
-              SloppyImportsCachedFs::new_without_stat_cache(
-                self.workspace_factory.sys.clone(),
-              )
-            } else {
-              SloppyImportsCachedFs::new(self.workspace_factory.sys.clone())
-            },
-          ))))
-        } else {
-          Ok(None)
-        }
-      })
-      .map(|v| v.as_ref())
-  }
-
   pub async fn workspace_resolver(
     &self,
   ) -> Result<&WorkspaceResolverRc<TSys>, anyhow::Error> {
@@ -815,7 +778,7 @@ impl<TSys: WorkspaceFactorySys> ResolverFactory<TSys> {
             Some(import_map) => import_map.get().await?,
             None => None,
           };
-          let options = deno_config::workspace::CreateResolverOptions {
+          let options = crate::workspace::CreateResolverOptions {
             pkg_json_dep_resolution: match self
               .options
               .package_json_dep_resolution
@@ -834,9 +797,24 @@ impl<TSys: WorkspaceFactorySys> ResolverFactory<TSys> {
               }
             },
             specified_import_map,
+            sloppy_imports_options: if self.options.unstable_sloppy_imports
+              || self
+                .workspace_factory
+                .workspace_directory()?
+                .workspace
+                .has_unstable("sloppy-imports")
+            {
+              SloppyImportsOptions::Enabled
+            } else {
+              SloppyImportsOptions::Disabled
+            },
+            fs_cache_options: FsCacheOptions::Enabled,
           };
-          let resolver = workspace
-            .create_resolver(self.workspace_factory.sys.clone(), options)?;
+          let resolver = WorkspaceResolver::from_workspace(
+            workspace,
+            self.workspace_factory.sys.clone(),
+            options,
+          )?;
           if !resolver.diagnostics().is_empty() {
             // todo(dsherret): do not log this in this crate... that should be
             // a CLI responsibility
diff --git a/resolvers/deno/lib.rs b/resolvers/deno/lib.rs
index 26272af3bcb1ca..4ecc222ba6f8d7 100644
--- a/resolvers/deno/lib.rs
+++ b/resolvers/deno/lib.rs
@@ -7,11 +7,6 @@ use std::path::PathBuf;
 
 use boxed_error::Boxed;
 use deno_cache_dir::npm::NpmCacheDir;
-use deno_config::workspace::MappedResolution;
-use deno_config::workspace::MappedResolutionDiagnostic;
-use deno_config::workspace::MappedResolutionError;
-use deno_config::workspace::WorkspaceResolvePkgJsonFolderError;
-use deno_config::workspace::WorkspaceResolver;
 use deno_error::JsError;
 use deno_package_json::PackageJsonDepValue;
 use deno_package_json::PackageJsonDepValueParseError;
@@ -31,9 +26,6 @@ use npm::NpmReqResolverRc;
 use npm::ResolveIfForNpmPackageErrorKind;
 use npm::ResolvePkgFolderFromDenoReqError;
 use npm::ResolveReqWithSubPathErrorKind;
-use sloppy_imports::SloppyImportResolverFs;
-use sloppy_imports::SloppyImportsResolutionKind;
-use sloppy_imports::SloppyImportsResolverRc;
 use sys_traits::FsCanonicalize;
 use sys_traits::FsMetadata;
 use sys_traits::FsRead;
@@ -41,12 +33,18 @@ use sys_traits::FsReadDir;
 use thiserror::Error;
 use url::Url;
 
+use crate::workspace::MappedResolution;
+use crate::workspace::MappedResolutionDiagnostic;
+use crate::workspace::MappedResolutionError;
+use crate::workspace::WorkspaceResolvePkgJsonFolderError;
+use crate::workspace::WorkspaceResolver;
+
 pub mod cjs;
 pub mod factory;
 pub mod npm;
 pub mod npmrc;
-pub mod sloppy_imports;
 mod sync;
+pub mod workspace;
 
 #[allow(clippy::disallowed_types)]
 pub type WorkspaceResolverRc<TSys> =
@@ -128,7 +126,6 @@ pub struct DenoResolverOptions<
   TInNpmPackageChecker: InNpmPackageChecker,
   TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
   TNpmPackageFolderResolver: NpmPackageFolderResolver,
-  TSloppyImportResolverFs: SloppyImportResolverFs,
   TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
 > {
   pub in_npm_pkg_checker: TInNpmPackageChecker,
@@ -140,8 +137,6 @@ pub struct DenoResolverOptions<
       TSys,
     >,
   >,
-  pub sloppy_imports_resolver:
-    Option<SloppyImportsResolverRc<TSloppyImportResolverFs>>,
   pub workspace_resolver: WorkspaceResolverRc<TSys>,
   /// Whether "bring your own node_modules" is enabled where Deno does not
   /// setup the node_modules directories automatically, but instead uses
@@ -155,14 +150,12 @@ pub type DenoResolverRc<
   TInNpmPackageChecker,
   TIsBuiltInNodeModuleChecker,
   TNpmPackageFolderResolver,
-  TSloppyImportResolverFs,
   TSys,
 > = crate::sync::MaybeArc<
   DenoResolver<
     TInNpmPackageChecker,
     TIsBuiltInNodeModuleChecker,
     TNpmPackageFolderResolver,
-    TSloppyImportResolverFs,
     TSys,
   >,
 >;
@@ -173,7 +166,6 @@ pub type DefaultDenoResolverRc<TSys> = DenoResolverRc<
   npm::DenoInNpmPackageChecker,
   node_resolver::DenoIsBuiltInNodeModuleChecker,
   npm::NpmResolver<TSys>,
-  sloppy_imports::SloppyImportsCachedFs<TSys>,
   TSys,
 >;
 
@@ -184,7 +176,6 @@ pub struct DenoResolver<
   TInNpmPackageChecker: InNpmPackageChecker,
   TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
   TNpmPackageFolderResolver: NpmPackageFolderResolver,
-  TSloppyImportResolverFs: SloppyImportResolverFs,
   TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
 > {
   in_npm_pkg_checker: TInNpmPackageChecker,
@@ -196,8 +187,6 @@ pub struct DenoResolver<
       TSys,
     >,
   >,
-  sloppy_imports_resolver:
-    Option<SloppyImportsResolverRc<TSloppyImportResolverFs>>,
   workspace_resolver: WorkspaceResolverRc<TSys>,
   is_byonm: bool,
   maybe_vendor_specifier: Option<Url>,
@@ -207,14 +196,12 @@ impl<
     TInNpmPackageChecker: InNpmPackageChecker,
     TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
     TNpmPackageFolderResolver: NpmPackageFolderResolver,
-    TSloppyImportResolverFs: SloppyImportResolverFs,
     TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
   >
   DenoResolver<
     TInNpmPackageChecker,
     TIsBuiltInNodeModuleChecker,
     TNpmPackageFolderResolver,
-    TSloppyImportResolverFs,
     TSys,
   >
 {
@@ -223,14 +210,12 @@ impl<
       TInNpmPackageChecker,
       TIsBuiltInNodeModuleChecker,
       TNpmPackageFolderResolver,
-      TSloppyImportResolverFs,
       TSys,
     >,
   ) -> Self {
     Self {
       in_npm_pkg_checker: options.in_npm_pkg_checker,
       node_and_npm_resolver: options.node_and_req_resolver,
-      sloppy_imports_resolver: options.sloppy_imports_resolver,
       workspace_resolver: options.workspace_resolver,
       is_byonm: options.is_byonm,
       maybe_vendor_specifier: options
@@ -277,33 +262,10 @@ impl<
         MappedResolution::Normal {
           specifier,
           maybe_diagnostic: current_diagnostic,
-        }
-        | MappedResolution::ImportMap {
-          specifier,
-          maybe_diagnostic: current_diagnostic,
+          ..
         } => {
           maybe_diagnostic = current_diagnostic;
-          // do sloppy imports resolution if enabled
-          if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
-            Ok(
-              sloppy_imports_resolver
-                .resolve(
-                  &specifier,
-                  match resolution_kind {
-                    NodeResolutionKind::Execution => {
-                      SloppyImportsResolutionKind::Execution
-                    }
-                    NodeResolutionKind::Types => {
-                      SloppyImportsResolutionKind::Types
-                    }
-                  },
-                )
-                .map(|s| s.into_specifier())
-                .unwrap_or(specifier),
-            )
-          } else {
-            Ok(specifier)
-          }
+          Ok(specifier)
         }
         MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
           Ok(specifier)
diff --git a/resolvers/deno/sloppy_imports.rs b/resolvers/deno/sloppy_imports.rs
deleted file mode 100644
index 8a43be16aa3917..00000000000000
--- a/resolvers/deno/sloppy_imports.rs
+++ /dev/null
@@ -1,582 +0,0 @@
-// Copyright 2018-2025 the Deno authors. MIT license.
-
-use std::borrow::Cow;
-use std::path::Path;
-use std::path::PathBuf;
-
-use deno_media_type::MediaType;
-use deno_path_util::url_from_file_path;
-use deno_path_util::url_to_file_path;
-use sys_traits::FsMetadata;
-use sys_traits::FsMetadataValue;
-use url::Url;
-
-use crate::sync::MaybeDashMap;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum SloppyImportsFsEntry {
-  File,
-  Dir,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum SloppyImportsResolution {
-  /// Ex. `./file.js` to `./file.ts`
-  JsToTs(Url),
-  /// Ex. `./file` to `./file.ts`
-  NoExtension(Url),
-  /// Ex. `./dir` to `./dir/index.ts`
-  Directory(Url),
-}
-
-impl SloppyImportsResolution {
-  pub fn as_specifier(&self) -> &Url {
-    match self {
-      Self::JsToTs(specifier) => specifier,
-      Self::NoExtension(specifier) => specifier,
-      Self::Directory(specifier) => specifier,
-    }
-  }
-
-  pub fn into_specifier(self) -> Url {
-    match self {
-      Self::JsToTs(specifier) => specifier,
-      Self::NoExtension(specifier) => specifier,
-      Self::Directory(specifier) => specifier,
-    }
-  }
-
-  pub fn as_suggestion_message(&self) -> String {
-    format!("Maybe {}", self.as_base_message())
-  }
-
-  pub fn as_quick_fix_message(&self) -> String {
-    let message = self.as_base_message();
-    let mut chars = message.chars();
-    format!(
-      "{}{}.",
-      chars.next().unwrap().to_uppercase(),
-      chars.as_str()
-    )
-  }
-
-  fn as_base_message(&self) -> String {
-    match self {
-      SloppyImportsResolution::JsToTs(specifier) => {
-        let media_type = MediaType::from_specifier(specifier);
-        format!("change the extension to '{}'", media_type.as_ts_extension())
-      }
-      SloppyImportsResolution::NoExtension(specifier) => {
-        let media_type = MediaType::from_specifier(specifier);
-        format!("add a '{}' extension", media_type.as_ts_extension())
-      }
-      SloppyImportsResolution::Directory(specifier) => {
-        let file_name = specifier
-          .path()
-          .rsplit_once('/')
-          .map(|(_, file_name)| file_name)
-          .unwrap_or(specifier.path());
-        format!("specify path to '{}' file in directory instead", file_name)
-      }
-    }
-  }
-}
-
-/// The kind of resolution currently being done.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum SloppyImportsResolutionKind {
-  /// Resolving for code that will be executed.
-  Execution,
-  /// Resolving for code that will be used for type information.
-  Types,
-}
-
-impl SloppyImportsResolutionKind {
-  pub fn is_types(&self) -> bool {
-    *self == SloppyImportsResolutionKind::Types
-  }
-}
-
-impl From<SloppyImportsResolutionKind>
-  for deno_config::workspace::ResolutionKind
-{
-  fn from(value: SloppyImportsResolutionKind) -> Self {
-    match value {
-      SloppyImportsResolutionKind::Execution => Self::Execution,
-      SloppyImportsResolutionKind::Types => Self::Types,
-    }
-  }
-}
-
-pub trait SloppyImportResolverFs {
-  fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry>;
-
-  fn is_file(&self, path: &Path) -> bool {
-    self.stat_sync(path) == Some(SloppyImportsFsEntry::File)
-  }
-}
-
-#[allow(clippy::disallowed_types)]
-pub type SloppyImportsResolverRc<TSloppyImportResolverFs> =
-  crate::sync::MaybeArc<SloppyImportsResolver<TSloppyImportResolverFs>>;
-
-#[derive(Debug)]
-pub struct SloppyImportsResolver<Fs: SloppyImportResolverFs> {
-  fs: Fs,
-}
-
-impl<Fs: SloppyImportResolverFs> SloppyImportsResolver<Fs> {
-  pub fn new(fs: Fs) -> Self {
-    Self { fs }
-  }
-
-  pub fn resolve(
-    &self,
-    specifier: &Url,
-    resolution_kind: SloppyImportsResolutionKind,
-  ) -> Option<SloppyImportsResolution> {
-    fn path_without_ext(
-      path: &Path,
-      media_type: MediaType,
-    ) -> Option<Cow<str>> {
-      let old_path_str = path.to_string_lossy();
-      match media_type {
-        MediaType::Unknown => Some(old_path_str),
-        _ => old_path_str
-          .strip_suffix(media_type.as_ts_extension())
-          .map(|s| Cow::Owned(s.to_string())),
-      }
-    }
-
-    fn media_types_to_paths(
-      path_no_ext: &str,
-      original_media_type: MediaType,
-      probe_media_type_types: Vec<MediaType>,
-      reason: SloppyImportsResolutionReason,
-    ) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
-      probe_media_type_types
-        .into_iter()
-        .filter(|media_type| *media_type != original_media_type)
-        .map(|media_type| {
-          (
-            PathBuf::from(format!(
-              "{}{}",
-              path_no_ext,
-              media_type.as_ts_extension()
-            )),
-            reason,
-          )
-        })
-        .collect::<Vec<_>>()
-    }
-
-    if specifier.scheme() != "file" {
-      return None;
-    }
-
-    let path = url_to_file_path(specifier).ok()?;
-
-    #[derive(Clone, Copy)]
-    enum SloppyImportsResolutionReason {
-      JsToTs,
-      NoExtension,
-      Directory,
-    }
-
-    let probe_paths: Vec<(PathBuf, SloppyImportsResolutionReason)> =
-      match self.fs.stat_sync(&path) {
-        Some(SloppyImportsFsEntry::File) => {
-          if resolution_kind.is_types() {
-            let media_type = MediaType::from_specifier(specifier);
-            // attempt to resolve the .d.ts file before the .js file
-            let probe_media_type_types = match media_type {
-              MediaType::JavaScript => {
-                vec![(MediaType::Dts), MediaType::JavaScript]
-              }
-              MediaType::Mjs => {
-                vec![MediaType::Dmts, MediaType::Dts, MediaType::Mjs]
-              }
-              MediaType::Cjs => {
-                vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
-              }
-              _ => return None,
-            };
-            let path_no_ext = path_without_ext(&path, media_type)?;
-            media_types_to_paths(
-              &path_no_ext,
-              media_type,
-              probe_media_type_types,
-              SloppyImportsResolutionReason::JsToTs,
-            )
-          } else {
-            return None;
-          }
-        }
-        entry @ None | entry @ Some(SloppyImportsFsEntry::Dir) => {
-          let media_type = MediaType::from_specifier(specifier);
-          let probe_media_type_types = match media_type {
-            MediaType::JavaScript => (
-              if resolution_kind.is_types() {
-                vec![MediaType::TypeScript, MediaType::Tsx, MediaType::Dts]
-              } else {
-                vec![MediaType::TypeScript, MediaType::Tsx]
-              },
-              SloppyImportsResolutionReason::JsToTs,
-            ),
-            MediaType::Jsx => {
-              (vec![MediaType::Tsx], SloppyImportsResolutionReason::JsToTs)
-            }
-            MediaType::Mjs => (
-              if resolution_kind.is_types() {
-                vec![MediaType::Mts, MediaType::Dmts, MediaType::Dts]
-              } else {
-                vec![MediaType::Mts]
-              },
-              SloppyImportsResolutionReason::JsToTs,
-            ),
-            MediaType::Cjs => (
-              if resolution_kind.is_types() {
-                vec![MediaType::Cts, MediaType::Dcts, MediaType::Dts]
-              } else {
-                vec![MediaType::Cts]
-              },
-              SloppyImportsResolutionReason::JsToTs,
-            ),
-            MediaType::TypeScript
-            | MediaType::Mts
-            | MediaType::Cts
-            | MediaType::Dts
-            | MediaType::Dmts
-            | MediaType::Dcts
-            | MediaType::Tsx
-            | MediaType::Json
-            | MediaType::Wasm
-            | MediaType::Css
-            | MediaType::SourceMap => {
-              return None;
-            }
-            MediaType::Unknown => (
-              if resolution_kind.is_types() {
-                vec![
-                  MediaType::TypeScript,
-                  MediaType::Tsx,
-                  MediaType::Mts,
-                  MediaType::Dts,
-                  MediaType::Dmts,
-                  MediaType::Dcts,
-                  MediaType::JavaScript,
-                  MediaType::Jsx,
-                  MediaType::Mjs,
-                ]
-              } else {
-                vec![
-                  MediaType::TypeScript,
-                  MediaType::JavaScript,
-                  MediaType::Tsx,
-                  MediaType::Jsx,
-                  MediaType::Mts,
-                  MediaType::Mjs,
-                ]
-              },
-              SloppyImportsResolutionReason::NoExtension,
-            ),
-          };
-          let mut probe_paths = match path_without_ext(&path, media_type) {
-            Some(path_no_ext) => media_types_to_paths(
-              &path_no_ext,
-              media_type,
-              probe_media_type_types.0,
-              probe_media_type_types.1,
-            ),
-            None => vec![],
-          };
-
-          if matches!(entry, Some(SloppyImportsFsEntry::Dir)) {
-            // try to resolve at the index file
-            if resolution_kind.is_types() {
-              probe_paths.push((
-                path.join("index.ts"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-
-              probe_paths.push((
-                path.join("index.mts"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.d.ts"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.d.mts"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.js"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.mjs"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.tsx"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.jsx"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-            } else {
-              probe_paths.push((
-                path.join("index.ts"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.mts"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.tsx"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.js"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.mjs"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-              probe_paths.push((
-                path.join("index.jsx"),
-                SloppyImportsResolutionReason::Directory,
-              ));
-            }
-          }
-          if probe_paths.is_empty() {
-            return None;
-          }
-          probe_paths
-        }
-      };
-
-    for (probe_path, reason) in probe_paths {
-      if self.fs.is_file(&probe_path) {
-        if let Ok(specifier) = url_from_file_path(&probe_path) {
-          match reason {
-            SloppyImportsResolutionReason::JsToTs => {
-              return Some(SloppyImportsResolution::JsToTs(specifier));
-            }
-            SloppyImportsResolutionReason::NoExtension => {
-              return Some(SloppyImportsResolution::NoExtension(specifier));
-            }
-            SloppyImportsResolutionReason::Directory => {
-              return Some(SloppyImportsResolution::Directory(specifier));
-            }
-          }
-        }
-      }
-    }
-
-    None
-  }
-}
-
-#[derive(Debug)]
-pub struct SloppyImportsCachedFs<TSys: FsMetadata> {
-  sys: TSys,
-  cache: Option<MaybeDashMap<PathBuf, Option<SloppyImportsFsEntry>>>,
-}
-
-impl<TSys: FsMetadata> SloppyImportsCachedFs<TSys> {
-  pub fn new(sys: TSys) -> Self {
-    Self {
-      sys,
-      cache: Some(Default::default()),
-    }
-  }
-
-  pub fn new_without_stat_cache(sys: TSys) -> Self {
-    Self { sys, cache: None }
-  }
-}
-
-impl<TSys: FsMetadata> SloppyImportResolverFs for SloppyImportsCachedFs<TSys> {
-  fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry> {
-    if let Some(cache) = &self.cache {
-      if let Some(entry) = cache.get(path) {
-        return *entry;
-      }
-    }
-
-    let entry = self.sys.fs_metadata(path).ok().and_then(|stat| {
-      if stat.file_type().is_file() {
-        Some(SloppyImportsFsEntry::File)
-      } else if stat.file_type().is_dir() {
-        Some(SloppyImportsFsEntry::Dir)
-      } else {
-        None
-      }
-    });
-
-    if let Some(cache) = &self.cache {
-      cache.insert(path.to_owned(), entry);
-    }
-    entry
-  }
-}
-
-#[cfg(test)]
-mod test {
-  use test_util::TestContext;
-
-  use super::*;
-
-  #[test]
-  fn test_unstable_sloppy_imports() {
-    fn resolve(specifier: &Url) -> Option<SloppyImportsResolution> {
-      resolve_with_resolution_kind(
-        specifier,
-        SloppyImportsResolutionKind::Execution,
-      )
-    }
-
-    fn resolve_types(specifier: &Url) -> Option<SloppyImportsResolution> {
-      resolve_with_resolution_kind(
-        specifier,
-        SloppyImportsResolutionKind::Types,
-      )
-    }
-
-    fn resolve_with_resolution_kind(
-      specifier: &Url,
-      resolution_kind: SloppyImportsResolutionKind,
-    ) -> Option<SloppyImportsResolution> {
-      struct RealSloppyImportsResolverFs;
-      impl SloppyImportResolverFs for RealSloppyImportsResolverFs {
-        fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry> {
-          #[allow(clippy::disallowed_methods)]
-          let stat = std::fs::metadata(path).ok()?;
-          if stat.is_dir() {
-            Some(SloppyImportsFsEntry::Dir)
-          } else if stat.is_file() {
-            Some(SloppyImportsFsEntry::File)
-          } else {
-            None
-          }
-        }
-      }
-
-      SloppyImportsResolver::new(RealSloppyImportsResolverFs)
-        .resolve(specifier, resolution_kind)
-    }
-
-    let context = TestContext::default();
-    let temp_dir = context.temp_dir().path();
-
-    // scenarios like resolving ./example.js to ./example.ts
-    for (ext_from, ext_to) in [("js", "ts"), ("js", "tsx"), ("mjs", "mts")] {
-      let ts_file = temp_dir.join(format!("file.{}", ext_to));
-      ts_file.write("");
-      assert_eq!(resolve(&ts_file.url_file()), None);
-      assert_eq!(
-        resolve(
-          &temp_dir
-            .url_dir()
-            .join(&format!("file.{}", ext_from))
-            .unwrap()
-        ),
-        Some(SloppyImportsResolution::JsToTs(ts_file.url_file())),
-      );
-      ts_file.remove_file();
-    }
-
-    // no extension scenarios
-    for ext in ["js", "ts", "js", "tsx", "jsx", "mjs", "mts"] {
-      let file = temp_dir.join(format!("file.{}", ext));
-      file.write("");
-      assert_eq!(
-        resolve(
-          &temp_dir
-            .url_dir()
-            .join("file") // no ext
-            .unwrap()
-        ),
-        Some(SloppyImportsResolution::NoExtension(file.url_file()))
-      );
-      file.remove_file();
-    }
-
-    // .ts and .js exists, .js specified (goes to specified)
-    {
-      let ts_file = temp_dir.join("file.ts");
-      ts_file.write("");
-      let js_file = temp_dir.join("file.js");
-      js_file.write("");
-      assert_eq!(resolve(&js_file.url_file()), None);
-    }
-
-    // only js exists, .js specified
-    {
-      let js_only_file = temp_dir.join("js_only.js");
-      js_only_file.write("");
-      assert_eq!(resolve(&js_only_file.url_file()), None);
-      assert_eq!(resolve_types(&js_only_file.url_file()), None);
-    }
-
-    // resolving a directory to an index file
-    {
-      let routes_dir = temp_dir.join("routes");
-      routes_dir.create_dir_all();
-      let index_file = routes_dir.join("index.ts");
-      index_file.write("");
-      assert_eq!(
-        resolve(&routes_dir.url_file()),
-        Some(SloppyImportsResolution::Directory(index_file.url_file())),
-      );
-    }
-
-    // both a directory and a file with specifier is present
-    {
-      let api_dir = temp_dir.join("api");
-      api_dir.create_dir_all();
-      let bar_file = api_dir.join("bar.ts");
-      bar_file.write("");
-      let api_file = temp_dir.join("api.ts");
-      api_file.write("");
-      assert_eq!(
-        resolve(&api_dir.url_file()),
-        Some(SloppyImportsResolution::NoExtension(api_file.url_file())),
-      );
-    }
-  }
-
-  #[test]
-  fn test_sloppy_import_resolution_suggestion_message() {
-    // directory
-    assert_eq!(
-      SloppyImportsResolution::Directory(
-        Url::parse("file:///dir/index.js").unwrap()
-      )
-      .as_suggestion_message(),
-      "Maybe specify path to 'index.js' file in directory instead"
-    );
-    // no ext
-    assert_eq!(
-      SloppyImportsResolution::NoExtension(
-        Url::parse("file:///dir/index.mjs").unwrap()
-      )
-      .as_suggestion_message(),
-      "Maybe add a '.mjs' extension"
-    );
-    // js to ts
-    assert_eq!(
-      SloppyImportsResolution::JsToTs(
-        Url::parse("file:///dir/index.mts").unwrap()
-      )
-      .as_suggestion_message(),
-      "Maybe change the extension to '.mts'"
-    );
-  }
-}
diff --git a/resolvers/deno/workspace.rs b/resolvers/deno/workspace.rs
new file mode 100644
index 00000000000000..b7169497222435
--- /dev/null
+++ b/resolvers/deno/workspace.rs
@@ -0,0 +1,2812 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+// use super::UrlRc;
+
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+use std::fmt;
+use std::path::Path;
+use std::path::PathBuf;
+
+use deno_config::deno_json::ConfigFile;
+use deno_config::deno_json::ConfigFileError;
+use deno_config::workspace::ResolverWorkspaceJsrPackage;
+use deno_config::workspace::Workspace;
+use deno_error::JsError;
+use deno_media_type::MediaType;
+use deno_package_json::PackageJsonDepValue;
+use deno_package_json::PackageJsonDepValueParseError;
+use deno_package_json::PackageJsonDepWorkspaceReq;
+use deno_package_json::PackageJsonDepsRc;
+use deno_package_json::PackageJsonRc;
+use deno_path_util::url_from_directory_path;
+use deno_path_util::url_from_file_path;
+use deno_path_util::url_to_file_path;
+use deno_semver::jsr::JsrPackageReqReference;
+use deno_semver::package::PackageReq;
+use deno_semver::RangeSetOrTag;
+use deno_semver::Version;
+use deno_semver::VersionReq;
+use import_map::specifier::SpecifierError;
+use import_map::ImportMap;
+use import_map::ImportMapDiagnostic;
+use import_map::ImportMapError;
+use import_map::ImportMapErrorKind;
+use import_map::ImportMapWithDiagnostics;
+use indexmap::IndexMap;
+use node_resolver::NodeResolutionKind;
+use serde::Deserialize;
+use serde::Serialize;
+use sys_traits::FsMetadata;
+use sys_traits::FsMetadataValue;
+use sys_traits::FsRead;
+use thiserror::Error;
+use url::Url;
+
+use crate::sync::new_rc;
+use crate::sync::MaybeDashMap;
+
+#[allow(clippy::disallowed_types)]
+type UrlRc = crate::sync::MaybeArc<Url>;
+
+#[derive(Debug)]
+struct PkgJsonResolverFolderConfig {
+  deps: PackageJsonDepsRc,
+  pkg_json: PackageJsonRc,
+}
+
+#[derive(Debug, Error, JsError)]
+pub enum WorkspaceResolverCreateError {
+  #[class(inherit)]
+  #[error("Failed loading import map specified in '{referrer}'")]
+  ImportMapFetch {
+    referrer: Url,
+    #[source]
+    #[inherit]
+    source: Box<ConfigFileError>,
+  },
+  #[class(inherit)]
+  #[error(transparent)]
+  ImportMap(
+    #[from]
+    #[inherit]
+    ImportMapError,
+  ),
+}
+
+/// Whether to resolve dependencies by reading the dependencies list
+/// from a package.json
+#[derive(
+  Debug, Default, Serialize, Deserialize, Copy, Clone, PartialEq, Eq,
+)]
+pub enum PackageJsonDepResolution {
+  /// Resolves based on the dep entries in the package.json.
+  #[default]
+  Enabled,
+  /// Doesn't use the package.json to resolve dependencies. Let's the caller
+  /// resolve based on the file system.
+  Disabled,
+}
+
+#[derive(
+  Debug, Default, Serialize, Deserialize, Copy, Clone, PartialEq, Eq,
+)]
+pub enum SloppyImportsOptions {
+  Enabled,
+  #[default]
+  Disabled,
+}
+
+/// Toggle FS metadata caching when probing files for sloppy imports and
+/// `compilerOptions.rootDirs` resolution.
+#[derive(
+  Debug, Default, Serialize, Deserialize, Copy, Clone, PartialEq, Eq,
+)]
+pub enum FsCacheOptions {
+  #[default]
+  Enabled,
+  Disabled,
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct CreateResolverOptions {
+  pub pkg_json_dep_resolution: PackageJsonDepResolution,
+  pub specified_import_map: Option<SpecifiedImportMap>,
+  pub sloppy_imports_options: SloppyImportsOptions,
+  pub fs_cache_options: FsCacheOptions,
+}
+
+#[derive(Debug, Clone)]
+pub struct SpecifiedImportMap {
+  pub base_url: Url,
+  pub value: serde_json::Value,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum MappedResolutionDiagnostic {
+  ConstraintNotMatchedLocalVersion {
+    /// If it was for a patch (true) or workspace (false) member.
+    is_patch: bool,
+    reference: JsrPackageReqReference,
+    local_version: Version,
+  },
+}
+
+impl std::fmt::Display for MappedResolutionDiagnostic {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    match self {
+      Self::ConstraintNotMatchedLocalVersion {
+        is_patch,
+        reference,
+        local_version,
+      } => {
+        write!(
+          f,
+          "{0} '{1}@{2}' was not used because it did not match '{1}@{3}'",
+          if *is_patch {
+            "Patch"
+          } else {
+            "Workspace member"
+          },
+          reference.req().name,
+          local_version,
+          reference.req().version_req
+        )
+      }
+    }
+  }
+}
+
+#[derive(Debug, Clone)]
+pub enum MappedResolution<'a> {
+  Normal {
+    specifier: Url,
+    used_import_map: bool,
+    sloppy_reason: Option<SloppyImportsResolutionReason>,
+    used_compiler_options_root_dirs: bool,
+    maybe_diagnostic: Option<Box<MappedResolutionDiagnostic>>,
+  },
+  WorkspaceJsrPackage {
+    specifier: Url,
+    pkg_req_ref: JsrPackageReqReference,
+  },
+  /// Resolved a bare specifier to a package.json that was a workspace member.
+  WorkspaceNpmPackage {
+    target_pkg_json: &'a PackageJsonRc,
+    pkg_name: &'a str,
+    sub_path: Option<String>,
+  },
+  PackageJson {
+    pkg_json: &'a PackageJsonRc,
+    alias: &'a str,
+    sub_path: Option<String>,
+    dep_result: &'a Result<PackageJsonDepValue, PackageJsonDepValueParseError>,
+  },
+}
+
+#[derive(Debug, Clone, Error, JsError)]
+#[class(type)]
+pub enum WorkspaceResolveError {
+  #[error("Failed joining '{}' to '{}'. {:#}", .sub_path, .base, .error)]
+  InvalidExportPath {
+    base: Url,
+    sub_path: String,
+    error: url::ParseError,
+  },
+  #[error("Unknown export '{}' for '{}'.\n  Package exports:\n{}", export_name, package_name, .exports.iter().map(|e| format!(" * {}", e)).collect::<Vec<_>>().join("\n"))]
+  UnknownExport {
+    package_name: String,
+    export_name: String,
+    exports: Vec<String>,
+  },
+}
+
+#[derive(Debug, Error, JsError)]
+pub enum MappedResolutionError {
+  #[class(inherit)]
+  #[error(transparent)]
+  Specifier(#[from] SpecifierError),
+  #[class(inherit)]
+  #[error(transparent)]
+  ImportMap(#[from] ImportMapError),
+  #[class(inherit)]
+  #[error(transparent)]
+  Workspace(#[from] WorkspaceResolveError),
+}
+
+impl MappedResolutionError {
+  pub fn is_unmapped_bare_specifier(&self) -> bool {
+    match self {
+      MappedResolutionError::Specifier(err) => match err {
+        SpecifierError::InvalidUrl(_) => false,
+        SpecifierError::ImportPrefixMissing { .. } => true,
+      },
+      MappedResolutionError::ImportMap(err) => {
+        matches!(**err, ImportMapErrorKind::UnmappedBareSpecifier(_, _))
+      }
+      MappedResolutionError::Workspace(_) => false,
+    }
+  }
+}
+
+#[derive(Error, Debug, JsError)]
+#[class(inherit)]
+#[error(transparent)]
+pub struct WorkspaceResolvePkgJsonFolderError(
+  Box<WorkspaceResolvePkgJsonFolderErrorKind>,
+);
+
+impl WorkspaceResolvePkgJsonFolderError {
+  pub fn as_kind(&self) -> &WorkspaceResolvePkgJsonFolderErrorKind {
+    &self.0
+  }
+
+  pub fn into_kind(self) -> WorkspaceResolvePkgJsonFolderErrorKind {
+    *self.0
+  }
+}
+
+impl<E> From<E> for WorkspaceResolvePkgJsonFolderError
+where
+  WorkspaceResolvePkgJsonFolderErrorKind: From<E>,
+{
+  fn from(err: E) -> Self {
+    WorkspaceResolvePkgJsonFolderError(Box::new(
+      WorkspaceResolvePkgJsonFolderErrorKind::from(err),
+    ))
+  }
+}
+
+#[derive(Debug, Error, JsError, Clone, PartialEq, Eq)]
+#[class(type)]
+pub enum WorkspaceResolvePkgJsonFolderErrorKind {
+  #[error("Could not find package.json with name '{0}' in workspace.")]
+  NotFound(String),
+  #[error("Found package.json in workspace, but version '{1}' didn't satisy constraint '{0}'.")]
+  VersionNotSatisfied(VersionReq, Version),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum CachedMetadataFsEntry {
+  File,
+  Dir,
+}
+
+#[derive(Debug)]
+struct CachedMetadataFs<TSys: FsMetadata> {
+  sys: TSys,
+  cache: Option<MaybeDashMap<PathBuf, Option<CachedMetadataFsEntry>>>,
+}
+
+impl<TSys: FsMetadata> CachedMetadataFs<TSys> {
+  fn new(sys: TSys, options: FsCacheOptions) -> Self {
+    Self {
+      sys,
+      cache: match options {
+        FsCacheOptions::Enabled => Some(Default::default()),
+        FsCacheOptions::Disabled => None,
+      },
+    }
+  }
+
+  fn stat_sync(&self, path: &Path) -> Option<CachedMetadataFsEntry> {
+    if let Some(cache) = &self.cache {
+      if let Some(entry) = cache.get(path) {
+        return *entry;
+      }
+    }
+    let entry = self.sys.fs_metadata(path).ok().and_then(|stat| {
+      if stat.file_type().is_file() {
+        Some(CachedMetadataFsEntry::File)
+      } else if stat.file_type().is_dir() {
+        Some(CachedMetadataFsEntry::Dir)
+      } else {
+        None
+      }
+    });
+    if let Some(cache) = &self.cache {
+      cache.insert(path.to_owned(), entry);
+    }
+    entry
+  }
+
+  fn is_file(&self, path: &Path) -> bool {
+    self.stat_sync(path) == Some(CachedMetadataFsEntry::File)
+  }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SloppyImportsResolutionReason {
+  /// Ex. `./file.js` to `./file.ts`
+  JsToTs,
+  /// Ex. `./file` to `./file.ts`
+  NoExtension,
+  /// Ex. `./dir` to `./dir/index.ts`
+  Directory,
+}
+
+impl SloppyImportsResolutionReason {
+  pub fn suggestion_message_for_specifier(&self, specifier: &Url) -> String {
+    format!("Maybe {}", self.base_message_for_specifier(specifier))
+  }
+
+  pub fn quick_fix_message_for_specifier(&self, specifier: &Url) -> String {
+    let message = self.base_message_for_specifier(specifier);
+    let mut chars = message.chars();
+    format!(
+      "{}{}.",
+      chars.next().unwrap().to_uppercase(),
+      chars.as_str()
+    )
+  }
+
+  fn base_message_for_specifier(&self, specifier: &Url) -> String {
+    match self {
+      Self::JsToTs => {
+        let media_type = MediaType::from_specifier(specifier);
+        format!("change the extension to '{}'", media_type.as_ts_extension())
+      }
+      Self::NoExtension => {
+        let media_type = MediaType::from_specifier(specifier);
+        format!("add a '{}' extension", media_type.as_ts_extension())
+      }
+      Self::Directory => {
+        let file_name = specifier
+          .path()
+          .rsplit_once('/')
+          .map(|(_, file_name)| file_name)
+          .unwrap_or(specifier.path());
+        format!("specify path to '{}' file in directory instead", file_name)
+      }
+    }
+  }
+}
+
+#[derive(Debug)]
+struct SloppyImportsResolver<TSys: FsMetadata> {
+  fs: CachedMetadataFs<TSys>,
+  enabled: bool,
+}
+
+impl<TSys: FsMetadata> SloppyImportsResolver<TSys> {
+  fn new(fs: CachedMetadataFs<TSys>, options: SloppyImportsOptions) -> Self {
+    Self {
+      fs,
+      enabled: match options {
+        SloppyImportsOptions::Enabled => true,
+        SloppyImportsOptions::Disabled => false,
+      },
+    }
+  }
+
+  fn resolve(
+    &self,
+    specifier: &Url,
+    resolution_kind: ResolutionKind,
+  ) -> Option<(Url, SloppyImportsResolutionReason)> {
+    if !self.enabled {
+      return None;
+    }
+
+    fn path_without_ext(
+      path: &Path,
+      media_type: MediaType,
+    ) -> Option<Cow<str>> {
+      let old_path_str = path.to_string_lossy();
+      match media_type {
+        MediaType::Unknown => Some(old_path_str),
+        _ => old_path_str
+          .strip_suffix(media_type.as_ts_extension())
+          .map(|s| Cow::Owned(s.to_string())),
+      }
+    }
+
+    fn media_types_to_paths(
+      path_no_ext: &str,
+      original_media_type: MediaType,
+      probe_media_type_types: Vec<MediaType>,
+      reason: SloppyImportsResolutionReason,
+    ) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
+      probe_media_type_types
+        .into_iter()
+        .filter(|media_type| *media_type != original_media_type)
+        .map(|media_type| {
+          (
+            PathBuf::from(format!(
+              "{}{}",
+              path_no_ext,
+              media_type.as_ts_extension()
+            )),
+            reason,
+          )
+        })
+        .collect::<Vec<_>>()
+    }
+
+    if specifier.scheme() != "file" {
+      return None;
+    }
+
+    let path = url_to_file_path(specifier).ok()?;
+
+    let probe_paths: Vec<(PathBuf, SloppyImportsResolutionReason)> =
+      match self.fs.stat_sync(&path) {
+        Some(CachedMetadataFsEntry::File) => {
+          if resolution_kind.is_types() {
+            let media_type = MediaType::from_specifier(specifier);
+            // attempt to resolve the .d.ts file before the .js file
+            let probe_media_type_types = match media_type {
+              MediaType::JavaScript => {
+                vec![(MediaType::Dts), MediaType::JavaScript]
+              }
+              MediaType::Mjs => {
+                vec![MediaType::Dmts, MediaType::Dts, MediaType::Mjs]
+              }
+              MediaType::Cjs => {
+                vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
+              }
+              _ => return None,
+            };
+            let path_no_ext = path_without_ext(&path, media_type)?;
+            media_types_to_paths(
+              &path_no_ext,
+              media_type,
+              probe_media_type_types,
+              SloppyImportsResolutionReason::JsToTs,
+            )
+          } else {
+            return None;
+          }
+        }
+        entry @ None | entry @ Some(CachedMetadataFsEntry::Dir) => {
+          let media_type = MediaType::from_specifier(specifier);
+          let probe_media_type_types = match media_type {
+            MediaType::JavaScript => (
+              if resolution_kind.is_types() {
+                vec![MediaType::TypeScript, MediaType::Tsx, MediaType::Dts]
+              } else {
+                vec![MediaType::TypeScript, MediaType::Tsx]
+              },
+              SloppyImportsResolutionReason::JsToTs,
+            ),
+            MediaType::Jsx => {
+              (vec![MediaType::Tsx], SloppyImportsResolutionReason::JsToTs)
+            }
+            MediaType::Mjs => (
+              if resolution_kind.is_types() {
+                vec![MediaType::Mts, MediaType::Dmts, MediaType::Dts]
+              } else {
+                vec![MediaType::Mts]
+              },
+              SloppyImportsResolutionReason::JsToTs,
+            ),
+            MediaType::Cjs => (
+              if resolution_kind.is_types() {
+                vec![MediaType::Cts, MediaType::Dcts, MediaType::Dts]
+              } else {
+                vec![MediaType::Cts]
+              },
+              SloppyImportsResolutionReason::JsToTs,
+            ),
+            MediaType::TypeScript
+            | MediaType::Mts
+            | MediaType::Cts
+            | MediaType::Dts
+            | MediaType::Dmts
+            | MediaType::Dcts
+            | MediaType::Tsx
+            | MediaType::Json
+            | MediaType::Wasm
+            | MediaType::Css
+            | MediaType::SourceMap => {
+              return None;
+            }
+            MediaType::Unknown => (
+              if resolution_kind.is_types() {
+                vec![
+                  MediaType::TypeScript,
+                  MediaType::Tsx,
+                  MediaType::Mts,
+                  MediaType::Dts,
+                  MediaType::Dmts,
+                  MediaType::Dcts,
+                  MediaType::JavaScript,
+                  MediaType::Jsx,
+                  MediaType::Mjs,
+                ]
+              } else {
+                vec![
+                  MediaType::TypeScript,
+                  MediaType::JavaScript,
+                  MediaType::Tsx,
+                  MediaType::Jsx,
+                  MediaType::Mts,
+                  MediaType::Mjs,
+                ]
+              },
+              SloppyImportsResolutionReason::NoExtension,
+            ),
+          };
+          let mut probe_paths = match path_without_ext(&path, media_type) {
+            Some(path_no_ext) => media_types_to_paths(
+              &path_no_ext,
+              media_type,
+              probe_media_type_types.0,
+              probe_media_type_types.1,
+            ),
+            None => vec![],
+          };
+
+          if matches!(entry, Some(CachedMetadataFsEntry::Dir)) {
+            // try to resolve at the index file
+            if resolution_kind.is_types() {
+              probe_paths.push((
+                path.join("index.ts"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+
+              probe_paths.push((
+                path.join("index.mts"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.d.ts"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.d.mts"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.js"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.mjs"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.tsx"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.jsx"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+            } else {
+              probe_paths.push((
+                path.join("index.ts"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.mts"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.tsx"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.js"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.mjs"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+              probe_paths.push((
+                path.join("index.jsx"),
+                SloppyImportsResolutionReason::Directory,
+              ));
+            }
+          }
+          if probe_paths.is_empty() {
+            return None;
+          }
+          probe_paths
+        }
+      };
+
+    for (probe_path, reason) in probe_paths {
+      if self.fs.is_file(&probe_path) {
+        if let Ok(specifier) = url_from_file_path(&probe_path) {
+          return Some((specifier, reason));
+        }
+      }
+    }
+
+    None
+  }
+}
+
+pub fn sloppy_imports_resolve<TSys: FsMetadata>(
+  specifier: &Url,
+  resolution_kind: ResolutionKind,
+  sys: TSys,
+) -> Option<(Url, SloppyImportsResolutionReason)> {
+  SloppyImportsResolver::new(
+    CachedMetadataFs::new(sys, FsCacheOptions::Enabled),
+    SloppyImportsOptions::Enabled,
+  )
+  .resolve(specifier, resolution_kind)
+}
+
+#[allow(clippy::disallowed_types)]
+type SloppyImportsResolverRc<T> =
+  crate::sync::MaybeArc<SloppyImportsResolver<T>>;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum CompilerOptionsRootDirsDiagnostic {
+  InvalidType(Url),
+  InvalidEntryType(Url, usize),
+  UnexpectedError(Url, String),
+  UnexpectedEntryError(Url, usize, String),
+}
+
+impl fmt::Display for CompilerOptionsRootDirsDiagnostic {
+  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    match self {
+      Self::InvalidType(s) => write!(f, "Invalid value for \"compilerOptions.rootDirs\" (\"{s}\"). Expected a string."),
+      Self::InvalidEntryType(s, i) => write!(f, "Invalid value for \"compilerOptions.rootDirs[{i}]\" (\"{s}\"). Expected a string."),
+      Self::UnexpectedError(s, message) => write!(f, "Unexpected error while parsing \"compilerOptions.rootDirs\" (\"{s}\"): {message}"),
+      Self::UnexpectedEntryError(s, i, message) => write!(f, "Unexpected error while parsing \"compilerOptions.rootDirs[{i}]\" (\"{s}\"): {message}"),
+    }
+  }
+}
+
+#[derive(Debug)]
+struct CompilerOptionsRootDirsResolver<TSys: FsMetadata> {
+  root_dirs_from_root: Vec<Url>,
+  root_dirs_by_member: BTreeMap<Url, Option<Vec<Url>>>,
+  diagnostics: Vec<CompilerOptionsRootDirsDiagnostic>,
+  sloppy_imports_resolver: SloppyImportsResolverRc<TSys>,
+}
+
+impl<TSys: FsMetadata> CompilerOptionsRootDirsResolver<TSys> {
+  fn from_workspace(
+    workspace: &Workspace,
+    sloppy_imports_resolver: SloppyImportsResolverRc<TSys>,
+  ) -> Self {
+    let mut diagnostics: Vec<CompilerOptionsRootDirsDiagnostic> = Vec::new();
+    fn get_root_dirs(
+      config_file: &ConfigFile,
+      dir_url: &Url,
+      diagnostics: &mut Vec<CompilerOptionsRootDirsDiagnostic>,
+    ) -> Option<Vec<Url>> {
+      let dir_path = url_to_file_path(dir_url)
+        .inspect_err(|err| {
+          diagnostics.push(CompilerOptionsRootDirsDiagnostic::UnexpectedError(
+            config_file.specifier.clone(),
+            err.to_string(),
+          ));
+        })
+        .ok()?;
+      let root_dirs = config_file
+        .json
+        .compiler_options
+        .as_ref()?
+        .as_object()?
+        .get("rootDirs")?
+        .as_array();
+      if root_dirs.is_none() {
+        diagnostics.push(CompilerOptionsRootDirsDiagnostic::InvalidType(
+          config_file.specifier.clone(),
+        ));
+      }
+      let root_dirs = root_dirs?
+        .iter()
+        .enumerate()
+        .filter_map(|(i, s)| {
+          let s = s.as_str();
+          if s.is_none() {
+            diagnostics.push(
+              CompilerOptionsRootDirsDiagnostic::InvalidEntryType(
+                config_file.specifier.clone(),
+                i,
+              ),
+            );
+          }
+          url_from_directory_path(&dir_path.join(s?))
+            .inspect_err(|err| {
+              diagnostics.push(
+                CompilerOptionsRootDirsDiagnostic::UnexpectedEntryError(
+                  config_file.specifier.clone(),
+                  i,
+                  err.to_string(),
+                ),
+              );
+            })
+            .ok()
+        })
+        .collect();
+      Some(root_dirs)
+    }
+    let root_deno_json = workspace.root_deno_json();
+    let root_dirs_from_root = root_deno_json
+      .and_then(|c| {
+        let root_dir_url = c
+          .specifier
+          .join(".")
+          .inspect_err(|err| {
+            diagnostics.push(
+              CompilerOptionsRootDirsDiagnostic::UnexpectedError(
+                c.specifier.clone(),
+                err.to_string(),
+              ),
+            );
+          })
+          .ok()?;
+        get_root_dirs(c, &root_dir_url, &mut diagnostics)
+      })
+      .unwrap_or_default();
+    let root_dirs_by_member = workspace
+      .resolver_deno_jsons()
+      .filter_map(|c| {
+        if let Some(root_deno_json) = root_deno_json {
+          if c.specifier == root_deno_json.specifier {
+            return None;
+          }
+        }
+        let dir_url = c
+          .specifier
+          .join(".")
+          .inspect_err(|err| {
+            diagnostics.push(
+              CompilerOptionsRootDirsDiagnostic::UnexpectedError(
+                c.specifier.clone(),
+                err.to_string(),
+              ),
+            );
+          })
+          .ok()?;
+        let root_dirs = get_root_dirs(c, &dir_url, &mut diagnostics);
+        Some((dir_url, root_dirs))
+      })
+      .collect();
+    Self {
+      root_dirs_from_root,
+      root_dirs_by_member,
+      diagnostics,
+      sloppy_imports_resolver,
+    }
+  }
+
+  fn new_raw(
+    root_dirs_from_root: Vec<Url>,
+    root_dirs_by_member: BTreeMap<Url, Option<Vec<Url>>>,
+    sloppy_imports_resolver: SloppyImportsResolverRc<TSys>,
+  ) -> Self {
+    Self {
+      root_dirs_from_root,
+      root_dirs_by_member,
+      diagnostics: Default::default(),
+      sloppy_imports_resolver,
+    }
+  }
+
+  fn resolve_types(
+    &self,
+    specifier: &Url,
+    referrer: &Url,
+  ) -> Option<(Url, Option<SloppyImportsResolutionReason>)> {
+    if specifier.scheme() != "file" || referrer.scheme() != "file" {
+      return None;
+    }
+    let root_dirs = self
+      .root_dirs_by_member
+      .iter()
+      .rfind(|(s, _)| referrer.as_str().starts_with(s.as_str()))
+      .and_then(|(_, r)| r.as_ref())
+      .unwrap_or(&self.root_dirs_from_root);
+    let (matched_root_dir, suffix) = root_dirs
+      .iter()
+      .filter_map(|r| {
+        let suffix = specifier.as_str().strip_prefix(r.as_str())?;
+        Some((r, suffix))
+      })
+      .max_by_key(|(r, _)| r.as_str().len())?;
+    for root_dir in root_dirs {
+      if root_dir == matched_root_dir {
+        continue;
+      }
+      let Ok(candidate_specifier) = root_dir.join(suffix) else {
+        continue;
+      };
+      let Ok(candidate_path) = url_to_file_path(&candidate_specifier) else {
+        continue;
+      };
+      if self.sloppy_imports_resolver.fs.is_file(&candidate_path) {
+        return Some((candidate_specifier, None));
+      } else if let Some((candidate_specifier, sloppy_reason)) = self
+        .sloppy_imports_resolver
+        .resolve(&candidate_specifier, ResolutionKind::Types)
+      {
+        return Some((candidate_specifier, Some(sloppy_reason)));
+      }
+    }
+    None
+  }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ResolutionKind {
+  /// Resolving for code that will be executed.
+  Execution,
+  /// Resolving for code that will be used for type information.
+  Types,
+}
+
+impl ResolutionKind {
+  pub fn is_types(&self) -> bool {
+    *self == ResolutionKind::Types
+  }
+}
+
+impl From<NodeResolutionKind> for ResolutionKind {
+  fn from(value: NodeResolutionKind) -> Self {
+    match value {
+      NodeResolutionKind::Execution => Self::Execution,
+      NodeResolutionKind::Types => Self::Types,
+    }
+  }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum WorkspaceResolverDiagnostic<'a> {
+  ImportMap(&'a ImportMapDiagnostic),
+  CompilerOptionsRootDirs(&'a CompilerOptionsRootDirsDiagnostic),
+}
+
+impl fmt::Display for WorkspaceResolverDiagnostic<'_> {
+  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    match self {
+      Self::ImportMap(d) => write!(f, "Import map: {d}"),
+      Self::CompilerOptionsRootDirs(d) => d.fmt(f),
+    }
+  }
+}
+
+#[derive(Debug)]
+pub struct WorkspaceResolver<TSys: FsMetadata + FsRead> {
+  workspace_root: UrlRc,
+  jsr_pkgs: Vec<ResolverWorkspaceJsrPackage>,
+  maybe_import_map: Option<ImportMapWithDiagnostics>,
+  pkg_jsons: BTreeMap<UrlRc, PkgJsonResolverFolderConfig>,
+  pkg_json_dep_resolution: PackageJsonDepResolution,
+  sloppy_imports_options: SloppyImportsOptions,
+  fs_cache_options: FsCacheOptions,
+  sloppy_imports_resolver: SloppyImportsResolverRc<TSys>,
+  compiler_options_root_dirs_resolver: CompilerOptionsRootDirsResolver<TSys>,
+}
+
+impl<TSys: FsMetadata + FsRead> WorkspaceResolver<TSys> {
+  pub fn from_workspace(
+    workspace: &Workspace,
+    sys: TSys,
+    options: CreateResolverOptions,
+  ) -> Result<Self, WorkspaceResolverCreateError> {
+    fn resolve_import_map(
+      sys: &impl FsRead,
+      workspace: &Workspace,
+      specified_import_map: Option<SpecifiedImportMap>,
+    ) -> Result<Option<ImportMapWithDiagnostics>, WorkspaceResolverCreateError>
+    {
+      let root_deno_json = workspace.root_deno_json();
+      let deno_jsons = workspace.resolver_deno_jsons().collect::<Vec<_>>();
+
+      let (import_map_url, import_map) = match specified_import_map {
+        Some(SpecifiedImportMap {
+          base_url,
+          value: import_map,
+        }) => (base_url, import_map),
+        None => {
+          if !deno_jsons.iter().any(|p| p.is_package())
+            && !deno_jsons.iter().any(|c| {
+              c.json.import_map.is_some()
+                || c.json.scopes.is_some()
+                || c.json.imports.is_some()
+                || c
+                  .json
+                  .compiler_options
+                  .as_ref()
+                  .and_then(|v| v.as_object()?.get("rootDirs")?.as_array())
+                  .is_some_and(|a| a.len() > 1)
+            })
+          {
+            // no configs have an import map and none are a package, so exit
+            return Ok(None);
+          }
+
+          let config_specified_import_map = match root_deno_json.as_ref() {
+            Some(deno_json) => deno_json
+              .to_import_map_value(sys)
+              .map_err(|source| WorkspaceResolverCreateError::ImportMapFetch {
+                referrer: deno_json.specifier.clone(),
+                source: Box::new(source),
+              })?
+              .unwrap_or_else(|| {
+                (
+                  Cow::Borrowed(&deno_json.specifier),
+                  serde_json::Value::Object(Default::default()),
+                )
+              }),
+            None => (
+              Cow::Owned(workspace.root_dir().join("deno.json").unwrap()),
+              serde_json::Value::Object(Default::default()),
+            ),
+          };
+          let base_import_map_config = import_map::ext::ImportMapConfig {
+            base_url: config_specified_import_map.0.into_owned(),
+            import_map_value: config_specified_import_map.1,
+          };
+          let child_import_map_configs = deno_jsons
+            .iter()
+            .filter(|f| {
+              Some(&f.specifier)
+                != root_deno_json.as_ref().map(|c| &c.specifier)
+            })
+            .map(|config| import_map::ext::ImportMapConfig {
+              base_url: config.specifier.clone(),
+              import_map_value: {
+                // don't include scopes here
+                let mut value = serde_json::Map::with_capacity(1);
+                if let Some(imports) = &config.json.imports {
+                  value.insert("imports".to_string(), imports.clone());
+                }
+                value.into()
+              },
+            })
+            .collect::<Vec<_>>();
+          let (import_map_url, import_map) =
+            ::import_map::ext::create_synthetic_import_map(
+              base_import_map_config,
+              child_import_map_configs,
+            );
+          let import_map = import_map::ext::expand_import_map_value(import_map);
+          log::debug!(
+            "Workspace config generated this import map {}",
+            serde_json::to_string_pretty(&import_map).unwrap()
+          );
+          (import_map_url, import_map)
+        }
+      };
+      Ok(Some(import_map::parse_from_value(
+        import_map_url,
+        import_map,
+      )?))
+    }
+
+    let maybe_import_map =
+      resolve_import_map(&sys, workspace, options.specified_import_map)?;
+    let jsr_pkgs = workspace.resolver_jsr_pkgs().collect::<Vec<_>>();
+    let pkg_jsons = workspace
+      .resolver_pkg_jsons()
+      .map(|(dir_url, pkg_json)| {
+        let deps = pkg_json.resolve_local_package_json_deps();
+        (
+          dir_url.clone(),
+          PkgJsonResolverFolderConfig {
+            deps: deps.clone(),
+            pkg_json: pkg_json.clone(),
+          },
+        )
+      })
+      .collect::<BTreeMap<_, _>>();
+
+    let fs = CachedMetadataFs::new(sys, options.fs_cache_options);
+    let sloppy_imports_resolver = new_rc(SloppyImportsResolver::new(
+      fs,
+      options.sloppy_imports_options,
+    ));
+    let compiler_options_root_dirs_resolver =
+      CompilerOptionsRootDirsResolver::from_workspace(
+        workspace,
+        sloppy_imports_resolver.clone(),
+      );
+
+    Ok(Self {
+      workspace_root: workspace.root_dir().clone(),
+      pkg_json_dep_resolution: options.pkg_json_dep_resolution,
+      jsr_pkgs,
+      maybe_import_map,
+      pkg_jsons,
+      sloppy_imports_options: options.sloppy_imports_options,
+      fs_cache_options: options.fs_cache_options,
+      sloppy_imports_resolver,
+      compiler_options_root_dirs_resolver,
+    })
+  }
+
+  /// Creates a new WorkspaceResolver from the specified import map and package.jsons.
+  ///
+  /// Generally, create this from a Workspace instead.
+  #[allow(clippy::too_many_arguments)]
+  pub fn new_raw(
+    workspace_root: UrlRc,
+    maybe_import_map: Option<ImportMap>,
+    jsr_pkgs: Vec<ResolverWorkspaceJsrPackage>,
+    pkg_jsons: Vec<PackageJsonRc>,
+    pkg_json_dep_resolution: PackageJsonDepResolution,
+    sloppy_imports_options: SloppyImportsOptions,
+    fs_cache_options: FsCacheOptions,
+    root_dirs_from_root: Vec<Url>,
+    root_dirs_by_member: BTreeMap<Url, Option<Vec<Url>>>,
+    sys: TSys,
+  ) -> Self {
+    let maybe_import_map =
+      maybe_import_map.map(|import_map| ImportMapWithDiagnostics {
+        import_map,
+        diagnostics: Default::default(),
+      });
+    let pkg_jsons = pkg_jsons
+      .into_iter()
+      .map(|pkg_json| {
+        let deps = pkg_json.resolve_local_package_json_deps();
+        (
+          new_rc(
+            url_from_directory_path(pkg_json.path.parent().unwrap()).unwrap(),
+          ),
+          PkgJsonResolverFolderConfig {
+            deps: deps.clone(),
+            pkg_json,
+          },
+        )
+      })
+      .collect::<BTreeMap<_, _>>();
+    let fs = CachedMetadataFs::new(sys, fs_cache_options);
+    let sloppy_imports_resolver =
+      new_rc(SloppyImportsResolver::new(fs, sloppy_imports_options));
+    let compiler_options_root_dirs_resolver =
+      CompilerOptionsRootDirsResolver::new_raw(
+        root_dirs_from_root,
+        root_dirs_by_member,
+        sloppy_imports_resolver.clone(),
+      );
+    Self {
+      workspace_root,
+      jsr_pkgs,
+      maybe_import_map,
+      pkg_jsons,
+      pkg_json_dep_resolution,
+      sloppy_imports_options,
+      fs_cache_options,
+      sloppy_imports_resolver,
+      compiler_options_root_dirs_resolver,
+    }
+  }
+
+  /// Prepare the workspace resolver for serialization
+  ///
+  /// The most significant preparation involves converting
+  /// absolute paths into relative (based on `root_dir_url`).
+  /// It also takes care of pre-serializing non-serde internal data.
+  pub fn to_serializable(
+    &self,
+    root_dir_url: &Url,
+  ) -> SerializableWorkspaceResolver {
+    let root_dir_url = BaseUrl(root_dir_url);
+    SerializableWorkspaceResolver {
+      import_map: self.maybe_import_map().map(|i| {
+        SerializedWorkspaceResolverImportMap {
+          specifier: root_dir_url.make_relative_if_descendant(i.base_url()),
+          json: Cow::Owned(i.to_json()),
+        }
+      }),
+      jsr_pkgs: self
+        .jsr_packages()
+        .map(|pkg| SerializedResolverWorkspaceJsrPackage {
+          relative_base: root_dir_url.make_relative_if_descendant(&pkg.base),
+          name: Cow::Borrowed(&pkg.name),
+          version: Cow::Borrowed(&pkg.version),
+          exports: Cow::Borrowed(&pkg.exports),
+        })
+        .collect(),
+      package_jsons: self
+        .package_jsons()
+        .map(|pkg_json| {
+          (
+            root_dir_url
+              .make_relative_if_descendant(&pkg_json.specifier())
+              .into_owned(),
+            serde_json::to_value(pkg_json).unwrap(),
+          )
+        })
+        .collect(),
+      pkg_json_resolution: self.pkg_json_dep_resolution(),
+      sloppy_imports_options: self.sloppy_imports_options,
+      fs_cache_options: self.fs_cache_options,
+      root_dirs_from_root: self
+        .compiler_options_root_dirs_resolver
+        .root_dirs_from_root
+        .iter()
+        .map(|s| root_dir_url.make_relative_if_descendant(s))
+        .collect(),
+      root_dirs_by_member: self
+        .compiler_options_root_dirs_resolver
+        .root_dirs_by_member
+        .iter()
+        .map(|(s, r)| {
+          (
+            root_dir_url.make_relative_if_descendant(s),
+            r.as_ref().map(|r| {
+              r.iter()
+                .map(|s| root_dir_url.make_relative_if_descendant(s))
+                .collect()
+            }),
+          )
+        })
+        .collect(),
+    }
+  }
+
+  /// Deserialize a `WorkspaceResolver`
+  ///
+  /// Deserialization of `WorkspaceResolver`s is made in two steps. First
+  /// the serialized data must be deserialized in to `SerializableWorkspaceResolver`
+  /// (usually with serde), and then this method converts it into a `WorkspaceResolver`.
+  ///
+  /// This second step involves mainly converting the relative paths within
+  /// `SerializableWorkspaceResolver` into absolute paths using `root_dir_url`.
+  pub fn try_from_serializable(
+    root_dir_url: Url,
+    serializable_workspace_resolver: SerializableWorkspaceResolver,
+    sys: TSys,
+  ) -> Result<Self, ImportMapError> {
+    let import_map = match serializable_workspace_resolver.import_map {
+      Some(import_map) => Some(
+        import_map::parse_from_json_with_options(
+          root_dir_url.join(&import_map.specifier).unwrap(),
+          &import_map.json,
+          import_map::ImportMapOptions {
+            address_hook: None,
+            expand_imports: true,
+          },
+        )?
+        .import_map,
+      ),
+      None => None,
+    };
+    let pkg_jsons = serializable_workspace_resolver
+      .package_jsons
+      .into_iter()
+      .map(|(relative_path, json)| {
+        let path =
+          url_to_file_path(&root_dir_url.join(&relative_path).unwrap())
+            .unwrap();
+        let pkg_json =
+          deno_package_json::PackageJson::load_from_value(path, json);
+        PackageJsonRc::new(pkg_json)
+      })
+      .collect();
+    let jsr_packages = serializable_workspace_resolver
+      .jsr_pkgs
+      .into_iter()
+      .map(|pkg| ResolverWorkspaceJsrPackage {
+        is_patch: false, // only used for enhancing the diagnostics, which are discarded when serializing
+        base: root_dir_url.join(&pkg.relative_base).unwrap(),
+        name: pkg.name.into_owned(),
+        version: pkg.version.into_owned(),
+        exports: pkg.exports.into_owned(),
+      })
+      .collect();
+    let root_dirs_from_root = serializable_workspace_resolver
+      .root_dirs_from_root
+      .iter()
+      .map(|s| root_dir_url.join(s).unwrap())
+      .collect();
+    let root_dirs_by_member = serializable_workspace_resolver
+      .root_dirs_by_member
+      .iter()
+      .map(|(s, r)| {
+        (
+          root_dir_url.join(s).unwrap(),
+          r.as_ref()
+            .map(|r| r.iter().map(|s| root_dir_url.join(s).unwrap()).collect()),
+        )
+      })
+      .collect();
+    Ok(Self::new_raw(
+      UrlRc::new(root_dir_url),
+      import_map,
+      jsr_packages,
+      pkg_jsons,
+      serializable_workspace_resolver.pkg_json_resolution,
+      serializable_workspace_resolver.sloppy_imports_options,
+      serializable_workspace_resolver.fs_cache_options,
+      root_dirs_from_root,
+      root_dirs_by_member,
+      sys,
+    ))
+  }
+
+  pub fn maybe_import_map(&self) -> Option<&ImportMap> {
+    self.maybe_import_map.as_ref().map(|c| &c.import_map)
+  }
+
+  pub fn package_jsons(&self) -> impl Iterator<Item = &PackageJsonRc> {
+    self.pkg_jsons.values().map(|c| &c.pkg_json)
+  }
+
+  pub fn jsr_packages(
+    &self,
+  ) -> impl Iterator<Item = &ResolverWorkspaceJsrPackage> {
+    self.jsr_pkgs.iter()
+  }
+
+  pub fn diagnostics(&self) -> Vec<WorkspaceResolverDiagnostic<'_>> {
+    self
+      .compiler_options_root_dirs_resolver
+      .diagnostics
+      .iter()
+      .map(WorkspaceResolverDiagnostic::CompilerOptionsRootDirs)
+      .chain(
+        self
+          .maybe_import_map
+          .as_ref()
+          .iter()
+          .flat_map(|c| &c.diagnostics)
+          .map(WorkspaceResolverDiagnostic::ImportMap),
+      )
+      .collect()
+  }
+
+  pub fn resolve<'a>(
+    &'a self,
+    specifier: &str,
+    referrer: &Url,
+    resolution_kind: ResolutionKind,
+  ) -> Result<MappedResolution<'a>, MappedResolutionError> {
+    // 1. Attempt to resolve with the import map and normally first
+    let mut used_import_map = false;
+    let resolve_result = if let Some(import_map) = &self.maybe_import_map {
+      used_import_map = true;
+      import_map
+        .import_map
+        .resolve(specifier, referrer)
+        .map_err(MappedResolutionError::ImportMap)
+    } else {
+      import_map::specifier::resolve_import(specifier, referrer)
+        .map_err(MappedResolutionError::Specifier)
+    };
+    let resolve_error = match resolve_result {
+      Ok(mut specifier) => {
+        let mut used_compiler_options_root_dirs = false;
+        let mut sloppy_reason = None;
+        if let Some((probed_specifier, probed_sloppy_reason)) = self
+          .sloppy_imports_resolver
+          .resolve(&specifier, resolution_kind)
+        {
+          specifier = probed_specifier;
+          sloppy_reason = Some(probed_sloppy_reason);
+        } else if resolution_kind.is_types() {
+          if let Some((probed_specifier, probed_sloppy_reason)) = self
+            .compiler_options_root_dirs_resolver
+            .resolve_types(&specifier, referrer)
+          {
+            used_compiler_options_root_dirs = true;
+            specifier = probed_specifier;
+            sloppy_reason = probed_sloppy_reason;
+          }
+        }
+        return self.maybe_resolve_specifier_to_workspace_jsr_pkg(
+          MappedResolution::Normal {
+            specifier,
+            used_import_map,
+            used_compiler_options_root_dirs,
+            sloppy_reason,
+            maybe_diagnostic: None,
+          },
+        );
+      }
+      Err(err) => err,
+    };
+
+    // 2. Try to resolve the bare specifier to a workspace member
+    if resolve_error.is_unmapped_bare_specifier() {
+      for member in &self.jsr_pkgs {
+        if let Some(path) = specifier.strip_prefix(&member.name) {
+          if path.is_empty() || path.starts_with('/') {
+            let path = path.strip_prefix('/').unwrap_or(path);
+            let pkg_req_ref = match JsrPackageReqReference::from_str(&format!(
+              "jsr:{}{}/{}",
+              member.name,
+              member
+                .version
+                .as_ref()
+                .map(|v| format!("@^{}", v))
+                .unwrap_or_else(String::new),
+              path
+            )) {
+              Ok(pkg_req_ref) => pkg_req_ref,
+              Err(_) => {
+                // Ignore the error as it will be surfaced as a diagnostic
+                // in workspace.diagnostics() routine.
+                continue;
+              }
+            };
+            return self.resolve_workspace_jsr_pkg(member, pkg_req_ref);
+          }
+        }
+      }
+    }
+
+    if self.pkg_json_dep_resolution == PackageJsonDepResolution::Enabled {
+      // 2. Attempt to resolve from the package.json dependencies.
+      let mut previously_found_dir = false;
+      for (dir_url, pkg_json_folder) in self.pkg_jsons.iter().rev() {
+        if !referrer.as_str().starts_with(dir_url.as_str()) {
+          if previously_found_dir {
+            break;
+          } else {
+            continue;
+          }
+        }
+        previously_found_dir = true;
+
+        for (bare_specifier, dep_result) in pkg_json_folder
+          .deps
+          .dependencies
+          .iter()
+          .chain(pkg_json_folder.deps.dev_dependencies.iter())
+        {
+          if let Some(path) = specifier.strip_prefix(bare_specifier.as_str()) {
+            if path.is_empty() || path.starts_with('/') {
+              let sub_path = path.strip_prefix('/').unwrap_or(path);
+              return Ok(MappedResolution::PackageJson {
+                pkg_json: &pkg_json_folder.pkg_json,
+                alias: bare_specifier,
+                sub_path: if sub_path.is_empty() {
+                  None
+                } else {
+                  Some(sub_path.to_string())
+                },
+                dep_result,
+              });
+            }
+          }
+        }
+      }
+
+      // 3. Finally try to resolve to a workspace npm package if inside the workspace.
+      if referrer.as_str().starts_with(self.workspace_root.as_str()) {
+        for pkg_json_folder in self.pkg_jsons.values() {
+          let Some(name) = &pkg_json_folder.pkg_json.name else {
+            continue;
+          };
+          let Some(path) = specifier.strip_prefix(name) else {
+            continue;
+          };
+          if path.is_empty() || path.starts_with('/') {
+            let sub_path = path.strip_prefix('/').unwrap_or(path);
+            return Ok(MappedResolution::WorkspaceNpmPackage {
+              target_pkg_json: &pkg_json_folder.pkg_json,
+              pkg_name: name,
+              sub_path: if sub_path.is_empty() {
+                None
+              } else {
+                Some(sub_path.to_string())
+              },
+            });
+          }
+        }
+      }
+    }
+
+    // wasn't found, so surface the initial resolve error
+    Err(resolve_error)
+  }
+
+  fn maybe_resolve_specifier_to_workspace_jsr_pkg<'a>(
+    &'a self,
+    resolution: MappedResolution<'a>,
+  ) -> Result<MappedResolution<'a>, MappedResolutionError> {
+    let specifier = match resolution {
+      MappedResolution::Normal { ref specifier, .. } => specifier,
+      _ => return Ok(resolution),
+    };
+    if specifier.scheme() != "jsr" {
+      return Ok(resolution);
+    }
+    let mut maybe_diagnostic = None;
+    if let Ok(package_req_ref) =
+      JsrPackageReqReference::from_specifier(specifier)
+    {
+      for pkg in &self.jsr_pkgs {
+        if pkg.name == package_req_ref.req().name {
+          if let Some(version) = &pkg.version {
+            if package_req_ref.req().version_req.matches(version) {
+              return self.resolve_workspace_jsr_pkg(pkg, package_req_ref);
+            } else {
+              maybe_diagnostic = Some(Box::new(
+                MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion {
+                  is_patch: pkg.is_patch,
+                  reference: package_req_ref.clone(),
+                  local_version: version.clone(),
+                },
+              ));
+            }
+          } else {
+            // always resolve to workspace packages with no version
+            return self.resolve_workspace_jsr_pkg(pkg, package_req_ref);
+          }
+        }
+      }
+    }
+    Ok(match resolution {
+      MappedResolution::Normal {
+        specifier,
+        used_import_map,
+        used_compiler_options_root_dirs,
+        sloppy_reason,
+        ..
+      } => MappedResolution::Normal {
+        specifier,
+        used_import_map,
+        used_compiler_options_root_dirs,
+        sloppy_reason,
+        maybe_diagnostic,
+      },
+      _ => return Ok(resolution),
+    })
+  }
+
+  fn resolve_workspace_jsr_pkg<'a>(
+    &'a self,
+    pkg: &'a ResolverWorkspaceJsrPackage,
+    pkg_req_ref: JsrPackageReqReference,
+  ) -> Result<MappedResolution<'a>, MappedResolutionError> {
+    let export_name = pkg_req_ref.export_name();
+    match pkg.exports.get(export_name.as_ref()) {
+      Some(sub_path) => match pkg.base.join(sub_path) {
+        Ok(specifier) => Ok(MappedResolution::WorkspaceJsrPackage {
+          specifier,
+          pkg_req_ref,
+        }),
+        Err(err) => Err(
+          WorkspaceResolveError::InvalidExportPath {
+            base: pkg.base.clone(),
+            sub_path: sub_path.to_string(),
+            error: err,
+          }
+          .into(),
+        ),
+      },
+      None => Err(
+        WorkspaceResolveError::UnknownExport {
+          package_name: pkg.name.clone(),
+          export_name: export_name.to_string(),
+          exports: pkg.exports.keys().cloned().collect(),
+        }
+        .into(),
+      ),
+    }
+  }
+
+  pub fn resolve_workspace_pkg_json_folder_for_npm_specifier(
+    &self,
+    pkg_req: &PackageReq,
+  ) -> Option<&Path> {
+    if pkg_req.version_req.tag().is_some() {
+      return None;
+    }
+
+    self
+      .resolve_workspace_pkg_json_folder_for_pkg_json_dep(
+        &pkg_req.name,
+        &PackageJsonDepWorkspaceReq::VersionReq(pkg_req.version_req.clone()),
+      )
+      .ok()
+  }
+
+  pub fn resolve_workspace_pkg_json_folder_for_pkg_json_dep(
+    &self,
+    name: &str,
+    workspace_version_req: &PackageJsonDepWorkspaceReq,
+  ) -> Result<&Path, WorkspaceResolvePkgJsonFolderError> {
+    // this is not conditional on pkg_json_dep_resolution because we want
+    // to be able to do this resolution to figure out mapping an npm specifier
+    // to a workspace folder when using BYONM
+    let pkg_json = self
+      .package_jsons()
+      .find(|p| p.name.as_deref() == Some(name));
+    let Some(pkg_json) = pkg_json else {
+      return Err(
+        WorkspaceResolvePkgJsonFolderErrorKind::NotFound(name.to_string())
+          .into(),
+      );
+    };
+    match workspace_version_req {
+      PackageJsonDepWorkspaceReq::VersionReq(version_req) => {
+        match version_req.inner() {
+          RangeSetOrTag::RangeSet(set) => {
+            if let Some(version) = pkg_json
+              .version
+              .as_ref()
+              .and_then(|v| Version::parse_from_npm(v).ok())
+            {
+              if set.satisfies(&version) {
+                Ok(pkg_json.dir_path())
+              } else {
+                Err(
+                  WorkspaceResolvePkgJsonFolderErrorKind::VersionNotSatisfied(
+                    version_req.clone(),
+                    version,
+                  )
+                  .into(),
+                )
+              }
+            } else {
+              // just match it
+              Ok(pkg_json.dir_path())
+            }
+          }
+          RangeSetOrTag::Tag(_) => {
+            // always match tags
+            Ok(pkg_json.dir_path())
+          }
+        }
+      }
+      PackageJsonDepWorkspaceReq::Tilde | PackageJsonDepWorkspaceReq::Caret => {
+        // always match tilde and caret requirements
+        Ok(pkg_json.dir_path())
+      }
+    }
+  }
+
+  pub fn pkg_json_dep_resolution(&self) -> PackageJsonDepResolution {
+    self.pkg_json_dep_resolution
+  }
+
+  pub fn sloppy_imports_enabled(&self) -> bool {
+    match self.sloppy_imports_options {
+      SloppyImportsOptions::Enabled => true,
+      SloppyImportsOptions::Disabled => false,
+    }
+  }
+
+  pub fn has_compiler_options_root_dirs(&self) -> bool {
+    !self
+      .compiler_options_root_dirs_resolver
+      .root_dirs_from_root
+      .is_empty()
+      || self
+        .compiler_options_root_dirs_resolver
+        .root_dirs_by_member
+        .values()
+        .flatten()
+        .any(|r| !r.is_empty())
+  }
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct SerializedWorkspaceResolverImportMap<'a> {
+  #[serde(borrow)]
+  pub specifier: Cow<'a, str>,
+  #[serde(borrow)]
+  pub json: Cow<'a, str>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct SerializedResolverWorkspaceJsrPackage<'a> {
+  #[serde(borrow)]
+  pub relative_base: Cow<'a, str>,
+  #[serde(borrow)]
+  pub name: Cow<'a, str>,
+  pub version: Cow<'a, Option<Version>>,
+  pub exports: Cow<'a, IndexMap<String, String>>,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct SerializableWorkspaceResolver<'a> {
+  #[serde(borrow)]
+  pub import_map: Option<SerializedWorkspaceResolverImportMap<'a>>,
+  #[serde(borrow)]
+  pub jsr_pkgs: Vec<SerializedResolverWorkspaceJsrPackage<'a>>,
+  pub package_jsons: Vec<(String, serde_json::Value)>,
+  pub pkg_json_resolution: PackageJsonDepResolution,
+  pub sloppy_imports_options: SloppyImportsOptions,
+  pub fs_cache_options: FsCacheOptions,
+  pub root_dirs_from_root: Vec<Cow<'a, str>>,
+  pub root_dirs_by_member: BTreeMap<Cow<'a, str>, Option<Vec<Cow<'a, str>>>>,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct BaseUrl<'a>(&'a Url);
+
+impl BaseUrl<'_> {
+  fn make_relative_if_descendant<'a>(&self, target: &'a Url) -> Cow<'a, str> {
+    if target.scheme() != "file" {
+      return Cow::Borrowed(target.as_str());
+    }
+
+    match self.0.make_relative(target) {
+      Some(relative) => {
+        if relative.starts_with("../") {
+          Cow::Borrowed(target.as_str())
+        } else {
+          Cow::Owned(relative)
+        }
+      }
+      None => Cow::Borrowed(target.as_str()),
+    }
+  }
+}
+
+#[cfg(test)]
+mod test {
+  use std::path::Path;
+  use std::path::PathBuf;
+
+  use deno_config::workspace::WorkspaceDirectory;
+  use deno_config::workspace::WorkspaceDiscoverOptions;
+  use deno_config::workspace::WorkspaceDiscoverStart;
+  use deno_path_util::url_from_directory_path;
+  use deno_path_util::url_from_file_path;
+  use deno_semver::VersionReq;
+  use serde_json::json;
+  use sys_traits::impls::InMemorySys;
+  use sys_traits::FsCanonicalize;
+  use url::Url;
+
+  use super::*;
+
+  pub struct UnreachableSys;
+
+  impl sys_traits::BaseFsMetadata for UnreachableSys {
+    type Metadata = sys_traits::impls::RealFsMetadata;
+
+    #[doc(hidden)]
+    fn base_fs_metadata(
+      &self,
+      _path: &Path,
+    ) -> std::io::Result<Self::Metadata> {
+      unreachable!()
+    }
+
+    #[doc(hidden)]
+    fn base_fs_symlink_metadata(
+      &self,
+      _path: &Path,
+    ) -> std::io::Result<Self::Metadata> {
+      unreachable!()
+    }
+  }
+
+  impl sys_traits::BaseFsRead for UnreachableSys {
+    fn base_fs_read(
+      &self,
+      _path: &Path,
+    ) -> std::io::Result<Cow<'static, [u8]>> {
+      unreachable!()
+    }
+  }
+
+  fn root_dir() -> PathBuf {
+    if cfg!(windows) {
+      PathBuf::from("C:\\Users\\user")
+    } else {
+      PathBuf::from("/home/user")
+    }
+  }
+
+  #[test]
+  fn pkg_json_resolution() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "workspace": [
+          "a",
+          "b",
+          "c",
+        ]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("a/deno.json"),
+      json!({
+        "imports": {
+          "b": "./index.js",
+        },
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("b/package.json"),
+      json!({
+        "dependencies": {
+          "pkg": "npm:pkg@^1.0.0",
+        },
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("c/package.json"),
+      json!({
+        "name": "pkg",
+        "version": "0.5.0"
+      }),
+    );
+    let workspace = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace);
+    assert_eq!(resolver.diagnostics(), Vec::new());
+    let resolve = |name: &str, referrer: &str| {
+      resolver.resolve(
+        name,
+        &url_from_file_path(&deno_path_util::normalize_path(
+          root_dir().join(referrer),
+        ))
+        .unwrap(),
+        ResolutionKind::Execution,
+      )
+    };
+    match resolve("pkg", "b/index.js").unwrap() {
+      MappedResolution::PackageJson {
+        alias,
+        sub_path,
+        dep_result,
+        ..
+      } => {
+        assert_eq!(alias, "pkg");
+        assert_eq!(sub_path, None);
+        dep_result.as_ref().unwrap();
+      }
+      value => unreachable!("{:?}", value),
+    }
+    match resolve("pkg/sub-path", "b/index.js").unwrap() {
+      MappedResolution::PackageJson {
+        alias,
+        sub_path,
+        dep_result,
+        ..
+      } => {
+        assert_eq!(alias, "pkg");
+        assert_eq!(sub_path.unwrap(), "sub-path");
+        dep_result.as_ref().unwrap();
+      }
+      value => unreachable!("{:?}", value),
+    }
+
+    // pkg is not a dependency in this folder, so it should resolve
+    // to the workspace member
+    match resolve("pkg", "index.js").unwrap() {
+      MappedResolution::WorkspaceNpmPackage {
+        pkg_name,
+        sub_path,
+        target_pkg_json,
+      } => {
+        assert_eq!(pkg_name, "pkg");
+        assert_eq!(sub_path, None);
+        assert_eq!(target_pkg_json.dir_path(), root_dir().join("c"));
+      }
+      _ => unreachable!(),
+    }
+    match resolve("pkg/sub-path", "index.js").unwrap() {
+      MappedResolution::WorkspaceNpmPackage {
+        pkg_name,
+        sub_path,
+        target_pkg_json,
+      } => {
+        assert_eq!(pkg_name, "pkg");
+        assert_eq!(sub_path.unwrap(), "sub-path");
+        assert_eq!(target_pkg_json.dir_path(), root_dir().join("c"));
+      }
+      _ => unreachable!(),
+    }
+
+    // won't resolve the package outside the workspace
+    assert!(resolve("pkg", "../outside-workspace.js").is_err());
+  }
+
+  #[test]
+  fn single_pkg_no_import_map() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "name": "@scope/pkg",
+        "version": "1.0.0",
+        "exports": "./mod.ts"
+      }),
+    );
+    let workspace = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace);
+    assert_eq!(resolver.diagnostics(), Vec::new());
+    let result = resolver
+      .resolve(
+        "@scope/pkg",
+        &url_from_file_path(&root_dir().join("file.ts")).unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap();
+    match result {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(
+          specifier,
+          url_from_file_path(&root_dir().join("mod.ts")).unwrap()
+        );
+      }
+      _ => unreachable!(),
+    }
+  }
+
+  #[test]
+  fn resolve_workspace_pkg_json_folder() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("package.json"),
+      json!({
+        "workspaces": [
+          "a",
+          "b",
+          "no-version"
+        ]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("a/package.json"),
+      json!({
+        "name": "@scope/a",
+        "version": "1.0.0",
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("b/package.json"),
+      json!({
+        "name": "@scope/b",
+        "version": "2.0.0",
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("no-version/package.json"),
+      json!({
+        "name": "@scope/no-version",
+      }),
+    );
+    let workspace = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace);
+    // resolve for pkg json dep
+    {
+      let resolve = |name: &str, req: &str| {
+        resolver.resolve_workspace_pkg_json_folder_for_pkg_json_dep(
+          name,
+          &PackageJsonDepWorkspaceReq::VersionReq(
+            VersionReq::parse_from_npm(req).unwrap(),
+          ),
+        )
+      };
+      assert_eq!(
+        resolve("non-existent", "*").map_err(|e| e.into_kind()),
+        Err(WorkspaceResolvePkgJsonFolderErrorKind::NotFound(
+          "non-existent".to_string()
+        ))
+      );
+      assert_eq!(
+        resolve("@scope/a", "6").map_err(|e| e.into_kind()),
+        Err(WorkspaceResolvePkgJsonFolderErrorKind::VersionNotSatisfied(
+          VersionReq::parse_from_npm("6").unwrap(),
+          Version::parse_from_npm("1.0.0").unwrap(),
+        ))
+      );
+      assert_eq!(resolve("@scope/a", "1").unwrap(), root_dir().join("a"));
+      assert_eq!(resolve("@scope/a", "*").unwrap(), root_dir().join("a"));
+      assert_eq!(
+        resolve("@scope/a", "workspace").unwrap(),
+        root_dir().join("a")
+      );
+      assert_eq!(resolve("@scope/b", "2").unwrap(), root_dir().join("b"));
+      // just match any tags with the workspace
+      assert_eq!(resolve("@scope/a", "latest").unwrap(), root_dir().join("a"));
+
+      // match any version for a pkg with no version
+      assert_eq!(
+        resolve("@scope/no-version", "1").unwrap(),
+        root_dir().join("no-version")
+      );
+      assert_eq!(
+        resolve("@scope/no-version", "20").unwrap(),
+        root_dir().join("no-version")
+      );
+    }
+    // resolve for specifier
+    {
+      let resolve = |pkg_req: &str| {
+        resolver.resolve_workspace_pkg_json_folder_for_npm_specifier(
+          &PackageReq::from_str(pkg_req).unwrap(),
+        )
+      };
+      assert_eq!(resolve("non-existent@*"), None);
+      assert_eq!(
+        resolve("@scope/no-version@1").unwrap(),
+        root_dir().join("no-version")
+      );
+
+      // won't match for tags
+      assert_eq!(resolve("@scope/a@workspace"), None);
+      assert_eq!(resolve("@scope/a@latest"), None);
+    }
+  }
+
+  #[test]
+  fn resolve_workspace_pkg_json_workspace_deno_json_import_map() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("package.json"),
+      json!({
+        "workspaces": ["*"]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("a/package.json"),
+      json!({
+        "name": "@scope/a",
+        "version": "1.0.0",
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("a/deno.json"),
+      json!({
+        "name": "@scope/jsr-pkg",
+        "version": "1.0.0",
+        "exports": "./mod.ts"
+      }),
+    );
+
+    let workspace = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace);
+    {
+      let resolution = resolver
+        .resolve(
+          "@scope/jsr-pkg",
+          &url_from_file_path(&root_dir().join("b.ts")).unwrap(),
+          ResolutionKind::Execution,
+        )
+        .unwrap();
+      match resolution {
+        MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+          assert_eq!(
+            specifier,
+            url_from_file_path(&root_dir().join("a/mod.ts")).unwrap()
+          );
+        }
+        _ => unreachable!(),
+      }
+    }
+    {
+      let resolution_err = resolver
+        .resolve(
+          "@scope/jsr-pkg/not-found-export",
+          &url_from_file_path(&root_dir().join("b.ts")).unwrap(),
+          ResolutionKind::Execution,
+        )
+        .unwrap_err();
+      match resolution_err {
+        MappedResolutionError::Workspace(
+          WorkspaceResolveError::UnknownExport {
+            package_name,
+            export_name,
+            exports,
+          },
+        ) => {
+          assert_eq!(package_name, "@scope/jsr-pkg");
+          assert_eq!(export_name, "./not-found-export");
+          assert_eq!(exports, vec!["."]);
+        }
+        _ => unreachable!(),
+      }
+    }
+  }
+
+  #[test]
+  fn root_member_imports_and_scopes() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "workspace": ["member"],
+        "imports": {
+          "@scope/pkg": "jsr:@scope/pkg@1",
+        },
+        "scopes": {
+          "https://deno.land/x/": {
+            "@scope/pkg": "jsr:@scope/pkg@2",
+          },
+        },
+      }),
+    );
+    // Overrides `rootDirs` from workspace root.
+    sys.fs_insert_json(
+      root_dir().join("member/deno.json"),
+      json!({
+        "imports": {
+          "@scope/pkg": "jsr:@scope/pkg@3",
+        },
+        // will ignore this scopes because it's not in the root
+        "scopes": {
+          "https://deno.land/x/other": {
+            "@scope/pkg": "jsr:@scope/pkg@4",
+          },
+        },
+      }),
+    );
+
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = WorkspaceResolver::from_workspace(
+      &workspace_dir.workspace,
+      sys.clone(),
+      super::CreateResolverOptions {
+        pkg_json_dep_resolution: PackageJsonDepResolution::Enabled,
+        specified_import_map: None,
+        sloppy_imports_options: SloppyImportsOptions::Disabled,
+        fs_cache_options: FsCacheOptions::Enabled,
+      },
+    )
+    .unwrap();
+    assert_eq!(
+      serde_json::from_str::<serde_json::Value>(
+        &resolver.maybe_import_map().unwrap().to_json()
+      )
+      .unwrap(),
+      json!({
+        "imports": {
+          "@scope/pkg": "jsr:@scope/pkg@1",
+          "@scope/pkg/": "jsr:/@scope/pkg@1/",
+        },
+        "scopes": {
+          "https://deno.land/x/": {
+            "@scope/pkg": "jsr:@scope/pkg@2",
+            "@scope/pkg/": "jsr:/@scope/pkg@2/",
+          },
+          "./member/": {
+            "@scope/pkg": "jsr:@scope/pkg@3",
+            "@scope/pkg/": "jsr:/@scope/pkg@3/",
+          },
+        },
+      }),
+    );
+  }
+
+  #[test]
+  fn resolve_sloppy_imports() {
+    let sys = InMemorySys::default();
+    let root_url = url_from_file_path(
+      &sys_traits::impls::RealSys.fs_canonicalize("/").unwrap(),
+    )
+    .unwrap();
+    let fs = CachedMetadataFs::new(sys.clone(), FsCacheOptions::Enabled);
+    let sloppy_imports_resolver =
+      SloppyImportsResolver::new(fs, SloppyImportsOptions::Enabled);
+
+    // scenarios like resolving ./example.js to ./example.ts
+    for (file_from, file_to) in [
+      ("file1.js", "file1.ts"),
+      ("file2.js", "file2.tsx"),
+      ("file3.mjs", "file3.mts"),
+    ] {
+      let specifier = root_url.join(file_to).unwrap();
+      sys.fs_insert(url_to_file_path(&specifier).unwrap(), "");
+      let sloppy_specifier = root_url.join(file_from).unwrap();
+      assert_eq!(
+        sloppy_imports_resolver.resolve(&specifier, ResolutionKind::Execution),
+        None,
+      );
+      assert_eq!(
+        sloppy_imports_resolver
+          .resolve(&sloppy_specifier, ResolutionKind::Execution),
+        Some((specifier, SloppyImportsResolutionReason::JsToTs)),
+      );
+    }
+
+    // no extension scenarios
+    for file in [
+      "file10.js",
+      "file11.ts",
+      "file12.js",
+      "file13.tsx",
+      "file14.jsx",
+      "file15.mjs",
+      "file16.mts",
+    ] {
+      let specifier = root_url.join(file).unwrap();
+      sys.fs_insert(url_to_file_path(&specifier).unwrap(), "");
+      let sloppy_specifier =
+        root_url.join(file.split_once('.').unwrap().0).unwrap();
+      assert_eq!(
+        sloppy_imports_resolver.resolve(&specifier, ResolutionKind::Execution),
+        None,
+      );
+      assert_eq!(
+        sloppy_imports_resolver
+          .resolve(&sloppy_specifier, ResolutionKind::Execution),
+        Some((specifier, SloppyImportsResolutionReason::NoExtension)),
+      );
+    }
+
+    // .ts and .js exists, .js specified (goes to specified)
+    {
+      let ts_specifier = root_url.join("ts_and_js.ts").unwrap();
+      sys.fs_insert(url_to_file_path(&ts_specifier).unwrap(), "");
+      let js_specifier = root_url.join("ts_and_js.js").unwrap();
+      sys.fs_insert(url_to_file_path(&js_specifier).unwrap(), "");
+      assert_eq!(
+        sloppy_imports_resolver
+          .resolve(&js_specifier, ResolutionKind::Execution),
+        None,
+      );
+    }
+
+    // only js exists, .js specified
+    {
+      let specifier = root_url.join("js_only.js").unwrap();
+      sys.fs_insert(url_to_file_path(&specifier).unwrap(), "");
+      assert_eq!(
+        sloppy_imports_resolver.resolve(&specifier, ResolutionKind::Execution),
+        None,
+      );
+      assert_eq!(
+        sloppy_imports_resolver.resolve(&specifier, ResolutionKind::Types),
+        None,
+      );
+    }
+
+    // resolving a directory to an index file
+    {
+      let specifier = root_url.join("routes/index.ts").unwrap();
+      sys.fs_insert(url_to_file_path(&specifier).unwrap(), "");
+      let sloppy_specifier = root_url.join("routes").unwrap();
+      assert_eq!(
+        sloppy_imports_resolver
+          .resolve(&sloppy_specifier, ResolutionKind::Execution),
+        Some((specifier, SloppyImportsResolutionReason::Directory)),
+      );
+    }
+
+    // both a directory and a file with specifier is present
+    {
+      let specifier = root_url.join("api.ts").unwrap();
+      sys.fs_insert(url_to_file_path(&specifier).unwrap(), "");
+      let bar_specifier = root_url.join("api/bar.ts").unwrap();
+      sys.fs_insert(url_to_file_path(&bar_specifier).unwrap(), "");
+      let sloppy_specifier = root_url.join("api").unwrap();
+      assert_eq!(
+        sloppy_imports_resolver
+          .resolve(&sloppy_specifier, ResolutionKind::Execution),
+        Some((specifier, SloppyImportsResolutionReason::NoExtension)),
+      );
+    }
+  }
+
+  #[test]
+  fn test_sloppy_import_resolution_suggestion_message() {
+    // directory
+    assert_eq!(
+      SloppyImportsResolutionReason::Directory
+        .suggestion_message_for_specifier(
+          &Url::parse("file:///dir/index.js").unwrap()
+        )
+        .as_str(),
+      "Maybe specify path to 'index.js' file in directory instead"
+    );
+    // no ext
+    assert_eq!(
+      SloppyImportsResolutionReason::NoExtension
+        .suggestion_message_for_specifier(
+          &Url::parse("file:///dir/index.mjs").unwrap()
+        )
+        .as_str(),
+      "Maybe add a '.mjs' extension"
+    );
+    // js to ts
+    assert_eq!(
+      SloppyImportsResolutionReason::JsToTs
+        .suggestion_message_for_specifier(
+          &Url::parse("file:///dir/index.mts").unwrap()
+        )
+        .as_str(),
+      "Maybe change the extension to '.mts'"
+    );
+  }
+
+  #[test]
+  fn resolve_compiler_options_root_dirs() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "workspace": ["member", "member2"],
+        "compilerOptions": {
+          "rootDirs": ["member", "member2", "member2_types"],
+        },
+      }),
+    );
+    // Overrides `rootDirs` from workspace root.
+    sys.fs_insert_json(
+      root_dir().join("member/deno.json"),
+      json!({
+        "compilerOptions": {
+          "rootDirs": ["foo", "foo_types"],
+        },
+      }),
+    );
+    // Use `rootDirs` from workspace root.
+    sys.fs_insert_json(root_dir().join("member2/deno.json"), json!({}));
+    sys.fs_insert(root_dir().join("member/foo_types/import.ts"), "");
+    sys.fs_insert(root_dir().join("member2_types/import.ts"), "");
+    // This file should be ignored. It would be used if `member/deno.json` had
+    // no `rootDirs`.
+    sys.fs_insert(root_dir().join("member2_types/foo/import.ts"), "");
+
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = WorkspaceResolver::from_workspace(
+      &workspace_dir.workspace,
+      sys.clone(),
+      super::CreateResolverOptions {
+        pkg_json_dep_resolution: PackageJsonDepResolution::Enabled,
+        specified_import_map: None,
+        sloppy_imports_options: SloppyImportsOptions::Disabled,
+        fs_cache_options: FsCacheOptions::Enabled,
+      },
+    )
+    .unwrap();
+    let root_dir_url = workspace_dir.workspace.root_dir();
+
+    let referrer = root_dir_url.join("member/foo/mod.ts").unwrap();
+    let resolution = resolver
+      .resolve("./import.ts", &referrer, ResolutionKind::Types)
+      .unwrap();
+    let MappedResolution::Normal {
+      specifier,
+      sloppy_reason,
+      used_compiler_options_root_dirs,
+      ..
+    } = &resolution
+    else {
+      unreachable!("{:#?}", &resolution);
+    };
+    assert_eq!(
+      specifier.as_str(),
+      root_dir_url
+        .join("member/foo_types/import.ts")
+        .unwrap()
+        .as_str()
+    );
+    assert_eq!(sloppy_reason, &None);
+    assert!(used_compiler_options_root_dirs);
+
+    let referrer = root_dir_url.join("member2/mod.ts").unwrap();
+    let resolution = resolver
+      .resolve("./import.ts", &referrer, ResolutionKind::Types)
+      .unwrap();
+    let MappedResolution::Normal {
+      specifier,
+      sloppy_reason,
+      used_compiler_options_root_dirs,
+      ..
+    } = &resolution
+    else {
+      unreachable!("{:#?}", &resolution);
+    };
+    assert_eq!(
+      specifier.as_str(),
+      root_dir_url
+        .join("member2_types/import.ts")
+        .unwrap()
+        .as_str()
+    );
+    assert_eq!(sloppy_reason, &None);
+    assert!(used_compiler_options_root_dirs);
+
+    // Ignore rootDirs for `ResolutionKind::Execution`.
+    let referrer = root_dir_url.join("member/foo/mod.ts").unwrap();
+    let resolution = resolver
+      .resolve("./import.ts", &referrer, ResolutionKind::Execution)
+      .unwrap();
+    let MappedResolution::Normal {
+      specifier,
+      sloppy_reason,
+      used_compiler_options_root_dirs,
+      ..
+    } = &resolution
+    else {
+      unreachable!("{:#?}", &resolution);
+    };
+    assert_eq!(
+      specifier.as_str(),
+      root_dir_url.join("member/foo/import.ts").unwrap().as_str()
+    );
+    assert_eq!(sloppy_reason, &None);
+    assert!(!used_compiler_options_root_dirs);
+
+    // Ignore rootDirs for `ResolutionKind::Execution`.
+    let referrer = root_dir_url.join("member2/mod.ts").unwrap();
+    let resolution = resolver
+      .resolve("./import.ts", &referrer, ResolutionKind::Execution)
+      .unwrap();
+    let MappedResolution::Normal {
+      specifier,
+      sloppy_reason,
+      used_compiler_options_root_dirs,
+      ..
+    } = &resolution
+    else {
+      unreachable!("{:#?}", &resolution);
+    };
+    assert_eq!(
+      specifier.as_str(),
+      root_dir_url.join("member2/import.ts").unwrap().as_str()
+    );
+    assert_eq!(sloppy_reason, &None);
+    assert!(!used_compiler_options_root_dirs);
+  }
+
+  #[test]
+  fn resolve_compiler_options_root_dirs_and_sloppy_imports() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "compilerOptions": {
+          "rootDirs": ["subdir", "subdir_types"],
+        },
+      }),
+    );
+    sys.fs_insert(root_dir().join("subdir_types/import.ts"), "");
+
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = WorkspaceResolver::from_workspace(
+      &workspace_dir.workspace,
+      sys.clone(),
+      super::CreateResolverOptions {
+        pkg_json_dep_resolution: PackageJsonDepResolution::Enabled,
+        specified_import_map: None,
+        sloppy_imports_options: SloppyImportsOptions::Enabled,
+        fs_cache_options: FsCacheOptions::Enabled,
+      },
+    )
+    .unwrap();
+    let root_dir_url = workspace_dir.workspace.root_dir();
+
+    let referrer = root_dir_url.join("subdir/mod.ts").unwrap();
+    let resolution = resolver
+      .resolve("./import", &referrer, ResolutionKind::Types)
+      .unwrap();
+    let MappedResolution::Normal {
+      specifier,
+      sloppy_reason,
+      used_compiler_options_root_dirs,
+      ..
+    } = &resolution
+    else {
+      unreachable!("{:#?}", &resolution);
+    };
+    assert_eq!(
+      specifier.as_str(),
+      root_dir_url
+        .join("subdir_types/import.ts")
+        .unwrap()
+        .as_str()
+    );
+    assert_eq!(
+      sloppy_reason,
+      &Some(SloppyImportsResolutionReason::NoExtension)
+    );
+    assert!(used_compiler_options_root_dirs);
+  }
+
+  #[test]
+  fn specified_import_map() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(root_dir().join("deno.json"), json!({}));
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = WorkspaceResolver::from_workspace(
+      &workspace_dir.workspace,
+      sys,
+      super::CreateResolverOptions {
+        pkg_json_dep_resolution: PackageJsonDepResolution::Enabled,
+        specified_import_map: Some(SpecifiedImportMap {
+          base_url: url_from_directory_path(&root_dir()).unwrap(),
+          value: json!({
+            "imports": {
+              "b": "./b/mod.ts",
+            },
+          }),
+        }),
+        sloppy_imports_options: SloppyImportsOptions::Disabled,
+        fs_cache_options: FsCacheOptions::Enabled,
+      },
+    )
+    .unwrap();
+    let root = url_from_directory_path(&root_dir()).unwrap();
+    match resolver
+      .resolve(
+        "b",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::Normal { specifier, .. } => {
+        assert_eq!(specifier, root.join("b/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+  }
+
+  #[test]
+  fn workspace_specified_import_map() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "workspace": ["./a"]
+      }),
+    );
+    sys.fs_insert_json(root_dir().join("a").join("deno.json"), json!({}));
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    WorkspaceResolver::from_workspace(
+      &workspace_dir.workspace,
+      UnreachableSys,
+      super::CreateResolverOptions {
+        pkg_json_dep_resolution: PackageJsonDepResolution::Enabled,
+        specified_import_map: Some(SpecifiedImportMap {
+          base_url: url_from_directory_path(&root_dir()).unwrap(),
+          value: json!({
+            "imports": {
+              "b": "./b/mod.ts",
+            },
+          }),
+        }),
+        sloppy_imports_options: SloppyImportsOptions::Disabled,
+        fs_cache_options: FsCacheOptions::Enabled,
+      },
+    )
+    .unwrap();
+  }
+
+  #[test]
+  fn resolves_patch_member_with_version() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "patch": ["../patch"]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("../patch/deno.json"),
+      json!({
+        "name": "@scope/patch",
+        "version": "1.0.0",
+        "exports": "./mod.ts"
+      }),
+    );
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace_dir);
+    let root = url_from_directory_path(&root_dir()).unwrap();
+    match resolver
+      .resolve(
+        "@scope/patch",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(specifier, root.join("../patch/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+    // matching version
+    match resolver
+      .resolve(
+        "jsr:@scope/patch@1",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(specifier, root.join("../patch/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+    // not matching version
+    match resolver
+      .resolve(
+        "jsr:@scope/patch@2",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::Normal {
+        specifier,
+        maybe_diagnostic,
+        ..
+      } => {
+        assert_eq!(specifier, Url::parse("jsr:@scope/patch@2").unwrap());
+        assert_eq!(
+          maybe_diagnostic,
+          Some(Box::new(
+            MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion {
+              is_patch: true,
+              reference: JsrPackageReqReference::from_str("jsr:@scope/patch@2")
+                .unwrap(),
+              local_version: Version::parse_from_npm("1.0.0").unwrap(),
+            }
+          ))
+        );
+      }
+      _ => unreachable!(),
+    }
+  }
+
+  #[test]
+  fn resolves_patch_member_no_version() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "patch": ["../patch"]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("../patch/deno.json"),
+      json!({
+        "name": "@scope/patch",
+        "exports": "./mod.ts"
+      }),
+    );
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace_dir);
+    let root = url_from_directory_path(&root_dir()).unwrap();
+    match resolver
+      .resolve(
+        "@scope/patch",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(specifier, root.join("../patch/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+    // always resolves, no matter what version
+    match resolver
+      .resolve(
+        "jsr:@scope/patch@12",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(specifier, root.join("../patch/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+  }
+
+  #[test]
+  fn resolves_workspace_member() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "workspace": ["./member"]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("./member/deno.json"),
+      json!({
+        "name": "@scope/member",
+        "version": "1.0.0",
+        "exports": "./mod.ts"
+      }),
+    );
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace_dir);
+    let root = url_from_directory_path(&root_dir()).unwrap();
+    match resolver
+      .resolve(
+        "@scope/member",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(specifier, root.join("./member/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+    // matching version
+    match resolver
+      .resolve(
+        "jsr:@scope/member@1",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(specifier, root.join("./member/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+    // not matching version
+    match resolver
+      .resolve(
+        "jsr:@scope/member@2",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::Normal {
+        specifier,
+        maybe_diagnostic,
+        ..
+      } => {
+        assert_eq!(specifier, Url::parse("jsr:@scope/member@2").unwrap());
+        assert_eq!(
+          maybe_diagnostic,
+          Some(Box::new(
+            MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion {
+              is_patch: false,
+              reference: JsrPackageReqReference::from_str(
+                "jsr:@scope/member@2"
+              )
+              .unwrap(),
+              local_version: Version::parse_from_npm("1.0.0").unwrap(),
+            }
+          ))
+        );
+      }
+      _ => unreachable!(),
+    }
+  }
+
+  #[test]
+  fn resolves_patch_workspace() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "imports": {
+          "@std/fs": "jsr:@std/fs@0.200.0"
+        },
+        "patch": ["../patch"]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("../patch/deno.json"),
+      json!({
+        "workspace": ["./member"]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("../patch/member/deno.json"),
+      json!({
+        "name": "@scope/patch",
+        "version": "1.0.0",
+        "exports": "./mod.ts",
+        "imports": {
+          "@std/fs": "jsr:@std/fs@1"
+        }
+      }),
+    );
+    let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace_dir);
+    let root = url_from_directory_path(&root_dir()).unwrap();
+    match resolver
+      .resolve(
+        "jsr:@scope/patch@1",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
+        assert_eq!(specifier, root.join("../patch/member/mod.ts").unwrap());
+      }
+      _ => unreachable!(),
+    }
+    // resolving @std/fs from root
+    match resolver
+      .resolve(
+        "@std/fs",
+        &root.join("main.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::Normal { specifier, .. } => {
+        assert_eq!(specifier, Url::parse("jsr:@std/fs@0.200.0").unwrap());
+      }
+      _ => unreachable!(),
+    }
+    // resolving @std/fs in patched package
+    match resolver
+      .resolve(
+        "@std/fs",
+        &root.join("../patch/member/mod.ts").unwrap(),
+        ResolutionKind::Execution,
+      )
+      .unwrap()
+    {
+      MappedResolution::Normal { specifier, .. } => {
+        assert_eq!(specifier, Url::parse("jsr:@std/fs@1").unwrap());
+      }
+      _ => unreachable!(),
+    }
+  }
+
+  #[test]
+  fn invalid_package_name_with_slashes() {
+    let sys = InMemorySys::default();
+    sys.fs_insert_json(
+      root_dir().join("deno.json"),
+      json!({
+        "workspace": ["./libs/math"]
+      }),
+    );
+    sys.fs_insert_json(
+      root_dir().join("libs/math/deno.json"),
+      json!({
+        "name": "@deno-test/libs/math", // Invalid package name containing slashes
+        "version": "1.0.0",
+        "exports": "./mod.ts"
+      }),
+    );
+    let workspace = workspace_at_start_dir(&sys, &root_dir());
+    let resolver = create_resolver(&workspace);
+    let result = resolver.resolve(
+      "@deno-test/libs/math",
+      &url_from_file_path(&root_dir().join("main.ts")).unwrap(),
+      ResolutionKind::Execution,
+    );
+    // Resolve shouldn't panic and tt should result in unmapped
+    // bare specifier error as the package name is invalid.
+    assert!(result.err().unwrap().is_unmapped_bare_specifier());
+
+    let diagnostics = workspace.workspace.diagnostics();
+    assert_eq!(diagnostics.len(), 1);
+    assert!(diagnostics
+      .first()
+      .unwrap()
+      .to_string()
+      .starts_with(r#"Invalid workspace member name "@deno-test/libs/math"."#));
+  }
+
+  fn create_resolver(
+    workspace_dir: &WorkspaceDirectory,
+  ) -> WorkspaceResolver<UnreachableSys> {
+    WorkspaceResolver::from_workspace(
+      &workspace_dir.workspace,
+      UnreachableSys,
+      super::CreateResolverOptions {
+        pkg_json_dep_resolution: PackageJsonDepResolution::Enabled,
+        specified_import_map: None,
+        sloppy_imports_options: SloppyImportsOptions::Disabled,
+        fs_cache_options: FsCacheOptions::Enabled,
+      },
+    )
+    .unwrap()
+  }
+
+  fn workspace_at_start_dir(
+    sys: &InMemorySys,
+    start_dir: &Path,
+  ) -> WorkspaceDirectory {
+    WorkspaceDirectory::discover(
+      sys,
+      WorkspaceDiscoverStart::Paths(&[start_dir.to_path_buf()]),
+      &WorkspaceDiscoverOptions {
+        discover_pkg_json: true,
+        ..Default::default()
+      },
+    )
+    .unwrap()
+  }
+}
diff --git a/resolvers/node/resolution.rs b/resolvers/node/resolution.rs
index 495c71bc8383ce..4ed7f9229bd8a3 100644
--- a/resolvers/node/resolution.rs
+++ b/resolvers/node/resolution.rs
@@ -110,15 +110,6 @@ pub enum NodeResolutionKind {
   Types,
 }
 
-impl From<NodeResolutionKind> for deno_config::workspace::ResolutionKind {
-  fn from(value: NodeResolutionKind) -> Self {
-    match value {
-      NodeResolutionKind::Execution => Self::Execution,
-      NodeResolutionKind::Types => Self::Types,
-    }
-  }
-}
-
 impl NodeResolutionKind {
   pub fn is_types(&self) -> bool {
     matches!(self, NodeResolutionKind::Types)


Test patch:
diff --git a/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/__test__.jsonc b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/__test__.jsonc
new file mode 100644
index 00000000000000..bf7e7e1c94c814
--- /dev/null
+++ b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/__test__.jsonc
@@ -0,0 +1,4 @@
+{
+  "args": "check --quiet subdir/mod.ts",
+  "output": ""
+}
diff --git a/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/deno.json b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/deno.json
new file mode 100644
index 00000000000000..d2148a5bb5be73
--- /dev/null
+++ b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/deno.json
@@ -0,0 +1,6 @@
+{
+  "compilerOptions": {
+    "rootDirs": ["subdir", "subdir_types"]
+  },
+  "unstable": ["sloppy-imports"]
+}
diff --git a/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/subdir/mod.ts b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/subdir/mod.ts
new file mode 100644
index 00000000000000..1de59ebda0eca9
--- /dev/null
+++ b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/subdir/mod.ts
@@ -0,0 +1,3 @@
+import type { someType } from "./import";
+const foo: someType = "";
+console.log(foo);
diff --git a/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/subdir_types/import.ts b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/subdir_types/import.ts
new file mode 100644
index 00000000000000..423236068f24f4
--- /dev/null
+++ b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/subdir_types/import.ts
@@ -0,0 +1,1 @@
+export type someType = string;