Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ name = "resolver"
[dependencies]
cfg-if = "1"
indexmap = { version = "2", features = ["serde"] }
json-strip-comments = "3"
json-strip-comments = "3.1"
once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
papaya = "0.2"
parking_lot = "0.12"
Expand Down
4 changes: 2 additions & 2 deletions src/cache/cache_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,12 @@ impl<Fs: FileSystem> Cache<Fs> {
os_string.push(".json");
Cow::Owned(PathBuf::from(os_string))
};
let mut tsconfig_string = self
let tsconfig_string = self
.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| {
TsConfig::parse(root, &tsconfig_path, tsconfig_string).map_err(|error| {
ResolveError::from_serde_json_error(tsconfig_path.to_path_buf(), &error)
})?;
callback(&mut tsconfig)?;
Expand Down
10 changes: 10 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1926,3 +1926,13 @@ fn resolve_file_protocol(specifier: &str) -> Result<Cow<'_, str>, ResolveError>
Ok(Cow::Borrowed(specifier))
}
}

/// Strip BOM in place by replacing with spaces (no reallocation)
/// UTF-8 BOM is 3 bytes: 0xEF, 0xBB, 0xBF
pub(crate) fn replace_bom_with_whitespace(s: &mut [u8]) {
if s.starts_with(b"\xEF\xBB\xBF") {
s[0] = b' ';
s[1] = b' ';
s[2] = b' ';
}
}
16 changes: 6 additions & 10 deletions src/package_json/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use std::{

use serde_json::Value;

use crate::{FileSystem, JSONError, ResolveError, path::PathUtil, replace_bom_with_whitespace};

use super::{ImportsExportsKind, PackageType, SideEffects};
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil};

/// Serde implementation for the deserialized `package.json`.
///
Expand Down Expand Up @@ -226,20 +227,15 @@ impl PackageJson {
realpath: PathBuf,
json: Vec<u8>,
) -> Result<Self, JSONError> {
// Strip BOM - UTF-8 BOM is 3 bytes: 0xEF, 0xBB, 0xBF
let json_bytes = if json.starts_with(b"\xEF\xBB\xBF") { &json[3..] } else { &json[..] };

// Check if empty after BOM stripping
super::check_if_empty(json_bytes, &path)?;

// Parse JSON directly from bytes
let value = serde_json::from_slice::<Value>(json_bytes).map_err(|error| JSONError {
let mut json = json;
replace_bom_with_whitespace(&mut json);
super::check_if_empty(&json, &path)?;
let value = serde_json::from_slice::<Value>(&json).map_err(|error| JSONError {
path: path.clone(),
message: error.to_string(),
line: error.line(),
column: error.column(),
})?;

Ok(Self { path, realpath, value })
}

Expand Down
15 changes: 5 additions & 10 deletions src/package_json/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use self_cell::MutBorrow;
use simd_json::{BorrowedValue, prelude::*};

use super::{ImportsExportsKind, PackageType, SideEffects};
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil};
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil, replace_bom_with_whitespace};

// Use simd_json's Object type which handles the hasher correctly based on features
type BorrowedObject<'a> = simd_json::value::borrowed::Object<'a>;
Expand Down Expand Up @@ -260,19 +260,14 @@ impl PackageJson {
realpath: PathBuf,
json: Vec<u8>,
) -> Result<Self, JSONError> {
// Strip BOM in place by replacing with spaces (no reallocation)
let mut json_bytes = json;
if json_bytes.starts_with(b"\xEF\xBB\xBF") {
json_bytes[0] = b' ';
json_bytes[1] = b' ';
json_bytes[2] = b' ';
}
let mut json = json;
replace_bom_with_whitespace(&mut json);

// Check if empty after BOM stripping
super::check_if_empty(&json_bytes, &path)?;
super::check_if_empty(&json, &path)?;

// Create the self-cell with the JSON bytes and parsed BorrowedValue
let cell = PackageJsonCell::try_new(MutBorrow::new(json_bytes), |bytes| {
let cell = PackageJsonCell::try_new(MutBorrow::new(json), |bytes| {
// Use MutBorrow to safely get mutable access for simd_json parsing
simd_json::to_borrowed_value(bytes.borrow_mut())
})
Expand Down
8 changes: 4 additions & 4 deletions src/tests/tsconfig_extends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ fn test_extend_tsconfig_no_override_existing() {
let parent_path = Path::new("/parent/tsconfig.json");
let child_path = Path::new("/child/tsconfig.json");

let mut parent_config = serde_json::json!({
let parent_config = serde_json::json!({
"compilerOptions": {
"baseUrl": "./src",
"jsx": "react-jsx",
Expand All @@ -171,15 +171,15 @@ fn test_extend_tsconfig_no_override_existing() {
})
.to_string();

let mut child_config = serde_json::json!({
let child_config = serde_json::json!({
"compilerOptions": {
"jsx": "preserve" // This should NOT be overridden
}
})
.to_string();

let parent_tsconfig = TsConfig::parse(true, parent_path, &mut parent_config).unwrap().build();
let mut child_tsconfig = TsConfig::parse(true, child_path, &mut child_config).unwrap();
let parent_tsconfig = TsConfig::parse(true, parent_path, parent_config).unwrap().build();
let mut child_tsconfig = TsConfig::parse(true, child_path, child_config).unwrap();

// Perform the extension
child_tsconfig.extend_tsconfig(&parent_tsconfig);
Expand Down
12 changes: 6 additions & 6 deletions src/tests/tsconfig_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ fn empty() {
#[test]
fn test_paths() {
let path = Path::new("/foo/tsconfig.json");
let mut tsconfig_json = serde_json::json!({
let tsconfig_json = serde_json::json!({
"compilerOptions": {
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"],
Expand All @@ -188,7 +188,7 @@ fn test_paths() {
}
})
.to_string();
let tsconfig = TsConfig::parse(true, path, &mut tsconfig_json).unwrap().build();
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();

let data = [
("jquery", vec!["/foo/node_modules/jquery/dist/jquery"]),
Expand All @@ -212,13 +212,13 @@ fn test_paths() {
#[test]
fn test_base_url() {
let path = Path::new("/foo/tsconfig.json");
let mut tsconfig_json = serde_json::json!({
let tsconfig_json = serde_json::json!({
"compilerOptions": {
"baseUrl": "./src"
}
})
.to_string();
let tsconfig = TsConfig::parse(true, path, &mut tsconfig_json).unwrap().build();
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();

let data = [
("foo", vec!["/foo/src/foo"]),
Expand All @@ -237,7 +237,7 @@ fn test_base_url() {
#[test]
fn test_paths_and_base_url() {
let path = Path::new("/foo/tsconfig.json");
let mut tsconfig_json = serde_json::json!({
let tsconfig_json = serde_json::json!({
"compilerOptions": {
"baseUrl": "./src",
"paths": {
Expand All @@ -249,7 +249,7 @@ fn test_paths_and_base_url() {
}
})
.to_string();
let tsconfig = TsConfig::parse(true, path, &mut tsconfig_json).unwrap().build();
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();

let data = [
("test", vec!["/foo/src/generated/test", "/foo/src/test"]),
Expand Down
27 changes: 11 additions & 16 deletions src/tsconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use indexmap::IndexMap;
use rustc_hash::FxHasher;
use serde::Deserialize;

use crate::{TsconfigReferences, path::PathUtil};
use crate::{TsconfigReferences, path::PathUtil, replace_bom_with_whitespace};

const TEMPLATE_VARIABLE: &str = "${configDir}";

Expand All @@ -23,7 +23,7 @@ pub struct ProjectReference {
pub path: PathBuf,
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TsConfig {
/// Whether this is the caller tsconfig.
Expand Down Expand Up @@ -66,11 +66,15 @@ impl TsConfig {
/// # Errors
///
/// * Any error that can be returned by `serde_json::from_str()`.
pub fn parse(root: bool, path: &Path, json: &mut str) -> Result<Self, serde_json::Error> {
let json = trim_start_matches_mut(json, '\u{feff}'); // strip bom
_ = json_strip_comments::strip(json);
let mut tsconfig: Self =
serde_json::from_str(if json.trim().is_empty() { "{}" } else { json })?;
pub fn parse(root: bool, path: &Path, json: String) -> Result<Self, serde_json::Error> {
let mut json = json.into_bytes();
replace_bom_with_whitespace(&mut json);
_ = json_strip_comments::strip_slice(&mut json);
let mut tsconfig: Self = if json.iter().all(u8::is_ascii_whitespace) {
Self::default()
} else {
serde_json::from_slice(&json)?
};
tsconfig.root = root;
tsconfig.path = path.to_path_buf();
Ok(tsconfig)
Expand Down Expand Up @@ -488,12 +492,3 @@ pub enum ExtendsField {
Single(String),
Multiple(Vec<String>),
}

fn trim_start_matches_mut(s: &mut str, pat: char) -> &mut str {
if s.starts_with(pat) {
// trim the prefix
&mut s[pat.len_utf8()..]
} else {
s
}
}