diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2024-07-25 19:08:14 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-25 19:08:14 -0400 |
commit | 3bf147fe287ac779b20d318daba56b336f356adf (patch) | |
tree | 3b5bfe2a1ad918b275a2cd08f7dcc05f90a180ab /ext | |
parent | 0cf7f268a7df7711ac6ab8c2c67b4d7abf454fcd (diff) |
refactor: decouple node resolution from deno_core (#24724)
Diffstat (limited to 'ext')
-rw-r--r-- | ext/fs/clippy.toml | 38 | ||||
-rw-r--r-- | ext/fs/sync.rs | 62 | ||||
-rw-r--r-- | ext/node/Cargo.toml | 3 | ||||
-rw-r--r-- | ext/node/global.rs | 2 | ||||
-rw-r--r-- | ext/node/lib.rs | 161 | ||||
-rw-r--r-- | ext/node/ops/require.rs | 17 | ||||
-rw-r--r-- | ext/node/ops/worker_threads.rs | 14 | ||||
-rw-r--r-- | ext/node/path.rs | 50 | ||||
-rw-r--r-- | ext/node/polyfill.rs | 14 | ||||
-rw-r--r-- | ext/node_resolver/Cargo.toml | 32 | ||||
-rw-r--r-- | ext/node_resolver/README.md | 6 | ||||
-rw-r--r-- | ext/node_resolver/analyze.rs (renamed from ext/node/analyze.rs) | 63 | ||||
-rw-r--r-- | ext/node_resolver/clippy.toml | 48 | ||||
-rw-r--r-- | ext/node_resolver/env.rs | 39 | ||||
-rw-r--r-- | ext/node_resolver/errors.rs (renamed from ext/node/errors.rs) | 32 | ||||
-rw-r--r-- | ext/node_resolver/lib.rs | 26 | ||||
-rw-r--r-- | ext/node_resolver/npm.rs | 41 | ||||
-rw-r--r-- | ext/node_resolver/package_json.rs (renamed from ext/node/package_json.rs) | 25 | ||||
-rw-r--r-- | ext/node_resolver/path.rs | 142 | ||||
-rw-r--r-- | ext/node_resolver/resolution.rs (renamed from ext/node/resolution.rs) | 207 | ||||
-rw-r--r-- | ext/node_resolver/sync.rs | 86 |
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 + +[](https://crates.io/crates/node_resolver) +[](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 {} +} |