use std::fs;
use std::path::{Path, PathBuf};
use crate::cli::script_executor::ModuleSystem;
use std::io::Read;
#[derive(Debug, Clone)]
pub struct ResolutionResult {
pub path: PathBuf,
pub module_type: ModuleType,
pub is_package: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ModuleType {
JavaScript,
Json,
Native,
Package,
BuiltIn,
}
pub struct ModuleResolver {
current_dir: PathBuf,
cache: HashMap<String, ResolutionResult>,
search_paths: Vec<PathBuf>,
}
impl ModuleResolver {
pub fn new(current_dir: PathBuf) -> Self {
let mut resolver = Self {
current_dir,
cache: HashMap::new(),
search_paths: Vec::new(),
};
resolver.build_search_paths();
resolver
}
pub fn resolve(&mut self, request: &str, parent: &Path) -> Result<ResolutionResult, String> {
let cache_key: _ = format!("{}:{}", parent.display(), request);
if let Some(cached) = self.cache.get(&cache_key) {
return Ok(cached.clone());
}
let result: _ = match request {
_ if self.is_builtin_module(request) => {
self.resolve_builtin(request)
}
_ if request.starts_with("./") || request.starts_with("../") => {
self.resolve_relative(request, parent)
}
_ if request.starts_with("/") => {
self.resolve_absolute(request)
}
_ => {
self.resolve_from_node_modules(request, parent)
}
};
if let Ok(ref res) = result {
self.cache.insert(cache_key, res.clone());
}
result
}
fn is_builtin_module(&self, name: &str) -> bool {
matches!(
name,
"assert" | "buffer" | "child_process" | "cluster" | "crypto" | "dns" |
"domain" | "events" | "fs" | "http" | "https" | "net" | "os" |
"path" | "querystring" | "readline" | "repl" | "stream" | "string_decoder" |
"timers" | "tls" | "tty" | "url" | "util" | "v8" | "vm" | "wasi" |
"worker_threads" | "zlib"
)
}
fn resolve_builtin(&self, name: &str) -> Result<ResolutionResult, String> {
Ok(ResolutionResult {
path: PathBuf::from(name),
module_type: ModuleType::BuiltIn,
is_package: false,
})
}
fn resolve_relative(&self, request: &str, parent: &Path) -> Result<ResolutionResult, String> {
let parent_dir: _ = parent.parent().unwrap_or(parent);
let mut candidate = parent_dir.join(request);
let extensions: _ = ["", ".js", ".json", ".node"];
for ext in &extensions {
if ext.is_empty() {
if candidate.join("package.json").exists() {
return self.resolve_package(candidate);
}
} else {
let candidate_with_ext: _ = {
let mut temp = candidate.clone();
temp.set_extension(ext.trim_start_matches('.'));
temp
};
if candidate_with_ext.exists() {
let module_type: _ = self.get_module_type(&candidate_with_ext);
return Ok(ResolutionResult {
path: candidate_with_ext,
module_type,
is_package: false,
});
}
}
}
Err(format!("Cannot find module '{}' from '{}'", request, parent.display()))
}
fn resolve_absolute(&self, request: &str) -> Result<ResolutionResult, String> {
let path: _ = Path::new(request);
if path.exists() {
return Ok(ResolutionResult {
path: path.to_path_buf(),
module_type: self.get_module_type(path),
is_package: false,
});
}
let package_path: _ = path.clone().join("package.json");
if package_path.exists() {
return self.resolve_package(path.to_path_buf());
}
Err(format!("Cannot find module '{}'", request))
}
fn resolve_from_node_modules(&self, request: &str, parent: &Path) -> Result<ResolutionResult, String> {
let mut current_dir = parent.parent().unwrap_or(parent).to_path_buf();
while current_dir.pop() {
let node_modules: _ = current_dir.join("node_modules").join(request);
let package_json: _ = node_modules.join("package.json");
if package_json.exists() {
return self.resolve_package(node_modules);
}
let extensions: _ = ["", ".js", ".json", ".node"];
for ext in &extensions {
let candidate: _ = {
let mut temp = node_modules.clone();
if !ext.is_empty() {
temp.set_extension(ext.trim_start_matches('.'));
}
temp
};
if candidate.exists() {
let module_type: _ = self.get_module_type(&candidate);
return Ok(ResolutionResult {
path: candidate,
module_type,
is_package: false,
});
}
}
}
Err(format!("Cannot find module '{}'", request))
}
fn resolve_package(&self, package_path: PathBuf) -> Result<ResolutionResult, String> {
let package_json: _ = package_path.join("package.json");
if !package_json.exists() {
return Err(format!("Package not found at {}", package_path.display()));
}
match fs::read_to_string(&package_json) {
Ok(content) => {
match serde_json::from_str::<serde_json::Value>(&content) {
Ok(package) => {
let main: _ = package.get("main")
.and_then(|m| m.as_str())
.unwrap_or("index.js");
let mut main_path = package_path.join(main);
if Path::new(main).extension().is_none() {
let extensions: _ = [".js", ".json", ".node"];
for ext in &extensions {
let candidate: _ = package_path.join(format!("{}{}", main, ext));
if candidate.exists() {
main_path = candidate.clone();
break;
}
}
}
let final_path: _ = main_path.clone();
let module_type: _ = self.get_module_type(&final_path);
Ok(ResolutionResult {
path: final_path,
module_type,
is_package: true,
})
}
Err(_) => {
let index_path: _ = package_path.join("index.js");
if index_path.exists() {
Ok(ResolutionResult {
path: index_path,
module_type: ModuleType::JavaScript,
is_package: true,
})
} else {
Err(format!("Invalid package.json in {}", package_path.display()))
}
}
}
}
Err(_) => Err(format!("Cannot read package.json at {}", package_json.display())),
}
}
fn get_module_type(&self, path: &Path) -> ModuleType {
match path.extension().and_then(|e| e.to_str()) {
Some("js") | Some("mjs") => ModuleType::JavaScript,
Some("json") => ModuleType::Json,
Some("node") => ModuleType::Native,
_ => ModuleType::JavaScript, }
}
fn build_search_paths(&mut self) {
let mut dir = self.current_dir.clone();
while dir.pop() {
self.search_paths.push(dir.join("node_modules"));
}
if let Ok(home) = std::env::var("HOME") {
self.search_paths.push(PathBuf::from(home).join(".node_modules"));
}
if let Ok(node_path) = std::env::var("NODE_PATH") {
for path in std::env::split_paths(&node_path) {
self.search_paths.push(path);
}
}
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
pub fn search_paths(&self) -> &[PathBuf] {
&self.search_paths
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::collections::{BTreeMap, HashMap};
#[test]
fn test_resolve_builtin_module() {
let mut resolver = ModuleResolver::new(PathBuf::from("/test"));
let result: _ = resolver.resolve("fs", Path::new("/test/script.js"));
assert!(result.is_ok());
let result: _ = result.unwrap();
assert_eq!(result.module_type, ModuleType::BuiltIn);
assert_eq!(result.path, PathBuf::from("fs"));
}
#[test]
fn test_resolve_relative_path() {
let mut resolver = ModuleResolver::new(PathBuf::from("/test"));
let result: _ = resolver.resolve("./utils", Path::new("/test/script.js"));
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_is_builtin_module() {
let mut resolver = ModuleResolver::new(PathBuf::from("/test"));
assert!(resolver.is_builtin_module("fs"));
assert!(resolver.is_builtin_module("http"));
assert!(!resolver.is_builtin_module("lodash"));
}
#[test]
fn test_get_module_type() {
let mut resolver = ModuleResolver::new(PathBuf::from("/test"));
assert_eq!(resolver.get_module_type(Path::new("test.js")), ModuleType::JavaScript);
assert_eq!(resolver.get_module_type(Path::new("test.json")), ModuleType::Json);
assert_eq!(resolver.get_module_type(Path::new("test.node")), ModuleType::Native);
}
}