From 7c3e29d42186df197c564133abaa2257fa677647 Mon Sep 17 00:00:00 2001 From: Boshen Date: Mon, 14 Aug 2023 11:57:17 +0800 Subject: [PATCH] feat(resolver): expose raw package_json value; improve print debug (#738) --- crates/oxc_resolver/Cargo.toml | 20 ++++++------ crates/oxc_resolver/src/cache.rs | 4 +++ crates/oxc_resolver/src/lib.rs | 42 ++++++++++++++++++++----- crates/oxc_resolver/src/options.rs | 2 +- crates/oxc_resolver/src/package_json.rs | 15 +++++++++ crates/oxc_resolver/src/resolution.rs | 33 +++++++++++++++++-- 6 files changed, 97 insertions(+), 19 deletions(-) diff --git a/crates/oxc_resolver/Cargo.toml b/crates/oxc_resolver/Cargo.toml index 1bc284266..6ee3818df 100644 --- a/crates/oxc_resolver/Cargo.toml +++ b/crates/oxc_resolver/Cargo.toml @@ -13,23 +13,25 @@ rust-version.workspace = true categories.workspace = true [dependencies] -tracing = { workspace = true } -dashmap = { workspace = true } -serde = { workspace = true, features = ["derive"] } # derive for Deserialize from package.json -serde_json = { workspace = true, features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]` +tracing = { workspace = true } +dashmap = { workspace = true } +serde = { workspace = true, features = ["derive"] } # derive for Deserialize from package.json +serde_json = { workspace = true, features = [ + "preserve_order", +] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]` rustc-hash = { workspace = true } -indexmap = { workspace = true, features = ["serde"] } # serde for Deserialize from package.json -dunce = "1.0.4" +indexmap = { workspace = true, features = ["serde"] } # serde for Deserialize from package.json +dunce = "1.0.4" # Use `std::sync::OnceLock::get_or_try_init` when it is stable. once_cell = "1.18.0" [dev-dependencies] -static_assertions = { workspace = true } criterion = { workspace = true } -rayon = { workspace = true } # for benchmark nodejs-resolver = "0.0.88" # for benchmark -vfs = "0.9.0" # for testing with in memory file system +rayon = { workspace = true } # for benchmark +static_assertions = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } +vfs = "0.9.0" # for testing with in memory file system [target.'cfg(not(target_env = "msvc"))'.dev-dependencies] jemallocator = { workspace = true } diff --git a/crates/oxc_resolver/src/cache.rs b/crates/oxc_resolver/src/cache.rs index 8460c77db..db726f28d 100644 --- a/crates/oxc_resolver/src/cache.rs +++ b/crates/oxc_resolver/src/cache.rs @@ -44,6 +44,10 @@ impl Cache { Self { fs, ..Self::default() } } + pub fn clear(&self) { + self.cache.clear(); + } + pub fn value(&self, path: &Path) -> CachedPath { let hash = { let mut hasher = FxHasher::default(); diff --git a/crates/oxc_resolver/src/lib.rs b/crates/oxc_resolver/src/lib.rs index 64d101856..f4a302834 100644 --- a/crates/oxc_resolver/src/lib.rs +++ b/crates/oxc_resolver/src/lib.rs @@ -32,14 +32,16 @@ use std::{ cell::RefCell, cmp::Ordering, ffi::OsStr, + fmt, ops::Deref, path::{Path, PathBuf}, + sync::Arc, }; use crate::{ cache::{Cache, CachedPath}, file_system::FileSystemOs, - package_json::{ExportsField, ExportsKey, MatchObject, PackageJson}, + package_json::{ExportsField, ExportsKey, MatchObject}, path::PathUtil, specifier::{Specifier, SpecifierKind}, }; @@ -47,6 +49,7 @@ pub use crate::{ error::{JSONError, ResolveError}, file_system::{FileMetadata, FileSystem}, options::{Alias, AliasValue, EnforceExtension, ResolveOptions, Restriction}, + package_json::PackageJson, resolution::Resolution, }; @@ -56,7 +59,13 @@ pub type Resolver = ResolverGeneric; /// Generic implementation of the resolver, can be configured by the [FileSystem] trait. pub struct ResolverGeneric { options: ResolveOptions, - cache: Cache, + cache: Arc>, +} + +impl fmt::Debug for ResolverGeneric { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.options.fmt(f) + } } type ResolveState = Result, ResolveError>; @@ -118,11 +127,24 @@ impl Default for ResolverGeneric { impl ResolverGeneric { pub fn new(options: ResolveOptions) -> Self { - Self { options: options.sanitize(), cache: Cache::default() } + Self { options: options.sanitize(), cache: Arc::new(Cache::default()) } } pub fn new_with_file_system(file_system: Fs, options: ResolveOptions) -> Self { - Self { cache: Cache::new(file_system), ..Self::new(options) } + Self { cache: Arc::new(Cache::new(file_system)), ..Self::new(options) } + } + + #[must_use] + pub fn clone_with_options(&self, options: ResolveOptions) -> Self { + Self { options: options.sanitize(), cache: Arc::clone(&self.cache) } + } + + pub fn options(&self) -> &ResolveOptions { + &self.options + } + + pub fn clear_cache(&self) { + self.cache.clear(); } /// Resolve `specifier` at `path` @@ -156,7 +178,12 @@ impl ResolverGeneric { // enhanced-resolve: restrictions self.check_restrictions(&path)?; let mut ctx = ctx.borrow_mut(); - Ok(Resolution { path, query: ctx.query.take(), fragment: ctx.fragment.take() }) + Ok(Resolution { + path, + query: ctx.query.take(), + fragment: ctx.fragment.take(), + package_json: cached_path.find_package_json(&self.cache.fs, &self.options)?, + }) } /// require(X) from module at path Y @@ -698,7 +725,7 @@ impl ResolverGeneric { let new_specifier = Specifier::parse(&new_specifier) .map_err(ResolveError::Specifier)?; ctx.with_fully_specified(false); - // Override query and fragment from the alias + // Alias may contain `?query`, pass it along. ctx.with_query_fragment(specifier.query, specifier.fragment); match self.require(cached_path, &new_specifier, ctx) { Err(ResolveError::NotFound(_)) => { /* noop */ } @@ -708,7 +735,8 @@ impl ResolverGeneric { } } AliasValue::Ignore => { - return Err(ResolveError::Ignored(cached_path.path().join(alias_key))); + let path = cached_path.path().normalize_with(alias_key); + return Err(ResolveError::Ignored(path)); } } } diff --git a/crates/oxc_resolver/src/options.rs b/crates/oxc_resolver/src/options.rs index 9e6033a9f..9ba0ee0fd 100644 --- a/crates/oxc_resolver/src/options.rs +++ b/crates/oxc_resolver/src/options.rs @@ -144,7 +144,7 @@ impl EnforceExtension { pub type Alias = Vec<(String, Vec)>; /// Alias Value for [ResolveOptions::alias] and [ResolveOptions::fallback]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum AliasValue { /// The path value Path(String), diff --git a/crates/oxc_resolver/src/package_json.rs b/crates/oxc_resolver/src/package_json.rs index 38548ba44..7f5d14a71 100644 --- a/crates/oxc_resolver/src/package_json.rs +++ b/crates/oxc_resolver/src/package_json.rs @@ -4,6 +4,7 @@ use std::{ hash::BuildHasherDefault, path::{Path, PathBuf}, + sync::Arc, }; use indexmap::IndexMap; @@ -20,6 +21,10 @@ pub struct PackageJson { #[serde(skip)] path: PathBuf, + #[serde(skip)] + #[serde(default)] + raw_json: Arc, + /// The "name" field defines your package's name. /// The "name" field can be used in addition to the "exports" field to self-reference a package using its name. /// @@ -166,6 +171,7 @@ impl PackageJson { } package_json.path = path; + package_json.raw_json = Arc::new(package_json_value); Ok(package_json) } @@ -188,6 +194,15 @@ impl PackageJson { } /// Directory to `package.json` + pub fn raw_json(&self) -> &serde_json::Value { + self.raw_json.as_ref() + } + + /// Directory to `package.json` + /// + /// # Panics + /// + /// * When the package.json path is misconfigured. pub fn directory(&self) -> &Path { debug_assert!(self.path.file_name().is_some_and(|x| x == "package.json")); self.path.parent().unwrap() diff --git a/crates/oxc_resolver/src/resolution.rs b/crates/oxc_resolver/src/resolution.rs index f8b49702c..527ef4a17 100644 --- a/crates/oxc_resolver/src/resolution.rs +++ b/crates/oxc_resolver/src/resolution.rs @@ -1,7 +1,12 @@ -use std::path::{Path, PathBuf}; +use crate::package_json::PackageJson; +use std::{ + fmt, + path::{Path, PathBuf}, + sync::Arc, +}; /// The final path resolution with optional `?query` and `#fragment`. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct Resolution { pub(crate) path: PathBuf, @@ -10,8 +15,27 @@ pub struct Resolution { /// path fragment `#query`, contains `#`. pub(crate) fragment: Option, + + pub(crate) package_json: Option>, } +impl fmt::Debug for Resolution { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resolution") + .field("path", &self.path) + .field("query", &self.query) + .field("fragment", &self.fragment) + .finish() + } +} + +impl PartialEq for Resolution { + fn eq(&self, other: &Self) -> bool { + self.path == other.path && self.query == other.query && self.fragment == other.fragment + } +} +impl Eq for Resolution {} + impl Resolution { /// Returns the path without query and fragment pub fn path(&self) -> &Path { @@ -33,6 +57,10 @@ impl Resolution { self.fragment.as_deref() } + pub fn package_json(&self) -> Option<&Arc> { + self.package_json.as_ref() + } + /// Returns the full path with query and fragment pub fn full_path(&self) -> PathBuf { let mut path = self.path.clone().into_os_string(); @@ -52,6 +80,7 @@ fn test() { path: PathBuf::from("foo"), query: Some("?query".to_string()), fragment: Some("#fragment".to_string()), + package_json: None, }; assert_eq!(resolution.path(), Path::new("foo")); assert_eq!(resolution.query(), Some("?query"));