diff options
Diffstat (limited to 'cli/compat/esm_resolver.rs')
-rw-r--r-- | cli/compat/esm_resolver.rs | 1288 |
1 files changed, 0 insertions, 1288 deletions
diff --git a/cli/compat/esm_resolver.rs b/cli/compat/esm_resolver.rs deleted file mode 100644 index 2a5da4fe1..000000000 --- a/cli/compat/esm_resolver.rs +++ /dev/null @@ -1,1288 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::node::errors; -use crate::resolver::ImportMapResolver; -use deno_core::error::generic_error; -use deno_core::error::AnyError; -use deno_core::serde_json; -use deno_core::serde_json::Map; -use deno_core::serde_json::Value; -use deno_core::url::Url; -use deno_core::ModuleSpecifier; -use deno_graph::source::ResolveResponse; -use deno_graph::source::Resolver; -use deno_runtime::deno_node::DEFAULT_CONDITIONS; -use regex::Regex; -use std::path::PathBuf; - -#[derive(Debug, Default)] -pub struct NodeEsmResolver { - maybe_import_map_resolver: Option<ImportMapResolver>, -} - -impl NodeEsmResolver { - pub fn new(maybe_import_map_resolver: Option<ImportMapResolver>) -> Self { - Self { - maybe_import_map_resolver, - } - } -} - -impl Resolver for NodeEsmResolver { - fn resolve( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - ) -> ResolveResponse { - // First try to resolve using import map, ignoring any errors - if !specifier.starts_with("node:") { - if let Some(import_map_resolver) = &self.maybe_import_map_resolver { - let response = import_map_resolver.resolve(specifier, referrer); - if !matches!(response, ResolveResponse::Err(_)) { - return response; - } - } - } - - let current_dir = match std::env::current_dir() { - Ok(path) => path, - Err(err) => return ResolveResponse::Err(err.into()), - }; - let node_resolution = - node_resolve(specifier, referrer.as_str(), ¤t_dir); - - match node_resolution { - Ok(resolve_response) => { - // If node resolution succeeded, return the specifier - resolve_response - } - Err(err) => { - // If node resolution failed, check if it's because of unsupported - // URL scheme, and if so try to resolve using regular resolution algorithm - if err - .to_string() - .starts_with("[ERR_UNSUPPORTED_ESM_URL_SCHEME]") - { - return match deno_core::resolve_import(specifier, referrer.as_str()) { - Ok(specifier) => ResolveResponse::Esm(specifier), - Err(err) => ResolveResponse::Err(err.into()), - }; - } - - ResolveResponse::Err(err) - } - } - } -} - -/// This function is an implementation of `defaultResolve` in -/// `lib/internal/modules/esm/resolve.js` from Node. -fn node_resolve( - specifier: &str, - referrer: &str, - cwd: &std::path::Path, -) -> Result<ResolveResponse, AnyError> { - // TODO(bartlomieju): skipped "policy" part as we don't plan to support it - - if let Some(resolved) = crate::node::try_resolve_builtin_module(specifier) { - return Ok(ResolveResponse::Esm(resolved)); - } - - if let Ok(url) = Url::parse(specifier) { - if url.scheme() == "data" { - return Ok(ResolveResponse::Specifier(url)); - } - - let protocol = url.scheme(); - - if protocol == "node" { - let split_specifier = url.as_str().split(':'); - let specifier = split_specifier.skip(1).collect::<String>(); - if let Some(resolved) = - crate::node::try_resolve_builtin_module(&specifier) - { - return Ok(ResolveResponse::Esm(resolved)); - } else { - return Err(generic_error(format!("Unknown module {}", specifier))); - } - } - - if protocol != "file" && protocol != "data" { - return Err(errors::err_unsupported_esm_url_scheme(&url)); - } - - if referrer.starts_with("data:") { - let referrer_url = Url::parse(referrer)?; - let url = referrer_url.join(specifier).map_err(AnyError::from)?; - return Ok(ResolveResponse::Specifier(url)); - } - } - - let is_main = referrer.is_empty(); - let parent_url = if is_main { - Url::from_directory_path(cwd).unwrap() - } else { - Url::parse(referrer).expect("referrer was not proper url") - }; - - let conditions = DEFAULT_CONDITIONS; - let url = module_resolve(specifier, &parent_url, conditions)?; - - let resolve_response = if url.as_str().starts_with("http") { - ResolveResponse::Esm(url) - } else if url.as_str().ends_with(".js") { - let package_config = get_package_scope_config(&url)?; - if package_config.typ == "module" { - ResolveResponse::Esm(url) - } else { - ResolveResponse::CommonJs(url) - } - } else if url.as_str().ends_with(".cjs") { - ResolveResponse::CommonJs(url) - } else { - ResolveResponse::Esm(url) - }; - // TODO(bartlomieju): skipped checking errors for commonJS resolution and - // "preserveSymlinksMain"/"preserveSymlinks" options. - Ok(resolve_response) -} - -fn to_file_path(url: &ModuleSpecifier) -> PathBuf { - url - .to_file_path() - .unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {}", url)) -} - -fn to_file_path_string(url: &ModuleSpecifier) -> String { - to_file_path(url).display().to_string() -} - -fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool { - if specifier.is_empty() { - return false; - } - - if specifier.starts_with('/') { - return true; - } - - is_relative_specifier(specifier) -} - -// TODO(ry) We very likely have this utility function elsewhere in Deno. -fn is_relative_specifier(specifier: &str) -> bool { - let specifier_len = specifier.len(); - let specifier_chars: Vec<_> = specifier.chars().collect(); - - if !specifier_chars.is_empty() && specifier_chars[0] == '.' { - if specifier_len == 1 || specifier_chars[1] == '/' { - return true; - } - if specifier_chars[1] == '.' - && (specifier_len == 2 || specifier_chars[2] == '/') - { - return true; - } - } - false -} - -fn module_resolve( - specifier: &str, - base: &ModuleSpecifier, - conditions: &[&str], -) -> Result<ModuleSpecifier, AnyError> { - let resolved = if should_be_treated_as_relative_or_absolute_path(specifier) { - base.join(specifier)? - } else if specifier.starts_with('#') { - package_imports_resolve(specifier, base, conditions)? - } else if let Ok(resolved) = Url::parse(specifier) { - resolved - } else { - package_resolve(specifier, base, conditions)? - }; - finalize_resolution(resolved, base) -} - -fn finalize_resolution( - resolved: ModuleSpecifier, - base: &ModuleSpecifier, -) -> Result<ModuleSpecifier, AnyError> { - // TODO(bartlomieju): this is not part of Node resolution algorithm - // (as it doesn't support http/https); but I had to short circuit here - // for remote modules because they are mainly used to polyfill `node` built - // in modules. Another option would be to leave the resolved URLs - // as `node:<module_name>` and do the actual remapping to std's polyfill - // in module loader. I'm not sure which approach is better. - if resolved.scheme().starts_with("http") { - return Ok(resolved); - } - - let encoded_sep_re = Regex::new(r"%2F|%2C").expect("bad regex"); - - if encoded_sep_re.is_match(resolved.path()) { - return Err(errors::err_invalid_module_specifier( - resolved.path(), - "must not include encoded \"/\" or \"\\\\\" characters", - Some(to_file_path_string(base)), - )); - } - - let path = to_file_path(&resolved); - - // TODO(bartlomieju): currently not supported - // if (getOptionValue('--experimental-specifier-resolution') === 'node') { - // ... - // } - - let p_str = path.to_str().unwrap(); - let p = if p_str.ends_with('/') { - p_str[p_str.len() - 1..].to_string() - } else { - p_str.to_string() - }; - - let (is_dir, is_file) = if let Ok(stats) = std::fs::metadata(&p) { - (stats.is_dir(), stats.is_file()) - } else { - (false, false) - }; - if is_dir { - return Err(errors::err_unsupported_dir_import( - resolved.as_str(), - base.as_str(), - )); - } else if !is_file { - return Err(errors::err_module_not_found( - resolved.as_str(), - base.as_str(), - "module", - )); - } - - Ok(resolved) -} - -fn throw_import_not_defined( - specifier: &str, - package_json_url: Option<ModuleSpecifier>, - base: &ModuleSpecifier, -) -> AnyError { - errors::err_package_import_not_defined( - specifier, - package_json_url.map(|u| to_file_path_string(&u.join(".").unwrap())), - &to_file_path_string(base), - ) -} - -fn pattern_key_compare(a: &str, b: &str) -> i32 { - let a_pattern_index = a.find('*'); - let b_pattern_index = b.find('*'); - - let base_len_a = if let Some(index) = a_pattern_index { - index + 1 - } else { - a.len() - }; - let base_len_b = if let Some(index) = b_pattern_index { - index + 1 - } else { - b.len() - }; - - if base_len_a > base_len_b { - return -1; - } - - if base_len_b > base_len_a { - return 1; - } - - if a_pattern_index.is_none() { - return 1; - } - - if b_pattern_index.is_none() { - return -1; - } - - if a.len() > b.len() { - return -1; - } - - if b.len() > a.len() { - return 1; - } - - 0 -} - -fn package_imports_resolve( - name: &str, - base: &ModuleSpecifier, - conditions: &[&str], -) -> Result<ModuleSpecifier, AnyError> { - if name == "#" || name.starts_with("#/") || name.ends_with('/') { - let reason = "is not a valid internal imports specifier name"; - return Err(errors::err_invalid_module_specifier( - name, - reason, - Some(to_file_path_string(base)), - )); - } - - let mut package_json_url = None; - - let package_config = get_package_scope_config(base)?; - if package_config.exists { - package_json_url = - Some(Url::from_file_path(package_config.pjsonpath).unwrap()); - if let Some(imports) = &package_config.imports { - if imports.contains_key(name) && !name.contains('*') { - let maybe_resolved = resolve_package_target( - package_json_url.clone().unwrap(), - imports.get(name).unwrap().to_owned(), - "".to_string(), - name.to_string(), - base, - false, - true, - conditions, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } - } else { - let mut best_match = ""; - let mut best_match_subpath = None; - for key in imports.keys() { - let pattern_index = key.find('*'); - if let Some(pattern_index) = pattern_index { - let key_sub = &key[0..=pattern_index]; - if name.starts_with(key_sub) { - let pattern_trailer = &key[pattern_index + 1..]; - if name.len() > key.len() - && name.ends_with(&pattern_trailer) - && pattern_key_compare(best_match, key) == 1 - && key.rfind('*') == Some(pattern_index) - { - best_match = key; - best_match_subpath = Some( - name[pattern_index..=(name.len() - pattern_trailer.len())] - .to_string(), - ); - } - } - } - } - - if !best_match.is_empty() { - let target = imports.get(best_match).unwrap().to_owned(); - let maybe_resolved = resolve_package_target( - package_json_url.clone().unwrap(), - target, - best_match_subpath.unwrap(), - best_match.to_string(), - base, - true, - true, - conditions, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } - } - } - } - } - - Err(throw_import_not_defined(name, package_json_url, base)) -} - -fn is_conditional_exports_main_sugar( - exports: &Value, - package_json_url: &ModuleSpecifier, - base: &ModuleSpecifier, -) -> Result<bool, AnyError> { - if exports.is_string() || exports.is_array() { - return Ok(true); - } - - if exports.is_null() || !exports.is_object() { - return Ok(false); - } - - let exports_obj = exports.as_object().unwrap(); - let mut is_conditional_sugar = false; - let mut i = 0; - for key in exports_obj.keys() { - let cur_is_conditional_sugar = key.is_empty() || !key.starts_with('.'); - if i == 0 { - is_conditional_sugar = cur_is_conditional_sugar; - i += 1; - } else if is_conditional_sugar != cur_is_conditional_sugar { - return Err(errors::err_invalid_package_config( - &to_file_path_string(package_json_url), - Some(base.as_str().to_string()), - Some("\"exports\" cannot contains some keys starting with \'.\' and some not. - The exports object must either be an object of package subpath keys - or an object of main entry condition name keys only.".to_string()) - )); - } - } - - Ok(is_conditional_sugar) -} - -fn throw_invalid_package_target( - subpath: String, - target: String, - package_json_url: &ModuleSpecifier, - internal: bool, - base: &ModuleSpecifier, -) -> AnyError { - errors::err_invalid_package_target( - to_file_path_string(&package_json_url.join(".").unwrap()), - subpath, - target, - internal, - Some(base.as_str().to_string()), - ) -} - -fn throw_invalid_subpath( - subpath: String, - package_json_url: &ModuleSpecifier, - internal: bool, - base: &ModuleSpecifier, -) -> AnyError { - let ie = if internal { "imports" } else { "exports" }; - let reason = format!( - "request is not a valid subpath for the \"{}\" resolution of {}", - ie, - to_file_path_string(package_json_url) - ); - errors::err_invalid_module_specifier( - &subpath, - &reason, - Some(to_file_path_string(base)), - ) -} - -#[allow(clippy::too_many_arguments)] -fn resolve_package_target_string( - target: String, - subpath: String, - match_: String, - package_json_url: ModuleSpecifier, - base: &ModuleSpecifier, - pattern: bool, - internal: bool, - conditions: &[&str], -) -> Result<ModuleSpecifier, AnyError> { - if !subpath.is_empty() && !pattern && !target.ends_with('/') { - return Err(throw_invalid_package_target( - match_, - target, - &package_json_url, - internal, - base, - )); - } - - let invalid_segment_re = - Regex::new(r"(^|\|/)(..?|node_modules)(\|/|$)").expect("bad regex"); - let pattern_re = Regex::new(r"\*").expect("bad regex"); - - if !target.starts_with("./") { - if internal && !target.starts_with("../") && !target.starts_with('/') { - let is_url = Url::parse(&target).is_ok(); - if !is_url { - let export_target = if pattern { - pattern_re - .replace(&target, |_caps: ®ex::Captures| subpath.clone()) - .to_string() - } else { - format!("{}{}", target, subpath) - }; - return package_resolve(&export_target, &package_json_url, conditions); - } - } - return Err(throw_invalid_package_target( - match_, - target, - &package_json_url, - internal, - base, - )); - } - - if invalid_segment_re.is_match(&target[2..]) { - return Err(throw_invalid_package_target( - match_, - target, - &package_json_url, - internal, - base, - )); - } - - let resolved = package_json_url.join(&target)?; - let resolved_path = resolved.path(); - let package_url = package_json_url.join(".").unwrap(); - let package_path = package_url.path(); - - if !resolved_path.starts_with(package_path) { - return Err(throw_invalid_package_target( - match_, - target, - &package_json_url, - internal, - base, - )); - } - - if subpath.is_empty() { - return Ok(resolved); - } - - if invalid_segment_re.is_match(&subpath) { - let request = if pattern { - match_.replace('*', &subpath) - } else { - format!("{}{}", match_, subpath) - }; - return Err(throw_invalid_subpath( - request, - &package_json_url, - internal, - base, - )); - } - - if pattern { - let replaced = pattern_re - .replace(resolved.as_str(), |_caps: ®ex::Captures| subpath.clone()); - let url = Url::parse(&replaced)?; - return Ok(url); - } - - Ok(resolved.join(&subpath)?) -} - -#[allow(clippy::too_many_arguments)] -fn resolve_package_target( - package_json_url: ModuleSpecifier, - target: Value, - subpath: String, - package_subpath: String, - base: &ModuleSpecifier, - pattern: bool, - internal: bool, - conditions: &[&str], -) -> Result<Option<ModuleSpecifier>, AnyError> { - if let Some(target) = target.as_str() { - return Ok(Some(resolve_package_target_string( - target.to_string(), - subpath, - package_subpath, - package_json_url, - base, - pattern, - internal, - conditions, - )?)); - } else if let Some(target_arr) = target.as_array() { - if target_arr.is_empty() { - return Ok(None); - } - - let mut last_error = None; - for target_item in target_arr { - let resolved_result = resolve_package_target( - package_json_url.clone(), - target_item.to_owned(), - subpath.clone(), - package_subpath.clone(), - base, - pattern, - internal, - conditions, - ); - - if let Err(e) = resolved_result { - let err_string = e.to_string(); - last_error = Some(e); - if err_string.starts_with("[ERR_INVALID_PACKAGE_TARGET]") { - continue; - } - return Err(last_error.unwrap()); - } - let resolved = resolved_result.unwrap(); - if resolved.is_none() { - last_error = None; - continue; - } - return Ok(resolved); - } - if last_error.is_none() { - return Ok(None); - } - return Err(last_error.unwrap()); - } else if let Some(target_obj) = target.as_object() { - for key in target_obj.keys() { - // TODO(bartlomieju): verify that keys are not numeric - // return Err(errors::err_invalid_package_config( - // to_file_path_string(package_json_url), - // Some(base.as_str().to_string()), - // Some("\"exports\" cannot contain numeric property keys.".to_string()), - // )); - - if key == "default" || conditions.contains(&key.as_str()) { - let condition_target = target_obj.get(key).unwrap().to_owned(); - let resolved = resolve_package_target( - package_json_url.clone(), - condition_target, - subpath.clone(), - package_subpath.clone(), - base, - pattern, - internal, - conditions, - )?; - if resolved.is_none() { - continue; - } - return Ok(resolved); - } - } - } else if target.is_null() { - return Ok(None); - } - - Err(throw_invalid_package_target( - package_subpath, - target.to_string(), - &package_json_url, - internal, - base, - )) -} - -fn throw_exports_not_found( - subpath: String, - package_json_url: &ModuleSpecifier, - base: &ModuleSpecifier, -) -> AnyError { - errors::err_package_path_not_exported( - to_file_path_string(&package_json_url.join(".").unwrap()), - subpath, - Some(to_file_path_string(base)), - ) -} - -fn package_exports_resolve( - package_json_url: ModuleSpecifier, - package_subpath: String, - package_config: PackageConfig, - base: &ModuleSpecifier, - conditions: &[&str], -) -> Result<ModuleSpecifier, AnyError> { - let exports = &package_config.exports.unwrap(); - - let exports_map = - if is_conditional_exports_main_sugar(exports, &package_json_url, base)? { - let mut map = Map::new(); - map.insert(".".to_string(), exports.to_owned()); - map - } else { - exports.as_object().unwrap().to_owned() - }; - - if exports_map.contains_key(&package_subpath) - && package_subpath.find('*').is_none() - && !package_subpath.ends_with('/') - { - let target = exports_map.get(&package_subpath).unwrap().to_owned(); - let resolved = resolve_package_target( - package_json_url.clone(), - target, - "".to_string(), - package_subpath.to_string(), - base, - false, - false, - conditions, - )?; - if resolved.is_none() { - return Err(throw_exports_not_found( - package_subpath, - &package_json_url, - base, - )); - } - return Ok(resolved.unwrap()); - } - - let mut best_match = ""; - let mut best_match_subpath = None; - for key in exports_map.keys() { - let pattern_index = key.find('*'); - if let Some(pattern_index) = pattern_index { - let key_sub = &key[0..=pattern_index]; - if package_subpath.starts_with(key_sub) { - // When this reaches EOL, this can throw at the top of the whole function: - // - // if (StringPrototypeEndsWith(packageSubpath, '/')) - // throwInvalidSubpath(packageSubpath) - // - // To match "imports" and the spec. - if package_subpath.ends_with('/') { - // TODO(bartlomieju): - // emitTrailingSlashPatternDeprecation(); - } - let pattern_trailer = &key[pattern_index + 1..]; - if package_subpath.len() > key.len() - && package_subpath.ends_with(&pattern_trailer) - && pattern_key_compare(best_match, key) == 1 - && key.rfind('*') == Some(pattern_index) - { - best_match = key; - best_match_subpath = Some( - package_subpath - [pattern_index..=(package_subpath.len() - pattern_trailer.len())] - .to_string(), - ); - } - } - } - } - - if !best_match.is_empty() { - let target = exports.get(best_match).unwrap().to_owned(); - let maybe_resolved = resolve_package_target( - package_json_url.clone(), - target, - best_match_subpath.unwrap(), - best_match.to_string(), - base, - true, - false, - conditions, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } else { - return Err(throw_exports_not_found( - package_subpath, - &package_json_url, - base, - )); - } - } - - Err(throw_exports_not_found( - package_subpath, - &package_json_url, - base, - )) -} - -fn package_resolve( - specifier: &str, - base: &ModuleSpecifier, - conditions: &[&str], -) -> Result<ModuleSpecifier, AnyError> { - let (package_name, package_subpath, is_scoped) = - parse_package_name(specifier, base)?; - - // ResolveSelf - let package_config = get_package_scope_config(base)?; - if package_config.exists { - let package_json_url = - Url::from_file_path(&package_config.pjsonpath).unwrap(); - if package_config.name.as_ref() == Some(&package_name) { - if let Some(exports) = &package_config.exports { - if !exports.is_null() { - return package_exports_resolve( - package_json_url, - package_subpath, - package_config, - base, - conditions, - ); - } - } - } - } - - let mut package_json_url = - base.join(&format!("./node_modules/{}/package.json", package_name))?; - let mut package_json_path = to_file_path(&package_json_url); - let mut last_path; - loop { - let p_str = package_json_path.to_str().unwrap(); - let package_str_len = "/package.json".len(); - let p = p_str[0..=p_str.len() - package_str_len].to_string(); - let is_dir = if let Ok(stats) = std::fs::metadata(&p) { - stats.is_dir() - } else { - false - }; - if !is_dir { - last_path = package_json_path; - - let prefix = if is_scoped { - "../../../../node_modules/" - } else { - "../../../node_modules/" - }; - package_json_url = package_json_url - .join(&format!("{}{}/package.json", prefix, package_name))?; - package_json_path = to_file_path(&package_json_url); - if package_json_path.to_str().unwrap().len() - == last_path.to_str().unwrap().len() - { - break; - } else { - continue; - } - } - - // Package match. - let package_config = - get_package_config(package_json_path.clone(), specifier, Some(base))?; - if package_config.exports.is_some() { - return package_exports_resolve( - package_json_url, - package_subpath, - package_config, - base, - conditions, - ); - } - if package_subpath == "." { - return legacy_main_resolve(&package_json_url, &package_config, base); - } - - return package_json_url - .join(&package_subpath) - .map_err(AnyError::from); - } - - Err(errors::err_module_not_found( - &package_json_url - .join(".") - .unwrap() - .to_file_path() - .unwrap() - .display() - .to_string(), - &to_file_path_string(base), - "package", - )) -} - -fn parse_package_name( - specifier: &str, - base: &ModuleSpecifier, -) -> Result<(String, String, bool), AnyError> { - let mut separator_index = specifier.find('/'); - let mut valid_package_name = true; - let mut is_scoped = false; - if specifier.is_empty() { - valid_package_name = false; - } else if specifier.starts_with('@') { - is_scoped = true; - if let Some(index) = separator_index { - separator_index = specifier[index + 1..].find('/'); - } else { - valid_package_name = false; - } - } - - let package_name = if let Some(index) = separator_index { - specifier[0..index].to_string() - } else { - specifier.to_string() - }; - - // Package name cannot have leading . and cannot have percent-encoding or separators. - for ch in package_name.chars() { - if ch == '%' || ch == '\\' { - valid_package_name = false; - break; - } - } - - if !valid_package_name { - return Err(errors::err_invalid_module_specifier( - specifier, - "is not a valid package name", - Some(to_file_path_string(base)), - )); - } - - let package_subpath = if let Some(index) = separator_index { - format!(".{}", specifier.chars().skip(index).collect::<String>()) - } else { - ".".to_string() - }; - - Ok((package_name, package_subpath, is_scoped)) -} - -#[derive(Clone, Debug)] -struct PackageConfig { - exists: bool, - exports: Option<Value>, - imports: Option<Map<String, Value>>, - main: Option<String>, - name: Option<String>, - pjsonpath: PathBuf, - typ: String, -} - -pub fn check_if_should_use_esm_loader( - main_module: &ModuleSpecifier, -) -> Result<bool, AnyError> { - let s = main_module.as_str(); - if s.ends_with(".mjs") { - return Ok(true); - } - if s.ends_with(".cjs") { - return Ok(false); - } - let package_config = get_package_scope_config(main_module)?; - Ok(package_config.typ == "module") -} - -fn get_package_config( - path: PathBuf, - specifier: &str, - maybe_base: Option<&ModuleSpecifier>, -) -> Result<PackageConfig, AnyError> { - // TODO(bartlomieju): - // if let Some(existing) = package_json_cache.get(path) { - // return Ok(existing.clone()); - // } - - let result = std::fs::read_to_string(&path); - - let source = result.unwrap_or_else(|_| "".to_string()); - if source.is_empty() { - let package_config = PackageConfig { - pjsonpath: path, - exists: false, - main: None, - name: None, - typ: "none".to_string(), - exports: None, - imports: None, - }; - // TODO(bartlomieju): - // package_json_cache.set(package_json_path, package_config.clone()); - return Ok(package_config); - } - - let package_json: Value = serde_json::from_str(&source).map_err(|err| { - let base_msg = maybe_base.map(|base| { - format!("\"{}\" from {}", specifier, to_file_path(base).display()) - }); - errors::err_invalid_package_config( - &path.display().to_string(), - base_msg, - Some(err.to_string()), - ) - })?; - - let imports_val = package_json.get("imports"); - let main_val = package_json.get("main"); - let name_val = package_json.get("name"); - let typ_val = package_json.get("type"); - let exports = package_json.get("exports").map(|e| e.to_owned()); - - let imports = if let Some(imp) = imports_val { - imp.as_object().map(|imp| imp.to_owned()) - } else { - None - }; - let main = if let Some(m) = main_val { - m.as_str().map(|m| m.to_string()) - } else { - None - }; - let name = if let Some(n) = name_val { - n.as_str().map(|n| n.to_string()) - } else { - None - }; - - // Ignore unknown types for forwards compatibility - let typ = if let Some(t) = typ_val { - if let Some(t) = t.as_str() { - if t != "module" && t != "commonjs" { - "none".to_string() - } else { - t.to_string() - } - } else { - "none".to_string() - } - } else { - "none".to_string() - }; - - let package_config = PackageConfig { - pjsonpath: path, - exists: true, - main, - name, - typ, - exports, - imports, - }; - // TODO(bartlomieju): - // package_json_cache.set(package_json_path, package_config.clone()); - Ok(package_config) -} - -fn get_package_scope_config( - resolved: &ModuleSpecifier, -) -> Result<PackageConfig, AnyError> { - let mut package_json_url = resolved.join("./package.json")?; - - loop { - let package_json_path = package_json_url.path(); - - if package_json_path.ends_with("node_modules/package.json") { - break; - } - - let package_config = get_package_config( - to_file_path(&package_json_url), - resolved.as_str(), - None, - )?; - - if package_config.exists { - return Ok(package_config); - } - - let last_package_json_url = package_json_url.clone(); - package_json_url = package_json_url.join("../package.json")?; - - // TODO(bartlomieju): I'm not sure this will work properly - // Terminates at root where ../package.json equals ../../package.json - // (can't just check "/package.json" for Windows support) - if package_json_url.path() == last_package_json_url.path() { - break; - } - } - - let package_json_path = to_file_path(&package_json_url); - let package_config = PackageConfig { - pjsonpath: package_json_path, - exists: false, - main: None, - name: None, - typ: "none".to_string(), - exports: None, - imports: None, - }; - - // TODO(bartlomieju): - // package_json_cache.set(package_json_path, package_config.clone()); - - Ok(package_config) -} - -fn file_exists(path_url: &ModuleSpecifier) -> bool { - if let Ok(stats) = std::fs::metadata(to_file_path(path_url)) { - stats.is_file() - } else { - false - } -} - -fn legacy_main_resolve( - package_json_url: &ModuleSpecifier, - package_config: &PackageConfig, - _base: &ModuleSpecifier, -) -> Result<ModuleSpecifier, AnyError> { - let mut guess; - - if let Some(main) = &package_config.main { - guess = package_json_url.join(&format!("./{}", main))?; - if file_exists(&guess) { - return Ok(guess); - } - - let mut found = false; - for ext in [ - ".js", - ".json", - ".node", - "/index.js", - "/index.json", - "/index.node", - ] { - guess = package_json_url.join(&format!("./{}{}", main, ext))?; - if file_exists(&guess) { - found = true; - break; - } - } - - if found { - // TODO(bartlomieju): emitLegacyIndexDeprecation() - return Ok(guess); - } - } - - for p in ["./index.js", "./index.json", "./index.node"] { - guess = package_json_url.join(p)?; - if file_exists(&guess) { - // TODO(bartlomieju): emitLegacyIndexDeprecation() - return Ok(guess); - } - } - - Err(generic_error("not found")) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::deno_std::CURRENT_STD_URL; - - fn testdir(name: &str) -> PathBuf { - let c = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - c.join("compat/testdata/").join(name) - } - - #[test] - fn basic() { - let cwd = testdir("basic"); - let main = Url::from_file_path(cwd.join("main.js")).unwrap(); - let actual = node_resolve("foo", main.as_str(), &cwd).unwrap(); - let expected = - Url::from_file_path(cwd.join("node_modules/foo/index.js")).unwrap(); - assert!(matches!(actual, ResolveResponse::Esm(_))); - assert_eq!(actual.to_result().unwrap(), expected); - - let actual = node_resolve( - "data:application/javascript,console.log(\"Hello%20Deno\");", - main.as_str(), - &cwd, - ) - .unwrap(); - let expected = - Url::parse("data:application/javascript,console.log(\"Hello%20Deno\");") - .unwrap(); - assert!(matches!(actual, ResolveResponse::Specifier(_))); - assert_eq!(actual.to_result().unwrap(), expected); - } - - #[test] - fn deep() { - let cwd = testdir("deep"); - let main = Url::from_file_path(cwd.join("a/b/c/d/main.js")).unwrap(); - let actual = node_resolve("foo", main.as_str(), &cwd).unwrap(); - let expected = - Url::from_file_path(cwd.join("node_modules/foo/index.js")).unwrap(); - matches!(actual, ResolveResponse::Esm(_)); - assert_eq!(actual.to_result().unwrap(), expected); - } - - #[test] - fn package_subpath() { - let cwd = testdir("subpath"); - let main = Url::from_file_path(cwd.join("main.js")).unwrap(); - let actual = node_resolve("foo", main.as_str(), &cwd).unwrap(); - let expected = - Url::from_file_path(cwd.join("node_modules/foo/index.js")).unwrap(); - matches!(actual, ResolveResponse::CommonJs(_)); - assert_eq!(actual.to_result().unwrap(), expected); - let actual = node_resolve("foo/server.js", main.as_str(), &cwd).unwrap(); - let expected = - Url::from_file_path(cwd.join("node_modules/foo/server.js")).unwrap(); - matches!(actual, ResolveResponse::CommonJs(_)); - assert_eq!(actual.to_result().unwrap(), expected); - } - - #[test] - fn basic_deps() { - let cwd = testdir("basic_deps"); - let main = Url::from_file_path(cwd.join("main.js")).unwrap(); - let actual = node_resolve("foo", main.as_str(), &cwd).unwrap(); - let foo_js = - Url::from_file_path(cwd.join("node_modules/foo/foo.js")).unwrap(); - assert!(matches!(actual, ResolveResponse::Esm(_))); - assert_eq!(actual.to_result().unwrap(), foo_js); - - let actual = node_resolve("bar", foo_js.as_str(), &cwd).unwrap(); - - let bar_js = - Url::from_file_path(cwd.join("node_modules/bar/bar.js")).unwrap(); - assert!(matches!(actual, ResolveResponse::Esm(_))); - assert_eq!(actual.to_result().unwrap(), bar_js); - } - - #[test] - fn builtin_http() { - let cwd = testdir("basic"); - let main = Url::from_file_path(cwd.join("main.js")).unwrap(); - let expected = CURRENT_STD_URL.join("node/http.ts").unwrap(); - - let actual = node_resolve("http", main.as_str(), &cwd).unwrap(); - assert!(matches!(actual, ResolveResponse::Esm(_))); - assert_eq!(actual.to_result().unwrap(), expected); - - let actual = node_resolve("node:http", main.as_str(), &cwd).unwrap(); - assert!(matches!(actual, ResolveResponse::Esm(_))); - assert_eq!(actual.to_result().unwrap(), expected); - } - - #[test] - fn conditional_exports() { - // check that `exports` mapping works correctly - let cwd = testdir("conditions"); - let main = Url::from_file_path(cwd.join("main.js")).unwrap(); - let actual = node_resolve("imports_exports", main.as_str(), &cwd).unwrap(); - let expected = Url::from_file_path( - cwd.join("node_modules/imports_exports/import_export.js"), - ) - .unwrap(); - assert!(matches!(actual, ResolveResponse::CommonJs(_))); - assert_eq!(actual.to_result().unwrap(), expected); - - // check that `imports` mapping works correctly - let cwd = testdir("conditions/node_modules/imports_exports"); - let main = Url::from_file_path(cwd.join("import_export.js")).unwrap(); - let actual = node_resolve("#dep", main.as_str(), &cwd).unwrap(); - let expected = Url::from_file_path(cwd.join("import_polyfill.js")).unwrap(); - assert!(matches!(actual, ResolveResponse::CommonJs(_))); - assert_eq!(actual.to_result().unwrap(), expected); - } - - #[test] - fn test_is_relative_specifier() { - assert!(is_relative_specifier("./foo.js")); - assert!(!is_relative_specifier("https://deno.land/std/node/http.ts")); - } - - #[test] - fn test_check_if_should_use_esm_loader() { - let basic = testdir("basic"); - let main = Url::from_file_path(basic.join("main.js")).unwrap(); - assert!(check_if_should_use_esm_loader(&main).unwrap()); - - let cjs = Url::from_file_path(basic.join("main.cjs")).unwrap(); - assert!(!check_if_should_use_esm_loader(&cjs).unwrap()); - - let not_esm = testdir("not_esm"); - let main = Url::from_file_path(not_esm.join("main.js")).unwrap(); - assert!(!check_if_should_use_esm_loader(&main).unwrap()); - } -} |