Skip to content

Commit dde55f7

Browse files
committed
refactor utf8 path conversions
1 parent 8975e38 commit dde55f7

13 files changed

Lines changed: 115 additions & 60 deletions

File tree

napi/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ pub struct ResolveResult {
2626
async fn resolve(resolver: &Resolver, path: &Path, request: &str) -> ResolveResult {
2727
match resolver.resolve(path, request).await {
2828
Ok(resolution) => ResolveResult {
29-
path: Some(resolution.full_path().to_string_lossy().to_string()),
29+
path: Some(
30+
resolution
31+
.full_path()
32+
.to_str()
33+
.expect("path should be UTF-8")
34+
.to_string(),
35+
),
3036
error: None,
3137
module_type: resolution
3238
.package_json()

src/file_system.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,12 @@ impl FileSystem for FileSystemOs {
263263
path_buf.pop();
264264
}
265265
Component::Normal(seg) => {
266-
path_buf.push(seg.to_string_lossy().trim_end_matches('\0'));
266+
path_buf.push(
267+
seg
268+
.to_str()
269+
.expect("path should be UTF-8")
270+
.trim_end_matches('\0'),
271+
);
267272
}
268273
Component::RootDir => {
269274
path_buf = PathBuf::from("/");

src/lib.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ use crate::{
9090
cache::{Cache, CachedPath},
9191
context::ResolveContext as Ctx,
9292
package_json::JSONMap,
93-
path::{PathUtil, SLASH_START},
93+
path::{path_to_str, PathUtil, SLASH_START},
9494
specifier::Specifier,
9595
tsconfig::{ExtendsField, ProjectReference, TsConfig},
9696
};
@@ -239,7 +239,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
239239
}
240240

241241
/// Wrap `resolve_impl` with `tracing` information
242-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = %directory.to_string_lossy(), specifier = specifier)))]
242+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_str(directory), specifier = specifier)))]
243243
async fn resolve_tracing(
244244
&self,
245245
directory: &Path,
@@ -436,7 +436,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
436436
}
437437

438438
// 3. If X begins with './' or '/' or '../'
439-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = %cached_path.path().to_string_lossy())))]
439+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = path_to_str(cached_path.path()))))]
440440
async fn require_relative(
441441
&self,
442442
cached_path: &CachedPath,
@@ -536,7 +536,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
536536
Ok((parsed, None))
537537
}
538538

539-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = %cached_path.path().to_string_lossy())))]
539+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = path_to_str(cached_path.path()))))]
540540
async fn load_package_self_or_node_modules(
541541
&self,
542542
cached_path: &CachedPath,
@@ -590,7 +590,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
590590
Ok(None)
591591
}
592592

593-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = %cached_path.path().to_string_lossy())))]
593+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_str(cached_path.path()))))]
594594
async fn load_as_file(&self, cached_path: &CachedPath, ctx: &mut Ctx) -> ResolveResult {
595595
// enhanced-resolve feature: extension_alias
596596
if let Some(path) = self.load_extension_alias(cached_path, ctx).await? {
@@ -653,7 +653,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
653653
self.load_index(cached_path, ctx).await
654654
}
655655

656-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = %cached_path.path().to_string_lossy())))]
656+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = path_to_str(cached_path.path()))))]
657657
async fn load_as_file_or_directory(
658658
&self,
659659
cached_path: &CachedPath,
@@ -681,7 +681,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
681681
Ok(None)
682682
}
683683

684-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = %path.path().to_string_lossy())))]
684+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_str(path.path()))))]
685685
async fn load_extensions(
686686
&self,
687687
path: &CachedPath,
@@ -691,10 +691,10 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
691691
if ctx.fully_specified {
692692
return Ok(None);
693693
}
694-
let path = path.path().as_os_str();
694+
let path = path_to_str(path.path());
695695
// 8 is wild guess for max extension length
696696
let mut path_with_extension_buffer = String::with_capacity(path.len() + 8);
697-
path_with_extension_buffer.push_str(&path.to_string_lossy());
697+
path_with_extension_buffer.push_str(path);
698698
let base_len = path_with_extension_buffer.len();
699699

700700
for extension in extensions {
@@ -708,7 +708,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
708708
Ok(None)
709709
}
710710

711-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = %cached_path.path().to_string_lossy())))]
711+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_str(cached_path.path()))))]
712712
async fn load_realpath(
713713
&self,
714714
cached_path: &CachedPath,
@@ -758,7 +758,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
758758
true
759759
}
760760

761-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = %cached_path.path().to_string_lossy())))]
761+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_str(cached_path.path()))))]
762762
async fn load_index(&self, cached_path: &CachedPath, ctx: &mut Ctx) -> ResolveResult {
763763
for main_file in &self.options.main_files {
764764
let main_path = cached_path.path().normalize_with(main_file);
@@ -798,7 +798,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
798798
}
799799
}
800800
// enhanced-resolve: try file as alias
801-
let alias_specifier = cached_path.path().to_string_lossy();
801+
let alias_specifier = path_to_str(cached_path.path());
802802
if let Some(path) = self
803803
.load_alias(cached_path, &alias_specifier, &self.options.alias, ctx)
804804
.await?
@@ -812,7 +812,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
812812
Ok(None)
813813
}
814814

815-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = %cached_path.path().to_string_lossy())))]
815+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = path_to_str(cached_path.path()))))]
816816
async fn load_node_modules(
817817
&self,
818818
cached_path: &CachedPath,
@@ -956,7 +956,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
956956
}
957957

958958
#[cfg(feature = "yarn_pnp")]
959-
#[cfg_attr(feature = "enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = %cached_path.path().to_string_lossy())))]
959+
#[cfg_attr(feature = "enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(path = path_to_str(cached_path.path()))))]
960960
fn find_pnp_manifest(&self, cached_path: &CachedPath) -> Option<Arc<(PathBuf, pnp::Manifest)>> {
961961
// 1. Already have a manifest → return it (covers global cache paths too)
962962
if let Some(manifest) = self.pnp_manifest.load_full() {
@@ -1000,7 +1000,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
10001000
}
10011001

10021002
#[cfg(feature = "yarn_pnp")]
1003-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = %cached_path.path().to_string_lossy())))]
1003+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = path_to_str(cached_path.path()))))]
10041004
async fn load_pnp(
10051005
&self,
10061006
cached_path: &CachedPath,
@@ -1108,7 +1108,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
11081108
Ok(None)
11091109
}
11101110

1111-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = %cached_path.path().to_string_lossy())))]
1111+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = path_to_str(cached_path.path()))))]
11121112
async fn load_package_self(
11131113
&self,
11141114
cached_path: &CachedPath,
@@ -1152,7 +1152,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
11521152
}
11531153

11541154
/// RESOLVE_ESM_MATCH(MATCH)
1155-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = %cached_path.path().to_string_lossy())))]
1155+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = specifier, path = path_to_str(cached_path.path()))))]
11561156
async fn resolve_esm_match(
11571157
&self,
11581158
specifier: &str,
@@ -1192,7 +1192,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
11921192
}
11931193

11941194
/// enhanced-resolve: AliasFieldPlugin for [ResolveOptions::alias_fields]
1195-
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = module_specifier, path = %cached_path.path().to_string_lossy())))]
1195+
#[cfg_attr(feature="enable_instrument", tracing::instrument(level=tracing::Level::DEBUG, skip_all, fields(specifier = module_specifier, path = path_to_str(cached_path.path()))))]
11961196
async fn load_browser_field(
11971197
&self,
11981198
cached_path: &CachedPath,
@@ -1332,7 +1332,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
13321332
Cow::Borrowed(alias_value)
13331333
} else {
13341334
let normalized = alias_path.normalize_with(tail);
1335-
Cow::Owned(normalized.to_string_lossy().to_string())
1335+
Cow::Owned(path_to_str(&normalized).to_string())
13361336
}
13371337
};
13381338

@@ -1397,14 +1397,14 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
13971397
// Create a meaningful error message.
13981398
let dir = path.parent().unwrap().to_path_buf();
13991399
let filename_without_extension = Path::new(filename).with_extension("");
1400-
let filename_without_extension = filename_without_extension.to_string_lossy();
1400+
let filename_without_extension = path_to_str(&filename_without_extension);
14011401
let files = extensions
14021402
.iter()
14031403
.map(|ext| format!("{filename_without_extension}{ext}"))
14041404
.collect::<Vec<_>>()
14051405
.join(",");
14061406
Err(ResolveError::ExtensionAlias(
1407-
filename.to_string_lossy().to_string(),
1407+
filename.to_str().expect("path should be UTF-8").to_string(),
14081408
files,
14091409
dir,
14101410
))

src/path.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ use std::path::{Component, Path, PathBuf};
77

88
pub const SLASH_START: &[char; 2] = &['/', '\\'];
99

10+
pub(crate) fn path_to_str(path: &Path) -> &str {
11+
path.to_str().expect("path should be UTF-8")
12+
}
13+
1014
/// Extension trait to add path normalization to std's [`Path`].
1115
pub trait PathUtil {
1216
/// Normalize this path without performing I/O.

src/tests/alias.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ async fn infinite_recursion() {
152152
}
153153

154154
fn check_slash(path: &Path) {
155-
let s = path.to_string_lossy().to_string();
155+
let s = path.to_str().expect("path should be UTF-8").to_string();
156156
#[cfg(target_os = "windows")]
157157
{
158158
assert!(!s.contains('/'), "{s}");
@@ -186,7 +186,9 @@ async fn system_path() {
186186
let resolver = Resolver::new(ResolveOptions {
187187
alias: vec![(
188188
"@app".into(),
189-
vec![AliasValue::from(f.join("alias").to_string_lossy())],
189+
vec![AliasValue::from(
190+
f.join("alias").to_str().expect("path should be UTF-8"),
191+
)],
190192
)],
191193
..ResolveOptions::default()
192194
});
@@ -208,7 +210,7 @@ async fn system_path() {
208210
async fn alias_is_full_path() {
209211
let f = super::fixture();
210212
let dir = f.join("foo");
211-
let dir_str = dir.to_string_lossy().to_string();
213+
let dir_str = dir.to_str().expect("path should be UTF-8").to_string();
212214

213215
let resolver = Resolver::new(ResolveOptions {
214216
alias: vec![("@".into(), vec![AliasValue::Path(dir_str.clone())])],
@@ -258,7 +260,8 @@ async fn all_alias_values_are_not_found() {
258260
vec![AliasValue::Path(
259261
f.join("node_modules")
260262
.join("m2")
261-
.to_string_lossy()
263+
.to_str()
264+
.expect("path should be UTF-8")
262265
.to_string(),
263266
)],
264267
)],
@@ -318,7 +321,12 @@ async fn alias_try_fragment_as_path() {
318321
let resolver = Resolver::new(ResolveOptions {
319322
alias: vec![(
320323
"#".to_string(),
321-
vec![AliasValue::Path(f.join("#").to_string_lossy().to_string())],
324+
vec![AliasValue::Path(
325+
f.join("#")
326+
.to_str()
327+
.expect("path should be UTF-8")
328+
.to_string(),
329+
)],
322330
)],
323331
..ResolveOptions::default()
324332
});

src/tests/browser_field.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ async fn crypto_js() {
162162
alias_fields: vec![vec!["browser".into()]],
163163
fallback: vec![(
164164
"crypto".into(),
165-
vec![AliasValue::from(f.join("lib.js").to_string_lossy())],
165+
vec![AliasValue::from(
166+
f.join("lib.js").to_str().expect("path should be UTF-8"),
167+
)],
166168
)],
167169
..ResolveOptions::default()
168170
});

src/tests/memory_fs.rs

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

66
use crate::{FileMetadata, FileSystem};
77

8+
fn path_to_str(path: &Path) -> &str {
9+
path.to_str().expect("path should be UTF-8")
10+
}
11+
812
#[derive(Default)]
913
pub struct MemoryFS {
1014
fs: vfs::MemoryFS,
@@ -32,13 +36,13 @@ impl MemoryFS {
3236
let fs = &mut self.fs;
3337
// Create all parent directories
3438
for path in path.ancestors().collect::<Vec<_>>().iter().rev() {
35-
let path = path.to_string_lossy();
36-
if !fs.exists(path.as_ref()).unwrap() {
37-
fs.create_dir(path.as_ref()).unwrap();
39+
let path = path_to_str(path);
40+
if !fs.exists(path).unwrap() {
41+
fs.create_dir(path).unwrap();
3842
}
3943
}
4044
// Create file
41-
let mut file = fs.create_file(path.to_string_lossy().as_ref()).unwrap();
45+
let mut file = fs.create_file(path_to_str(path)).unwrap();
4246
file.write_all(content.as_bytes()).unwrap();
4347
}
4448
}
@@ -48,7 +52,7 @@ impl FileSystem for MemoryFS {
4852
use vfs::FileSystem;
4953
let mut file = self
5054
.fs
51-
.open_file(path.to_string_lossy().as_ref())
55+
.open_file(path_to_str(path))
5256
.map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?;
5357
let mut buffer = String::new();
5458
file.read_to_string(&mut buffer).unwrap();
@@ -63,7 +67,7 @@ impl FileSystem for MemoryFS {
6367
use vfs::FileSystem;
6468
let metadata = self
6569
.fs
66-
.metadata(path.to_string_lossy().as_ref())
70+
.metadata(path_to_str(path))
6771
.map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?;
6872
let is_file = metadata.file_type == vfs::VfsFileType::File;
6973
let is_dir = metadata.file_type == vfs::VfsFileType::Directory;

src/tests/missing.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,19 @@ async fn alias_and_extensions() {
8282
(
8383
"@scope-js/package-name/dir$".into(),
8484
vec![AliasValue::Path(
85-
f.join("foo/index.js").to_string_lossy().to_string(),
85+
f.join("foo/index.js")
86+
.to_str()
87+
.expect("path should be UTF-8")
88+
.to_string(),
8689
)],
8790
),
8891
(
8992
"react-dom".into(),
9093
vec![AliasValue::Path(
91-
f.join("foo/index.js").to_string_lossy().to_string(),
94+
f.join("foo/index.js")
95+
.to_str()
96+
.expect("path should be UTF-8")
97+
.to_string(),
9298
)],
9399
),
94100
],

0 commit comments

Comments
 (0)