Repository: denoland/deno (language: rust)

Issue:
rootDirs and SvelteKit
deno 2.0.6 (stable, release, aarch64-apple-darwin)
v8 12.9.202.13-rusty
typescript 5.6.2

Hi,

I am using SvelteKit and I am trying to reproduce [this example](https://svelte.dev/docs/kit/load#Making-fetch-requests):

```ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ fetch, params }) => {
	const res = await fetch(`/api/items/${params.id}`);
	const item = await res.json();

	return { item };
};
```

But I have this error:
<img width="1126" alt="Screenshot 2024-11-14 at 9 53 54 AM" src="https://github.com/user-attachments/assets/b1fb5624-bd27-43f2-b204-cb4771829c47">


From what I understand, it's maybe because the default SvelteKit `tsconfig.json` has the `rootDirs` options set to `["..", "./types"]` but this is not supported by Deno and/or the Deno VSCode extension.

```ts
{
  "extends": "./.svelte-kit/tsconfig.json",
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "rootDirs": ["..", "./types"]
  }
  // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
  // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
  //
  // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
  // from the referenced tsconfig.json - TypeScript does not merge them in
}
```

Thank you!


Discussion / hints:
@nayeemrmn is already working on this

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index e29f791feca13c..5f609532a9a8ea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1528,9 +1528,9 @@ dependencies = [
 
 [[package]]
 name = "deno_config"
-version = "0.46.0"
+version = "0.47.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08fe512a72c4300bd997c6849450a1f050da0c909a2a4fbdc44891647392bacf"
+checksum = "2f7883c48549bab8e446a58c64ee3d106a13052d2ff5e864de765a60260cb02b"
 dependencies = [
  "boxed_error",
  "capacity_builder 0.5.0",
@@ -5278,6 +5278,7 @@ dependencies = [
  "async-trait",
  "boxed_error",
  "dashmap",
+ "deno_config",
  "deno_error",
  "deno_media_type",
  "deno_package_json",
@@ -7910,9 +7911,9 @@ dependencies = [
 
 [[package]]
 name = "sys_traits"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b46ac05dfbe9fd3a9703eff20e17f5b31e7b6a54daf27a421dcd56c7a27ecdd"
+checksum = "638f0e61b5134e56b2abdf4c704fd44672603f15ca09013f314649056f3fee4d"
 dependencies = [
  "filetime",
  "getrandom",
diff --git a/Cargo.toml b/Cargo.toml
index 61367d98f44b6e..4ffcd867c31ce7 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.181.0", path = "./bench_util" }
-deno_config = { version = "=0.46.0", features = ["workspace"] }
+deno_config = { version = "=0.47.1", features = ["workspace"] }
 deno_lockfile = "=0.24.0"
 deno_media_type = { version = "=0.2.5", features = ["module_specifier"] }
 deno_npm = "=0.27.2"
@@ -204,7 +204,7 @@ slab = "0.4"
 smallvec = "1.8"
 socket2 = { version = "0.5.3", features = ["all"] }
 spki = "0.7.2"
-sys_traits = "=0.1.7"
+sys_traits = "=0.1.8"
 tar = "=0.4.40"
 tempfile = "3.4.0"
 termcolor = "1.1.3"
diff --git a/cli/factory.rs b/cli/factory.rs
index e41fa1b73e91b4..d4ad8fae52073e 100644
--- a/cli/factory.rs
+++ b/cli/factory.rs
@@ -711,7 +711,7 @@ impl CliFactory {
 
   pub async fn workspace_resolver(
     &self,
-  ) -> Result<&Arc<WorkspaceResolver>, AnyError> {
+  ) -> Result<&Arc<WorkspaceResolver<CliSys>>, AnyError> {
     self.initialize_npm_resolution_if_managed().await?;
     self.resolver_factory()?.workspace_resolver().await
   }
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 44e9bf4dfd0c08..fd7337df263dc8 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -1343,9 +1343,11 @@ impl CodeActionCollection {
         .tree
         .data_for_specifier(file_referrer?)?;
       let workspace_resolver = config_data.resolver.clone();
-      let npm_ref = if let Ok(resolution) =
-        workspace_resolver.resolve(&dep_key, document.specifier())
-      {
+      let npm_ref = if let Ok(resolution) = workspace_resolver.resolve(
+        &dep_key,
+        document.specifier(),
+        deno_config::workspace::ResolutionKind::Execution,
+      ) {
         let specifier = match resolution {
           MappedResolution::Normal { specifier, .. }
           | MappedResolution::ImportMap { specifier, .. } => specifier,
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 4ca4255fea1c51..e4570031fb7884 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -1205,7 +1205,7 @@ pub struct ConfigData {
   pub vendor_dir: Option<PathBuf>,
   pub lockfile: Option<Arc<CliLockfile>>,
   pub npmrc: Option<Arc<ResolvedNpmRc>>,
-  pub resolver: Arc<WorkspaceResolver>,
+  pub resolver: Arc<WorkspaceResolver<CliSys>>,
   pub sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
   pub import_map_from_settings: Option<ModuleSpecifier>,
   pub unstable: BTreeSet<String>,
@@ -1572,7 +1572,7 @@ impl ConfigData {
     let resolver = member_dir
       .workspace
       .create_resolver(
-        &CliSys::default(),
+        CliSys::default(),
         CreateResolverOptions {
           pkg_json_dep_resolution,
           specified_import_map,
@@ -1593,11 +1593,14 @@ impl ConfigData {
           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!(
-        "  Import map diagnostics:\n{}",
+        "  Resolver diagnostics:\n{}",
         resolver
           .diagnostics()
           .iter()
diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs
index f0ab45f5aef69d..a21d0f7d9af8a3 100644
--- a/cli/lsp/resolver.rs
+++ b/cli/lsp/resolver.rs
@@ -851,6 +851,9 @@ impl<'a> ResolverFactory<'a> {
               Vec::new(),
               Vec::new(),
               PackageJsonDepResolution::Disabled,
+              Default::default(),
+              Default::default(),
+              self.sys.clone(),
             ))
           }),
         is_byonm: self.config_data.map(|d| d.byonm).unwrap_or(false),
diff --git a/cli/rt/run.rs b/cli/rt/run.rs
index 53190f24339e13..df5388e0d1a18e 100644
--- a/cli/rt/run.rs
+++ b/cli/rt/run.rs
@@ -104,7 +104,7 @@ struct SharedModuleLoaderState {
   npm_registry_permission_checker: NpmRegistryReadPermissionChecker<DenoRtSys>,
   npm_req_resolver: Arc<DenoRtNpmReqResolver>,
   vfs: Arc<FileBackedVfs>,
-  workspace_resolver: WorkspaceResolver,
+  workspace_resolver: WorkspaceResolver<DenoRtSys>,
 }
 
 impl SharedModuleLoaderState {
@@ -201,10 +201,11 @@ impl ModuleLoader for EmbeddedModuleLoader {
       );
     }
 
-    let mapped_resolution = self
-      .shared
-      .workspace_resolver
-      .resolve(raw_specifier, &referrer);
+    let mapped_resolution = self.shared.workspace_resolver.resolve(
+      raw_specifier,
+      &referrer,
+      deno_config::workspace::ResolutionKind::Execution,
+    );
 
     match mapped_resolution {
       Ok(MappedResolution::WorkspaceJsrPackage { specifier, .. }) => {
@@ -859,6 +860,9 @@ pub async fn run(
         .collect(),
       pkg_jsons,
       metadata.workspace_resolver.pkg_json_resolution,
+      Default::default(),
+      Default::default(),
+      sys.clone(),
     )
   };
   let code_cache = match metadata.code_cache_key {
diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json
index d644072f4cfa5b..ce1bad07c6e81c 100644
--- a/cli/schemas/config-file.v1.json
+++ b/cli/schemas/config-file.v1.json
@@ -175,6 +175,14 @@
           "default": false,
           "markdownDescription": "Raise an error when a function parameter isn't read\n\nSee more: https://www.typescriptlang.org/tsconfig#noUnusedParameters"
         },
+        "rootDirs": {
+          "description": "Declare many “virtual” directories acting as a single root.",
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "markdownDescription": "Declare many “virtual” directories acting as a single root.\n\nSee more: https://www.typescriptlang.org/tsconfig#rootDirs"
+        },
         "strict": {
           "description": "Enable all strict type checking options.",
           "type": "boolean",
diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs
index 59744a0c72509e..4f0ce5a3c00f63 100644
--- a/cli/standalone/binary.rs
+++ b/cli/standalone/binary.rs
@@ -62,6 +62,7 @@ use crate::http_util::HttpClientProvider;
 use crate::node::CliCjsCodeAnalyzer;
 use crate::npm::CliNpmResolver;
 use crate::resolver::CliCjsTracker;
+use crate::sys::CliSys;
 use crate::util::archive;
 use crate::util::progress_bar::ProgressBar;
 use crate::util::progress_bar::ProgressBarStyle;
@@ -194,7 +195,7 @@ pub struct DenoCompileBinaryWriter<'a> {
   emitter: &'a Emitter,
   http_client_provider: &'a HttpClientProvider,
   npm_resolver: &'a CliNpmResolver,
-  workspace_resolver: &'a WorkspaceResolver,
+  workspace_resolver: &'a WorkspaceResolver<CliSys>,
   npm_system_info: NpmSystemInfo,
 }
 
@@ -208,7 +209,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
     emitter: &'a Emitter,
     http_client_provider: &'a HttpClientProvider,
     npm_resolver: &'a CliNpmResolver,
-    workspace_resolver: &'a WorkspaceResolver,
+    workspace_resolver: &'a WorkspaceResolver<CliSys>,
     npm_system_info: NpmSystemInfo,
   ) -> Self {
     Self {
diff --git a/cli/tools/info.rs b/cli/tools/info.rs
index e297cd5b72a3eb..e6bf1baa36b5d2 100644
--- a/cli/tools/info.rs
+++ b/cli/tools/info.rs
@@ -56,9 +56,11 @@ pub async fn info(
     let cwd_url =
       url::Url::from_directory_path(cli_options.initial_cwd()).unwrap();
 
-    let maybe_import_specifier = if let Ok(resolved) =
-      resolver.resolve(&specifier, &cwd_url)
-    {
+    let maybe_import_specifier = if let Ok(resolved) = resolver.resolve(
+      &specifier,
+      &cwd_url,
+      deno_config::workspace::ResolutionKind::Execution,
+    ) {
       match resolved {
         deno_config::workspace::MappedResolution::Normal {
           specifier, ..
diff --git a/cli/tools/lint/rules/mod.rs b/cli/tools/lint/rules/mod.rs
index c3d96e46aa4f85..f8c65428aca6ad 100644
--- a/cli/tools/lint/rules/mod.rs
+++ b/cli/tools/lint/rules/mod.rs
@@ -16,6 +16,7 @@ use deno_lint::rules::LintRule;
 use deno_lint::tags;
 
 use crate::resolver::CliSloppyImportsResolver;
+use crate::sys::CliSys;
 
 mod no_sloppy_imports;
 mod no_slow_types;
@@ -141,13 +142,13 @@ impl ConfiguredRules {
 
 pub struct LintRuleProvider {
   sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
-  workspace_resolver: Option<Arc<WorkspaceResolver>>,
+  workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
 }
 
 impl LintRuleProvider {
   pub fn new(
     sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
-    workspace_resolver: Option<Arc<WorkspaceResolver>>,
+    workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
   ) -> Self {
     Self {
       sloppy_imports_resolver,
diff --git a/cli/tools/lint/rules/no_sloppy_imports.rs b/cli/tools/lint/rules/no_sloppy_imports.rs
index cf84b3b24f99f6..34eeef521d95ae 100644
--- a/cli/tools/lint/rules/no_sloppy_imports.rs
+++ b/cli/tools/lint/rules/no_sloppy_imports.rs
@@ -24,18 +24,19 @@ 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>>,
+  workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
 }
 
 impl NoSloppyImportsRule {
   pub fn new(
     sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
-    workspace_resolver: Option<Arc<WorkspaceResolver>>,
+    workspace_resolver: Option<Arc<WorkspaceResolver<CliSys>>>,
   ) -> Self {
     NoSloppyImportsRule {
       sloppy_imports_resolver,
@@ -174,7 +175,7 @@ impl LintRule for NoSloppyImportsRule {
 
 #[derive(Debug)]
 struct SloppyImportCaptureResolver<'a> {
-  workspace_resolver: &'a WorkspaceResolver,
+  workspace_resolver: &'a WorkspaceResolver<CliSys>,
   sloppy_imports_resolver: &'a CliSloppyImportsResolver,
   captures: RefCell<HashMap<Range, SloppyImportsResolution>>,
 }
@@ -188,7 +189,18 @@ impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
   ) -> Result<deno_ast::ModuleSpecifier, deno_graph::source::ResolveError> {
     let resolution = self
       .workspace_resolver
-      .resolve(specifier_text, &referrer_range.specifier)
+      .resolve(
+        specifier_text,
+        &referrer_range.specifier,
+        match resolution_kind {
+          ResolutionKind::Execution => {
+            deno_config::workspace::ResolutionKind::Execution
+          }
+          ResolutionKind::Types => {
+            deno_config::workspace::ResolutionKind::Types
+          }
+        },
+      )
       .map_err(|err| ResolveError::Other(JsErrorBox::from_err(err)))?;
 
     match resolution {
diff --git a/cli/tools/registry/unfurl.rs b/cli/tools/registry/unfurl.rs
index 66610217f3f61e..469a19fdf4124f 100644
--- a/cli/tools/registry/unfurl.rs
+++ b/cli/tools/registry/unfurl.rs
@@ -33,6 +33,7 @@ use deno_semver::Version;
 use deno_semver::VersionReq;
 
 use crate::resolver::CliSloppyImportsResolver;
+use crate::sys::CliSys;
 
 #[derive(Debug, Clone)]
 pub enum SpecifierUnfurlerDiagnostic {
@@ -190,14 +191,14 @@ enum UnfurlSpecifierError {
 
 pub struct SpecifierUnfurler {
   sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
-  workspace_resolver: Arc<WorkspaceResolver>,
+  workspace_resolver: Arc<WorkspaceResolver<CliSys>>,
   bare_node_builtins: bool,
 }
 
 impl SpecifierUnfurler {
   pub fn new(
     sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
-    workspace_resolver: Arc<WorkspaceResolver>,
+    workspace_resolver: Arc<WorkspaceResolver<CliSys>>,
     bare_node_builtins: bool,
   ) -> Self {
     debug_assert_eq!(
@@ -252,9 +253,11 @@ impl SpecifierUnfurler {
     specifier: &str,
     resolution_kind: SloppyImportsResolutionKind,
   ) -> Result<Option<String>, UnfurlSpecifierError> {
-    let resolved = if let Ok(resolved) =
-      self.workspace_resolver.resolve(specifier, referrer)
-    {
+    let resolved = if let Ok(resolved) = self.workspace_resolver.resolve(
+      specifier,
+      referrer,
+      resolution_kind.into(),
+    ) {
       match resolved {
         MappedResolution::Normal { specifier, .. }
         | MappedResolution::ImportMap { specifier, .. } => Some(specifier),
@@ -758,6 +761,9 @@ mod tests {
       }],
       vec![Arc::new(package_json)],
       deno_config::workspace::PackageJsonDepResolution::Enabled,
+      Default::default(),
+      Default::default(),
+      CliSys::default(),
     );
     let unfurler = SpecifierUnfurler::new(
       Some(Arc::new(CliSloppyImportsResolver::new(
@@ -900,6 +906,7 @@ export type * from "./c.d.ts";
       cwd.join("package.json"),
       json!({ "workspaces": ["./publish", "./subtract", "./add"] }),
     );
+    let sys = CliSys::default();
     let workspace_resolver = WorkspaceResolver::new_raw(
       Arc::new(ModuleSpecifier::from_directory_path(&cwd).unwrap()),
       None,
@@ -920,8 +927,10 @@ export type * from "./c.d.ts";
         Arc::new(pkg_json_publishing),
       ],
       deno_config::workspace::PackageJsonDepResolution::Enabled,
+      Default::default(),
+      Default::default(),
+      sys.clone(),
     );
-    let sys = CliSys::default();
     let unfurler = SpecifierUnfurler::new(
       Some(Arc::new(CliSloppyImportsResolver::new(
         SloppyImportsCachedFs::new(sys),
diff --git a/resolvers/deno/factory.rs b/resolvers/deno/factory.rs
index 95db8415d4798a..e993d8c7342162 100644
--- a/resolvers/deno/factory.rs
+++ b/resolvers/deno/factory.rs
@@ -203,43 +203,32 @@ pub struct WorkspaceFactoryOptions<
 pub type WorkspaceFactoryRc<TSys> =
   crate::sync::MaybeArc<WorkspaceFactory<TSys>>;
 
-pub struct WorkspaceFactory<
-  TSys: EnvCacheDir
-    + EnvHomeDir
-    + EnvVar
-    + EnvCurrentDir
-    + FsCanonicalize
-    + FsCreateDirAll
-    + FsMetadata
-    + FsOpen
-    + FsRead
-    + FsReadDir
-    + FsRemoveFile
-    + FsRename
-    + SystemRandom
-    + SystemTimeNow
-    + ThreadSleep
-    + std::fmt::Debug
-    + MaybeSend
-    + MaybeSync
-    + Clone
-    + 'static,
-> {
-  sys: TSys,
-  deno_dir_path: DenoDirPathProviderRc<TSys>,
-  global_http_cache: Deferred<GlobalHttpCacheRc<TSys>>,
-  http_cache: Deferred<HttpCacheRc>,
-  node_modules_dir_path: Deferred<Option<PathBuf>>,
-  npm_cache_dir: Deferred<NpmCacheDirRc>,
-  npmrc: Deferred<ResolvedNpmRcRc>,
-  node_modules_dir_mode: Deferred<NodeModulesDirMode>,
-  workspace_directory: Deferred<WorkspaceDirectoryRc>,
-  initial_cwd: PathBuf,
-  options: WorkspaceFactoryOptions<TSys>,
+pub trait WorkspaceFactorySys:
+  EnvCacheDir
+  + EnvHomeDir
+  + EnvVar
+  + EnvCurrentDir
+  + FsCanonicalize
+  + FsCreateDirAll
+  + FsMetadata
+  + FsOpen
+  + FsRead
+  + FsReadDir
+  + FsRemoveFile
+  + FsRename
+  + SystemRandom
+  + SystemTimeNow
+  + ThreadSleep
+  + std::fmt::Debug
+  + MaybeSend
+  + MaybeSync
+  + Clone
+  + 'static
+{
 }
 
 impl<
-    TSys: EnvCacheDir
+    T: EnvCacheDir
       + EnvHomeDir
       + EnvVar
       + EnvCurrentDir
@@ -259,8 +248,25 @@ impl<
       + MaybeSync
       + Clone
       + 'static,
-  > WorkspaceFactory<TSys>
+  > WorkspaceFactorySys for T
 {
+}
+
+pub struct WorkspaceFactory<TSys: WorkspaceFactorySys> {
+  sys: TSys,
+  deno_dir_path: DenoDirPathProviderRc<TSys>,
+  global_http_cache: Deferred<GlobalHttpCacheRc<TSys>>,
+  http_cache: Deferred<HttpCacheRc>,
+  node_modules_dir_path: Deferred<Option<PathBuf>>,
+  npm_cache_dir: Deferred<NpmCacheDirRc>,
+  npmrc: Deferred<ResolvedNpmRcRc>,
+  node_modules_dir_mode: Deferred<NodeModulesDirMode>,
+  workspace_directory: Deferred<WorkspaceDirectoryRc>,
+  initial_cwd: PathBuf,
+  options: WorkspaceFactoryOptions<TSys>,
+}
+
+impl<TSys: WorkspaceFactorySys> WorkspaceFactory<TSys> {
   pub fn new(
     sys: TSys,
     initial_cwd: PathBuf,
@@ -561,28 +567,7 @@ pub struct ResolverFactoryOptions {
   pub unstable_sloppy_imports: bool,
 }
 
-pub struct ResolverFactory<
-  TSys: EnvCacheDir
-    + EnvCurrentDir
-    + EnvHomeDir
-    + EnvVar
-    + FsCanonicalize
-    + FsCreateDirAll
-    + FsMetadata
-    + FsOpen
-    + FsRead
-    + FsReadDir
-    + FsRemoveFile
-    + FsRename
-    + ThreadSleep
-    + SystemRandom
-    + SystemTimeNow
-    + std::fmt::Debug
-    + MaybeSend
-    + MaybeSync
-    + Clone
-    + 'static,
-> {
+pub struct ResolverFactory<TSys: WorkspaceFactorySys> {
   options: ResolverFactoryOptions,
   deno_resolver: async_once_cell::OnceCell<DefaultDenoResolverRc<TSys>>,
   in_npm_package_checker: Deferred<DenoInNpmPackageChecker>,
@@ -608,32 +593,10 @@ pub struct ResolverFactory<
   sloppy_imports_resolver:
     Deferred<Option<SloppyImportsResolverRc<SloppyImportsCachedFs<TSys>>>>,
   workspace_factory: WorkspaceFactoryRc<TSys>,
-  workspace_resolver: async_once_cell::OnceCell<WorkspaceResolverRc>,
+  workspace_resolver: async_once_cell::OnceCell<WorkspaceResolverRc<TSys>>,
 }
 
-impl<
-    TSys: EnvCacheDir
-      + EnvCurrentDir
-      + EnvHomeDir
-      + EnvVar
-      + FsCanonicalize
-      + FsCreateDirAll
-      + FsMetadata
-      + FsOpen
-      + FsRead
-      + FsReadDir
-      + FsRemoveFile
-      + FsRename
-      + ThreadSleep
-      + SystemRandom
-      + SystemTimeNow
-      + std::fmt::Debug
-      + MaybeSend
-      + MaybeSync
-      + Clone
-      + 'static,
-  > ResolverFactory<TSys>
-{
+impl<TSys: WorkspaceFactorySys> ResolverFactory<TSys> {
   pub fn new(
     workspace_factory: WorkspaceFactoryRc<TSys>,
     options: ResolverFactoryOptions,
@@ -834,7 +797,7 @@ impl<
 
   pub async fn workspace_resolver(
     &self,
-  ) -> Result<&WorkspaceResolverRc, anyhow::Error> {
+  ) -> Result<&WorkspaceResolverRc<TSys>, anyhow::Error> {
     self
       .workspace_resolver
       .get_or_try_init(
@@ -865,13 +828,13 @@ impl<
             },
             specified_import_map,
           };
-          let resolver =
-            workspace.create_resolver(&self.workspace_factory.sys, options)?;
+          let resolver = workspace
+            .create_resolver(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
             log::warn!(
-              "Import map diagnostics:\n{}",
+              "Resolver diagnostics:\n{}",
               resolver
                 .diagnostics()
                 .iter()
diff --git a/resolvers/deno/lib.rs b/resolvers/deno/lib.rs
index 0ac073821cb671..26272af3bcb1ca 100644
--- a/resolvers/deno/lib.rs
+++ b/resolvers/deno/lib.rs
@@ -49,7 +49,8 @@ pub mod sloppy_imports;
 mod sync;
 
 #[allow(clippy::disallowed_types)]
-pub type WorkspaceResolverRc = crate::sync::MaybeArc<WorkspaceResolver>;
+pub type WorkspaceResolverRc<TSys> =
+  crate::sync::MaybeArc<WorkspaceResolver<TSys>>;
 
 #[allow(clippy::disallowed_types)]
 pub(crate) type NpmCacheDirRc = crate::sync::MaybeArc<NpmCacheDir>;
@@ -141,7 +142,7 @@ pub struct DenoResolverOptions<
   >,
   pub sloppy_imports_resolver:
     Option<SloppyImportsResolverRc<TSloppyImportResolverFs>>,
-  pub workspace_resolver: WorkspaceResolverRc,
+  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
   /// what already exists on the file system.
@@ -197,7 +198,7 @@ pub struct DenoResolver<
   >,
   sloppy_imports_resolver:
     Option<SloppyImportsResolverRc<TSloppyImportResolverFs>>,
-  workspace_resolver: WorkspaceResolverRc,
+  workspace_resolver: WorkspaceResolverRc<TSys>,
   is_byonm: bool,
   maybe_vendor_specifier: Option<Url>,
 }
@@ -269,7 +270,7 @@ impl<
     // Attempt to resolve with the workspace resolver
     let result: Result<_, DenoResolveError> = self
       .workspace_resolver
-      .resolve(raw_specifier, referrer)
+      .resolve(raw_specifier, referrer, resolution_kind.into())
       .map_err(|err| err.into());
     let result = match result {
       Ok(resolution) => match resolution {
diff --git a/resolvers/deno/sloppy_imports.rs b/resolvers/deno/sloppy_imports.rs
index 486d2dab1e8902..8a43be16aa3917 100644
--- a/resolvers/deno/sloppy_imports.rs
+++ b/resolvers/deno/sloppy_imports.rs
@@ -97,6 +97,17 @@ impl SloppyImportsResolutionKind {
   }
 }
 
+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>;
 
diff --git a/resolvers/node/Cargo.toml b/resolvers/node/Cargo.toml
index 0fef5205336789..03065dc05623ea 100644
--- a/resolvers/node/Cargo.toml
+++ b/resolvers/node/Cargo.toml
@@ -21,6 +21,7 @@ anyhow.workspace = true
 async-trait.workspace = true
 boxed_error.workspace = true
 dashmap.workspace = true
+deno_config.workspace = true
 deno_error.workspace = true
 deno_media_type.workspace = true
 deno_package_json.workspace = true
diff --git a/resolvers/node/resolution.rs b/resolvers/node/resolution.rs
index 0764698998dd7d..481e4d679f581f 100644
--- a/resolvers/node/resolution.rs
+++ b/resolvers/node/resolution.rs
@@ -109,6 +109,15 @@ 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/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs
index dad292951d5641..a07d71a1cbdbb1 100644
--- a/tests/integration/lsp_tests.rs
+++ b/tests/integration/lsp_tests.rs
@@ -306,7 +306,6 @@ fn lsp_import_map() {
   }
 }"#;
   temp_dir.write("import-map.json", import_map);
-  temp_dir.create_dir_all("lib");
   temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
 
   let mut client = context.new_lsp_command().build();
@@ -439,7 +438,6 @@ fn lsp_import_map_config_file() {
   }
 }"#,
   );
-  temp_dir.create_dir_all("lib");
   temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
 
   let mut client = context.new_lsp_command().build();
@@ -501,7 +499,6 @@ fn lsp_import_map_embedded_in_config_file() {
   }
 }"#,
   );
-  temp_dir.create_dir_all("lib");
   temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
 
   let mut client = context.new_lsp_command().build();
@@ -555,7 +552,6 @@ fn lsp_import_map_embedded_in_config_file_after_initialize() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
   temp_dir.write("deno.embedded_import_map.jsonc", "{}");
-  temp_dir.create_dir_all("lib");
   temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
 
   let mut client = context.new_lsp_command().build();
@@ -627,7 +623,6 @@ fn lsp_import_map_embedded_in_config_file_after_initialize() {
 fn lsp_import_map_config_file_auto_discovered() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("lib");
   temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
 
   let mut client = context.new_lsp_command().capture_stderr().build();
@@ -747,7 +742,6 @@ fn lsp_import_map_config_file_auto_discovered_symlink() {
     .use_temp_cwd()
     .build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("lib");
   temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
 
   let mut client = context.new_lsp_command().capture_stderr().build();
@@ -755,7 +749,6 @@ fn lsp_import_map_config_file_auto_discovered_symlink() {
 
   // now create a symlink in the current directory to a subdir/deno.json
   // and ensure the watched files notification still works
-  temp_dir.create_dir_all("subdir");
   temp_dir.write("subdir/deno.json", r#"{ }"#);
   temp_dir.symlink_file(
     temp_dir.path().join("subdir").join("deno.json"),
@@ -1052,10 +1045,6 @@ fn lsp_workspace_enable_paths_no_workspace_configuration() {
 fn lsp_did_refresh_deno_configuration_tree_notification() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("workspace/member1");
-  temp_dir.create_dir_all("workspace/member2");
-  temp_dir.create_dir_all("non_workspace1");
-  temp_dir.create_dir_all("non_workspace2");
   temp_dir.write(
     "workspace/deno.json",
     json!({
@@ -1541,7 +1530,6 @@ fn lsp_import_map_import_completions() {
     })
     .to_string(),
   );
-  temp_dir.create_dir_all("lib");
   temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
 
   let mut client = context.new_lsp_command().build();
@@ -2168,7 +2156,6 @@ fn lsp_workspace_disable_enable_paths() {
   fn run_test(use_trailing_slash: bool) {
     let context = TestContextBuilder::new().use_temp_cwd().build();
     let temp_dir = context.temp_dir();
-    temp_dir.create_dir_all("worker");
     temp_dir.write("worker/shared.ts", "export const a = 1");
     temp_dir.write("worker/other.ts", "import { a } from './shared.ts';\na;");
     temp_dir.write("worker/node.ts", "Buffer.alloc(1);");
@@ -2386,13 +2373,11 @@ fn lsp_workspace_disable_enable_paths() {
 fn lsp_exclude_config() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("other");
   temp_dir.write(
     "other/shared.ts",
     // this should not be found in the "find references" since this file is excluded
     "import { a } from '../worker/shared.ts'; console.log(a);",
   );
-  temp_dir.create_dir_all("worker");
   temp_dir.write("worker/shared.ts", "export const a = 1");
   temp_dir.write(
     "deno.json",
@@ -6425,7 +6410,6 @@ fn lsp_code_actions_deno_types_for_npm() {
     })
     .to_string(),
   );
-  temp_dir.create_dir_all("managed_node_modules");
   temp_dir.write(
     "managed_node_modules/deno.json",
     json!({
@@ -13848,7 +13832,6 @@ fn lsp_closed_file_find_references() {
 fn lsp_closed_file_find_references_low_document_pre_load() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("sub_dir");
   temp_dir.write("./other_file.ts", "export const b = 5;");
   temp_dir.write("./sub_dir/mod.ts", "export const a = 5;");
   temp_dir.write(
@@ -13893,8 +13876,6 @@ fn lsp_closed_file_find_references_excluded_path() {
   // the config file from being pre-loaded
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("sub_dir");
-  temp_dir.create_dir_all("other_dir/sub_dir");
   temp_dir.write("./sub_dir/mod.ts", "export const a = 5;");
   temp_dir.write(
     "./sub_dir/mod.test.ts",
@@ -14374,8 +14355,6 @@ fn lsp_vendor_dir() {
 fn lsp_deno_json_scopes_import_map() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2/project3");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -14508,8 +14487,6 @@ fn lsp_deno_json_scopes_vendor_dir() {
     .use_temp_cwd()
     .build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2/project3");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -14693,8 +14670,6 @@ fn lsp_deno_json_scopes_node_modules_dir() {
     .use_temp_cwd()
     .build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2/project3");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -14878,8 +14853,6 @@ fn lsp_deno_json_scopes_node_modules_dir() {
 fn lsp_deno_json_scopes_ts_config() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write(
     "project2/deno.json",
@@ -14952,8 +14925,6 @@ fn lsp_deno_json_scopes_ts_config() {
 fn lsp_deno_json_scopes_declaration_files() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write("project2/deno.json", json!({}).to_string());
   temp_dir.write("project1/foo.d.ts", "declare type Foo = number;\n");
@@ -15020,8 +14991,6 @@ fn lsp_deno_json_scopes_declaration_files() {
 fn lsp_deno_json_scopes_find_references() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write("project2/deno.json", json!({}).to_string());
   let file1 = source_file(
@@ -15064,8 +15033,6 @@ fn lsp_deno_json_scopes_find_references() {
 fn lsp_deno_json_scopes_file_rename_import_edits() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write("project2/deno.json", json!({}).to_string());
   let file1 = source_file(temp_dir.path().join("project1/file.ts"), "");
@@ -15112,8 +15079,6 @@ fn lsp_deno_json_scopes_file_rename_import_edits() {
 fn lsp_deno_json_scopes_goto_implementations() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write("project2/deno.json", json!({}).to_string());
   let file1 = source_file(
@@ -15153,9 +15118,6 @@ fn lsp_deno_json_scopes_goto_implementations() {
 fn lsp_deno_json_scopes_call_hierarchy() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
-  temp_dir.create_dir_all("project3");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write("project2/deno.json", json!({}).to_string());
   temp_dir.write("project3/deno.json", json!({}).to_string());
@@ -15266,8 +15228,6 @@ fn lsp_deno_json_scopes_call_hierarchy() {
 fn lsp_deno_json_scopes_rename_symbol() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write("project2/deno.json", json!({}).to_string());
   let file1 = source_file(
@@ -15326,8 +15286,6 @@ fn lsp_deno_json_scopes_rename_symbol() {
 fn lsp_deno_json_scopes_search_symbol() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1");
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project1/deno.json", json!({}).to_string());
   temp_dir.write("project2/deno.json", json!({}).to_string());
   let file1 = source_file(
@@ -15382,7 +15340,6 @@ fn lsp_deno_json_workspace_fmt_config() {
     })
     .to_string(),
   );
-  temp_dir.create_dir_all("project1");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -15392,7 +15349,6 @@ fn lsp_deno_json_workspace_fmt_config() {
     })
     .to_string(),
   );
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project2/deno.json", json!({}).to_string());
   let mut client = context.new_lsp_command().build();
   client.initialize_default();
@@ -15507,7 +15463,6 @@ fn lsp_deno_json_workspace_lint_config() {
     })
     .to_string(),
   );
-  temp_dir.create_dir_all("project1");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -15519,7 +15474,6 @@ fn lsp_deno_json_workspace_lint_config() {
     })
     .to_string(),
   );
-  temp_dir.create_dir_all("project2");
   temp_dir.write("project2/deno.json", json!({}).to_string());
   let mut client = context.new_lsp_command().build();
   client.initialize_default();
@@ -15644,7 +15598,6 @@ fn lsp_deno_json_workspace_lint_config() {
 fn lsp_deno_json_workspace_import_map() {
   let context = TestContextBuilder::new().use_temp_cwd().build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1/project2");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -15747,7 +15700,6 @@ fn lsp_workspace_lockfile() {
     .use_temp_cwd()
     .build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1/project2");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -15812,7 +15764,6 @@ fn lsp_deno_json_workspace_vendor_dir() {
     .use_temp_cwd()
     .build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1/project2");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -15884,7 +15835,6 @@ fn lsp_deno_json_workspace_node_modules_dir() {
     .use_temp_cwd()
     .build();
   let temp_dir = context.temp_dir();
-  temp_dir.create_dir_all("project1/project2");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -15963,7 +15913,6 @@ fn lsp_deno_json_workspace_jsr_resolution() {
     })
     .to_string(),
   );
-  temp_dir.create_dir_all("project1");
   temp_dir.write(
     "project1/deno.json",
     json!({
@@ -16013,6 +15962,66 @@ fn lsp_deno_json_workspace_jsr_resolution() {
   client.shutdown();
 }
 
+#[test]
+fn lsp_workspace_compiler_options_root_dirs() {
+  let context = TestContextBuilder::new().use_temp_cwd().build();
+  let temp_dir = context.temp_dir();
+  temp_dir.write(
+    "deno.json",
+    json!({
+      "workspace": ["member"],
+      "compilerOptions": {
+        "rootDirs": ["member", "non_member", "non_member_types"],
+      },
+    })
+    .to_string(),
+  );
+  temp_dir.write(
+    "member/deno.json",
+    json!({
+      "compilerOptions": {
+        "rootDirs": ["subdir", "subdir_types"],
+      },
+    })
+    .to_string(),
+  );
+  let file1 = source_file(
+    temp_dir.path().join("member/subdir/mod.ts"),
+    r#"
+      import type { someType } from "./import.ts";
+      const foo: someType = "";
+      console.log(foo);
+    "#,
+  );
+  source_file(
+    temp_dir.path().join("member/subdir_types/import.ts"),
+    r#"
+      export type someType = string;
+    "#,
+  );
+  let file2 = source_file(
+    temp_dir.path().join("non_member/mod.ts"),
+    r#"
+      import type { someType } from "./import.ts";
+      const foo: someType = 0;
+      console.log(foo);
+    "#,
+  );
+  source_file(
+    temp_dir.path().join("non_member_types/import.ts"),
+    r#"
+      export type someType = number;
+    "#,
+  );
+  let mut client = context.new_lsp_command().build();
+  client.initialize_default();
+  let diagnostics = client.did_open_file(&file1);
+  assert_eq!(json!(diagnostics.all()), json!([]));
+  let diagnostics = client.did_open_file(&file2);
+  assert_eq!(json!(diagnostics.all()), json!([]));
+  client.shutdown();
+}
+
 #[test]
 fn lsp_npm_workspace() {
   let context = TestContextBuilder::new()
@@ -16037,7 +16046,6 @@ fn lsp_npm_workspace() {
     .to_string(),
   );
   {
-    temp_dir.create_dir_all("packages/add");
     temp_dir.write(
       "packages/add/package.json",
       json!({
@@ -16053,7 +16061,6 @@ fn lsp_npm_workspace() {
     );
   }
   {
-    temp_dir.create_dir_all("packages/subtract");
     temp_dir.write(
       "packages/subtract/package.json",
       json!({
diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/__test__.jsonc b/tests/specs/check/workspace_compiler_options_root_dirs/__test__.jsonc
new file mode 100644
index 00000000000000..8e273fb5122b11
--- /dev/null
+++ b/tests/specs/check/workspace_compiler_options_root_dirs/__test__.jsonc
@@ -0,0 +1,4 @@
+{
+  "args": "check --quiet member/subdir/mod.ts non_member/mod.ts",
+  "output": ""
+}
diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/deno.json b/tests/specs/check/workspace_compiler_options_root_dirs/deno.json
new file mode 100644
index 00000000000000..828a3b51831823
--- /dev/null
+++ b/tests/specs/check/workspace_compiler_options_root_dirs/deno.json
@@ -0,0 +1,6 @@
+{
+  "workspace": ["member"],
+  "compilerOptions": {
+    "rootDirs": ["member", "non_member", "non_member_types"]
+  }
+}
diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/member/deno.json b/tests/specs/check/workspace_compiler_options_root_dirs/member/deno.json
new file mode 100644
index 00000000000000..6380aaefad9257
--- /dev/null
+++ b/tests/specs/check/workspace_compiler_options_root_dirs/member/deno.json
@@ -0,0 +1,5 @@
+{
+  "compilerOptions": {
+    "rootDirs": ["subdir", "subdir_types"]
+  }
+}
diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/member/subdir/mod.ts b/tests/specs/check/workspace_compiler_options_root_dirs/member/subdir/mod.ts
new file mode 100644
index 00000000000000..68aee671dc5954
--- /dev/null
+++ b/tests/specs/check/workspace_compiler_options_root_dirs/member/subdir/mod.ts
@@ -0,0 +1,3 @@
+import type { someType } from "./import.ts";
+const foo: someType = "";
+console.log(foo);
diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/member/subdir_types/import.ts b/tests/specs/check/workspace_compiler_options_root_dirs/member/subdir_types/import.ts
new file mode 100644
index 00000000000000..423236068f24f4
--- /dev/null
+++ b/tests/specs/check/workspace_compiler_options_root_dirs/member/subdir_types/import.ts
@@ -0,0 +1,1 @@
+export type someType = string;
diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/non_member/mod.ts b/tests/specs/check/workspace_compiler_options_root_dirs/non_member/mod.ts
new file mode 100644
index 00000000000000..ef2ed1e0b6bd7c
--- /dev/null
+++ b/tests/specs/check/workspace_compiler_options_root_dirs/non_member/mod.ts
@@ -0,0 +1,3 @@
+import type { someType } from "./import.ts";
+const foo: someType = 0;
+console.log(foo);
diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/non_member_types/import.ts b/tests/specs/check/workspace_compiler_options_root_dirs/non_member_types/import.ts
new file mode 100644
index 00000000000000..c891e2e06aa693
--- /dev/null
+++ b/tests/specs/check/workspace_compiler_options_root_dirs/non_member_types/import.ts
@@ -0,0 +1,1 @@
+export type someType = number;
diff --git a/tests/util/server/src/fs.rs b/tests/util/server/src/fs.rs
index 19b94731eb26a9..062ca59ef3a202 100644
--- a/tests/util/server/src/fs.rs
+++ b/tests/util/server/src/fs.rs
@@ -162,7 +162,11 @@ impl PathRef {
 
   #[track_caller]
   pub fn rename(&self, to: impl AsRef<Path>) {
-    fs::rename(self, self.join(to)).unwrap();
+    let to = self.join(to);
+    if let Some(parent_path) = to.as_path().parent() {
+      fs::create_dir_all(parent_path).unwrap()
+    }
+    fs::rename(self, to).unwrap();
   }
 
   #[track_caller]
@@ -173,6 +177,9 @@ impl PathRef {
 
   #[track_caller]
   pub fn write(&self, text: impl AsRef<[u8]>) {
+    if let Some(parent_path) = self.as_path().parent() {
+      fs::create_dir_all(parent_path).unwrap()
+    }
     fs::write(self, text).unwrap();
   }
 
@@ -188,17 +195,20 @@ impl PathRef {
     oldpath: impl AsRef<Path>,
     newpath: impl AsRef<Path>,
   ) {
+    let oldpath = self.as_path().join(oldpath);
+    let newpath = self.as_path().join(newpath);
+    if let Some(parent_path) = newpath.parent() {
+      fs::create_dir_all(parent_path).unwrap()
+    }
     #[cfg(unix)]
     {
       use std::os::unix::fs::symlink;
-      symlink(self.as_path().join(oldpath), self.as_path().join(newpath))
-        .unwrap();
+      symlink(oldpath, newpath).unwrap();
     }
     #[cfg(not(unix))]
     {
       use std::os::windows::fs::symlink_dir;
-      symlink_dir(self.as_path().join(oldpath), self.as_path().join(newpath))
-        .unwrap();
+      symlink_dir(oldpath, newpath).unwrap();
     }
   }
 
@@ -208,17 +218,20 @@ impl PathRef {
     oldpath: impl AsRef<Path>,
     newpath: impl AsRef<Path>,
   ) {
+    let oldpath = self.as_path().join(oldpath);
+    let newpath = self.as_path().join(newpath);
+    if let Some(parent_path) = newpath.as_path().parent() {
+      fs::create_dir_all(parent_path).unwrap()
+    }
     #[cfg(unix)]
     {
       use std::os::unix::fs::symlink;
-      symlink(self.as_path().join(oldpath), self.as_path().join(newpath))
-        .unwrap();
+      symlink(oldpath, newpath).unwrap();
     }
     #[cfg(not(unix))]
     {
       use std::os::windows::fs::symlink_file;
-      symlink_file(self.as_path().join(oldpath), self.as_path().join(newpath))
-        .unwrap();
+      symlink_file(oldpath, newpath).unwrap();
     }
   }