amberjs 1.17.0

JavaScript/TypeScript runtime built with Rust and V8
Documentation
// Module Resolution System
// Stage 56.3 - Node.js Compatible Module Resolution
//
// Provides comprehensive module resolution including:
// - Node.js module algorithm (.js → .json → .node)
// - node_modules search paths
// - Built-in module detection
// - Package.json "main" field support
/// - Relative and absolute path resolution
use std::fs;
use std::path::{Path, PathBuf};
use crate::cli::script_executor::ModuleSystem;
use std::io::Read;
/// Module resolution result
#[derive(Debug, Clone)]
pub struct ResolutionResult {
    /// Resolved file path
    pub path: PathBuf,
    /// Module type
    pub module_type: ModuleType,
    /// Whether it's a directory (package)
    pub is_package: bool,
}
/// Type of module
#[derive(Debug, Clone, PartialEq)]
pub enum ModuleType {
    /// JavaScript file
    JavaScript,
    /// JSON file
    Json,
    /// Native addon
    Native,
    /// Package directory with package.json
    Package,
    /// Built-in module
    BuiltIn,
}
/// Module resolver implementing Node.js algorithm
pub struct ModuleResolver {
    /// Current working directory for resolution
    current_dir: PathBuf,
    /// Module cache to avoid re-resolution
    cache: HashMap<String, ResolutionResult>,
    /// Search paths for node_modules
    search_paths: Vec<PathBuf>,
}
impl ModuleResolver {
    /// Create a new module resolver
    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
    }
    /// Resolve a module request (e.g., 'lodash', './utils', '/abs/path')
    pub fn resolve(&mut self, request: &str, parent: &Path) -> Result<ResolutionResult, String> {
        // Check cache first
        let cache_key: _ = format!("{}:{}", parent.display(), request);
        if let Some(cached) = self.cache.get(&cache_key) {
            return Ok(cached.clone());
        }
        let result: _ = match request {
            // Built-in modules
            _ if self.is_builtin_module(request) => {
                self.resolve_builtin(request)
            }
            // Relative paths
            _ if request.starts_with("./") || request.starts_with("../") => {
                self.resolve_relative(request, parent)
            }
            // Absolute paths
            _ if request.starts_with("/") => {
                self.resolve_absolute(request)
            }
            // Module name - search in node_modules
            _ => {
                self.resolve_from_node_modules(request, parent)
            }
        };
        // Cache the result
        if let Ok(ref res) = result {
            self.cache.insert(cache_key, res.clone());
        }
        result
    }
    /// Check if a module name is a built-in Node.js module
    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"
        )
    }
    /// Resolve built-in modules
    fn resolve_builtin(&self, name: &str) -> Result<ResolutionResult, String> {
        Ok(ResolutionResult {
            path: PathBuf::from(name),
            module_type: ModuleType::BuiltIn,
            is_package: false,
        })
    }
    /// Resolve relative module paths
    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);
        // Try extensions in order: .js → .json → .node
        let extensions: _ = ["", ".js", ".json", ".node"];
        for ext in &extensions {
            if ext.is_empty() {
                // Check if it's a directory with package.json
                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()))
    }
    /// Resolve absolute paths
    fn resolve_absolute(&self, request: &str) -> Result<ResolutionResult, String> {
        let path: _ = Path::new(request);
        // Try as file first
        if path.exists() {
            return Ok(ResolutionResult {
                path: path.to_path_buf(),
                module_type: self.get_module_type(path),
                is_package: false,
            });
        }
        // Try as directory with package.json
        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))
    }
    /// Resolve modules from node_modules directories
    fn resolve_from_node_modules(&self, request: &str, parent: &Path) -> Result<ResolutionResult, String> {
        // Start from parent directory and traverse up
        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);
            
            // Try as package
            let package_json: _ = node_modules.join("package.json");
            if package_json.exists() {
                return self.resolve_package(node_modules);
            }
            
            // Try as file with extensions
            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))
    }
    /// Resolve a package directory (with package.json)
    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()));
        }
        // Read package.json to find main entry point
        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);
                        
                        // Try extensions if not specified
                        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(_) => {
                        // Fallback to index.js
                        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())),
        }
    }
    /// Get module type based on file extension
    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, // Default
        }
    }
    /// Build search paths for node_modules
    fn build_search_paths(&mut self) {
        let mut dir = self.current_dir.clone();
        
        // Add current directory and all parent directories
        while dir.pop() {
            self.search_paths.push(dir.join("node_modules"));
        }
        
        // Add global node_modules paths (simplified)
        if let Ok(home) = std::env::var("HOME") {
            self.search_paths.push(PathBuf::from(home).join(".node_modules"));
        }
        
        // Add NODE_PATH if set
        if let Ok(node_path) = std::env::var("NODE_PATH") {
            for path in std::env::split_paths(&node_path) {
                self.search_paths.push(path);
            }
        }
    }
    /// Clear the module cache
    pub fn clear_cache(&mut self) {
        self.cache.clear();
    }
    /// Get all search paths
    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"));
        // Will fail without actual files, but tests the logic
        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);
    }
}