Skip to content
Open
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
12 changes: 2 additions & 10 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,7 @@ impl<'a> Transpiler<'a> {
dir,
bun_paths::SEP,
);
break 'name self.resolver.bust_dir_cache(
bun_paths::string_paths::without_trailing_slash_windows_path(
buster_name,
),
);
break 'name self.resolver.bust_dir_cache(buster_name);
}
}

Expand All @@ -431,11 +427,7 @@ impl<'a> Transpiler<'a> {
else {
break 'name false;
};
self.resolver.bust_dir_cache(
bun_paths::string_paths::without_trailing_slash_windows_path(
buster_name.as_bytes(),
),
)
self.resolver.bust_dir_cache(buster_name.as_bytes())
Comment thread
robobun marked this conversation as resolved.
Resolved
};

// Only re-query if we previously had something cached.
Expand Down
4 changes: 1 addition & 3 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5498,9 +5498,7 @@ impl VirtualMachine {
};

// Only re-query if we previously had something cached.
if self.transpiler.resolver.bust_dir_cache(
bun_paths::string_paths::without_trailing_slash_windows_path(buster_name),
) {
if self.transpiler.resolver.bust_dir_cache(buster_name) {
Comment thread
robobun marked this conversation as resolved.
Resolved
continue;
}
return Err(crate::CrateError::ModuleNotFound);
Expand Down
9 changes: 3 additions & 6 deletions src/jsc/hot_reloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bun_core::Output;
use bun_core::ZStr;
#[cfg(not(windows))]
use bun_paths::SEP;
#[cfg(not(windows))]
use bun_paths::strings;
#[cfg(not(windows))]
use bun_resolver::fs::PathName;
Expand Down Expand Up @@ -944,9 +945,7 @@ where
// on windows we receive file events for all items affected by a directory change
// so we only need to clear the directory cache. all other effects will be handled
// by the file events
let _ = self.ctx_mut().bust_dir_cache(
strings::paths::without_trailing_slash_windows_path(file_path),
);
let _ = self.ctx_mut().bust_dir_cache(file_path);
continue;
}
#[cfg(not(windows))]
Expand Down Expand Up @@ -1062,9 +1061,7 @@ where
}
}

let _ = self.ctx_mut().bust_dir_cache(
strings::paths::without_trailing_slash_windows_path(file_path),
);
let _ = self.ctx_mut().bust_dir_cache(file_path);

// The watched entrypoint has a per-file inotify watch on its inode.
// An atomic rename (`rename(tmp, entrypoint)`) or a rm+recreate over
Expand Down
20 changes: 4 additions & 16 deletions src/node_path/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use tidy::{
#[cfg(windows)]
use bun_core::strings::CodeUnitsMut;
use bun_core::strings::{self, CodeUnits};
use bun_paths::string_paths::without_trailing_slash_native;
use bun_paths::{self, MAX_PATH_BYTES, Platform};

const PATH_MIN_WIDE: usize = 4096; // 4 KB
Expand Down Expand Up @@ -183,21 +184,6 @@ fn posix_cwd_t<T: PathCharCwd>(buf: &mut [T]) -> MaybeBuf<'_, T> {
Ok(cwd)
}

#[cfg(windows)]
#[inline]
fn without_trailing_slash(s: &[u8]) -> &[u8] {
bun_paths::string_paths::without_trailing_slash_windows_path(s)
}
#[cfg(not(windows))]
#[inline]
fn without_trailing_slash(s: &[u8]) -> &[u8] {
let mut end = s.len();
while end > 1 && s[end - 1] == CHAR_FORWARD_SLASH {
end -= 1;
}
&s[..end]
}

/// What [`resolve_posix_t`] puts in front of a path that is not absolute, where the system's own paths are POSIX paths.
#[cfg(not(windows))]
#[inline]
Expand All @@ -208,7 +194,9 @@ pub fn posix_working_directory() -> &'static [u8] {
fn working_directory() -> &'static [u8] {
match bun_core::working_directory_in_executable() {
Some(directory) => directory,
None => without_trailing_slash(bun_paths::fs::FileSystem::instance().top_level_dir()),
None => {
without_trailing_slash_native(bun_paths::fs::FileSystem::instance().top_level_dir())
}
}
}

Expand Down
42 changes: 42 additions & 0 deletions src/paths/string_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,23 @@ pub fn without_trailing_slash_windows_path(input: &[u8]) -> &[u8] {
path
}

/// Strips the trailing separators of the host and keeps the root: on POSIX only `/`, so `/a/b\` stays.
#[inline]
pub fn without_trailing_slash_native(input: &[u8]) -> &[u8] {
#[cfg(windows)]
{
without_trailing_slash_windows_path(input)
}
#[cfg(not(windows))]
{
let mut end = input.len();
while end > 1 && input[end - 1] == b'/' {
end -= 1;
}
&input[..end]
}
}

pub fn without_leading_path_separator(this: &[u8]) -> &[u8] {
strings::trim_left(this, &[crate::SEP])
}
Expand Down Expand Up @@ -705,4 +722,29 @@ mod tests {
assert!(fits_in_wide_path_buffer(&vec![0x80u8; 32758]));
assert!(fits_in_wide_path_buffer(&vec![0x80u8; 32757]));
}

#[test]
fn without_trailing_slash_native_strips_host_separators_only() {
let strip = |input: &'static str| {
core::str::from_utf8(without_trailing_slash_native(input.as_bytes())).unwrap()
};
assert_eq!(strip("/a/b"), "/a/b");
assert_eq!(strip("/a/b/"), "/a/b");
assert_eq!(strip("/a/b//"), "/a/b");
assert_eq!(strip("/"), "/");
assert_eq!(strip("//"), "/");
assert_eq!(strip(""), "");
if cfg!(windows) {
assert_eq!(strip("C:\\a\\"), "C:\\a");
assert_eq!(strip("C:\\a/"), "C:\\a");
assert_eq!(strip("C:\\"), "C:\\");
} else {
// A backslash is a file name byte on POSIX.
assert_eq!(strip("/a/b\\"), "/a/b\\");
assert_eq!(strip("/a/b\\\\"), "/a/b\\\\");
assert_eq!(strip("/a/b\\/"), "/a/b\\");
assert_eq!(strip("/a/b/\\"), "/a/b/\\");
assert_eq!(strip("\\"), "\\");
}
}
}
40 changes: 35 additions & 5 deletions src/resolver/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,25 @@ pub mod fs {
string_store_append_impl!(DirnameStore, dirname_store_backing);
string_store_append_impl!(FilenameStore, filename_store_backing);

/// `join_abs_string_buf_checked::<P>` for `name` in a directory that is open as `dir`: on POSIX a backslash there is a byte of a file name.
pub(crate) fn join_in_dir_checked<'b, P: bun_paths::resolve_path::PlatformT>(
cwd: &'b [u8],
dir: &[u8],
name: &[u8],
buf: &'b mut [u8],
) -> Option<&'b [u8]> {
use bun_core::strings::contains_char;
use bun_paths::resolve_path::{join_abs_string_buf_checked, join_z_buf_checked, platform};
let parts: [&[u8]; 2] = [dir, name];
if cfg!(not(windows))
&& dir.first() == Some(&b'/')
&& (contains_char(dir, b'\\') || contains_char(name, b'\\'))
{
return join_z_buf_checked::<platform::Posix>(buf, &parts).map(|path| path.as_bytes());
}
join_abs_string_buf_checked::<P>(cwd, buf, &parts)
}

// ── FileSystem ───────────────────────────────────────────────────────

/// Process-global filesystem facade for the resolver. Wraps the real-FS
Expand Down Expand Up @@ -353,6 +372,17 @@ pub mod fs {
join_abs_string_buf_checked::<platform::Loose>(self.top_level_dir(), buf, parts)
}

/// `abs_buf_checked` for `name` in a directory that is open as `dir`: see `join_in_dir_checked`.
pub(crate) fn abs_buf_in_dir_checked<'b>(
&self,
dir: &[u8],
name: &[u8],
buf: &'b mut [u8],
) -> Option<&'b [u8]> {
use bun_paths::resolve_path::platform;
join_in_dir_checked::<platform::Loose>(self.top_level_dir(), dir, name, buf)
}

/// Normalizes `str` (separators, `.`/`..` segments) into `buf`.
pub fn normalize_buf<'b>(&self, buf: &'b mut [u8], str: &[u8]) -> &'b [u8] {
use bun_paths::resolve_path::{normalize_string_buf, platform};
Expand Down Expand Up @@ -1079,7 +1109,7 @@ pub mod fs {
store_fd: bool,
iterator: I,
) -> crate::CrateResult<&'static mut EntriesOption> {
let dir = strings::paths::without_trailing_slash_windows_path(dir_maybe_trail_slash);
let dir = crate::Resolver::dir_cache_key(dir_maybe_trail_slash);

crate::Resolver::assert_valid_cache_key(dir);
let _unlock_guard = self.entries_mutex.lock_guard();
Expand Down Expand Up @@ -1216,7 +1246,7 @@ pub mod fs {
existing_fd: Fd,
store_fd: bool,
) -> crate::CrateResult<EntryCache> {
use bun_paths::resolve_path::{join_abs_string_buf_checked, platform};
use bun_paths::resolve_path::platform;
#[cfg(not(windows))]
use bun_sys::{FileKind, kind_from_mode};

Expand All @@ -1226,13 +1256,13 @@ pub mod fs {
fd: Fd::INVALID,
};

let combo: [&[u8]; 2] = [dir_, base];
let mut outpath = bun_paths::path_buffer_pool::get();
let join_capacity = outpath.len() - 2;
let Some(entry_path) = join_abs_string_buf_checked::<platform::Auto>(
let Some(entry_path) = join_in_dir_checked::<platform::Auto>(
self.cwd,
dir_,
base,
&mut outpath[..join_capacity],
&combo,
) else {
return Err(crate::Error::Sys(bun_errno::SystemErrno::ENAMETOOLONG));
};
Expand Down
23 changes: 19 additions & 4 deletions src/resolver/package_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,24 @@ impl PackageJSON {
macro_map
}

/// Interns the path of the `package.json` that `parse` reads in `dir`. `joined` is the loose join of the two.
fn intern_path(
r_fs: &fs::FileSystem,
dir: &[u8],
dirname_fd: Fd,
joined: &[u8],
) -> &'static [u8] {
let mut spill = Vec::new();
// Through `dirname_fd` the file is read as `package.json` in that open directory, so on POSIX its path keeps a backslash of `dir`.
let path =
if cfg!(not(windows)) && dirname_fd.is_valid() && strings::contains_char(dir, b'\\') {
r_fs.join(&mut spill, &[dir, b"package.json"])
} else {
joined
};
r_fs.dirname_store.append_slice(path).expect("unreachable")
}

#[inline(never)]
pub fn parse<const INCLUDE_DEPENDENCIES: IncludeDependencies>(
r: &mut resolver::Resolver<'_>,
Expand Down Expand Up @@ -422,10 +440,7 @@ impl PackageJSON {
);
return None;
};
let package_json_path = r_fs
.dirname_store
.append_slice(package_json_path_)
.expect("unreachable");
let package_json_path = Self::intern_path(r_fs, input_path, dirname_fd, package_json_path_);

// DirInfo cache is reused globally
// So we cannot free these
Expand Down
Loading
Loading