use ::std::cell::RefCell;
use ::std::path::{Path, PathBuf};
use ::std::ptr;
use ::std::ptr::NonNull;
use bun_core::ZBox;
use bun_sys::fs as bun_fs;
use mozjs::conversions::unsafe_jsstr_to_string;
use mozjs::glue::NewCompileOptions;
use mozjs::jsapi::*;
use mozjs::jsval::{JSVal, UndefinedValue};
use mozjs::rooted;
use crate::gc_store;
thread_local! {
static REQUIRE_DIR: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
}
pub fn cache_builtin(cx: &mut mozjs::context::JSContext, name: &str, obj: *mut JSObject) {
let cache_key = format!("builtin:{}", name);
gc_store::gc_store_insert(unsafe { cx.raw_cx() }, &cache_key, obj);
}
pub fn get_builtin(cx: *mut JSContext, name: &str) -> Option<*mut JSObject> {
let cache_key = format!("builtin:{}", name);
gc_store::gc_store_get(cx, &cache_key).filter(|p| !p.is_null())
}
pub fn cache_assert_strict(cx: &mut mozjs::context::JSContext) {
use mozjs::jsval::{ObjectValue, UndefinedValue};
use mozjs::rooted;
use mozjs::rust::wrappers2 as w2;
let assert_obj = gc_store::gc_store_get(unsafe { cx.raw_cx() }, "builtin:assert");
let Some(assert_obj) = assert_obj else { return };
if assert_obj.is_null() {
return;
}
rooted!(&in(cx) let strict_obj = unsafe { w2::JS_NewPlainObject(cx) });
if strict_obj.get().is_null() {
return;
}
unsafe {
rooted!(&in(cx) let assert_root = assert_obj);
let strict_h = strict_obj.handle();
for (name, _n_args) in &[
("ok", 1),
("equal", 2),
("notEqual", 2),
("deepEqual", 2),
("notDeepEqual", 2),
("strictEqual", 2),
("notStrictEqual", 2),
("deepStrictEqual", 2),
("throws", 1),
("rejects", 1),
("doesNotThrow", 1),
("fail", 0),
("ifError", 1),
] {
let mut fn_val = UndefinedValue();
let c_name = ZBox::from_bytes(name.as_bytes());
JS_GetProperty(
cx.raw_cx(),
assert_root.handle().into(),
c_name.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut fn_val,
},
);
if fn_val.is_object() {
rooted!(&in(cx) let fn_obj = fn_val.to_object());
rooted!(&in(cx) let fn_obj_val = ObjectValue(fn_obj.get()));
JS_DefineProperty(
cx.raw_cx(),
strict_h.into(),
c_name.as_ptr(),
fn_obj_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let mut ae_val = UndefinedValue();
JS_GetProperty(
cx.raw_cx(),
assert_root.handle().into(),
c"AssertionError".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ae_val,
},
);
if ae_val.is_object() {
rooted!(&in(cx) let ae_obj = ae_val.to_object());
rooted!(&in(cx) let ae_val2 = ObjectValue(ae_obj.get()));
JS_DefineProperty(
cx.raw_cx(),
strict_h.into(),
c"AssertionError".as_ptr(),
ae_val2.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
cache_builtin(cx, "assert/strict", strict_obj.get());
}
pub unsafe fn install_require_on_target(
cx: &mut mozjs::context::JSContext,
target: mozjs::rust::Handle<*mut JSObject>,
) {
mozjs::rust::wrappers2::JS_DefineFunction(
cx,
target,
c"require".as_ptr(),
::std::option::Option::Some(require_fn),
1,
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx) let mut require_val = mozjs::jsval::UndefinedValue());
unsafe {
JS_GetProperty(
cx.raw_cx(),
target.into(),
c"require".as_ptr(),
require_val.handle_mut().into(),
);
}
if require_val.get().is_object() {
rooted!(&in(cx) let require_obj = require_val.get().to_object());
unsafe { attach_require_resolve(cx, require_obj.handle()) };
unsafe { attach_require_cache(cx, require_obj.handle()) };
}
}
pub fn install_require(
cx: &mut mozjs::context::JSContext,
global: mozjs::rust::Handle<*mut JSObject>,
) {
unsafe {
mozjs::rust::wrappers2::JS_DefineFunction(
cx,
global,
c"require".as_ptr(),
::std::option::Option::Some(require_fn),
1,
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx) let mut require_val = mozjs::jsval::UndefinedValue());
JS_GetProperty(
cx.raw_cx(),
global.into(),
c"require".as_ptr(),
require_val.handle_mut().into(),
);
if require_val.get().is_object() {
rooted!(&in(cx) let require_obj = require_val.get().to_object());
attach_require_resolve(cx, require_obj.handle());
attach_require_cache(cx, require_obj.handle());
}
}
}
const REQUIRE_CACHE_KEY: &str = "require:module-cache";
unsafe fn get_or_create_require_cache(cx: *mut JSContext) -> *mut JSObject {
if let Some(existing) = gc_store::gc_store_get(cx, REQUIRE_CACHE_KEY)
&& !existing.is_null()
{
return existing;
}
let obj = mozjs_sys::jsapi::JS_NewPlainObject(cx);
if !obj.is_null() {
gc_store::gc_store_insert(cx, REQUIRE_CACHE_KEY, obj);
}
obj
}
unsafe fn attach_require_cache(
cx: &mut mozjs::context::JSContext,
require_obj: mozjs::rust::Handle<*mut JSObject>,
) {
let cache = get_or_create_require_cache(cx.raw_cx());
if cache.is_null() {
return;
}
rooted!(&in(cx) let cache_root = cache);
mozjs::rust::wrappers2::JS_DefineProperty3(
cx,
require_obj,
c"cache".as_ptr(),
cache_root.handle(),
JSPROP_ENUMERATE as u32,
);
}
unsafe fn record_module_in_cache(cx: *mut JSContext, canonical_path: &str, exports_val: Value) {
let cache = get_or_create_require_cache(cx);
if cache.is_null() {
return;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let cache_root = cache);
let module_obj = mozjs_sys::jsapi::JS_NewPlainObject(cx);
if module_obj.is_null() {
return;
}
rooted!(&in(cx_ref) let module_root = module_obj);
rooted!(&in(cx_ref) let exports_root = exports_val);
unsafe {
JS_DefineProperty(
cx,
module_root.handle().into(),
c"exports".as_ptr(),
exports_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
let path_str = JS_NewStringCopyN(
cx,
canonical_path.as_ptr() as *const ::std::os::raw::c_char,
canonical_path.len(),
);
if !path_str.is_null() {
rooted!(&in(cx_ref) let pv = mozjs::jsval::StringValue(&*path_str));
JS_DefineProperty(
cx,
module_root.handle().into(),
c"id".as_ptr(),
pv.handle().into(),
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty(
cx,
module_root.handle().into(),
c"filename".as_ptr(),
pv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
rooted!(&in(cx_ref) let loaded = mozjs::jsval::BooleanValue(true));
JS_DefineProperty(
cx,
module_root.handle().into(),
c"loaded".as_ptr(),
loaded.handle().into(),
JSPROP_ENUMERATE as u32,
);
let utf16: Vec<u16> = canonical_path.encode_utf16().collect();
let ok = JS_DefineUCProperty4(
cx,
cache_root.handle().into(),
utf16.as_ptr(),
utf16.len(),
module_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
let _ = ok;
}
}
unsafe fn lookup_require_cache(cx: *mut JSContext, args: &CallArgs, canonical_path: &str) -> Option<Value> {
let callee_obj = args.callee();
if callee_obj.is_null() {
return None;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let callee_root = callee_obj);
let mut cache_val = UndefinedValue();
JS_GetProperty(
cx,
callee_root.handle().into(),
c"cache".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cache_val,
},
);
if !cache_val.is_object() {
return None;
}
rooted!(&in(cx_ref) let cache_obj = cache_val.to_object());
let utf16: Vec<u16> = canonical_path.encode_utf16().collect();
let mut module_val = UndefinedValue();
let got = JS_GetUCProperty(
cx,
cache_obj.handle().into(),
utf16.as_ptr(),
utf16.len(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut module_val,
},
);
if !got || !module_val.is_object() {
return None;
}
rooted!(&in(cx_ref) let module_obj = module_val.to_object());
let mut exports_val = UndefinedValue();
JS_GetProperty(
cx,
module_obj.handle().into(),
c"exports".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut exports_val,
},
);
Some(exports_val)
}
unsafe fn callee_has_require_cache(cx: *mut JSContext, args: &CallArgs) -> bool {
let callee_obj = args.callee();
if callee_obj.is_null() {
return false;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let callee_root = callee_obj);
let mut cache_val = UndefinedValue();
JS_GetProperty(
cx,
callee_root.handle().into(),
c"cache".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cache_val,
},
);
cache_val.is_object()
}
unsafe fn attach_require_resolve(
cx: &mut mozjs::context::JSContext,
require_obj: mozjs::rust::Handle<*mut JSObject>,
) {
unsafe {
mozjs::rust::wrappers2::JS_DefineFunction(
cx,
require_obj,
c"resolve".as_ptr(),
::std::option::Option::Some(require_resolve_fn),
1,
0,
);
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn require_resolve_fn(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(
cx,
c"require.resolve() requires a module specifier".as_ptr(),
);
return false;
}
let spec_val = *args.get(0).ptr;
if !spec_val.is_string() {
JS_ReportErrorUTF8(cx, c"require.resolve() requires a string argument".as_ptr());
return false;
}
let specifier = crate::js_to_rust_string(cx, spec_val);
let base_dir = REQUIRE_DIR.with(|d| d.borrow().clone());
let builtin_key = specifier.strip_prefix("node:").unwrap_or(&specifier);
let known_builtins = [
"fs",
"path",
"crypto",
"os",
"url",
"events",
"net",
"http",
"https",
"child_process",
"util",
"assert",
"stream",
"zlib",
"dns",
"querystring",
"buffer",
"string_decoder",
"timers",
"readline",
"perf_hooks",
"tls",
"process",
"vm",
"tty",
"worker_threads",
"module",
"bun:test",
"bun:sqlite",
"bun:ffi",
"bun:wrap",
"harness",
"test",
"async_hooks",
"cluster",
"constants",
"dgram",
"diagnostics_channel",
"domain",
"http2",
"inspector",
"punycode",
"repl",
"trace_events",
"v8",
"sys",
"_http_agent",
"_http_client",
"_http_common",
"_http_incoming",
"_http_outgoing",
"_http_server",
"_stream_duplex",
"_stream_passthrough",
"_stream_readable",
"_stream_transform",
"_stream_wrap",
"_stream_writable",
"_tls_common",
"_tls_wrap",
"assert/strict",
"dns/promises",
"fs/promises",
"path/posix",
"path/win32",
"readline/promises",
"stream/consumers",
"stream/promises",
"stream/web",
"util/types",
"inspector/promises",
"timers/promises",
];
if known_builtins.contains(&builtin_key) {
let c_path = ZBox::from_bytes(builtin_key.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
if js_str.is_null() {
return false;
}
args.rval().set(mozjs::jsval::StringValue(&*js_str));
return true;
}
let resolved = match resolve_specifier(&specifier, base_dir.as_deref()) {
Some(p) => p,
None => {
let msg = format!("Cannot find module '{}'", specifier);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
};
let path_str = resolved.to_string_lossy().into_owned();
let c_path = ZBox::from_bytes(path_str.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
if js_str.is_null() {
return false;
}
args.rval().set(mozjs::jsval::StringValue(&*js_str));
true
}
pub fn set_require_dir(dir: PathBuf) {
REQUIRE_DIR.with(|d| *d.borrow_mut() = Some(dir));
}
pub fn get_require_dir() -> Option<PathBuf> {
REQUIRE_DIR.with(|d| d.borrow().clone())
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn require_fn(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"require() requires a module specifier".as_ptr());
return false;
}
let spec_val = *args.get(0).ptr;
if !spec_val.is_string() {
JS_ReportErrorUTF8(cx, c"require() requires a string argument".as_ptr());
return false;
}
let specifier = crate::js_to_rust_string(cx, spec_val);
let builtin_key = specifier.strip_prefix("node:").unwrap_or(&specifier);
let cache_key = format!("builtin:{}", builtin_key);
let cached = gc_store::gc_store_get(cx, &cache_key);
if let Some(existing) = cached
&& !existing.is_null()
{
args.rval().set(mozjs::jsval::ObjectValue(existing));
return true;
}
if builtin_key == "process" {
let global = JS::CurrentGlobalOrNull(cx);
if !global.is_null() {
let mut val = mozjs::jsval::UndefinedValue();
let c_prop = ZBox::from_bytes("process".as_bytes());
unsafe {
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let global_root = global);
JS_GetProperty(
cx,
global_root.handle().into(),
c_prop.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
},
);
}
if val.is_object() {
args.rval().set(val);
return true;
}
}
}
if let Some(bun_name) = specifier.strip_prefix("bun:") {
let bun_cache_key = format!("builtin:bun:{}", bun_name);
let cached = gc_store::gc_store_get(cx, &bun_cache_key);
if let Some(existing) = cached
&& !existing.is_null()
{
args.rval().set(mozjs::jsval::ObjectValue(existing));
return true;
}
let alt_cache_key = format!("builtin:bun:{}", specifier);
let alt_cached = gc_store::gc_store_get(cx, &alt_cache_key);
if let Some(existing) = alt_cached
&& !existing.is_null()
{
args.rval().set(mozjs::jsval::ObjectValue(existing));
return true;
}
}
let base_dir = REQUIRE_DIR.with(|d| d.borrow().clone());
let resolved = match resolve_specifier(&specifier, base_dir.as_deref()) {
Some(p) => p,
None => {
let msg = format!("Cannot find module '{}'", specifier);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
};
let canonical = match resolved.canonicalize() {
Ok(c) => c,
Err(_) => resolved.clone(),
};
let cache_key = canonical.to_string_lossy().into_owned();
let js_cache_authoritative = callee_has_require_cache(cx, &args);
if let Some(hit) = lookup_require_cache(cx, &args, &cache_key) {
args.rval().set(hit);
return true;
}
let cached = if js_cache_authoritative {
None
} else {
gc_store::gc_store_get(cx, &cache_key)
};
if let Some(existing) = cached
&& !existing.is_null()
{
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let existing_root = existing);
let mut prim_check = mozjs::jsval::UndefinedValue();
let prim_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut prim_check,
};
unsafe {
JS_GetProperty(
cx,
existing_root.handle().into(),
c"__primitive__".as_ptr(),
prim_h,
);
}
if !prim_check.is_undefined() {
args.rval().set(prim_check);
} else {
args.rval().set(mozjs::jsval::ObjectValue(existing));
}
return true;
}
let content = match bun_fs::read_to_string(&resolved.to_string_lossy()) {
Ok(c) => c,
Err(e) => {
let msg = format!("Cannot read module '{}': {}", specifier, e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
};
let exports_val = if resolved.extension().is_some_and(|e| e == "json") {
let obj = load_json_module(cx, &content, &specifier);
if obj.is_null() {
let msg = format!("Failed to parse JSON module '{}'", specifier);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
mozjs::jsval::ObjectValue(obj)
} else if is_esm_module(&resolved, &content) {
match load_esm_module(cx, &content, &resolved) {
Some(obj) if !obj.is_null() => mozjs::jsval::ObjectValue(obj),
_ => {
let msg = format!("Failed to load ESM module '{}'", specifier);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
}
} else {
match load_cjs_module(cx, &content, &resolved, base_dir.as_deref()) {
Some(val) => val,
None => {
let msg = format!("Failed to load module '{}'", specifier);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
}
};
let cache_obj = if exports_val.is_object() {
exports_val.to_object()
} else {
let wrapper = mozjs_sys::jsapi::JS_NewPlainObject(cx);
if !wrapper.is_null() {
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let wrapper_root = wrapper);
rooted!(&in(wrapped_cx) let val_root = exports_val);
JS_DefineProperty(
cx,
wrapper_root.handle().into(),
c"__primitive__".as_ptr(),
val_root.handle().into(),
0,
);
}
wrapper
};
if !cache_obj.is_null() {
gc_store::gc_store_insert(cx, &cache_key, cache_obj);
}
record_module_in_cache(cx, &cache_key, exports_val);
args.rval().set(exports_val);
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn load_json_module(cx: *mut JSContext, content: &str, specifier: &str) -> *mut JSObject {
let js_str = JS_NewStringCopyZ(cx, ZBox::from_bytes(content.as_bytes()).as_ptr());
if js_str.is_null() {
return ptr::null_mut();
}
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let str_root = js_str);
let mut rval = mozjs::jsval::UndefinedValue();
let rval_handle = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = mozjs_sys::jsapi::JS_ParseJSON1(cx, str_root.handle().into(), rval_handle);
if ok && rval.is_object() {
return rval.to_object();
}
JS_ClearPendingException(cx);
let msg = format!("Invalid JSON in module '{}'", specifier);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
ptr::null_mut()
}
#[inline]
unsafe fn get_prop(cx: *mut JSContext, obj: *mut JSObject, name: *const i8) -> Value {
let mut val = UndefinedValue();
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let obj_root = obj);
let val_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
};
JS_GetProperty(cx, obj_root.handle().into(), name, val_h);
val
}
#[inline]
unsafe fn get_prop_from_val(cx: *mut JSContext, val: Value, name: *const i8) -> Value {
if !val.is_object() {
return UndefinedValue();
}
get_prop(cx, val.to_object(), name)
}
fn is_esm_module(path: &Path, content: &str) -> bool {
match path.extension().and_then(|e| e.to_str()) {
Some("mjs") => return true,
Some("cjs") => return false,
_ => {}
}
let has_esm_marker = content.contains("import ")
|| content.contains("export ")
|| content.contains("export default")
|| content.contains("import *")
|| content.contains("import {");
let has_cjs_marker = content.contains("module.exports")
|| content.contains("exports.")
|| content.contains("exports[");
has_esm_marker && !has_cjs_marker
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn load_esm_module(cx: *mut JSContext, source: &str, path: &Path) -> Option<*mut JSObject> {
use mozjs::glue::NewCompileOptions;
use mozjs::rust::transform_str_to_source_text;
let filename_str = path.to_string_lossy().into_owned();
let c_filename = ZBox::from_bytes(filename_str.as_bytes());
let opts = NewCompileOptions(cx, c_filename.as_ptr(), 1);
if opts.is_null() {
return None;
}
let mut src = transform_str_to_source_text(source);
let module = mozjs_sys::jsapi::JS::CompileModule1(cx, opts, &mut src);
libc::free(opts as *mut _);
if module.is_null() {
return None;
}
let priv_url = path_to_file_url_require(path);
{
let c_url = ZBox::from_bytes(priv_url.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_url.as_ptr());
if !js_str.is_null() {
let val = mozjs::jsval::StringValue(&*js_str);
mozjs_sys::jsapi::JS::SetModulePrivate(module, &val as *const _);
}
}
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let module_root = module);
if !mozjs_sys::jsapi::JS::ModuleLink(cx, module_root.handle().into()) {
JS_ClearPendingException(cx);
return None;
}
let mut eval_rval = UndefinedValue();
let eval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut eval_rval,
};
if !mozjs_sys::jsapi::JS::ModuleEvaluate(cx, module_root.handle().into(), eval_h) {
JS_ClearPendingException(cx);
return None;
}
mozjs_sys::jsapi::js::RunJobs(cx);
let ns = mozjs_sys::jsapi::JS::GetModuleNamespace(cx, module_root.handle().into());
if ns.is_null() { None } else { Some(ns) }
}
fn path_to_file_url_require(path: &Path) -> String {
let s = path.to_string_lossy();
let mut out = String::with_capacity(s.len() + 7);
out.push_str("file://");
for b in s.bytes() {
let safe = b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~' | b'/');
if safe {
out.push(b as char);
} else {
out.push('%');
out.push_str(&format!("{:02X}", b));
}
}
out
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn load_cjs_module(
cx: *mut JSContext,
source: &str,
path: &Path,
_base_dir: Option<&Path>,
) -> Option<Value> {
let exports_obj = JS_NewPlainObject(cx);
if exports_obj.is_null() {
return None;
}
let dir = match path.parent() {
Some(d) => d,
None => return Some(mozjs::jsval::ObjectValue(exports_obj)),
};
let saved_dir = REQUIRE_DIR.with(|d| d.borrow().clone());
REQUIRE_DIR.with(|d| *d.borrow_mut() = Some(dir.to_path_buf()));
let global = CurrentGlobalOrNull(cx);
if global.is_null() {
REQUIRE_DIR.with(|d| *d.borrow_mut() = saved_dir);
return None;
}
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let global_root = global);
let old_exports = get_prop(cx, global, c"exports".as_ptr());
let old_module = get_prop(cx, global, c"module".as_ptr());
{
rooted!(&in(wrapped_cx) let ev = mozjs::jsval::ObjectValue(exports_obj));
JS_SetProperty(
cx,
global_root.handle().into(),
c"exports".as_ptr(),
ev.handle().into(),
);
}
{
let module_obj = JS_NewPlainObject(cx);
if !module_obj.is_null() {
rooted!(&in(wrapped_cx) let mv = mozjs::jsval::ObjectValue(module_obj));
JS_SetProperty(
cx,
global_root.handle().into(),
c"module".as_ptr(),
mv.handle().into(),
);
let fresh_exports = get_prop(cx, global, c"exports".as_ptr());
if fresh_exports.is_object() {
rooted!(&in(wrapped_cx) let fresh_exp_obj = fresh_exports.to_object());
rooted!(&in(wrapped_cx) let fev = mozjs::jsval::ObjectValue(fresh_exp_obj.get()));
let fresh_module = get_prop(cx, global, c"module".as_ptr());
if fresh_module.is_object() {
rooted!(&in(wrapped_cx) let fm = fresh_module.to_object());
JS_DefineProperty(
cx,
fm.handle().into(),
c"exports".as_ptr(),
fev.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
}
}
let filename_str = path.to_string_lossy().into_owned();
let c_filename = ZBox::from_vec(filename_str.into_bytes());
let opts = NewCompileOptions(cx, c_filename.as_ptr(), 1);
if opts.is_null() {
JS_DeleteProperty1(cx, global_root.handle().into(), c"exports".as_ptr());
JS_DeleteProperty1(cx, global_root.handle().into(), c"module".as_ptr());
REQUIRE_DIR.with(|d| *d.borrow_mut() = saved_dir);
return None;
}
let mut src = mozjs::rust::transform_str_to_source_text(source);
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = mozjs_sys::jsapi::JS::Evaluate2(cx, opts, &mut src, rval_h);
libc::free(opts as *mut _);
let module_after_eval = get_prop(cx, global, c"module".as_ptr());
let final_exports = get_prop_from_val(cx, module_after_eval, c"exports".as_ptr());
JS_DeleteProperty1(cx, global_root.handle().into(), c"exports".as_ptr());
JS_DeleteProperty1(cx, global_root.handle().into(), c"module".as_ptr());
if !old_exports.is_undefined() {
rooted!(&in(wrapped_cx) let restore_exports = old_exports);
JS_SetProperty(
cx,
global_root.handle().into(),
c"exports".as_ptr(),
restore_exports.handle().into(),
);
}
if !old_module.is_undefined() {
rooted!(&in(wrapped_cx) let restore_module = old_module);
JS_SetProperty(
cx,
global_root.handle().into(),
c"module".as_ptr(),
restore_module.handle().into(),
);
}
if !ok {
JS_ClearPendingException(cx);
REQUIRE_DIR.with(|d| *d.borrow_mut() = saved_dir);
return None;
}
mozjs_sys::jsapi::js::RunJobs(cx);
REQUIRE_DIR.with(|d| *d.borrow_mut() = saved_dir);
if !final_exports.is_undefined() {
return Some(final_exports);
}
let fallback = get_prop(cx, global, c"exports".as_ptr());
if !fallback.is_undefined() {
return Some(fallback);
}
Some(mozjs::jsval::UndefinedValue())
}
fn resolve_specifier(specifier: &str, base_dir: Option<&Path>) -> ::std::option::Option<PathBuf> {
if let Some(result) = bao_engine::module_loader::try_external_resolve(specifier, base_dir) {
return Some(result);
}
let path = Path::new(specifier);
if path.is_absolute() {
return try_resolve(path);
}
if specifier.starts_with("./") || specifier.starts_with("../") {
let base = base_dir.unwrap_or_else(|| Path::new("."));
let full = base.join(specifier);
return try_resolve(&full);
}
resolve_node_modules(specifier, base_dir)
}
fn try_resolve(path: &Path) -> ::std::option::Option<PathBuf> {
for ext in [".js", ".mjs", ".json", ".ts", ".tsx"] {
let candidate = PathBuf::from(format!("{}{}", path.display(), ext));
if candidate.exists() {
return Some(candidate);
}
}
if path.is_file() {
return Some(path.to_path_buf());
}
if path.is_dir() {
for name in ["index.js", "index.mjs", "index.ts"] {
let candidate = path.join(name);
if candidate.exists() {
return Some(candidate);
}
}
}
None
}
pub fn resolve_node_modules(
specifier: &str,
base_dir: Option<&Path>,
) -> ::std::option::Option<PathBuf> {
if specifier.is_empty() || specifier == "." || specifier == ".." {
return None;
}
let start = match base_dir {
Some(d) => d.to_path_buf(),
None => ::std::env::current_dir().ok()?,
};
let mut dir = start.as_path();
loop {
let nm = dir.join("node_modules");
if nm.is_dir() {
let target = nm.join(specifier);
if let Some(r) = try_resolve(&target) {
return Some(r);
}
}
dir = dir.parent()?;
}
}
#[cfg(test)]
mod tests {
use super::*;
use ::std::fs;
fn tempdir() -> tempfile::TempDir {
tempfile::TempDir::new().expect("create temp dir")
}
#[test]
fn test_try_resolve_js_extension() {
let dir = tempdir();
let file = dir.path().join("mod.js");
fs::write(&file, "").unwrap();
let result = try_resolve(dir.path().join("mod").as_path());
assert_eq!(result.unwrap().extension().unwrap(), "js");
}
#[test]
fn test_try_resolve_ts_extension() {
let dir = tempdir();
let file = dir.path().join("mod.ts");
fs::write(&file, "").unwrap();
let result = try_resolve(dir.path().join("mod").as_path());
assert!(result.is_some());
}
#[test]
fn test_try_resolve_exact_match() {
let dir = tempdir();
let file = dir.path().join("data.json");
fs::write(&file, "{}").unwrap();
let result = try_resolve(dir.path().join("data.json").as_path());
assert!(result.is_some());
}
#[test]
fn test_try_resolve_index_js() {
let dir = tempdir();
let pkg = dir.path().join("pkg");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("index.js"), "").unwrap();
let result = try_resolve(pkg.as_path());
assert!(result.is_some());
}
#[test]
fn test_try_resolve_not_found() {
let dir = tempdir();
let result = try_resolve(dir.path().join("nonexistent").as_path());
assert!(result.is_none());
}
#[test]
fn test_try_resolve_priority_js_over_mjs() {
let dir = tempdir();
fs::write(dir.path().join("mod.js"), "").unwrap();
fs::write(dir.path().join("mod.mjs"), "").unwrap();
let result = try_resolve(dir.path().join("mod").as_path()).unwrap();
assert_eq!(result.extension().unwrap(), "js");
}
#[test]
fn test_resolve_node_modules_finds_package() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("lodash");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules("lodash", Some(dir.path()));
assert!(result.is_some());
assert!(result.unwrap().to_str().unwrap().contains("lodash"));
}
#[test]
fn test_resolve_node_modules_not_found() {
let dir = tempdir();
let nm = dir.path().join("node_modules");
fs::create_dir_all(&nm).unwrap();
let result = resolve_node_modules("nonexistent", Some(dir.path()));
assert!(result.is_none());
}
#[test]
fn test_resolve_node_modules_traverses_up() {
let dir = tempdir();
let child = dir.path().join("sub").join("deep");
fs::create_dir_all(&child).unwrap();
let nm = dir.path().join("node_modules").join("pkg");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules("pkg", Some(&child));
assert!(result.is_some());
assert!(result.unwrap().to_str().unwrap().contains("pkg"));
}
#[test]
fn test_resolve_specifier_absolute() {
let dir = tempdir();
let file = dir.path().join("target.js");
fs::write(&file, "").unwrap();
let abs = file.to_str().unwrap().to_string();
let result = resolve_specifier(&abs, None);
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_relative() {
let dir = tempdir();
let file = dir.path().join("rel.js");
fs::write(&file, "").unwrap();
let result = resolve_specifier("./rel", Some(dir.path()));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_parent_relative() {
let dir = tempdir();
let child = dir.path().join("sub");
fs::create_dir_all(&child).unwrap();
let file = dir.path().join("parent.js");
fs::write(&file, "").unwrap();
let result = resolve_specifier("../parent", Some(&child));
assert!(result.is_some());
}
#[test]
fn test_try_resolve_mjs_extension() {
let dir = tempdir();
let file = dir.path().join("mod.mjs");
fs::write(&file, "export default 1;").unwrap();
let result = try_resolve(dir.path().join("mod").as_path());
assert!(result.is_some());
assert_eq!(result.unwrap().extension().unwrap(), "mjs");
}
#[test]
fn test_try_resolve_json_extension() {
let dir = tempdir();
let file = dir.path().join("data.json");
fs::write(&file, r#"{"key": "value"}"#).unwrap();
let result = try_resolve(dir.path().join("data").as_path());
assert!(result.is_some());
assert_eq!(result.unwrap().extension().unwrap(), "json");
}
#[test]
fn test_try_resolve_tsx_extension() {
let dir = tempdir();
let file = dir.path().join("component.tsx");
fs::write(&file, "export const X = () => <div/>;").unwrap();
let result = try_resolve(dir.path().join("component").as_path());
assert!(result.is_some());
assert_eq!(result.unwrap().extension().unwrap(), "tsx");
}
#[test]
fn test_try_resolve_extension_priority_order() {
let dir = tempdir();
fs::write(dir.path().join("only.tsx"), "").unwrap();
let result = try_resolve(dir.path().join("only").as_path()).unwrap();
assert_eq!(result.extension().unwrap(), "tsx");
fs::write(dir.path().join("only.ts"), "").unwrap();
let result = try_resolve(dir.path().join("only").as_path()).unwrap();
assert_eq!(result.extension().unwrap(), "ts");
fs::write(dir.path().join("only.json"), "{}").unwrap();
let result = try_resolve(dir.path().join("only").as_path()).unwrap();
assert_eq!(result.extension().unwrap(), "json");
fs::write(dir.path().join("only.mjs"), "").unwrap();
let result = try_resolve(dir.path().join("only").as_path()).unwrap();
assert_eq!(result.extension().unwrap(), "mjs");
fs::write(dir.path().join("only.js"), "").unwrap();
let result = try_resolve(dir.path().join("only").as_path()).unwrap();
assert_eq!(result.extension().unwrap(), "js");
}
#[test]
fn test_try_resolve_deeply_nested_file() {
let dir = tempdir();
let deep = dir.path().join("a").join("b").join("c").join("d").join("e");
fs::create_dir_all(&deep).unwrap();
let file = deep.join("nested.js");
fs::write(&file, "").unwrap();
let result = try_resolve(deep.join("nested").as_path());
assert!(result.is_some());
assert!(result.unwrap().ends_with("nested.js"));
}
#[test]
fn test_try_resolve_deeply_nested_directory_with_index() {
let dir = tempdir();
let deep = dir.path().join("x").join("y").join("z").join("pkg");
fs::create_dir_all(&deep).unwrap();
fs::write(deep.join("index.js"), "module.exports = {};").unwrap();
let result = try_resolve(deep.as_path());
assert!(result.is_some());
assert!(result.unwrap().ends_with("index.js"));
}
#[test]
fn test_try_resolve_empty_directory_no_index() {
let dir = tempdir();
let empty = dir.path().join("empty_dir");
fs::create_dir_all(&empty).unwrap();
let result = try_resolve(empty.as_path());
assert!(result.is_none());
}
#[test]
fn test_try_resolve_index_mjs_fallback() {
let dir = tempdir();
let pkg = dir.path().join("pkg_mjs");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("index.mjs"), "export default 1;").unwrap();
let result = try_resolve(pkg.as_path());
assert!(result.is_some());
assert!(result.unwrap().ends_with("index.mjs"));
}
#[test]
fn test_try_resolve_index_ts_fallback() {
let dir = tempdir();
let pkg = dir.path().join("pkg_ts");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("index.ts"), "export const x = 1;").unwrap();
let result = try_resolve(pkg.as_path());
assert!(result.is_some());
assert!(result.unwrap().ends_with("index.ts"));
}
#[test]
fn test_try_resolve_index_priority_js_over_mjs() {
let dir = tempdir();
let pkg = dir.path().join("pkg_priority");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("index.mjs"), "").unwrap();
fs::write(pkg.join("index.js"), "").unwrap();
let result = try_resolve(pkg.as_path()).unwrap();
assert!(result.ends_with("index.js"));
}
#[test]
fn test_try_resolve_index_priority_mjs_over_ts() {
let dir = tempdir();
let pkg = dir.path().join("pkg_mjs_ts");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("index.ts"), "").unwrap();
fs::write(pkg.join("index.mjs"), "").unwrap();
let result = try_resolve(pkg.as_path()).unwrap();
assert!(result.ends_with("index.mjs"));
}
#[test]
fn test_try_resolve_symlink_to_file() {
let dir = tempdir();
let target = dir.path().join("target.js");
fs::write(&target, "").unwrap();
let link = dir.path().join("link.js");
#[cfg(unix)]
{
use ::std::os::unix::fs::symlink;
symlink(&target, &link).expect("create symlink");
}
#[cfg(windows)]
{
return;
}
let result = try_resolve(dir.path().join("link").as_path());
assert!(result.is_some());
}
#[test]
fn test_try_resolve_symlink_to_directory_with_index() {
let dir = tempdir();
let target_dir = dir.path().join("target_pkg");
fs::create_dir_all(&target_dir).unwrap();
fs::write(target_dir.join("index.js"), "").unwrap();
let link_dir = dir.path().join("link_pkg");
#[cfg(unix)]
{
use ::std::os::unix::fs::symlink;
symlink(&target_dir, &link_dir).expect("create symlink");
}
#[cfg(windows)]
{
return;
}
let result = try_resolve(link_dir.as_path());
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_absolute_with_extension() {
let dir = tempdir();
let file = dir.path().join("absolute_target.js");
fs::write(&file, "").unwrap();
let abs = file.to_str().unwrap().to_string();
let result = resolve_specifier(&abs, None);
assert!(result.is_some());
assert_eq!(result.unwrap(), file);
}
#[test]
fn test_resolve_specifier_absolute_without_extension() {
let dir = tempdir();
let file = dir.path().join("no_ext.js");
fs::write(&file, "").unwrap();
let abs_no_ext = file.with_extension("").to_str().unwrap().to_string();
let result = resolve_specifier(&abs_no_ext, None);
assert!(result.is_some());
assert!(result.unwrap().ends_with("no_ext.js"));
}
#[test]
fn test_resolve_specifier_absolute_nonexistent() {
let result = resolve_specifier("/nonexistent/path/to/module", None);
assert!(result.is_none());
}
#[test]
fn test_resolve_specifier_dot_slash_current_dir() {
let dir = tempdir();
let file = dir.path().join("current.js");
fs::write(&file, "").unwrap();
let result = resolve_specifier("./current", Some(dir.path()));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_dot_slash_nested() {
let dir = tempdir();
let nested = dir.path().join("a").join("b");
fs::create_dir_all(&nested).unwrap();
let file = nested.join("nested.js");
fs::write(&file, "").unwrap();
let result = resolve_specifier("./a/b/nested", Some(dir.path()));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_double_dot_slash_traverses_up() {
let dir = tempdir();
let child = dir.path().join("child");
fs::create_dir_all(&child).unwrap();
let file = dir.path().join("up.js");
fs::write(&file, "").unwrap();
let result = resolve_specifier("../up", Some(&child));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_multiple_parent_traversals() {
let dir = tempdir();
let deep = dir.path().join("a").join("b").join("c");
fs::create_dir_all(&deep).unwrap();
let file = dir.path().join("root.js");
fs::write(&file, "").unwrap();
let result = resolve_specifier("../../../root", Some(&deep));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_relative_no_base_dir() {
let dir = tempdir();
let file = dir.path().join("rel.js");
fs::write(&file, "").unwrap();
let original = ::std::env::current_dir().unwrap();
::std::env::set_current_dir(dir.path()).unwrap();
let result = resolve_specifier("./rel", None);
::std::env::set_current_dir(&original).unwrap();
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_bare_falls_through_to_node_modules() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("mylib");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_specifier("mylib", Some(dir.path()));
assert!(result.is_some());
assert!(result.unwrap().to_str().unwrap().contains("mylib"));
}
#[test]
fn test_resolve_specifier_bare_not_in_node_modules() {
let dir = tempdir();
let nm = dir.path().join("node_modules");
fs::create_dir_all(&nm).unwrap();
let result = resolve_specifier("nonexistent_pkg", Some(dir.path()));
assert!(result.is_none());
}
#[test]
fn test_resolve_node_modules_empty_specifier() {
let dir = tempdir();
let nm = dir.path().join("node_modules");
fs::create_dir_all(&nm).unwrap();
let result = resolve_node_modules("", Some(dir.path()));
assert!(result.is_none());
}
#[test]
fn test_resolve_node_modules_with_dot_specifier() {
let dir = tempdir();
let nm = dir.path().join("node_modules");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules(".", Some(dir.path()));
assert!(result.is_none());
}
#[test]
fn test_resolve_node_modules_with_double_dot_specifier() {
let dir = tempdir();
let nm = dir.path().join("node_modules");
fs::create_dir_all(&nm).unwrap();
let result = resolve_node_modules("..", Some(dir.path()));
assert!(result.is_none());
}
#[test]
fn test_resolve_node_modules_base_dir_none_uses_cwd() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("cwd_pkg");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let original = ::std::env::current_dir().unwrap();
::std::env::set_current_dir(dir.path()).unwrap();
let result = resolve_node_modules("cwd_pkg", None);
::std::env::set_current_dir(&original).unwrap();
assert!(result.is_some());
}
#[test]
fn test_resolve_node_modules_deeply_nested_base_dir() {
let dir = tempdir();
let deep = dir.path().join("a").join("b").join("c").join("d").join("e");
fs::create_dir_all(&deep).unwrap();
let nm = dir.path().join("node_modules").join("deep_pkg");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules("deep_pkg", Some(&deep));
assert!(result.is_some());
}
#[test]
fn test_resolve_node_modules_finds_in_intermediate_node_modules() {
let dir = tempdir();
let mid = dir.path().join("mid");
fs::create_dir_all(&mid).unwrap();
let nm_root = dir.path().join("node_modules").join("root_pkg");
fs::create_dir_all(&nm_root).unwrap();
fs::write(nm_root.join("index.js"), "// root").unwrap();
let nm_mid = mid.join("node_modules").join("mid_pkg");
fs::create_dir_all(&nm_mid).unwrap();
fs::write(nm_mid.join("index.js"), "// mid").unwrap();
let result = resolve_node_modules("mid_pkg", Some(&mid)).unwrap();
assert!(result.to_str().unwrap().contains("mid_pkg"));
}
#[test]
fn test_resolve_specifier_with_hyphen() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("my-lib");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules("my-lib", Some(dir.path()));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_with_underscore() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("my_lib");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules("my_lib", Some(dir.path()));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_with_dot_in_name() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("lib.core");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules("lib.core", Some(dir.path()));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_with_numbers() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("pkg123");
fs::create_dir_all(&nm).unwrap();
fs::write(nm.join("index.js"), "").unwrap();
let result = resolve_node_modules("pkg123", Some(dir.path()));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_case_sensitive() {
let dir = tempdir();
let nm = dir.path().join("node_modules");
fs::create_dir_all(&nm).unwrap();
let pkg_lower = nm.join("mypkg");
fs::create_dir_all(&pkg_lower).unwrap();
fs::write(pkg_lower.join("index.js"), "").unwrap();
let result = resolve_node_modules("MyPkg", Some(dir.path()));
#[cfg(target_os = "linux")]
assert!(result.is_none());
}
#[test]
fn test_resolve_specifier_traversal_beyond_root() {
let dir = tempdir();
let file = dir.path().join("root.js");
fs::write(&file, "").unwrap();
let result = resolve_specifier("../../../../../etc/passwd", Some(dir.path()));
let _ = result; }
#[test]
fn test_resolve_node_modules_traversal_stops_at_root() {
let dir = tempdir();
let result = resolve_node_modules("nonexistent", Some(dir.path()));
assert!(result.is_none());
}
#[test]
fn test_try_resolve_with_trailing_slash() {
let dir = tempdir();
let pkg = dir.path().join("pkg_with_slash");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("index.js"), "").unwrap();
let path_with_slash = pkg.to_str().unwrap().to_string() + "/";
let result = try_resolve(Path::new(&path_with_slash));
assert!(result.is_some());
}
#[test]
fn test_resolve_specifier_with_subpath() {
let dir = tempdir();
let nm = dir.path().join("node_modules").join("pkg");
let sub = nm.join("lib").join("sub.js");
fs::create_dir_all(sub.parent().unwrap()).unwrap();
fs::write(&sub, "").unwrap();
let result = resolve_node_modules("pkg/lib/sub", Some(dir.path()));
assert!(result.is_some());
assert!(result.unwrap().ends_with("sub.js"));
}
#[test]
fn test_resolve_specifier_json_file_exact() {
let dir = tempdir();
let file = dir.path().join("config.json");
fs::write(&file, r#"{"name": "test"}"#).unwrap();
let result = resolve_specifier("./config.json", Some(dir.path()));
assert!(result.is_some());
assert!(result.unwrap().ends_with("config.json"));
}
#[test]
fn test_try_resolve_exact_file_path_no_extension_add() {
let dir = tempdir();
let file = dir.path().join("exact.js");
fs::write(&file, "").unwrap();
let result = try_resolve(&file);
assert!(result.is_some());
assert_eq!(result.unwrap(), file);
}
}