Skip to content

Commit 7777d62

Browse files
Manifest traits (#360)
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>
1 parent 2098f8a commit 7777d62

17 files changed

+1049
-545
lines changed

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
7070
papaya = "0.1.8"
7171
rustc-hash = { version = "2" }
7272
seize = { version = "0.4" }
73-
serde = { version = "1", features = ["derive"] } # derive for Deserialize from package.json
74-
serde_json = { version = "1", features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
73+
serde = { version = "1", features = ["derive"], optional = true } # derive for Deserialize from package.json
74+
serde_json = { version = "1", features = ["preserve_order"], optional = true } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
7575
simdutf8 = { version = "0.1" }
7676
thiserror = "1"
7777
tracing = "0.1"
@@ -89,8 +89,8 @@ vfs = "0.12.0" # for testing with in memory file system
8989
[features]
9090
default = ["fs_cache"]
9191
## Provides the `FsCache` implementation.
92-
fs_cache = []
93-
## Enables the [PackageJson::raw_json] API,
92+
fs_cache = ["dep:serde", "dep:serde_json"]
93+
## Enables the [PackageJsonSerde::raw_json] API,
9494
## which returns the `package.json` with `serde_json::Value`.
9595
package_json_raw_json_api = []
9696
## [Yarn Plug'n'Play](https://yarnpkg.com/features/pnp)

napi/src/lib.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99

1010
use napi::{bindgen_prelude::AsyncTask, Task};
1111
use napi_derive::napi;
12-
use oxc_resolver::{ResolveOptions, Resolver};
12+
use oxc_resolver::{PackageJson, ResolveOptions, Resolver};
1313

1414
use self::{
1515
options::{NapiResolveOptions, StrOrStrList},
@@ -32,11 +32,7 @@ fn resolve(resolver: &Resolver, path: &Path, request: &str) -> ResolveResult {
3232
Ok(resolution) => ResolveResult {
3333
path: Some(resolution.full_path().to_string_lossy().to_string()),
3434
error: None,
35-
module_type: resolution
36-
.package_json()
37-
.and_then(|p| p.r#type.as_ref())
38-
.and_then(|t| t.as_str())
39-
.map(|t| t.to_string()),
35+
module_type: resolution.package_json().and_then(|p| p.r#type()).map(|t| t.to_string()),
4036
},
4137
Err(err) => ResolveResult { path: None, module_type: None, error: Some(err.to_string()) },
4238
}

src/cache.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use std::{
2+
fmt::Debug,
23
path::{Path, PathBuf},
34
sync::Arc,
45
};
56

6-
use crate::{tsconfig::TsConfig, Ctx, PackageJson, ResolveError, ResolveOptions};
7+
use crate::{Ctx, PackageJson, ResolveError, ResolveOptions, TsConfig};
78

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

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

3942
/// Returns the tsconfig stored in the given path.
4043
///
@@ -43,14 +46,15 @@ pub trait Cache: Sized {
4346
///
4447
/// `callback` can be used for modifying the returned tsconfig with
4548
/// `extends`.
46-
fn get_tsconfig<F: FnOnce(&mut TsConfig) -> Result<(), ResolveError>>(
49+
fn get_tsconfig<F: FnOnce(&mut Self::Tc) -> Result<(), ResolveError>>(
4750
&self,
4851
root: bool,
4952
path: &Path,
5053
callback: F,
51-
) -> Result<Arc<TsConfig>, ResolveError>;
54+
) -> Result<Arc<Self::Tc>, ResolveError>;
5255
}
5356

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

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

7074
/// Find package.json of a path by traversing parent directories.
71-
///
72-
/// # Errors
73-
///
74-
/// * [ResolveError::JSON]
75+
#[allow(clippy::type_complexity)]
7576
fn find_package_json<C: Cache<Cp = Self>>(
7677
&self,
7778
options: &ResolveOptions,
7879
cache: &C,
7980
ctx: &mut Ctx,
80-
) -> Result<Option<(Self, Arc<PackageJson>)>, ResolveError>;
81+
) -> Result<Option<(Self, Arc<C::Pj>)>, ResolveError>;
8182

8283
#[must_use]
8384
fn add_extension<C: Cache<Cp = Self>>(&self, ext: &str, cache: &C) -> Self;

src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ impl ResolveError {
108108
}
109109

110110
#[must_use]
111+
#[cfg(feature = "fs_cache")]
111112
pub fn from_serde_json_error(path: PathBuf, error: &serde_json::Error) -> Self {
112113
Self::JSON(JSONError {
113114
path,

src/fs_cache.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ use rustc_hash::FxHasher;
2020
use crate::{
2121
cache::{Cache, CachedPath},
2222
context::ResolveContext as Ctx,
23-
package_json::PackageJson,
2423
path::PathUtil,
25-
FileMetadata, FileSystem, ResolveError, ResolveOptions, TsConfig,
24+
FileMetadata, FileSystem, PackageJsonSerde, ResolveError, ResolveOptions, TsConfig,
25+
TsConfigSerde,
2626
};
2727

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

4545
impl<Fs: FileSystem> Cache for FsCache<Fs> {
4646
type Cp = FsCachedPath;
47+
type Pj = PackageJsonSerde;
48+
type Tc = TsConfigSerde;
4749

4850
fn clear(&self) {
4951
self.paths.pin().clear();
@@ -109,7 +111,7 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
109111
path: &Self::Cp,
110112
options: &ResolveOptions,
111113
ctx: &mut Ctx,
112-
) -> Result<Option<(Self::Cp, Arc<PackageJson>)>, ResolveError> {
114+
) -> Result<Option<(Self::Cp, Arc<PackageJsonSerde>)>, ResolveError> {
113115
// Change to `std::sync::OnceLock::get_or_try_init` when it is stable.
114116
let result = path
115117
.package_json
@@ -123,7 +125,7 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
123125
} else {
124126
package_json_path.clone()
125127
};
126-
PackageJson::parse(package_json_path.clone(), real_path, &package_json_string)
128+
PackageJsonSerde::parse(package_json_path.clone(), real_path, &package_json_string)
127129
.map(|package_json| Some((path.clone(), (Arc::new(package_json)))))
128130
.map_err(|error| ResolveError::from_serde_json_error(package_json_path, &error))
129131
})
@@ -148,12 +150,12 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
148150
result
149151
}
150152

151-
fn get_tsconfig<F: FnOnce(&mut TsConfig) -> Result<(), ResolveError>>(
153+
fn get_tsconfig<F: FnOnce(&mut TsConfigSerde) -> Result<(), ResolveError>>(
152154
&self,
153155
root: bool,
154156
path: &Path,
155157
callback: F, // callback for modifying tsconfig with `extends`
156-
) -> Result<Arc<TsConfig>, ResolveError> {
158+
) -> Result<Arc<TsConfigSerde>, ResolveError> {
157159
let tsconfigs = self.tsconfigs.pin();
158160
if let Some(tsconfig) = tsconfigs.get(path) {
159161
return Ok(Arc::clone(tsconfig));
@@ -172,12 +174,13 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
172174
.fs
173175
.read_to_string(&tsconfig_path)
174176
.map_err(|_| ResolveError::TsconfigNotFound(path.to_path_buf()))?;
175-
let mut tsconfig =
176-
TsConfig::parse(root, &tsconfig_path, &mut tsconfig_string).map_err(|error| {
177+
let mut tsconfig = TsConfigSerde::parse(root, &tsconfig_path, &mut tsconfig_string)
178+
.map_err(|error| {
177179
ResolveError::from_serde_json_error(tsconfig_path.to_path_buf(), &error)
178180
})?;
179181
callback(&mut tsconfig)?;
180-
let tsconfig = Arc::new(tsconfig.build());
182+
tsconfig.expand_template_variables();
183+
let tsconfig = Arc::new(tsconfig);
181184
tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig));
182185
Ok(tsconfig)
183186
}
@@ -264,7 +267,7 @@ pub struct CachedPathImpl {
264267
canonicalized: OnceLock<Result<FsCachedPath, ResolveError>>,
265268
canonicalizing: AtomicU64,
266269
node_modules: OnceLock<Option<FsCachedPath>>,
267-
package_json: OnceLock<Option<(FsCachedPath, Arc<PackageJson>)>>,
270+
package_json: OnceLock<Option<(FsCachedPath, Arc<PackageJsonSerde>)>>,
268271
}
269272

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

0 commit comments

Comments
 (0)