summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-07-25 19:08:14 -0400
committerGitHub <noreply@github.com>2024-07-25 19:08:14 -0400
commit3bf147fe287ac779b20d318daba56b336f356adf (patch)
tree3b5bfe2a1ad918b275a2cd08f7dcc05f90a180ab /ext
parent0cf7f268a7df7711ac6ab8c2c67b4d7abf454fcd (diff)
refactor: decouple node resolution from deno_core (#24724)
Diffstat (limited to 'ext')
-rw-r--r--ext/fs/clippy.toml38
-rw-r--r--ext/fs/sync.rs62
-rw-r--r--ext/node/Cargo.toml3
-rw-r--r--ext/node/global.rs2
-rw-r--r--ext/node/lib.rs161
-rw-r--r--ext/node/ops/require.rs17
-rw-r--r--ext/node/ops/worker_threads.rs14
-rw-r--r--ext/node/path.rs50
-rw-r--r--ext/node/polyfill.rs14
-rw-r--r--ext/node_resolver/Cargo.toml32
-rw-r--r--ext/node_resolver/README.md6
-rw-r--r--ext/node_resolver/analyze.rs (renamed from ext/node/analyze.rs)63
-rw-r--r--ext/node_resolver/clippy.toml48
-rw-r--r--ext/node_resolver/env.rs39
-rw-r--r--ext/node_resolver/errors.rs (renamed from ext/node/errors.rs)32
-rw-r--r--ext/node_resolver/lib.rs26
-rw-r--r--ext/node_resolver/npm.rs41
-rw-r--r--ext/node_resolver/package_json.rs (renamed from ext/node/package_json.rs)25
-rw-r--r--ext/node_resolver/path.rs142
-rw-r--r--ext/node_resolver/resolution.rs (renamed from ext/node/resolution.rs)207
-rw-r--r--ext/node_resolver/sync.rs86
21 files changed, 727 insertions, 381 deletions
diff --git a/ext/fs/clippy.toml b/ext/fs/clippy.toml
index 023769214..943d28c6d 100644
--- a/ext/fs/clippy.toml
+++ b/ext/fs/clippy.toml
@@ -1,24 +1,24 @@
disallowed-methods = [
{ path = "std::env::current_dir", reason = "File system operations should be done using FileSystem trait" },
- { path = "std::path::Path::canonicalize", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::is_dir", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::is_file", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::is_symlink", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::metadata", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::read_dir", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::read_link", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::Path::try_exists", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::exists", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using NodeFs trait" },
- { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::canonicalize", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::is_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::is_file", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::is_symlink", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::metadata", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::read_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::read_link", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::try_exists", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::exists", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using FileSystem trait" },
{ path = "std::env::set_current_dir", reason = "File system operations should be done using FileSystem trait" },
{ path = "std::env::temp_dir", reason = "File system operations should be done using FileSystem trait" },
{ path = "std::fs::canonicalize", reason = "File system operations should be done using FileSystem trait" },
diff --git a/ext/fs/sync.rs b/ext/fs/sync.rs
index 83f1f8bc3..6a913f658 100644
--- a/ext/fs/sync.rs
+++ b/ext/fs/sync.rs
@@ -6,80 +6,18 @@ pub use inner::*;
mod inner {
#![allow(clippy::disallowed_types)]
- use std::ops::Deref;
- use std::ops::DerefMut;
pub use std::sync::Arc as MaybeArc;
pub use core::marker::Send as MaybeSend;
pub use core::marker::Sync as MaybeSync;
-
- pub struct MaybeArcMutexGuard<'lock, T>(std::sync::MutexGuard<'lock, T>);
-
- impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> {
- type Target = std::sync::MutexGuard<'lock, T>;
- fn deref(&self) -> &std::sync::MutexGuard<'lock, T> {
- &self.0
- }
- }
-
- impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> {
- fn deref_mut(&mut self) -> &mut std::sync::MutexGuard<'lock, T> {
- &mut self.0
- }
- }
-
- #[derive(Debug)]
- pub struct MaybeArcMutex<T>(std::sync::Arc<std::sync::Mutex<T>>);
- impl<T> MaybeArcMutex<T> {
- pub fn new(val: T) -> Self {
- Self(std::sync::Arc::new(std::sync::Mutex::new(val)))
- }
- }
-
- impl<'lock, T> MaybeArcMutex<T> {
- pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> {
- MaybeArcMutexGuard(self.0.lock().unwrap())
- }
- }
}
#[cfg(not(feature = "sync_fs"))]
mod inner {
- use std::ops::Deref;
- use std::ops::DerefMut;
pub use std::rc::Rc as MaybeArc;
pub trait MaybeSync {}
impl<T> MaybeSync for T where T: ?Sized {}
pub trait MaybeSend {}
impl<T> MaybeSend for T where T: ?Sized {}
-
- pub struct MaybeArcMutexGuard<'lock, T>(std::cell::RefMut<'lock, T>);
-
- impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> {
- type Target = std::cell::RefMut<'lock, T>;
- fn deref(&self) -> &std::cell::RefMut<'lock, T> {
- &self.0
- }
- }
-
- impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> {
- fn deref_mut(&mut self) -> &mut std::cell::RefMut<'lock, T> {
- &mut self.0
- }
- }
-
- #[derive(Debug)]
- pub struct MaybeArcMutex<T>(std::rc::Rc<std::cell::RefCell<T>>);
- impl<T> MaybeArcMutex<T> {
- pub fn new(val: T) -> Self {
- Self(std::rc::Rc::new(std::cell::RefCell::new(val)))
- }
- }
-
- impl<'lock, T> MaybeArcMutex<T> {
- pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> {
- MaybeArcMutexGuard(self.0.borrow_mut())
- }
- }
}
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index ed168eace..00afb64eb 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -14,7 +14,7 @@ description = "Node compatibility for Deno"
path = "lib.rs"
[features]
-sync_fs = ["deno_package_json/sync"]
+sync_fs = ["deno_package_json/sync", "node_resolver/sync"]
[dependencies]
aead-gcm-stream = "0.1"
@@ -55,6 +55,7 @@ libc.workspace = true
libz-sys.workspace = true
md-5 = { version = "0.10.5", features = ["oid"] }
md4 = "0.10.2"
+node_resolver.workspace = true
num-bigint.workspace = true
num-bigint-dig = "0.8.2"
num-integer = "0.1.45"
diff --git a/ext/node/global.rs b/ext/node/global.rs
index 7f901fd03..618e68494 100644
--- a/ext/node/global.rs
+++ b/ext/node/global.rs
@@ -6,7 +6,7 @@ use deno_core::v8;
use deno_core::v8::GetPropertyNamesArgs;
use deno_core::v8::MapFnTo;
-use crate::resolution::NodeResolverRc;
+use crate::NodeResolverRc;
// NOTE(bartlomieju): somehow calling `.map_fn_to()` multiple times on a function
// returns two different pointers. That shouldn't be the case as `.map_fn_to()`
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 21af5a094..2c8650577 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -5,7 +5,6 @@
use std::collections::HashSet;
use std::path::Path;
-use std::path::PathBuf;
use deno_core::error::AnyError;
use deno_core::located_script_name;
@@ -15,24 +14,20 @@ use deno_core::url::Url;
use deno_core::v8;
use deno_core::v8::ExternalReference;
use deno_core::JsRuntime;
-use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_fs::sync::MaybeSend;
use deno_fs::sync::MaybeSync;
+use node_resolver::NpmResolverRc;
use once_cell::sync::Lazy;
extern crate libz_sys as zlib;
-pub mod analyze;
-pub mod errors;
mod global;
mod ops;
-mod package_json;
-mod path;
mod polyfill;
-mod resolution;
pub use deno_package_json::PackageJson;
+pub use node_resolver::PathClean;
pub use ops::ipc::ChildPipeFd;
pub use ops::ipc::IpcJsonStreamResource;
use ops::vm;
@@ -40,17 +35,9 @@ pub use ops::vm::create_v8_context;
pub use ops::vm::init_global_template;
pub use ops::vm::ContextInitMode;
pub use ops::vm::VM_CONTEXT_INDEX;
-pub use package_json::load_pkg_json;
-pub use package_json::PackageJsonThreadLocalCache;
-pub use path::PathClean;
pub use polyfill::is_builtin_node_module;
pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES;
pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES_WITH_PREFIX;
-pub use resolution::NodeModuleKind;
-pub use resolution::NodeResolution;
-pub use resolution::NodeResolutionMode;
-pub use resolution::NodeResolver;
-use resolution::NodeResolverRc;
use crate::global::global_object_middleware;
use crate::global::global_template_middleware;
@@ -149,9 +136,12 @@ impl NodePermissions for deno_permissions::PermissionsContainer {
}
#[allow(clippy::disallowed_types)]
-pub type NpmResolverRc = deno_fs::sync::MaybeArc<dyn NpmResolver>;
+pub type NpmProcessStateProviderRc =
+ deno_fs::sync::MaybeArc<dyn NpmProcessStateProvider>;
-pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
+pub trait NpmProcessStateProvider:
+ std::fmt::Debug + MaybeSend + MaybeSync
+{
/// Gets a string containing the serialized npm state of the process.
///
/// This will be set on the `DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE` environment
@@ -161,34 +151,13 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
// This method is only used in the CLI.
String::new()
}
+}
- /// Resolves an npm package folder path from an npm package referrer.
- fn resolve_package_folder_from_package(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- ) -> Result<PathBuf, errors::PackageFolderResolveError>;
-
- fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool;
-
- fn in_npm_package_at_dir_path(&self, path: &Path) -> bool {
- let specifier =
- match ModuleSpecifier::from_directory_path(path.to_path_buf().clean()) {
- Ok(p) => p,
- Err(_) => return false,
- };
- self.in_npm_package(&specifier)
- }
-
- fn in_npm_package_at_file_path(&self, path: &Path) -> bool {
- let specifier =
- match ModuleSpecifier::from_file_path(path.to_path_buf().clean()) {
- Ok(p) => p,
- Err(_) => return false,
- };
- self.in_npm_package(&specifier)
- }
+#[allow(clippy::disallowed_types)]
+pub type NodeRequireResolverRc =
+ deno_fs::sync::MaybeArc<dyn NodeRequireResolver>;
+pub trait NodeRequireResolver: std::fmt::Debug + MaybeSend + MaybeSync {
fn ensure_read_permission(
&self,
permissions: &mut dyn NodePermissions,
@@ -223,10 +192,17 @@ fn op_node_is_promise_rejected(value: v8::Local<v8::Value>) -> bool {
#[op2]
#[string]
fn op_npm_process_state(state: &mut OpState) -> Result<String, AnyError> {
- let npm_resolver = state.borrow_mut::<NpmResolverRc>();
+ let npm_resolver = state.borrow_mut::<NpmProcessStateProviderRc>();
Ok(npm_resolver.get_npm_process_state())
}
+pub struct NodeExtInitServices {
+ pub node_require_resolver: NodeRequireResolverRc,
+ pub node_resolver: NodeResolverRc,
+ pub npm_process_state_provider: NpmProcessStateProviderRc,
+ pub npm_resolver: NpmResolverRc,
+}
+
deno_core::extension!(deno_node,
deps = [ deno_io, deno_fs ],
parameters = [P: NodePermissions],
@@ -643,21 +619,17 @@ deno_core::extension!(deno_node,
"node:zlib" = "zlib.ts",
],
options = {
- maybe_node_resolver: Option<NodeResolverRc>,
- maybe_npm_resolver: Option<NpmResolverRc>,
+ maybe_init: Option<NodeExtInitServices>,
fs: deno_fs::FileSystemRc,
},
state = |state, options| {
- // you should provide both of these or neither
- debug_assert_eq!(options.maybe_node_resolver.is_some(), options.maybe_npm_resolver.is_some());
-
state.put(options.fs.clone());
- if let Some(node_resolver) = &options.maybe_node_resolver {
- state.put(node_resolver.clone());
- }
- if let Some(npm_resolver) = &options.maybe_npm_resolver {
- state.put(npm_resolver.clone());
+ if let Some(init) = &options.maybe_init {
+ state.put(init.node_require_resolver.clone());
+ state.put(init.node_resolver.clone());
+ state.put(init.npm_resolver.clone());
+ state.put(init.npm_process_state_provider.clone());
}
},
global_template_middleware = global_template_middleware,
@@ -783,3 +755,84 @@ pub fn load_cjs_module(
js_runtime.execute_script(located_script_name!(), source_code)?;
Ok(())
}
+
+pub type NodeResolver = node_resolver::NodeResolver<DenoFsNodeResolverEnv>;
+#[allow(clippy::disallowed_types)]
+pub type NodeResolverRc =
+ deno_fs::sync::MaybeArc<node_resolver::NodeResolver<DenoFsNodeResolverEnv>>;
+
+#[derive(Debug)]
+pub struct DenoFsNodeResolverEnv {
+ fs: deno_fs::FileSystemRc,
+}
+
+impl DenoFsNodeResolverEnv {
+ pub fn new(fs: deno_fs::FileSystemRc) -> Self {
+ Self { fs }
+ }
+}
+
+impl node_resolver::env::NodeResolverEnv for DenoFsNodeResolverEnv {
+ fn is_builtin_node_module(&self, specifier: &str) -> bool {
+ is_builtin_node_module(specifier)
+ }
+
+ fn realpath_sync(
+ &self,
+ path: &std::path::Path,
+ ) -> std::io::Result<std::path::PathBuf> {
+ self
+ .fs
+ .realpath_sync(path)
+ .map_err(|err| err.into_io_error())
+ }
+
+ fn stat_sync(
+ &self,
+ path: &std::path::Path,
+ ) -> std::io::Result<node_resolver::env::NodeResolverFsStat> {
+ self
+ .fs
+ .stat_sync(path)
+ .map(|stat| node_resolver::env::NodeResolverFsStat {
+ is_file: stat.is_file,
+ is_dir: stat.is_directory,
+ is_symlink: stat.is_symlink,
+ })
+ .map_err(|err| err.into_io_error())
+ }
+
+ fn exists_sync(&self, path: &std::path::Path) -> bool {
+ self.fs.exists_sync(path)
+ }
+
+ fn pkg_json_fs(&self) -> &dyn deno_package_json::fs::DenoPkgJsonFs {
+ self
+ }
+}
+
+impl deno_package_json::fs::DenoPkgJsonFs for DenoFsNodeResolverEnv {
+ fn read_to_string_lossy(
+ &self,
+ path: &std::path::Path,
+ ) -> Result<String, std::io::Error> {
+ self
+ .fs
+ .read_text_file_lossy_sync(path, None)
+ .map_err(|err| err.into_io_error())
+ }
+}
+
+pub struct DenoPkgJsonFsAdapter<'a>(pub &'a dyn deno_fs::FileSystem);
+
+impl<'a> deno_package_json::fs::DenoPkgJsonFs for DenoPkgJsonFsAdapter<'a> {
+ fn read_to_string_lossy(
+ &self,
+ path: &Path,
+ ) -> Result<String, std::io::Error> {
+ self
+ .0
+ .read_text_file_lossy_sync(path, None)
+ .map_err(|err| err.into_io_error())
+ }
+}
diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs
index d03b3dd9c..d074234c3 100644
--- a/ext/node/ops/require.rs
+++ b/ext/node/ops/require.rs
@@ -10,16 +10,17 @@ use deno_core::JsRuntimeInspector;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_fs::FileSystemRc;
+use node_resolver::NodeModuleKind;
+use node_resolver::NodeResolutionMode;
+use node_resolver::REQUIRE_CONDITIONS;
use std::cell::RefCell;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
-use crate::resolution;
-use crate::resolution::NodeResolverRc;
-use crate::NodeModuleKind;
use crate::NodePermissions;
-use crate::NodeResolutionMode;
+use crate::NodeRequireResolverRc;
+use crate::NodeResolverRc;
use crate::NpmResolverRc;
use crate::PackageJson;
@@ -30,7 +31,7 @@ fn ensure_read_permission<P>(
where
P: NodePermissions + 'static,
{
- let resolver = state.borrow::<NpmResolverRc>().clone();
+ let resolver = state.borrow::<NodeRequireResolverRc>().clone();
let permissions = state.borrow_mut::<P>();
resolver.ensure_read_permission(permissions, file_path)
}
@@ -423,7 +424,7 @@ where
exports,
Some(&referrer),
NodeModuleKind::Cjs,
- resolution::REQUIRE_CONDITIONS,
+ REQUIRE_CONDITIONS,
NodeResolutionMode::Execution,
)?;
Ok(Some(if r.scheme() == "file" {
@@ -511,7 +512,7 @@ where
exports,
Some(&referrer),
NodeModuleKind::Cjs,
- resolution::REQUIRE_CONDITIONS,
+ REQUIRE_CONDITIONS,
NodeResolutionMode::Execution,
)?;
Ok(Some(if r.scheme() == "file" {
@@ -590,7 +591,7 @@ where
Some(&referrer_url),
NodeModuleKind::Cjs,
Some(&pkg),
- resolution::REQUIRE_CONDITIONS,
+ REQUIRE_CONDITIONS,
NodeResolutionMode::Execution,
)?;
Ok(Some(url_to_file_path_string(&url)?))
diff --git a/ext/node/ops/worker_threads.rs b/ext/node/ops/worker_threads.rs
index 182ba0118..c7ea4c52c 100644
--- a/ext/node/ops/worker_threads.rs
+++ b/ext/node/ops/worker_threads.rs
@@ -6,13 +6,13 @@ use deno_core::op2;
use deno_core::url::Url;
use deno_core::OpState;
use deno_fs::FileSystemRc;
+use node_resolver::NodeResolution;
use std::path::Path;
use std::path::PathBuf;
-use crate::resolution;
-use crate::resolution::NodeResolverRc;
use crate::NodePermissions;
-use crate::NpmResolverRc;
+use crate::NodeRequireResolverRc;
+use crate::NodeResolverRc;
fn ensure_read_permission<P>(
state: &mut OpState,
@@ -21,7 +21,7 @@ fn ensure_read_permission<P>(
where
P: NodePermissions + 'static,
{
- let resolver = state.borrow::<NpmResolverRc>().clone();
+ let resolver = state.borrow::<NodeRequireResolverRc>().clone();
let permissions = state.borrow_mut::<P>();
resolver.ensure_read_permission(permissions, file_path)
}
@@ -64,9 +64,9 @@ where
}
let node_resolver = state.borrow::<NodeResolverRc>();
match node_resolver.url_to_node_resolution(url)? {
- resolution::NodeResolution::Esm(u) => Ok(u.to_string()),
- resolution::NodeResolution::CommonJs(u) => wrap_cjs(u),
- _ => Err(generic_error("Neither ESM nor CJS")),
+ NodeResolution::Esm(u) => Ok(u.to_string()),
+ NodeResolution::CommonJs(u) => wrap_cjs(u),
+ NodeResolution::BuiltIn(_) => Err(generic_error("Neither ESM nor CJS")),
}
}
diff --git a/ext/node/path.rs b/ext/node/path.rs
deleted file mode 100644
index 0f151edaf..000000000
--- a/ext/node/path.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-use std::path::Component;
-use std::path::Path;
-use std::path::PathBuf;
-
-use deno_core::ModuleSpecifier;
-
-/// Extension to path_clean::PathClean
-pub trait PathClean<T> {
- fn clean(&self) -> T;
-}
-
-impl PathClean<PathBuf> for PathBuf {
- fn clean(&self) -> PathBuf {
- let path = path_clean::PathClean::clean(self);
- if cfg!(windows) && path.to_string_lossy().contains("..\\") {
- // temporary workaround because path_clean::PathClean::clean is
- // not good enough on windows
- let mut components = Vec::new();
-
- for component in path.components() {
- match component {
- Component::CurDir => {
- // skip
- }
- Component::ParentDir => {
- let maybe_last_component = components.pop();
- if !matches!(maybe_last_component, Some(Component::Normal(_))) {
- panic!("Error normalizing: {}", path.display());
- }
- }
- Component::Normal(_) | Component::RootDir | Component::Prefix(_) => {
- components.push(component);
- }
- }
- }
- components.into_iter().collect::<PathBuf>()
- } else {
- path
- }
- }
-}
-
-pub(crate) fn to_file_specifier(path: &Path) -> ModuleSpecifier {
- match ModuleSpecifier::from_file_path(path) {
- Ok(url) => url,
- Err(_) => panic!("Invalid path: {}", path.display()),
- }
-}
diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs
index 5847acc42..b4030a491 100644
--- a/ext/node/polyfill.rs
+++ b/ext/node/polyfill.rs
@@ -1,7 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use deno_core::ModuleSpecifier;
-
/// e.g. `is_builtin_node_module("assert")`
pub fn is_builtin_node_module(module_name: &str) -> bool {
SUPPORTED_BUILTIN_NODE_MODULES
@@ -9,18 +7,6 @@ pub fn is_builtin_node_module(module_name: &str) -> bool {
.any(|m| *m == module_name)
}
-/// Ex. returns `fs` for `node:fs`
-pub fn get_module_name_from_builtin_node_module_specifier(
- specifier: &ModuleSpecifier,
-) -> Option<&str> {
- if specifier.scheme() != "node" {
- return None;
- }
-
- let (_, specifier) = specifier.as_str().split_once(':')?;
- Some(specifier)
-}
-
macro_rules! generate_builtin_node_module_lists {
($( $module_name:literal ,)+) => {
pub static SUPPORTED_BUILTIN_NODE_MODULES: &[&str] = &[
diff --git a/ext/node_resolver/Cargo.toml b/ext/node_resolver/Cargo.toml
new file mode 100644
index 000000000..a636eaf9f
--- /dev/null
+++ b/ext/node_resolver/Cargo.toml
@@ -0,0 +1,32 @@
+# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "node_resolver"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+readme = "README.md"
+repository.workspace = true
+description = "Node.js module resolution algorithm used in Deno"
+
+[lib]
+path = "lib.rs"
+
+[features]
+sync = ["deno_package_json/sync"]
+
+[dependencies]
+anyhow.workspace = true
+async-trait.workspace = true
+deno_media_type.workspace = true
+deno_package_json.workspace = true
+futures.workspace = true
+lazy-regex.workspace = true
+once_cell.workspace = true
+path-clean = "=0.1.0"
+regex.workspace = true
+serde_json.workspace = true
+thiserror.workspace = true
+tokio.workspace = true
+url.workspace = true
diff --git a/ext/node_resolver/README.md b/ext/node_resolver/README.md
new file mode 100644
index 000000000..8f2f63ca1
--- /dev/null
+++ b/ext/node_resolver/README.md
@@ -0,0 +1,6 @@
+# Node Resolver
+
+[![crates](https://img.shields.io/crates/v/node_resolver.svg)](https://crates.io/crates/node_resolver)
+[![docs](https://docs.rs/node_resolver/badge.svg)](https://docs.rs/node_resolver)
+
+Provides Node.js compatible resolution for the Deno project.
diff --git a/ext/node/analyze.rs b/ext/node_resolver/analyze.rs
index 3513a8105..8d6a73424 100644
--- a/ext/node/analyze.rs
+++ b/ext/node_resolver/analyze.rs
@@ -5,17 +5,17 @@ use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
-use deno_core::anyhow;
-use deno_core::anyhow::Context;
-use deno_core::futures::future::LocalBoxFuture;
-use deno_core::futures::stream::FuturesUnordered;
-use deno_core::futures::FutureExt;
-use deno_core::futures::StreamExt;
-use deno_core::ModuleSpecifier;
+use futures::future::LocalBoxFuture;
+use futures::stream::FuturesUnordered;
+use futures::FutureExt;
+use futures::StreamExt;
use once_cell::sync::Lazy;
-use deno_core::error::AnyError;
+use anyhow::Context;
+use anyhow::Error as AnyError;
+use url::Url;
+use crate::env::NodeResolverEnv;
use crate::package_json::load_pkg_json;
use crate::path::to_file_specifier;
use crate::resolution::NodeResolverRc;
@@ -50,28 +50,33 @@ pub trait CjsCodeAnalyzer {
/// necessary.
async fn analyze_cjs(
&self,
- specifier: &ModuleSpecifier,
+ specifier: &Url,
maybe_source: Option<String>,
) -> Result<CjsAnalysis, AnyError>;
}
-pub struct NodeCodeTranslator<TCjsCodeAnalyzer: CjsCodeAnalyzer> {
+pub struct NodeCodeTranslator<
+ TCjsCodeAnalyzer: CjsCodeAnalyzer,
+ TNodeResolverEnv: NodeResolverEnv,
+> {
cjs_code_analyzer: TCjsCodeAnalyzer,
- fs: deno_fs::FileSystemRc,
- node_resolver: NodeResolverRc,
+ env: TNodeResolverEnv,
+ node_resolver: NodeResolverRc<TNodeResolverEnv>,
npm_resolver: NpmResolverRc,
}
-impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
+impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
+ NodeCodeTranslator<TCjsCodeAnalyzer, TNodeResolverEnv>
+{
pub fn new(
cjs_code_analyzer: TCjsCodeAnalyzer,
- fs: deno_fs::FileSystemRc,
- node_resolver: NodeResolverRc,
+ env: TNodeResolverEnv,
+ node_resolver: NodeResolverRc<TNodeResolverEnv>,
npm_resolver: NpmResolverRc,
) -> Self {
Self {
cjs_code_analyzer,
- fs,
+ env,
node_resolver,
npm_resolver,
}
@@ -85,7 +90,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
/// If successful a source code for equivalent ES module is returned.
pub async fn translate_cjs_to_esm(
&self,
- entry_specifier: &ModuleSpecifier,
+ entry_specifier: &Url,
source: Option<String>,
) -> Result<String, AnyError> {
let mut temp_var_count = 0;
@@ -173,7 +178,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
type AnalysisFuture<'a> = LocalBoxFuture<'a, Result<Analysis, AnyError>>;
- let mut handled_reexports: HashSet<ModuleSpecifier> = HashSet::default();
+ let mut handled_reexports: HashSet<Url> = HashSet::default();
handled_reexports.insert(entry_specifier.clone());
let mut analyze_futures: FuturesUnordered<AnalysisFuture<'a>> =
FuturesUnordered::new();
@@ -282,10 +287,10 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
fn resolve(
&self,
specifier: &str,
- referrer: &ModuleSpecifier,
+ referrer: &Url,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, AnyError> {
+ ) -> Result<Url, AnyError> {
if specifier.starts_with('/') {
todo!();
}
@@ -305,14 +310,14 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
let (package_specifier, package_subpath) =
parse_specifier(specifier).unwrap();
- // todo(dsherret): use not_found error on not found here
let module_dir = self.npm_resolver.resolve_package_folder_from_package(
package_specifier.as_str(),
referrer,
)?;
let package_json_path = module_dir.join("package.json");
- let maybe_package_json = load_pkg_json(&*self.fs, &package_json_path)?;
+ let maybe_package_json =
+ load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?;
if let Some(package_json) = maybe_package_json {
if let Some(exports) = &package_json.exports {
return self
@@ -332,11 +337,11 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
// old school
if package_subpath != "." {
let d = module_dir.join(package_subpath);
- if self.fs.is_dir_sync(&d) {
+ if self.env.is_dir_sync(&d) {
// subdir might have a package.json that specifies the entrypoint
let package_json_path = d.join("package.json");
let maybe_package_json =
- load_pkg_json(&*self.fs, &package_json_path)?;
+ load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?;
if let Some(package_json) = maybe_package_json {
if let Some(main) = package_json.main(NodeModuleKind::Cjs) {
return Ok(to_file_specifier(&d.join(main).clean()));
@@ -381,13 +386,13 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
referrer: &Path,
) -> Result<PathBuf, AnyError> {
let p = p.clean();
- if self.fs.exists_sync(&p) {
+ if self.env.exists_sync(&p) {
let file_name = p.file_name().unwrap();
let p_js =
p.with_file_name(format!("{}.js", file_name.to_str().unwrap()));
- if self.fs.is_file_sync(&p_js) {
+ if self.env.is_file_sync(&p_js) {
return Ok(p_js);
- } else if self.fs.is_dir_sync(&p) {
+ } else if self.env.is_dir_sync(&p) {
return Ok(p.join("index.js"));
} else {
return Ok(p);
@@ -396,14 +401,14 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
{
let p_js =
p.with_file_name(format!("{}.js", file_name.to_str().unwrap()));
- if self.fs.is_file_sync(&p_js) {
+ if self.env.is_file_sync(&p_js) {
return Ok(p_js);
}
}
{
let p_json =
p.with_file_name(format!("{}.json", file_name.to_str().unwrap()));
- if self.fs.is_file_sync(&p_json) {
+ if self.env.is_file_sync(&p_json) {
return Ok(p_json);
}
}
diff --git a/ext/node_resolver/clippy.toml b/ext/node_resolver/clippy.toml
new file mode 100644
index 000000000..86150781b
--- /dev/null
+++ b/ext/node_resolver/clippy.toml
@@ -0,0 +1,48 @@
+disallowed-methods = [
+ { path = "std::env::current_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::is_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::is_file", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::is_symlink", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::metadata", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::read_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::read_link", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::try_exists", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::exists", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::env::set_current_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::env::temp_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::copy", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::create_dir_all", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::create_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::DirBuilder::new", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::hard_link", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::metadata", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::OpenOptions::new", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::read_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::read_link", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::read_to_string", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::read", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::remove_dir_all", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::remove_dir", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::remove_file", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::rename", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::set_permissions", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::symlink_metadata", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::fs::write", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" },
+ { path = "std::path::Path::exists", reason = "File system operations should be done using NodeResolverFs trait" },
+]
+disallowed-types = [
+ { path = "std::sync::Arc", reason = "use crate::sync::MaybeArc instead" },
+]
diff --git a/ext/node_resolver/env.rs b/ext/node_resolver/env.rs
new file mode 100644
index 000000000..b520ece0f
--- /dev/null
+++ b/ext/node_resolver/env.rs
@@ -0,0 +1,39 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::path::Path;
+use std::path::PathBuf;
+
+use crate::sync::MaybeSend;
+use crate::sync::MaybeSync;
+
+pub struct NodeResolverFsStat {
+ pub is_file: bool,
+ pub is_dir: bool,
+ pub is_symlink: bool,
+}
+
+pub trait NodeResolverEnv: std::fmt::Debug + MaybeSend + MaybeSync {
+ fn is_builtin_node_module(&self, specifier: &str) -> bool;
+
+ fn realpath_sync(&self, path: &Path) -> std::io::Result<PathBuf>;
+
+ fn stat_sync(&self, path: &Path) -> std::io::Result<NodeResolverFsStat>;
+
+ fn exists_sync(&self, path: &Path) -> bool;
+
+ fn is_file_sync(&self, path: &Path) -> bool {
+ self
+ .stat_sync(path)
+ .map(|stat| stat.is_file)
+ .unwrap_or(false)
+ }
+
+ fn is_dir_sync(&self, path: &Path) -> bool {
+ self
+ .stat_sync(path)
+ .map(|stat| stat.is_dir)
+ .unwrap_or(false)
+ }
+
+ fn pkg_json_fs(&self) -> &dyn deno_package_json::fs::DenoPkgJsonFs;
+}
diff --git a/ext/node/errors.rs b/ext/node_resolver/errors.rs
index 64625d32f..4ba829eda 100644
--- a/ext/node/errors.rs
+++ b/ext/node_resolver/errors.rs
@@ -4,8 +4,8 @@ use std::borrow::Cow;
use std::fmt::Write;
use std::path::PathBuf;
-use deno_core::ModuleSpecifier;
use thiserror::Error;
+use url::Url;
use crate::NodeModuleKind;
use crate::NodeResolutionMode;
@@ -155,7 +155,7 @@ kinded_err!(PackageFolderResolveError, PackageFolderResolveErrorKind);
)]
pub struct PackageNotFoundError {
pub package_name: String,
- pub referrer: ModuleSpecifier,
+ pub referrer: Url,
/// Extra information about the referrer.
pub referrer_extra: Option<String>,
}
@@ -173,7 +173,7 @@ impl NodeJsErrorCoded for PackageNotFoundError {
referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default()
)]
pub struct ReferrerNotFoundError {
- pub referrer: ModuleSpecifier,
+ pub referrer: Url,
/// Extra information about the referrer.
pub referrer_extra: Option<String>,
}
@@ -188,7 +188,7 @@ impl NodeJsErrorCoded for ReferrerNotFoundError {
#[error("Failed resolving '{package_name}' from referrer '{referrer}'.")]
pub struct PackageFolderResolveIoError {
pub package_name: String,
- pub referrer: ModuleSpecifier,
+ pub referrer: Url,
#[source]
pub source: std::io::Error,
}
@@ -264,7 +264,7 @@ pub enum PackageSubpathResolveErrorKind {
pub struct PackageTargetNotFoundError {
pub pkg_json_path: PathBuf,
pub target: String,
- pub maybe_referrer: Option<ModuleSpecifier>,
+ pub maybe_referrer: Option<Url>,
pub referrer_kind: NodeModuleKind,
pub mode: NodeResolutionMode,
}
@@ -333,8 +333,8 @@ pub struct TypesNotFoundError(pub Box<TypesNotFoundErrorData>);
#[derive(Debug)]
pub struct TypesNotFoundErrorData {
- pub code_specifier: ModuleSpecifier,
- pub maybe_referrer: Option<ModuleSpecifier>,
+ pub code_specifier: Url,
+ pub maybe_referrer: Option<Url>,
}
impl NodeJsErrorCoded for TypesNotFoundError {
@@ -397,7 +397,7 @@ impl NodeJsErrorCoded for CanonicalizingPkgJsonDirError {
#[derive(Debug, Error)]
#[error("TypeScript files are not supported in npm packages: {specifier}")]
pub struct TypeScriptNotSupportedInNpmError {
- pub specifier: ModuleSpecifier,
+ pub specifier: Url,
}
impl NodeJsErrorCoded for TypeScriptNotSupportedInNpmError {
@@ -437,7 +437,7 @@ pub enum UrlToNodeResolutionErrorKind {
pub struct PackageImportNotDefinedError {
pub name: String,
pub package_json_path: Option<PathBuf>,
- pub maybe_referrer: Option<ModuleSpecifier>,
+ pub maybe_referrer: Option<Url>,
}
impl NodeJsErrorCoded for PackageImportNotDefinedError {
@@ -503,7 +503,7 @@ pub enum PackageResolveErrorKind {
#[error("Failed joining '{path}' from '{base}'.")]
pub struct NodeResolveRelativeJoinError {
pub path: String,
- pub base: ModuleSpecifier,
+ pub base: Url,
#[source]
pub source: url::ParseError,
}
@@ -568,8 +568,8 @@ impl NodeJsErrorCoded for FinalizeResolutionError {
maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default()
)]
pub struct ModuleNotFoundError {
- pub specifier: ModuleSpecifier,
- pub maybe_referrer: Option<ModuleSpecifier>,
+ pub specifier: Url,
+ pub maybe_referrer: Option<Url>,
pub typ: &'static str,
}
@@ -587,8 +587,8 @@ impl NodeJsErrorCoded for ModuleNotFoundError {
maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default(),
)]
pub struct UnsupportedDirImportError {
- pub dir_url: ModuleSpecifier,
- pub maybe_referrer: Option<ModuleSpecifier>,
+ pub dir_url: Url,
+ pub maybe_referrer: Option<Url>,
}
impl NodeJsErrorCoded for UnsupportedDirImportError {
@@ -603,7 +603,7 @@ pub struct InvalidPackageTargetError {
pub sub_path: String,
pub target: String,
pub is_import: bool,
- pub maybe_referrer: Option<ModuleSpecifier>,
+ pub maybe_referrer: Option<Url>,
}
impl std::error::Error for InvalidPackageTargetError {}
@@ -657,7 +657,7 @@ impl NodeJsErrorCoded for InvalidPackageTargetError {
pub struct PackagePathNotExportedError {
pub pkg_json_path: PathBuf,
pub subpath: String,
- pub maybe_referrer: Option<ModuleSpecifier>,
+ pub maybe_referrer: Option<Url>,
pub mode: NodeResolutionMode,
}
diff --git a/ext/node_resolver/lib.rs b/ext/node_resolver/lib.rs
new file mode 100644
index 000000000..1ab972ccf
--- /dev/null
+++ b/ext/node_resolver/lib.rs
@@ -0,0 +1,26 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+#![deny(clippy::print_stderr)]
+#![deny(clippy::print_stdout)]
+
+pub mod analyze;
+pub mod env;
+pub mod errors;
+mod npm;
+mod package_json;
+mod path;
+mod resolution;
+mod sync;
+
+pub use deno_package_json::PackageJson;
+pub use npm::NpmResolver;
+pub use npm::NpmResolverRc;
+pub use package_json::load_pkg_json;
+pub use package_json::PackageJsonThreadLocalCache;
+pub use path::PathClean;
+pub use resolution::NodeModuleKind;
+pub use resolution::NodeResolution;
+pub use resolution::NodeResolutionMode;
+pub use resolution::NodeResolver;
+pub use resolution::DEFAULT_CONDITIONS;
+pub use resolution::REQUIRE_CONDITIONS;
diff --git a/ext/node_resolver/npm.rs b/ext/node_resolver/npm.rs
new file mode 100644
index 000000000..77df57c48
--- /dev/null
+++ b/ext/node_resolver/npm.rs
@@ -0,0 +1,41 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::path::Path;
+use std::path::PathBuf;
+
+use url::Url;
+
+use crate::errors;
+use crate::path::PathClean;
+use crate::sync::MaybeSend;
+use crate::sync::MaybeSync;
+
+#[allow(clippy::disallowed_types)]
+pub type NpmResolverRc = crate::sync::MaybeArc<dyn NpmResolver>;
+
+pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
+ /// Resolves an npm package folder path from an npm package referrer.
+ fn resolve_package_folder_from_package(
+ &self,
+ specifier: &str,
+ referrer: &Url,
+ ) -> Result<PathBuf, errors::PackageFolderResolveError>;
+
+ fn in_npm_package(&self, specifier: &Url) -> bool;
+
+ fn in_npm_package_at_dir_path(&self, path: &Path) -> bool {
+ let specifier = match Url::from_directory_path(path.to_path_buf().clean()) {
+ Ok(p) => p,
+ Err(_) => return false,
+ };
+ self.in_npm_package(&specifier)
+ }
+
+ fn in_npm_package_at_file_path(&self, path: &Path) -> bool {
+ let specifier = match Url::from_file_path(path.to_path_buf().clean()) {
+ Ok(p) => p,
+ Err(_) => return false,
+ };
+ self.in_npm_package(&specifier)
+ }
+}
diff --git a/ext/node/package_json.rs b/ext/node_resolver/package_json.rs
index 877acfc7a..de750f1d7 100644
--- a/ext/node/package_json.rs
+++ b/ext/node_resolver/package_json.rs
@@ -33,31 +33,14 @@ impl deno_package_json::PackageJsonCache for PackageJsonThreadLocalCache {
}
}
-pub struct DenoPkgJsonFsAdapter<'a>(pub &'a dyn deno_fs::FileSystem);
-
-impl<'a> deno_package_json::fs::DenoPkgJsonFs for DenoPkgJsonFsAdapter<'a> {
- fn read_to_string_lossy(
- &self,
- path: &Path,
- ) -> Result<String, std::io::Error> {
- self
- .0
- .read_text_file_lossy_sync(path, None)
- .map_err(|err| err.into_io_error())
- }
-}
-
/// Helper to load a package.json file using the thread local cache
-/// in deno_node.
+/// in node_resolver.
pub fn load_pkg_json(
- fs: &dyn deno_fs::FileSystem,
+ fs: &dyn deno_package_json::fs::DenoPkgJsonFs,
path: &Path,
) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
- let result = PackageJson::load_from_path(
- path,
- &DenoPkgJsonFsAdapter(fs),
- Some(&PackageJsonThreadLocalCache),
- );
+ let result =
+ PackageJson::load_from_path(path, fs, Some(&PackageJsonThreadLocalCache));
match result {
Ok(pkg_json) => Ok(Some(pkg_json)),
Err(deno_package_json::PackageJsonLoadError::Io { source, .. })
diff --git a/ext/node_resolver/path.rs b/ext/node_resolver/path.rs
new file mode 100644
index 000000000..8c33285db
--- /dev/null
+++ b/ext/node_resolver/path.rs
@@ -0,0 +1,142 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::path::Component;
+use std::path::Path;
+use std::path::PathBuf;
+
+use url::Url;
+
+/// Extension to path_clean::PathClean
+pub trait PathClean<T> {
+ fn clean(&self) -> T;
+}
+
+impl PathClean<PathBuf> for PathBuf {
+ fn clean(&self) -> PathBuf {
+ let path = path_clean::PathClean::clean(self);
+ if cfg!(windows) && path.to_string_lossy().contains("..\\") {
+ // temporary workaround because path_clean::PathClean::clean is
+ // not good enough on windows
+ let mut components = Vec::new();
+
+ for component in path.components() {
+ match component {
+ Component::CurDir => {
+ // skip
+ }
+ Component::ParentDir => {
+ let maybe_last_component = components.pop();
+ if !matches!(maybe_last_component, Some(Component::Normal(_))) {
+ panic!("Error normalizing: {}", path.display());
+ }
+ }
+ Component::Normal(_) | Component::RootDir | Component::Prefix(_) => {
+ components.push(component);
+ }
+ }
+ }
+ components.into_iter().collect::<PathBuf>()
+ } else {
+ path
+ }
+ }
+}
+
+pub(crate) fn to_file_specifier(path: &Path) -> Url {
+ match Url::from_file_path(path) {
+ Ok(url) => url,
+ Err(_) => panic!("Invalid path: {}", path.display()),
+ }
+}
+
+// todo(dsherret): we have the below code also in deno_core and it
+// would be good to somehow re-use it in both places (we don't want
+// to create a dependency on deno_core here)
+
+#[cfg(not(windows))]
+#[inline]
+pub fn strip_unc_prefix(path: PathBuf) -> PathBuf {
+ path
+}
+
+/// Strips the unc prefix (ex. \\?\) from Windows paths.
+#[cfg(windows)]
+pub fn strip_unc_prefix(path: PathBuf) -> PathBuf {
+ use std::path::Component;
+ use std::path::Prefix;
+
+ let mut components = path.components();
+ match components.next() {
+ Some(Component::Prefix(prefix)) => {
+ match prefix.kind() {
+ // \\?\device
+ Prefix::Verbatim(device) => {
+ let mut path = PathBuf::new();
+ path.push(format!(r"\\{}\", device.to_string_lossy()));
+ path.extend(components.filter(|c| !matches!(c, Component::RootDir)));
+ path
+ }
+ // \\?\c:\path
+ Prefix::VerbatimDisk(_) => {
+ let mut path = PathBuf::new();
+ path.push(prefix.as_os_str().to_string_lossy().replace(r"\\?\", ""));
+ path.extend(components);
+ path
+ }
+ // \\?\UNC\hostname\share_name\path
+ Prefix::VerbatimUNC(hostname, share_name) => {
+ let mut path = PathBuf::new();
+ path.push(format!(
+ r"\\{}\{}\",
+ hostname.to_string_lossy(),
+ share_name.to_string_lossy()
+ ));
+ path.extend(components.filter(|c| !matches!(c, Component::RootDir)));
+ path
+ }
+ _ => path,
+ }
+ }
+ _ => path,
+ }
+}
+
+#[cfg(test)]
+mod test {
+ #[cfg(windows)]
+ #[test]
+ fn test_strip_unc_prefix() {
+ use std::path::PathBuf;
+
+ run_test(r"C:\", r"C:\");
+ run_test(r"C:\test\file.txt", r"C:\test\file.txt");
+
+ run_test(r"\\?\C:\", r"C:\");
+ run_test(r"\\?\C:\test\file.txt", r"C:\test\file.txt");
+
+ run_test(r"\\.\C:\", r"\\.\C:\");
+ run_test(r"\\.\C:\Test\file.txt", r"\\.\C:\Test\file.txt");
+
+ run_test(r"\\?\UNC\localhost\", r"\\localhost");
+ run_test(r"\\?\UNC\localhost\c$\", r"\\localhost\c$");
+ run_test(
+ r"\\?\UNC\localhost\c$\Windows\file.txt",
+ r"\\localhost\c$\Windows\file.txt",
+ );
+ run_test(r"\\?\UNC\wsl$\deno.json", r"\\wsl$\deno.json");
+
+ run_test(r"\\?\server1", r"\\server1");
+ run_test(r"\\?\server1\e$\", r"\\server1\e$\");
+ run_test(
+ r"\\?\server1\e$\test\file.txt",
+ r"\\server1\e$\test\file.txt",
+ );
+
+ fn run_test(input: &str, expected: &str) {
+ assert_eq!(
+ super::strip_unc_prefix(PathBuf::from(input)),
+ PathBuf::from(expected)
+ );
+ }
+ }
+}
diff --git a/ext/node/resolution.rs b/ext/node_resolver/resolution.rs
index 6417835a2..d7918c75c 100644
--- a/ext/node/resolution.rs
+++ b/ext/node_resolver/resolution.rs
@@ -5,16 +5,15 @@ use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
-use deno_core::anyhow::bail;
-use deno_core::error::AnyError;
-use deno_core::serde_json::Map;
-use deno_core::serde_json::Value;
-use deno_core::url::Url;
-use deno_core::ModuleSpecifier;
-use deno_fs::FileSystemRc;
+use anyhow::bail;
+use anyhow::Error as AnyError;
use deno_media_type::MediaType;
use deno_package_json::PackageJsonRc;
+use serde_json::Map;
+use serde_json::Value;
+use url::Url;
+use crate::env::NodeResolverEnv;
use crate::errors;
use crate::errors::CanonicalizingPkgJsonDirError;
use crate::errors::ClosestPkgJsonError;
@@ -49,12 +48,11 @@ use crate::errors::TypesNotFoundErrorData;
use crate::errors::UnsupportedDirImportError;
use crate::errors::UnsupportedEsmUrlSchemeError;
use crate::errors::UrlToNodeResolutionError;
-use crate::is_builtin_node_module;
+use crate::path::strip_unc_prefix;
use crate::path::to_file_specifier;
-use crate::polyfill::get_module_name_from_builtin_node_module_specifier;
use crate::NpmResolverRc;
-use crate::PackageJson;
use crate::PathClean;
+use deno_package_json::PackageJson;
pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"];
pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"];
@@ -76,21 +74,21 @@ impl NodeResolutionMode {
#[derive(Debug)]
pub enum NodeResolution {
- Esm(ModuleSpecifier),
- CommonJs(ModuleSpecifier),
+ Esm(Url),
+ CommonJs(Url),
BuiltIn(String),
}
impl NodeResolution {
- pub fn into_url(self) -> ModuleSpecifier {
+ pub fn into_url(self) -> Url {
match self {
Self::Esm(u) => u,
Self::CommonJs(u) => u,
Self::BuiltIn(specifier) => {
if specifier.starts_with("node:") {
- ModuleSpecifier::parse(&specifier).unwrap()
+ Url::parse(&specifier).unwrap()
} else {
- ModuleSpecifier::parse(&format!("node:{specifier}")).unwrap()
+ Url::parse(&format!("node:{specifier}")).unwrap()
}
}
}
@@ -98,7 +96,7 @@ impl NodeResolution {
pub fn into_specifier_and_media_type(
resolution: Option<Self>,
- ) -> (ModuleSpecifier, MediaType) {
+ ) -> (Url, MediaType) {
match resolution {
Some(NodeResolution::CommonJs(specifier)) => {
let media_type = MediaType::from_specifier(&specifier);
@@ -126,7 +124,7 @@ impl NodeResolution {
}
Some(resolution) => (resolution.into_url(), MediaType::Dts),
None => (
- ModuleSpecifier::parse("internal:///missing_dependency.d.ts").unwrap(),
+ Url::parse("internal:///missing_dependency.d.ts").unwrap(),
MediaType::Dts,
),
}
@@ -134,25 +132,25 @@ impl NodeResolution {
}
#[allow(clippy::disallowed_types)]
-pub type NodeResolverRc = deno_fs::sync::MaybeArc<NodeResolver>;
+pub type NodeResolverRc<TEnv> = crate::sync::MaybeArc<NodeResolver<TEnv>>;
#[derive(Debug)]
-pub struct NodeResolver {
- fs: FileSystemRc,
+pub struct NodeResolver<TEnv: NodeResolverEnv> {
+ env: TEnv,
npm_resolver: NpmResolverRc,
- in_npm_package_cache: deno_fs::sync::MaybeArcMutex<HashMap<String, bool>>,
+ in_npm_package_cache: crate::sync::MaybeArcMutex<HashMap<String, bool>>,
}
-impl NodeResolver {
- pub fn new(fs: FileSystemRc, npm_resolver: NpmResolverRc) -> Self {
+impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
+ pub fn new(env: TEnv, npm_resolver: NpmResolverRc) -> Self {
Self {
- fs,
+ env,
npm_resolver,
- in_npm_package_cache: deno_fs::sync::MaybeArcMutex::new(HashMap::new()),
+ in_npm_package_cache: crate::sync::MaybeArcMutex::new(HashMap::new()),
}
}
- pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool {
+ pub fn in_npm_package(&self, specifier: &Url) -> bool {
self.npm_resolver.in_npm_package(specifier)
}
@@ -163,12 +161,11 @@ impl NodeResolver {
return *result;
}
- let result =
- if let Ok(specifier) = deno_core::ModuleSpecifier::parse(&specifier) {
- self.npm_resolver.in_npm_package(&specifier)
- } else {
- false
- };
+ let result = if let Ok(specifier) = Url::parse(&specifier) {
+ self.npm_resolver.in_npm_package(&specifier)
+ } else {
+ false
+ };
cache.insert(specifier.into_owned(), result);
result
}
@@ -178,14 +175,14 @@ impl NodeResolver {
pub fn resolve(
&self,
specifier: &str,
- referrer: &ModuleSpecifier,
+ referrer: &Url,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
) -> Result<NodeResolution, NodeResolveError> {
// Note: if we are here, then the referrer is an esm module
// TODO(bartlomieju): skipped "policy" part as we don't plan to support it
- if crate::is_builtin_node_module(specifier) {
+ if self.env.is_builtin_node_module(specifier) {
return Ok(NodeResolution::BuiltIn(specifier.to_string()));
}
@@ -248,11 +245,11 @@ impl NodeResolver {
fn module_resolve(
&self,
specifier: &str,
- referrer: &ModuleSpecifier,
+ referrer: &Url,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, NodeResolveError> {
+ ) -> Result<Url, NodeResolveError> {
if should_be_treated_as_relative_or_absolute_path(specifier) {
Ok(referrer.join(specifier).map_err(|err| {
NodeResolveRelativeJoinError {
@@ -289,9 +286,9 @@ impl NodeResolver {
fn finalize_resolution(
&self,
- resolved: ModuleSpecifier,
- maybe_referrer: Option<&ModuleSpecifier>,
- ) -> Result<ModuleSpecifier, FinalizeResolutionError> {
+ resolved: Url,
+ maybe_referrer: Option<&Url>,
+ ) -> Result<Url, FinalizeResolutionError> {
let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C");
if encoded_sep_re.is_match(resolved.path()) {
@@ -325,9 +322,9 @@ impl NodeResolver {
p_str.to_string()
};
- let (is_dir, is_file) = if let Ok(stats) = self.fs.stat_sync(Path::new(&p))
+ let (is_dir, is_file) = if let Ok(stats) = self.env.stat_sync(Path::new(&p))
{
- (stats.is_directory, stats.is_file)
+ (stats.is_dir, stats.is_file)
} else {
(false, false)
};
@@ -357,7 +354,7 @@ impl NodeResolver {
&self,
package_dir: &Path,
package_subpath: Option<&str>,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
mode: NodeResolutionMode,
) -> Result<NodeResolution, ResolvePkgSubpathFromDenoModuleError> {
let node_module_kind = NodeModuleKind::Esm;
@@ -430,7 +427,7 @@ impl NodeResolver {
pub fn url_to_node_resolution(
&self,
- url: ModuleSpecifier,
+ url: Url,
) -> Result<NodeResolution, UrlToNodeResolutionError> {
let url_str = url.as_str().to_lowercase();
if url_str.starts_with("http") || url_str.ends_with(".json") {
@@ -459,11 +456,11 @@ impl NodeResolver {
fn path_to_declaration_url(
&self,
path: &Path,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
- ) -> Result<ModuleSpecifier, TypesNotFoundError> {
- fn probe_extensions(
- fs: &dyn deno_fs::FileSystem,
+ ) -> Result<Url, TypesNotFoundError> {
+ fn probe_extensions<TEnv: NodeResolverEnv>(
+ fs: &TEnv,
path: &Path,
lowercase_path: &str,
referrer_kind: NodeModuleKind,
@@ -514,11 +511,11 @@ impl NodeResolver {
return Ok(to_file_specifier(path));
}
if let Some(path) =
- probe_extensions(&*self.fs, path, &lowercase_path, referrer_kind)
+ probe_extensions(&self.env, path, &lowercase_path, referrer_kind)
{
return Ok(to_file_specifier(&path));
}
- if self.fs.is_dir_sync(path) {
+ if self.env.is_dir_sync(path) {
let resolution_result = self.resolve_package_dir_subpath(
path,
/* sub path */ ".",
@@ -535,7 +532,7 @@ impl NodeResolver {
}
let index_path = path.join("index.js");
if let Some(path) = probe_extensions(
- &*self.fs,
+ &self.env,
&index_path,
&index_path.to_string_lossy().to_lowercase(),
referrer_kind,
@@ -554,15 +551,15 @@ impl NodeResolver {
}
#[allow(clippy::too_many_arguments)]
- pub(super) fn package_imports_resolve(
+ pub fn package_imports_resolve(
&self,
name: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
referrer_pkg_json: Option<&PackageJson>,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageImportsResolveError> {
+ ) -> Result<Url, PackageImportsResolveError> {
if name == "#" || name.starts_with("#/") || name.ends_with('/') {
let reason = "is not a valid internal imports specifier name";
return Err(
@@ -659,13 +656,13 @@ impl NodeResolver {
subpath: &str,
match_: &str,
package_json_path: &Path,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
pattern: bool,
internal: bool,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageTargetResolveError> {
+ ) -> Result<Url, PackageTargetResolveError> {
if !subpath.is_empty() && !pattern && !target.ends_with('/') {
return Err(
InvalidPackageTargetError {
@@ -739,11 +736,8 @@ impl NodeResolver {
return match result {
Ok(url) => Ok(url),
Err(err) => {
- if is_builtin_node_module(target) {
- Ok(
- ModuleSpecifier::parse(&format!("node:{}", target))
- .unwrap(),
- )
+ if self.env.is_builtin_node_module(target) {
+ Ok(Url::parse(&format!("node:{}", target)).unwrap())
} else {
Err(err)
}
@@ -824,13 +818,13 @@ impl NodeResolver {
target: &Value,
subpath: &str,
package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
pattern: bool,
internal: bool,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<Option<ModuleSpecifier>, PackageTargetResolveError> {
+ ) -> Result<Option<Url>, PackageTargetResolveError> {
let result = self.resolve_package_target_inner(
package_json_path,
target,
@@ -880,13 +874,13 @@ impl NodeResolver {
target: &Value,
subpath: &str,
package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
pattern: bool,
internal: bool,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<Option<ModuleSpecifier>, PackageTargetResolveError> {
+ ) -> Result<Option<Url>, PackageTargetResolveError> {
if let Some(target) = target.as_str() {
let url = self.resolve_package_target_string(
target,
@@ -1007,11 +1001,11 @@ impl NodeResolver {
package_json_path: &Path,
package_subpath: &str,
package_exports: &Map<String, Value>,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageExportsResolveError> {
+ ) -> Result<Url, PackageExportsResolveError> {
if package_exports.contains_key(package_subpath)
&& package_subpath.find('*').is_none()
&& !package_subpath.ends_with('/')
@@ -1120,11 +1114,11 @@ impl NodeResolver {
pub(super) fn package_resolve(
&self,
specifier: &str,
- referrer: &ModuleSpecifier,
+ referrer: &Url,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageResolveError> {
+ ) -> Result<Url, PackageResolveError> {
let (package_name, package_subpath, _is_scoped) =
parse_npm_pkg_name(specifier, referrer)?;
@@ -1162,11 +1156,11 @@ impl NodeResolver {
&self,
package_name: &str,
package_subpath: &str,
- referrer: &ModuleSpecifier,
+ referrer: &Url,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageResolveError> {
+ ) -> Result<Url, PackageResolveError> {
let result = self.resolve_package_subpath_for_package_inner(
package_name,
package_subpath,
@@ -1175,7 +1169,7 @@ impl NodeResolver {
conditions,
mode,
);
- if mode.is_types() && !matches!(result, Ok(ModuleSpecifier { .. })) {
+ if mode.is_types() && !matches!(result, Ok(Url { .. })) {
// try to resolve with the @types package
let package_name = types_package_name(package_name);
if let Ok(result) = self.resolve_package_subpath_for_package_inner(
@@ -1197,11 +1191,11 @@ impl NodeResolver {
&self,
package_name: &str,
package_subpath: &str,
- referrer: &ModuleSpecifier,
+ referrer: &Url,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageResolveError> {
+ ) -> Result<Url, PackageResolveError> {
let package_dir_path = self
.npm_resolver
.resolve_package_folder_from_package(package_name, referrer)?;
@@ -1237,11 +1231,11 @@ impl NodeResolver {
&self,
package_dir_path: &Path,
package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageSubpathResolveError> {
+ ) -> Result<Url, PackageSubpathResolveError> {
let package_json_path = package_dir_path.join("package.json");
match self.load_package_json(&package_json_path)? {
Some(pkg_json) => self.resolve_package_subpath(
@@ -1271,11 +1265,11 @@ impl NodeResolver {
&self,
package_json: &PackageJson,
package_subpath: &str,
- referrer: Option<&ModuleSpecifier>,
+ referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, PackageSubpathResolveError> {
+ ) -> Result<Url, PackageSubpathResolveError> {
if let Some(exports) = &package_json.exports {
let result = self.package_exports_resolve(
&package_json.path,
@@ -1328,10 +1322,10 @@ impl NodeResolver {
&self,
directory: &Path,
package_subpath: &str,
- referrer: Option<&ModuleSpecifier>,
+ referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, TypesNotFoundError> {
+ ) -> Result<Url, TypesNotFoundError> {
assert_ne!(package_subpath, ".");
let file_path = directory.join(package_subpath);
if mode.is_types() {
@@ -1345,10 +1339,10 @@ impl NodeResolver {
&self,
directory: &Path,
package_subpath: &str,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, LegacyResolveError> {
+ ) -> Result<Url, LegacyResolveError> {
if package_subpath == "." {
self.legacy_index_resolve(directory, maybe_referrer, referrer_kind, mode)
} else {
@@ -1366,7 +1360,7 @@ impl NodeResolver {
pub fn get_closest_package_json(
&self,
- url: &ModuleSpecifier,
+ url: &Url,
) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
let Ok(file_path) = url.to_file_path() else {
return Ok(None);
@@ -1380,10 +1374,10 @@ impl NodeResolver {
) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
let parent_dir = file_path.parent().unwrap();
let current_dir =
- deno_core::strip_unc_prefix(self.fs.realpath_sync(parent_dir).map_err(
+ strip_unc_prefix(self.env.realpath_sync(parent_dir).map_err(
|source| CanonicalizingPkgJsonDirError {
dir_path: parent_dir.to_path_buf(),
- source: source.into_io_error(),
+ source,
},
)?);
for current_dir in current_dir.ancestors() {
@@ -1396,20 +1390,23 @@ impl NodeResolver {
Ok(None)
}
- pub(super) fn load_package_json(
+ pub fn load_package_json(
&self,
package_json_path: &Path,
) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
- crate::package_json::load_pkg_json(&*self.fs, package_json_path)
+ crate::package_json::load_pkg_json(
+ self.env.pkg_json_fs(),
+ package_json_path,
+ )
}
pub(super) fn legacy_main_resolve(
&self,
package_json: &PackageJson,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, LegacyResolveError> {
+ ) -> Result<Url, LegacyResolveError> {
let maybe_main = if mode.is_types() {
match package_json.types.as_ref() {
Some(types) => Some(types.as_str()),
@@ -1437,7 +1434,7 @@ impl NodeResolver {
if let Some(main) = maybe_main {
let guess = package_json.path.parent().unwrap().join(main).clean();
- if self.fs.is_file_sync(&guess) {
+ if self.env.is_file_sync(&guess) {
return Ok(to_file_specifier(&guess));
}
@@ -1466,7 +1463,7 @@ impl NodeResolver {
.unwrap()
.join(format!("{main}{ending}"))
.clean();
- if self.fs.is_file_sync(&guess) {
+ if self.env.is_file_sync(&guess) {
// TODO(bartlomieju): emitLegacyIndexDeprecation()
return Ok(to_file_specifier(&guess));
}
@@ -1484,10 +1481,10 @@ impl NodeResolver {
fn legacy_index_resolve(
&self,
directory: &Path,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
- ) -> Result<ModuleSpecifier, LegacyResolveError> {
+ ) -> Result<Url, LegacyResolveError> {
let index_file_names = if mode.is_types() {
// todo(dsherret): investigate exactly how typescript does this
match referrer_kind {
@@ -1499,7 +1496,7 @@ impl NodeResolver {
};
for index_file_name in index_file_names {
let guess = directory.join(index_file_name).clean();
- if self.fs.is_file_sync(&guess) {
+ if self.env.is_file_sync(&guess) {
// TODO(bartlomieju): emitLegacyIndexDeprecation()
return Ok(to_file_specifier(&guess));
}
@@ -1615,13 +1612,13 @@ fn resolve_bin_entry_value<'a>(
}
}
-fn to_file_path(url: &ModuleSpecifier) -> PathBuf {
+fn to_file_path(url: &Url) -> PathBuf {
url
.to_file_path()
.unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {url}"))
}
-fn to_file_path_string(url: &ModuleSpecifier) -> String {
+fn to_file_path_string(url: &Url) -> String {
to_file_path(url).display().to_string()
}
@@ -1696,7 +1693,7 @@ fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
path.with_file_name(format!("{file_name}.{ext}"))
}
-fn to_specifier_display_string(url: &ModuleSpecifier) -> String {
+fn to_specifier_display_string(url: &Url) -> String {
if let Ok(path) = url.to_file_path() {
path.display().to_string()
} else {
@@ -1708,7 +1705,7 @@ fn throw_invalid_subpath(
subpath: String,
package_json_path: &Path,
internal: bool,
- maybe_referrer: Option<&ModuleSpecifier>,
+ maybe_referrer: Option<&Url>,
) -> InvalidModuleSpecifierError {
let ie = if internal { "imports" } else { "exports" };
let reason = format!(
@@ -1725,7 +1722,7 @@ fn throw_invalid_subpath(
pub fn parse_npm_pkg_name(
specifier: &str,
- referrer: &ModuleSpecifier,
+ referrer: &Url,
) -> Result<(String, String, bool), InvalidModuleSpecifierError> {
let mut separator_index = specifier.find('/');
let mut valid_package_name = true;
@@ -1824,9 +1821,21 @@ fn types_package_name(package_name: &str) -> String {
format!("@types/{}", package_name.replace('/', "__"))
}
+/// Ex. returns `fs` for `node:fs`
+fn get_module_name_from_builtin_node_module_specifier(
+ specifier: &Url,
+) -> Option<&str> {
+ if specifier.scheme() != "node" {
+ return None;
+ }
+
+ let (_, specifier) = specifier.as_str().split_once(':')?;
+ Some(specifier)
+}
+
#[cfg(test)]
mod tests {
- use deno_core::serde_json::json;
+ use serde_json::json;
use super::*;
diff --git a/ext/node_resolver/sync.rs b/ext/node_resolver/sync.rs
new file mode 100644
index 000000000..f6689a56a
--- /dev/null
+++ b/ext/node_resolver/sync.rs
@@ -0,0 +1,86 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+pub use inner::*;
+
+#[cfg(feature = "sync")]
+mod inner {
+ #![allow(clippy::disallowed_types)]
+
+ use std::ops::Deref;
+ use std::ops::DerefMut;
+ pub use std::sync::Arc as MaybeArc;
+
+ pub struct MaybeArcMutexGuard<'lock, T>(std::sync::MutexGuard<'lock, T>);
+
+ impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> {
+ type Target = std::sync::MutexGuard<'lock, T>;
+ fn deref(&self) -> &std::sync::MutexGuard<'lock, T> {
+ &self.0
+ }
+ }
+
+ impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> {
+ fn deref_mut(&mut self) -> &mut std::sync::MutexGuard<'lock, T> {
+ &mut self.0
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct MaybeArcMutex<T>(std::sync::Arc<std::sync::Mutex<T>>);
+ impl<T> MaybeArcMutex<T> {
+ pub fn new(val: T) -> Self {
+ Self(std::sync::Arc::new(std::sync::Mutex::new(val)))
+ }
+ }
+
+ impl<'lock, T> MaybeArcMutex<T> {
+ pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> {
+ MaybeArcMutexGuard(self.0.lock().unwrap())
+ }
+ }
+
+ pub use core::marker::Send as MaybeSend;
+ pub use core::marker::Sync as MaybeSync;
+}
+
+#[cfg(not(feature = "sync"))]
+mod inner {
+ use std::ops::Deref;
+ use std::ops::DerefMut;
+
+ pub use std::rc::Rc as MaybeArc;
+
+ pub struct MaybeArcMutexGuard<'lock, T>(std::cell::RefMut<'lock, T>);
+
+ impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> {
+ type Target = std::cell::RefMut<'lock, T>;
+ fn deref(&self) -> &std::cell::RefMut<'lock, T> {
+ &self.0
+ }
+ }
+
+ impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> {
+ fn deref_mut(&mut self) -> &mut std::cell::RefMut<'lock, T> {
+ &mut self.0
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct MaybeArcMutex<T>(std::rc::Rc<std::cell::RefCell<T>>);
+ impl<T> MaybeArcMutex<T> {
+ pub fn new(val: T) -> Self {
+ Self(std::rc::Rc::new(std::cell::RefCell::new(val)))
+ }
+ }
+
+ impl<'lock, T> MaybeArcMutex<T> {
+ pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> {
+ MaybeArcMutexGuard(self.0.borrow_mut())
+ }
+ }
+
+ pub trait MaybeSync {}
+ impl<T> MaybeSync for T where T: ?Sized {}
+ pub trait MaybeSend {}
+ impl<T> MaybeSend for T where T: ?Sized {}
+}