Skip to content

Commit

Permalink
Manifest traits (#360)
Browse files Browse the repository at this point in the history
This introduces `PackageJson` and `TsConfig` traits. The old structs
have been renamed to `PackageJsonSerde` and TsConfigSerde` respectively.
Both are behind the `fs_cache` feature flag now. `serde` as a dependency
has become optional and is only needed for the `fs_cache` feature too.

For now, I have opted not to replace the `FileSystem::read_to_string()`
method with trait-specific versions yet, since this functionality is now
all encapsulated within the `FsCache`. Consumers that wish to use custom
implementation of the manifest traits most likely want to use a custom
cache altogether (I know this will be true for Biome at least), so I
didn't see much reason for additional complexity and breaking changes
there now.

~~I'm still working on a Biome PoC based on this, so I'll keep the PR on
Draft until I can verify everything works. Feedback is certainly already
welcome!~~ **Update:** See biomejs/biome#4929

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
arendjr and autofix-ci[bot] authored Jan 20, 2025
1 parent 2098f8a commit 7777d62
Show file tree
Hide file tree
Showing 17 changed files with 1,049 additions and 545 deletions.
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
papaya = "0.1.8"
rustc-hash = { version = "2" }
seize = { version = "0.4" }
serde = { version = "1", features = ["derive"] } # derive for Deserialize from package.json
serde_json = { version = "1", features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
serde = { version = "1", features = ["derive"], optional = true } # derive for Deserialize from package.json
serde_json = { version = "1", features = ["preserve_order"], optional = true } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
simdutf8 = { version = "0.1" }
thiserror = "1"
tracing = "0.1"
Expand All @@ -89,8 +89,8 @@ vfs = "0.12.0" # for testing with in memory file system
[features]
default = ["fs_cache"]
## Provides the `FsCache` implementation.
fs_cache = []
## Enables the [PackageJson::raw_json] API,
fs_cache = ["dep:serde", "dep:serde_json"]
## Enables the [PackageJsonSerde::raw_json] API,
## which returns the `package.json` with `serde_json::Value`.
package_json_raw_json_api = []
## [Yarn Plug'n'Play](https://yarnpkg.com/features/pnp)
Expand Down
8 changes: 2 additions & 6 deletions napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{

use napi::{bindgen_prelude::AsyncTask, Task};
use napi_derive::napi;
use oxc_resolver::{ResolveOptions, Resolver};
use oxc_resolver::{PackageJson, ResolveOptions, Resolver};

use self::{
options::{NapiResolveOptions, StrOrStrList},
Expand All @@ -32,11 +32,7 @@ fn resolve(resolver: &Resolver, path: &Path, request: &str) -> ResolveResult {
Ok(resolution) => ResolveResult {
path: Some(resolution.full_path().to_string_lossy().to_string()),
error: None,
module_type: resolution
.package_json()
.and_then(|p| p.r#type.as_ref())
.and_then(|t| t.as_str())
.map(|t| t.to_string()),
module_type: resolution.package_json().and_then(|p| p.r#type()).map(|t| t.to_string()),
},
Err(err) => ResolveResult { path: None, module_type: None, error: Some(err.to_string()) },
}
Expand Down
19 changes: 10 additions & 9 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use std::{
fmt::Debug,
path::{Path, PathBuf},
sync::Arc,
};

use crate::{tsconfig::TsConfig, Ctx, PackageJson, ResolveError, ResolveOptions};
use crate::{Ctx, PackageJson, ResolveError, ResolveOptions, TsConfig};

#[allow(clippy::missing_errors_doc)] // trait impls should be free to return any typesafe error
pub trait Cache: Sized {
type Cp: CachedPath + Clone;
type Pj: PackageJson;
type Tc: TsConfig + Debug;

/// Clears the cache.
fn clear(&self);
Expand All @@ -34,7 +37,7 @@ pub trait Cache: Sized {
path: &Self::Cp,
options: &ResolveOptions,
ctx: &mut Ctx,
) -> Result<Option<(Self::Cp, Arc<PackageJson>)>, ResolveError>;
) -> Result<Option<(Self::Cp, Arc<Self::Pj>)>, ResolveError>;

/// Returns the tsconfig stored in the given path.
///
Expand All @@ -43,14 +46,15 @@ pub trait Cache: Sized {
///
/// `callback` can be used for modifying the returned tsconfig with
/// `extends`.
fn get_tsconfig<F: FnOnce(&mut TsConfig) -> Result<(), ResolveError>>(
fn get_tsconfig<F: FnOnce(&mut Self::Tc) -> Result<(), ResolveError>>(
&self,
root: bool,
path: &Path,
callback: F,
) -> Result<Arc<TsConfig>, ResolveError>;
) -> Result<Arc<Self::Tc>, ResolveError>;
}

#[allow(clippy::missing_errors_doc)] // trait impls should be free to return any typesafe error
pub trait CachedPath: Sized {
fn path(&self) -> &Path;

Expand All @@ -68,16 +72,13 @@ pub trait CachedPath: Sized {
fn cached_node_modules<C: Cache<Cp = Self>>(&self, cache: &C, ctx: &mut Ctx) -> Option<Self>;

/// Find package.json of a path by traversing parent directories.
///
/// # Errors
///
/// * [ResolveError::JSON]
#[allow(clippy::type_complexity)]
fn find_package_json<C: Cache<Cp = Self>>(
&self,
options: &ResolveOptions,
cache: &C,
ctx: &mut Ctx,
) -> Result<Option<(Self, Arc<PackageJson>)>, ResolveError>;
) -> Result<Option<(Self, Arc<C::Pj>)>, ResolveError>;

#[must_use]
fn add_extension<C: Cache<Cp = Self>>(&self, ext: &str, cache: &C) -> Self;
Expand Down
1 change: 1 addition & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl ResolveError {
}

#[must_use]
#[cfg(feature = "fs_cache")]
pub fn from_serde_json_error(path: PathBuf, error: &serde_json::Error) -> Self {
Self::JSON(JSONError {
path,
Expand Down
27 changes: 15 additions & 12 deletions src/fs_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ use rustc_hash::FxHasher;
use crate::{
cache::{Cache, CachedPath},
context::ResolveContext as Ctx,
package_json::PackageJson,
path::PathUtil,
FileMetadata, FileSystem, ResolveError, ResolveOptions, TsConfig,
FileMetadata, FileSystem, PackageJsonSerde, ResolveError, ResolveOptions, TsConfig,
TsConfigSerde,
};

static THREAD_COUNT: AtomicU64 = AtomicU64::new(1);
Expand All @@ -39,11 +39,13 @@ thread_local! {
pub struct FsCache<Fs> {
pub(crate) fs: Fs,
paths: HashSet<FsCachedPath, BuildHasherDefault<IdentityHasher>>,
tsconfigs: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
tsconfigs: HashMap<PathBuf, Arc<TsConfigSerde>, BuildHasherDefault<FxHasher>>,
}

impl<Fs: FileSystem> Cache for FsCache<Fs> {
type Cp = FsCachedPath;
type Pj = PackageJsonSerde;
type Tc = TsConfigSerde;

fn clear(&self) {
self.paths.pin().clear();
Expand Down Expand Up @@ -109,7 +111,7 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
path: &Self::Cp,
options: &ResolveOptions,
ctx: &mut Ctx,
) -> Result<Option<(Self::Cp, Arc<PackageJson>)>, ResolveError> {
) -> Result<Option<(Self::Cp, Arc<PackageJsonSerde>)>, ResolveError> {
// Change to `std::sync::OnceLock::get_or_try_init` when it is stable.
let result = path
.package_json
Expand All @@ -123,7 +125,7 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
} else {
package_json_path.clone()
};
PackageJson::parse(package_json_path.clone(), real_path, &package_json_string)
PackageJsonSerde::parse(package_json_path.clone(), real_path, &package_json_string)
.map(|package_json| Some((path.clone(), (Arc::new(package_json)))))
.map_err(|error| ResolveError::from_serde_json_error(package_json_path, &error))
})
Expand All @@ -148,12 +150,12 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
result
}

fn get_tsconfig<F: FnOnce(&mut TsConfig) -> Result<(), ResolveError>>(
fn get_tsconfig<F: FnOnce(&mut TsConfigSerde) -> Result<(), ResolveError>>(
&self,
root: bool,
path: &Path,
callback: F, // callback for modifying tsconfig with `extends`
) -> Result<Arc<TsConfig>, ResolveError> {
) -> Result<Arc<TsConfigSerde>, ResolveError> {
let tsconfigs = self.tsconfigs.pin();
if let Some(tsconfig) = tsconfigs.get(path) {
return Ok(Arc::clone(tsconfig));
Expand All @@ -172,12 +174,13 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
.fs
.read_to_string(&tsconfig_path)
.map_err(|_| ResolveError::TsconfigNotFound(path.to_path_buf()))?;
let mut tsconfig =
TsConfig::parse(root, &tsconfig_path, &mut tsconfig_string).map_err(|error| {
let mut tsconfig = TsConfigSerde::parse(root, &tsconfig_path, &mut tsconfig_string)
.map_err(|error| {
ResolveError::from_serde_json_error(tsconfig_path.to_path_buf(), &error)
})?;
callback(&mut tsconfig)?;
let tsconfig = Arc::new(tsconfig.build());
tsconfig.expand_template_variables();
let tsconfig = Arc::new(tsconfig);
tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig));
Ok(tsconfig)
}
Expand Down Expand Up @@ -264,7 +267,7 @@ pub struct CachedPathImpl {
canonicalized: OnceLock<Result<FsCachedPath, ResolveError>>,
canonicalizing: AtomicU64,
node_modules: OnceLock<Option<FsCachedPath>>,
package_json: OnceLock<Option<(FsCachedPath, Arc<PackageJson>)>>,
package_json: OnceLock<Option<(FsCachedPath, Arc<PackageJsonSerde>)>>,
}

impl CachedPathImpl {
Expand Down Expand Up @@ -327,7 +330,7 @@ impl CachedPath for FsCachedPath {
options: &ResolveOptions,
cache: &C,
ctx: &mut Ctx,
) -> Result<Option<(Self, Arc<PackageJson>)>, ResolveError> {
) -> Result<Option<(Self, Arc<C::Pj>)>, ResolveError> {
let mut cache_value = self;
// Go up directories when the querying path is not a directory
while !cache.is_dir(cache_value, ctx) {
Expand Down
Loading

0 comments on commit 7777d62

Please sign in to comment.