diff --git a/Cargo.lock b/Cargo.lock index 0e3ed41..7f1186c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" + [[package]] name = "cast" version = "0.3.0" @@ -1021,6 +1027,7 @@ version = "0.8.0" dependencies = [ "arc-swap", "async-trait", + "camino", "cfg-if", "codspeed-criterion-compat", "dashmap", diff --git a/Cargo.toml b/Cargo.toml index 6c36207..bf5c14a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ tracing = "0.1.41" simd-json = { version = "0.17.0", features = ["serde_impl", "runtime-detection"], default-features = false } +camino = "1.2.1" cfg-if = "1.0" dunce = "1.0.5" # Normalize Windows paths to the most compatible format, avoiding UNC where possible indexmap = { version = "2.12.0", features = ["serde"] } diff --git a/src/lib.rs b/src/lib.rs index 6ca6ec6..98a906e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ use crate::{ cache::{Cache, CachedPath}, context::ResolveContext as Ctx, package_json::JSONMap, - path::{path_to_str, PathUtil, SLASH_START}, + path::{path_to_str, path_to_utf8, PathUtil, SLASH_START}, specifier::Specifier, tsconfig::{ExtendsField, ProjectReference, TsConfig}, }; @@ -691,7 +691,7 @@ impl ResolverGeneric { if ctx.fully_specified { return Ok(None); } - let path = path_to_str(path.path()); + let path = path_to_utf8(path.path()).as_str(); // 8 is wild guess for max extension length let mut path_with_extension_buffer = String::with_capacity(path.len() + 8); path_with_extension_buffer.push_str(path); @@ -1332,7 +1332,7 @@ impl ResolverGeneric { Cow::Borrowed(alias_value) } else { let normalized = alias_path.normalize_with(tail); - Cow::Owned(path_to_str(&normalized).to_string()) + Cow::Owned(path_to_utf8(&normalized).as_str().to_string()) } }; @@ -1397,14 +1397,14 @@ impl ResolverGeneric { // Create a meaningful error message. let dir = path.parent().unwrap().to_path_buf(); let filename_without_extension = Path::new(filename).with_extension(""); - let filename_without_extension = path_to_str(&filename_without_extension); + let filename_without_extension = path_to_utf8(&filename_without_extension).as_str(); let files = extensions .iter() .map(|ext| format!("{filename_without_extension}{ext}")) .collect::>() .join(","); Err(ResolveError::ExtensionAlias( - filename.to_str().expect("path should be UTF-8").to_string(), + path_to_utf8(Path::new(filename)).as_str().to_string(), files, dir, )) diff --git a/src/path.rs b/src/path.rs index 5034533..71b0d81 100644 --- a/src/path.rs +++ b/src/path.rs @@ -5,10 +5,16 @@ //! * [normalize_path](https://docs.rs/normalize-path) use std::path::{Component, Path, PathBuf}; +use camino::Utf8Path; + pub const SLASH_START: &[char; 2] = &['/', '\\']; +pub fn path_to_utf8(path: &Path) -> &Utf8Path { + Utf8Path::from_path(path).expect("path should be UTF-8") +} + pub fn path_to_str(path: &Path) -> &str { - path.to_str().expect("path should be UTF-8") + path_to_utf8(path).as_str() } /// Extension trait to add path normalization to std's [`Path`]. diff --git a/src/tsconfig.rs b/src/tsconfig.rs index a5a1d01..2db02c8 100644 --- a/src/tsconfig.rs +++ b/src/tsconfig.rs @@ -8,7 +8,7 @@ use indexmap::IndexMap; use rustc_hash::FxHasher; use serde::Deserialize; -use crate::path::{path_to_str, PathUtil}; +use crate::path::{path_to_utf8, PathUtil}; pub type CompilerOptionsPathsMap = IndexMap, BuildHasherDefault>; @@ -78,7 +78,7 @@ pub struct ProjectReference { } impl TsConfig { - #[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_str(path))))] + #[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_utf8(path).as_str())))] pub fn parse(root: bool, path: &Path, json: &mut str) -> Result { _ = json_strip_comments::strip(json); if json.trim().is_empty() { @@ -121,12 +121,14 @@ impl TsConfig { } } - let mut p = path_to_str(&self.compiler_options.paths_base).to_string(); + let mut p = path_to_utf8(&self.compiler_options.paths_base) + .as_str() + .to_string(); Self::substitute_template_variable(&dir, &mut p); self.compiler_options.paths_base = p.into(); if let Some(base_url) = self.compiler_options.base_url.as_mut() { - let mut p = path_to_str(base_url).to_string(); + let mut p = path_to_utf8(base_url).as_str().to_string(); Self::substitute_template_variable(&dir, &mut p); *base_url = p.into(); } @@ -258,9 +260,13 @@ impl TsConfig { fn substitute_template_variable(directory: &Path, path: &mut String) { if let Some(stripped_path) = path.strip_prefix(TEMPLATE_VARIABLE) { if let Some(unleashed_path) = stripped_path.strip_prefix("/") { - *path = path_to_str(&directory.join(unleashed_path)).to_string(); + *path = path_to_utf8(&directory.join(unleashed_path)) + .as_str() + .to_string(); } else { - *path = path_to_str(&directory.join(stripped_path)).to_string(); + *path = path_to_utf8(&directory.join(stripped_path)) + .as_str() + .to_string(); } } }