Skip to content

Commit cfa2279

Browse files
committed
refactor: change Tsconfig::parse to accept owned string; add replace_bom_with_whitespace (#859)
1 parent f38eab5 commit cfa2279

File tree

9 files changed

+47
-51
lines changed

9 files changed

+47
-51
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ name = "resolver"
7979
[dependencies]
8080
cfg-if = "1"
8181
indexmap = { version = "2", features = ["serde"] }
82-
json-strip-comments = "3"
82+
json-strip-comments = "3.1"
8383
once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
8484
papaya = "0.2"
8585
parking_lot = "0.12"

src/cache/cache_impl.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,12 @@ impl<Fs: FileSystem> Cache<Fs> {
227227
os_string.push(".json");
228228
Cow::Owned(PathBuf::from(os_string))
229229
};
230-
let mut tsconfig_string = self
230+
let tsconfig_string = self
231231
.fs
232232
.read_to_string(&tsconfig_path)
233233
.map_err(|_| ResolveError::TsconfigNotFound(path.to_path_buf()))?;
234234
let mut tsconfig =
235-
TsConfig::parse(root, &tsconfig_path, &mut tsconfig_string).map_err(|error| {
235+
TsConfig::parse(root, &tsconfig_path, tsconfig_string).map_err(|error| {
236236
ResolveError::from_serde_json_error(tsconfig_path.to_path_buf(), &error)
237237
})?;
238238
callback(&mut tsconfig)?;

src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,3 +1926,13 @@ fn resolve_file_protocol(specifier: &str) -> Result<Cow<'_, str>, ResolveError>
19261926
Ok(Cow::Borrowed(specifier))
19271927
}
19281928
}
1929+
1930+
/// Strip BOM in place by replacing with spaces (no reallocation)
1931+
/// UTF-8 BOM is 3 bytes: 0xEF, 0xBB, 0xBF
1932+
pub(crate) fn replace_bom_with_whitespace(s: &mut [u8]) {
1933+
if s.starts_with(b"\xEF\xBB\xBF") {
1934+
s[0] = b' ';
1935+
s[1] = b' ';
1936+
s[2] = b' ';
1937+
}
1938+
}

src/package_json/serde.rs

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

1010
use serde_json::Value;
1111

12+
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil, replace_bom_with_whitespace};
13+
1214
use super::{ImportsExportsKind, PackageType, SideEffects};
13-
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil};
1415

1516
/// Serde implementation for the deserialized `package.json`.
1617
///
@@ -226,20 +227,15 @@ impl PackageJson {
226227
realpath: PathBuf,
227228
json: Vec<u8>,
228229
) -> Result<Self, JSONError> {
229-
// Strip BOM - UTF-8 BOM is 3 bytes: 0xEF, 0xBB, 0xBF
230-
let json_bytes = if json.starts_with(b"\xEF\xBB\xBF") { &json[3..] } else { &json[..] };
231-
232-
// Check if empty after BOM stripping
233-
super::check_if_empty(json_bytes, &path)?;
234-
235-
// Parse JSON directly from bytes
236-
let value = serde_json::from_slice::<Value>(json_bytes).map_err(|error| JSONError {
230+
let mut json = json;
231+
replace_bom_with_whitespace(&mut json);
232+
super::check_if_empty(&json, &path)?;
233+
let value = serde_json::from_slice::<Value>(&json).map_err(|error| JSONError {
237234
path: path.clone(),
238235
message: error.to_string(),
239236
line: error.line(),
240237
column: error.column(),
241238
})?;
242-
243239
Ok(Self { path, realpath, value })
244240
}
245241

src/package_json/simd.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use self_cell::MutBorrow;
1111
use simd_json::{BorrowedValue, prelude::*};
1212

1313
use super::{ImportsExportsKind, PackageType, SideEffects};
14-
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil};
14+
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil, replace_bom_with_whitespace};
1515

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

271266
// Check if empty after BOM stripping
272-
super::check_if_empty(&json_bytes, &path)?;
267+
super::check_if_empty(&json, &path)?;
273268

274269
// Create the self-cell with the JSON bytes and parsed BorrowedValue
275-
let cell = PackageJsonCell::try_new(MutBorrow::new(json_bytes), |bytes| {
270+
let cell = PackageJsonCell::try_new(MutBorrow::new(json), |bytes| {
276271
// Use MutBorrow to safely get mutable access for simd_json parsing
277272
simd_json::to_borrowed_value(bytes.borrow_mut())
278273
})

src/tests/tsconfig_extends.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ fn test_extend_tsconfig_no_override_existing() {
162162
let parent_path = Path::new("/parent/tsconfig.json");
163163
let child_path = Path::new("/child/tsconfig.json");
164164

165-
let mut parent_config = serde_json::json!({
165+
let parent_config = serde_json::json!({
166166
"compilerOptions": {
167167
"baseUrl": "./src",
168168
"jsx": "react-jsx",
@@ -171,15 +171,15 @@ fn test_extend_tsconfig_no_override_existing() {
171171
})
172172
.to_string();
173173

174-
let mut child_config = serde_json::json!({
174+
let child_config = serde_json::json!({
175175
"compilerOptions": {
176176
"jsx": "preserve" // This should NOT be overridden
177177
}
178178
})
179179
.to_string();
180180

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

184184
// Perform the extension
185185
child_tsconfig.extend_tsconfig(&parent_tsconfig);

src/tests/tsconfig_paths.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ fn empty() {
175175
#[test]
176176
fn test_paths() {
177177
let path = Path::new("/foo/tsconfig.json");
178-
let mut tsconfig_json = serde_json::json!({
178+
let tsconfig_json = serde_json::json!({
179179
"compilerOptions": {
180180
"paths": {
181181
"jquery": ["node_modules/jquery/dist/jquery"],
@@ -188,7 +188,7 @@ fn test_paths() {
188188
}
189189
})
190190
.to_string();
191-
let tsconfig = TsConfig::parse(true, path, &mut tsconfig_json).unwrap().build();
191+
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();
192192

193193
let data = [
194194
("jquery", vec!["/foo/node_modules/jquery/dist/jquery"]),
@@ -212,13 +212,13 @@ fn test_paths() {
212212
#[test]
213213
fn test_base_url() {
214214
let path = Path::new("/foo/tsconfig.json");
215-
let mut tsconfig_json = serde_json::json!({
215+
let tsconfig_json = serde_json::json!({
216216
"compilerOptions": {
217217
"baseUrl": "./src"
218218
}
219219
})
220220
.to_string();
221-
let tsconfig = TsConfig::parse(true, path, &mut tsconfig_json).unwrap().build();
221+
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();
222222

223223
let data = [
224224
("foo", vec!["/foo/src/foo"]),
@@ -237,7 +237,7 @@ fn test_base_url() {
237237
#[test]
238238
fn test_paths_and_base_url() {
239239
let path = Path::new("/foo/tsconfig.json");
240-
let mut tsconfig_json = serde_json::json!({
240+
let tsconfig_json = serde_json::json!({
241241
"compilerOptions": {
242242
"baseUrl": "./src",
243243
"paths": {
@@ -249,7 +249,7 @@ fn test_paths_and_base_url() {
249249
}
250250
})
251251
.to_string();
252-
let tsconfig = TsConfig::parse(true, path, &mut tsconfig_json).unwrap().build();
252+
let tsconfig = TsConfig::parse(true, path, tsconfig_json).unwrap().build();
253253

254254
let data = [
255255
("test", vec!["/foo/src/generated/test", "/foo/src/test"]),

src/tsconfig.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use indexmap::IndexMap;
99
use rustc_hash::FxHasher;
1010
use serde::Deserialize;
1111

12-
use crate::{TsconfigReferences, path::PathUtil};
12+
use crate::{TsconfigReferences, path::PathUtil, replace_bom_with_whitespace};
1313

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

@@ -23,7 +23,7 @@ pub struct ProjectReference {
2323
pub path: PathBuf,
2424
}
2525

26-
#[derive(Debug, Deserialize)]
26+
#[derive(Debug, Default, Deserialize)]
2727
#[serde(rename_all = "camelCase")]
2828
pub struct TsConfig {
2929
/// Whether this is the caller tsconfig.
@@ -66,11 +66,15 @@ impl TsConfig {
6666
/// # Errors
6767
///
6868
/// * Any error that can be returned by `serde_json::from_str()`.
69-
pub fn parse(root: bool, path: &Path, json: &mut str) -> Result<Self, serde_json::Error> {
70-
let json = trim_start_matches_mut(json, '\u{feff}'); // strip bom
71-
_ = json_strip_comments::strip(json);
72-
let mut tsconfig: Self =
73-
serde_json::from_str(if json.trim().is_empty() { "{}" } else { json })?;
69+
pub fn parse(root: bool, path: &Path, json: String) -> Result<Self, serde_json::Error> {
70+
let mut json = json.into_bytes();
71+
replace_bom_with_whitespace(&mut json);
72+
_ = json_strip_comments::strip_slice(&mut json);
73+
let mut tsconfig: Self = if json.iter().all(u8::is_ascii_whitespace) {
74+
Self::default()
75+
} else {
76+
serde_json::from_slice(&json)?
77+
};
7478
tsconfig.root = root;
7579
tsconfig.path = path.to_path_buf();
7680
Ok(tsconfig)
@@ -488,12 +492,3 @@ pub enum ExtendsField {
488492
Single(String),
489493
Multiple(Vec<String>),
490494
}
491-
492-
fn trim_start_matches_mut(s: &mut str, pat: char) -> &mut str {
493-
if s.starts_with(pat) {
494-
// trim the prefix
495-
&mut s[pat.len_utf8()..]
496-
} else {
497-
s
498-
}
499-
}

0 commit comments

Comments
 (0)