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
10 changes: 2 additions & 8 deletions src/jsc/RuntimeTranspilerStore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,10 +910,7 @@ impl TranspilerJob {
.parse_maybe_return_file_only_allow_shared_buffer::<false, false>(parse_options, None)
else {
if is_watcher_enabled && input_file_fd.is_valid() {
if !is_node_override
&& bun_paths::is_absolute(path.text)
&& !strings::contains(path.text, b"node_modules")
{
if !is_node_override && bun_paths::is_absolute(path.text) {
if let Some(iw) = import_watcher {
// SAFETY: BACKREF — process-lifetime watcher; no other
// `&ImportWatcher` is live here, and `add_file` is
Expand All @@ -937,10 +934,7 @@ impl TranspilerJob {
};

if is_watcher_enabled && input_file_fd.is_valid() {
if !is_node_override
&& bun_paths::is_absolute(path.text)
&& !strings::contains(path.text, b"node_modules")
{
if !is_node_override && bun_paths::is_absolute(path.text) {
if let Some(iw) = import_watcher {
// SAFETY: BACKREF — process-lifetime watcher; no other
// `&ImportWatcher` is live here, and `add_file` is
Expand Down
5 changes: 5 additions & 0 deletions src/jsc/hot_reloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl ImportWatcher {
.items::<"package_json", Option<&'static bun_watcher::PackageJSON>>()[index as usize]
}

/// No package rule here: each caller decides.
#[inline]
pub fn add_file_by_path_slow(&mut self, file_path: &[u8]) -> bool {
match self {
Expand All @@ -62,6 +63,7 @@ impl ImportWatcher {
}
}

/// Declines a package file: the caller keeps `fd`.
#[inline]
pub fn add_file<const COPY_FILE_PATH: bool>(
&mut self,
Expand All @@ -75,6 +77,9 @@ impl ImportWatcher {
) -> bun_sys::Result<bun_watcher::FdOwnership> {
match self {
ImportWatcher::Hot(watcher) | ImportWatcher::Watch(watcher) => {
if strings::path_is_inside_node_modules(file_path) {
return Ok(bun_watcher::FdOwnership::Caller);
}
Comment thread
robobun marked this conversation as resolved.
Resolved
watcher.add_file::<COPY_FILE_PATH>(fd, file_path, hash, dir_fd, package_json)
Comment thread
robobun marked this conversation as resolved.
Resolved
}
ImportWatcher::None => Ok(bun_watcher::FdOwnership::Caller),
Expand Down
22 changes: 22 additions & 0 deletions src/paths/resolve_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2764,6 +2764,28 @@ mod tests {
h.iter().filter(|&&b| b == c).count()
}

/// Null when `nd` does not occur, like the kernel.
#[unsafe(no_mangle)]
unsafe extern "C" fn highway_memmem(
h: *const u8,
n: usize,
nd: *const u8,
k: usize,
) -> *const u8 {
// SAFETY: test stub; callers pass valid (ptr, len) pairs.
let (hay, nd) = unsafe {
(
core::slice::from_raw_parts(h, n),
core::slice::from_raw_parts(nd, k),
)
};
let first = n
.checked_sub(k)
.and_then(|end| (0..=end).find(|&i| hay[i..i + k] == *nd));
// SAFETY: `i` is an index into `hay`.
first.map_or(core::ptr::null(), |i| unsafe { h.add(i) })
}

/// `usize::MAX` when `nd` does not occur, like the kernel.
#[unsafe(no_mangle)]
unsafe extern "C" fn highway_memrmem16(
Expand Down
137 changes: 137 additions & 0 deletions src/paths/string_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,46 @@ pub fn path_contains_node_modules_folder(path: &[u8]) -> bool {
strings::index_of(path, crate::NODE_MODULES_NEEDLE).is_some()
}

/// Is the file `path` inside a directory named `node_modules` or `node_modules.nosync`? A file of that name is not.
#[inline]
pub fn path_is_inside_node_modules(path: &[u8]) -> bool {
has_node_modules_component(path, false, crate::Platform::AUTO)
}

/// Is `dir` named `node_modules` or `node_modules.nosync`, or inside such a directory? A trailing separator is optional.
#[inline]
pub fn dir_is_or_is_inside_node_modules(dir: &[u8]) -> bool {
has_node_modules_component(dir, true, crate::Platform::AUTO)
}

/// `last_component_counts` lets the end of `path` close a component, as a separator does.
#[inline]
fn has_node_modules_component(
path: &[u8],
last_component_counts: bool,
platform: crate::Platform,
) -> bool {
const NAME: &[u8] = b"node_modules";
// iCloud Drive skips this suffix: the store is `node_modules.nosync` behind a link named `node_modules`.
const NOSYNC: &[u8] = b".nosync";
let closes = |end: usize| match path.get(end) {
Some(&next) => platform.is_separator(next),
None => last_component_counts,
};
let mut from = 0;
while let Some(found) = strings::index_of(&path[from..], NAME) {
let start = from + found;
let end = start + NAME.len();
let opens = start == 0 || platform.is_separator(path[start - 1]);
let nosync = || path[end..].starts_with(NOSYNC) && closes(end + NOSYNC.len());
if opens && (closes(end) || nosync()) {
return true;
}
from = end;
}
false
}

pub use crate::is_sep_any as char_is_any_slash;

#[inline(always)]
Expand Down Expand Up @@ -705,4 +745,101 @@ mod tests {
assert!(fits_in_wide_path_buffer(&vec![0x80u8; 32758]));
assert!(fits_in_wide_path_buffer(&vec![0x80u8; 32757]));
}

#[test]
fn node_modules_is_matched_as_a_path_component() {
use crate::Platform::{Posix, Windows};
// (platform, path, as the path of a file, as the path of a directory)
let rows: &[(crate::Platform, &[u8], bool, bool)] = &[
(Posix, b"/p/node_modules/pkg/index.js", true, true),
(Posix, b"/p/node_modules/foo.js", true, true),
(
Posix,
b"/p/node_modules/.bun/pkg@1.0.0/node_modules/pkg/a.js",
true,
true,
),
(Posix, b"node_modules/pkg/index.js", true, true),
(Posix, b"/p/node_modules", false, true),
(Posix, b"/p/node_modules/", true, true),
(Posix, b"/p/src/node_modules", false, true),
(Posix, b"/p/node_modules-cleaner/src/util.js", false, false),
(Posix, b"/p/node_modules-cleaner/", false, false),
(Posix, b"/p/src/list-node_modules.js", false, false),
(Posix, b"/p/src/node_modules_helpers/util.js", false, false),
(Posix, b"/p/my-node_modules/a.js", false, false),
(Posix, b"/p/my-node_modules", false, false),
(Posix, b"/p/node_modules.nosync/pkg/a.js", true, true),
(Posix, b"node_modules.nosync/pkg/a.js", true, true),
(Posix, b"/p/node_modules.nosync", false, true),
(Posix, b"/p/node_modules.nosync/", true, true),
(
Posix,
b"/p/node_modules.nosync/node_modules/pkg/a.js",
true,
true,
),
(Posix, b"/p/node_modules.nosyncx/pkg/a.js", false, false),
(Posix, b"/p/my-node_modules.nosync/a.js", false, false),
(Posix, b"/p/node_modules.nosync\\pkg/a.js", false, false),
(Posix, b"/p/node_modules-report/src/util.js", false, false),
(Posix, b"/p/node_modules-cleaner.nosync/a.js", false, false),
(Posix, b"node_modules-cleaner/a.js", false, false),
(
Posix,
b"/p/node_modules-cleaner/node_modules/pkg/a.js",
true,
true,
),
(
Posix,
b"/p/node_modules-cleaner/my-node_modules/node_modules",
false,
true,
),
(Posix, b"/p/src/a.js", false, false),
(Posix, b"", false, false),
// A backslash is a byte of a file name there.
(Posix, b"/p/a\\node_modules\\b.js", false, false),
(Windows, b"C:\\p\\node_modules\\pkg\\a.js", true, true),
(Windows, b"C:/p/node_modules/pkg/a.js", true, true),
(Windows, b"C:\\p/node_modules\\pkg/a.js", true, true),
(Windows, b"C:\\p\\node_modules", false, true),
(Windows, b"C:\\p\\node_modules\\", true, true),
(
Windows,
b"C:\\p\\node_modules-cleaner\\src\\util.js",
false,
false,
),
(Windows, b"C:\\p\\my-node_modules\\a.js", false, false),
(
Windows,
b"C:\\p\\node_modules.nosync\\pkg\\a.js",
true,
true,
),
(Windows, b"C:\\p/node_modules.nosync\\pkg/a.js", true, true),
(Windows, b"C:\\p\\node_modules.nosync", false, true),
];
for &(platform, path, as_file, as_dir) in rows {
let got = (
has_node_modules_component(path, false, platform),
has_node_modules_component(path, true, platform),
);
assert_eq!(
got,
(as_file, as_dir),
"{platform:?} {}",
bstr::BStr::new(path)
);
}
assert!(path_is_inside_node_modules(b"/p/node_modules/pkg/index.js"));
assert!(!path_is_inside_node_modules(b"/p/node_modules"));
assert!(dir_is_or_is_inside_node_modules(b"/p/node_modules"));
assert!(path_is_inside_node_modules(
b"/p/node_modules.nosync/pkg/index.js"
));
assert!(dir_is_or_is_inside_node_modules(b"/p/node_modules.nosync"));
}
}
12 changes: 4 additions & 8 deletions src/runtime/jsc_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3470,8 +3470,9 @@ fn transpile_source_code_of_any_provenance<const DATA_ONLY: bool>(
if !unsafe { &*jsc_vm }.is_watcher_enabled() {
break 'auto_watch;
}
// `add_file` declines package files too. Asking first saves the open below.
if !bun_paths::is_absolute(path.text)
|| bun_core::strings::contains(path.text, b"node_modules")
|| bun_paths::strings::path_is_inside_node_modules(path.text)
{
break 'auto_watch;
}
Expand Down Expand Up @@ -3560,9 +3561,7 @@ fn transpile_source_code_of_any_provenance<const DATA_ONLY: bool>(
}
}

/// Register the just-opened file
/// with the dev-server watcher (if enabled, absolute, and not in
/// `node_modules`). Factored out of the two call sites.
/// Offers the file that was just opened to the `--watch`/`--hot` watcher.
#[inline]
fn maybe_watch_file(
jsc_vm: *mut VirtualMachine,
Expand All @@ -3580,10 +3579,7 @@ fn maybe_watch_file(
if !input_file_fd.is_valid() {
return;
}
if is_node_override
|| !bun_paths::is_absolute(path.text)
|| bun_core::strings::contains(path.text, b"node_modules")
{
if is_node_override || !bun_paths::is_absolute(path.text) {
return;
}
// SAFETY: `bun_watcher` is the `*mut ImportWatcher` set when
Expand Down
11 changes: 4 additions & 7 deletions src/watcher/Watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,9 +783,11 @@ impl Watcher {
Ok(FdOwnership::Watcher)
}

/// Not a package store or a directory inside one, and not outside the root directory.
#[inline]
fn is_eligible_directory(&self, dir: &[u8]) -> bool {
strings::contains(dir, self.top_level_dir()) && !strings::contains(dir, b"node_modules")
!bun_paths::strings::dir_is_or_is_inside_node_modules(dir)
&& strings::contains(dir, self.top_level_dir())
Comment thread
robobun marked this conversation as resolved.
Resolved
}

#[inline]
Expand Down Expand Up @@ -968,12 +970,7 @@ impl Watcher {
}

pub(crate) fn on_maybe_watch_directory(&mut self, file_path: &[u8], dir_fd: Fd) {
// We don't want to watch:
// - Directories outside the root directory
// - Directories inside node_modules
if !strings::contains(file_path, b"node_modules")
&& strings::contains(file_path, self.top_level_dir())
{
if self.is_eligible_directory(file_path) {
let _ = self.add_directory::<false>(dir_fd, file_path, Self::get_hash(file_path));
}
}
Expand Down
Loading
Loading