diff options
Diffstat (limited to 'ext/node_resolver/resolution.rs')
-rw-r--r-- | ext/node_resolver/resolution.rs | 2023 |
1 files changed, 0 insertions, 2023 deletions
diff --git a/ext/node_resolver/resolution.rs b/ext/node_resolver/resolution.rs deleted file mode 100644 index ad9dbb710..000000000 --- a/ext/node_resolver/resolution.rs +++ /dev/null @@ -1,2023 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::borrow::Cow; -use std::path::Path; -use std::path::PathBuf; - -use anyhow::bail; -use anyhow::Error as AnyError; -use deno_media_type::MediaType; -use deno_package_json::PackageJsonRc; -use serde_json::Map; -use serde_json::Value; -use url::Url; - -use crate::env::NodeResolverEnv; -use crate::errors; -use crate::errors::CanonicalizingPkgJsonDirError; -use crate::errors::ClosestPkgJsonError; -use crate::errors::DataUrlReferrerError; -use crate::errors::FinalizeResolutionError; -use crate::errors::InvalidModuleSpecifierError; -use crate::errors::InvalidPackageTargetError; -use crate::errors::LegacyResolveError; -use crate::errors::ModuleNotFoundError; -use crate::errors::NodeJsErrorCode; -use crate::errors::NodeJsErrorCoded; -use crate::errors::NodeResolveError; -use crate::errors::NodeResolveRelativeJoinError; -use crate::errors::PackageExportsResolveError; -use crate::errors::PackageImportNotDefinedError; -use crate::errors::PackageImportsResolveError; -use crate::errors::PackageImportsResolveErrorKind; -use crate::errors::PackageJsonLoadError; -use crate::errors::PackagePathNotExportedError; -use crate::errors::PackageResolveError; -use crate::errors::PackageSubpathResolveError; -use crate::errors::PackageSubpathResolveErrorKind; -use crate::errors::PackageTargetNotFoundError; -use crate::errors::PackageTargetResolveError; -use crate::errors::PackageTargetResolveErrorKind; -use crate::errors::ResolveBinaryCommandsError; -use crate::errors::ResolvePkgJsonBinExportError; -use crate::errors::ResolvePkgSubpathFromDenoModuleError; -use crate::errors::TypeScriptNotSupportedInNpmError; -use crate::errors::TypesNotFoundError; -use crate::errors::TypesNotFoundErrorData; -use crate::errors::UnsupportedDirImportError; -use crate::errors::UnsupportedEsmUrlSchemeError; -use crate::errors::UrlToNodeResolutionError; -use crate::path::strip_unc_prefix; -use crate::path::to_file_specifier; -use crate::NpmResolverRc; -use crate::PathClean; -use deno_package_json::PackageJson; - -pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"]; -pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"]; -static TYPES_ONLY_CONDITIONS: &[&str] = &["types"]; - -pub type NodeModuleKind = deno_package_json::NodeModuleKind; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum NodeResolutionMode { - Execution, - Types, -} - -impl NodeResolutionMode { - pub fn is_types(&self) -> bool { - matches!(self, NodeResolutionMode::Types) - } -} - -#[derive(Debug)] -pub enum NodeResolution { - Esm(Url), - CommonJs(Url), - BuiltIn(String), -} - -impl NodeResolution { - pub fn into_url(self) -> Url { - match self { - Self::Esm(u) => u, - Self::CommonJs(u) => u, - Self::BuiltIn(specifier) => { - if specifier.starts_with("node:") { - Url::parse(&specifier).unwrap() - } else { - Url::parse(&format!("node:{specifier}")).unwrap() - } - } - } - } - - pub fn into_specifier_and_media_type( - resolution: Option<Self>, - ) -> (Url, MediaType) { - match resolution { - Some(NodeResolution::CommonJs(specifier)) => { - let media_type = MediaType::from_specifier(&specifier); - ( - specifier, - match media_type { - MediaType::JavaScript | MediaType::Jsx => MediaType::Cjs, - MediaType::TypeScript | MediaType::Tsx => MediaType::Cts, - MediaType::Dts => MediaType::Dcts, - _ => media_type, - }, - ) - } - Some(NodeResolution::Esm(specifier)) => { - let media_type = MediaType::from_specifier(&specifier); - ( - specifier, - match media_type { - MediaType::JavaScript | MediaType::Jsx => MediaType::Mjs, - MediaType::TypeScript | MediaType::Tsx => MediaType::Mts, - MediaType::Dts => MediaType::Dmts, - _ => media_type, - }, - ) - } - Some(resolution) => (resolution.into_url(), MediaType::Dts), - None => ( - Url::parse("internal:///missing_dependency.d.ts").unwrap(), - MediaType::Dts, - ), - } - } -} - -#[allow(clippy::disallowed_types)] -pub type NodeResolverRc<TEnv> = crate::sync::MaybeArc<NodeResolver<TEnv>>; - -#[derive(Debug)] -pub struct NodeResolver<TEnv: NodeResolverEnv> { - env: TEnv, - npm_resolver: NpmResolverRc, -} - -impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> { - pub fn new(env: TEnv, npm_resolver: NpmResolverRc) -> Self { - Self { env, npm_resolver } - } - - pub fn in_npm_package(&self, specifier: &Url) -> bool { - self.npm_resolver.in_npm_package(specifier) - } - - /// This function is an implementation of `defaultResolve` in - /// `lib/internal/modules/esm/resolve.js` from Node. - pub fn resolve( - &self, - specifier: &str, - referrer: &Url, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result<NodeResolution, NodeResolveError> { - // Note: if we are here, then the referrer is an esm module - // TODO(bartlomieju): skipped "policy" part as we don't plan to support it - - if self.env.is_builtin_node_module(specifier) { - return Ok(NodeResolution::BuiltIn(specifier.to_string())); - } - - if let Ok(url) = Url::parse(specifier) { - if url.scheme() == "data" { - return Ok(NodeResolution::Esm(url)); - } - - if let Some(module_name) = - get_module_name_from_builtin_node_module_specifier(&url) - { - return Ok(NodeResolution::BuiltIn(module_name.to_string())); - } - - let protocol = url.scheme(); - - if protocol != "file" && protocol != "data" { - return Err( - UnsupportedEsmUrlSchemeError { - url_scheme: protocol.to_string(), - } - .into(), - ); - } - - // todo(dsherret): this seems wrong - if referrer.scheme() == "data" { - let url = referrer - .join(specifier) - .map_err(|source| DataUrlReferrerError { source })?; - return Ok(NodeResolution::Esm(url)); - } - } - - let url = self.module_resolve( - specifier, - referrer, - referrer_kind, - // even though the referrer may be CJS, if we're here that means we're doing ESM resolution - DEFAULT_CONDITIONS, - mode, - )?; - - let url = if mode.is_types() { - let file_path = to_file_path(&url); - self.path_to_declaration_url(&file_path, Some(referrer), referrer_kind)? - } else { - url - }; - - let url = self.finalize_resolution(url, Some(referrer))?; - let resolve_response = self.url_to_node_resolution(url)?; - // TODO(bartlomieju): skipped checking errors for commonJS resolution and - // "preserveSymlinksMain"/"preserveSymlinks" options. - Ok(resolve_response) - } - - fn module_resolve( - &self, - specifier: &str, - referrer: &Url, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, NodeResolveError> { - if should_be_treated_as_relative_or_absolute_path(specifier) { - Ok(referrer.join(specifier).map_err(|err| { - NodeResolveRelativeJoinError { - path: specifier.to_string(), - base: referrer.clone(), - source: err, - } - })?) - } else if specifier.starts_with('#') { - let pkg_config = self - .get_closest_package_json(referrer) - .map_err(PackageImportsResolveErrorKind::ClosestPkgJson) - .map_err(|err| PackageImportsResolveError(Box::new(err)))?; - Ok(self.package_imports_resolve( - specifier, - Some(referrer), - referrer_kind, - pkg_config.as_deref(), - conditions, - mode, - )?) - } else if let Ok(resolved) = Url::parse(specifier) { - Ok(resolved) - } else { - Ok(self.package_resolve( - specifier, - referrer, - referrer_kind, - conditions, - mode, - )?) - } - } - - fn finalize_resolution( - &self, - resolved: Url, - maybe_referrer: Option<&Url>, - ) -> Result<Url, FinalizeResolutionError> { - let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C"); - - if encoded_sep_re.is_match(resolved.path()) { - return Err( - errors::InvalidModuleSpecifierError { - request: resolved.to_string(), - reason: Cow::Borrowed( - "must not include encoded \"/\" or \"\\\\\" characters", - ), - maybe_referrer: maybe_referrer.map(to_file_path_string), - } - .into(), - ); - } - - if resolved.scheme() == "node" { - return Ok(resolved); - } - - 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) = self.env.stat_sync(Path::new(&p)) - { - (stats.is_dir, stats.is_file) - } else { - (false, false) - }; - if is_dir { - return Err( - UnsupportedDirImportError { - dir_url: resolved.clone(), - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - } - .into(), - ); - } else if !is_file { - return Err( - ModuleNotFoundError { - specifier: resolved, - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - typ: "module", - } - .into(), - ); - } - - Ok(resolved) - } - - pub fn resolve_package_subpath_from_deno_module( - &self, - package_dir: &Path, - package_subpath: Option<&str>, - maybe_referrer: Option<&Url>, - mode: NodeResolutionMode, - ) -> Result<NodeResolution, ResolvePkgSubpathFromDenoModuleError> { - let node_module_kind = NodeModuleKind::Esm; - let package_subpath = package_subpath - .map(|s| format!("./{s}")) - .unwrap_or_else(|| ".".to_string()); - let resolved_url = self.resolve_package_dir_subpath( - package_dir, - &package_subpath, - maybe_referrer, - node_module_kind, - DEFAULT_CONDITIONS, - mode, - )?; - let resolve_response = self.url_to_node_resolution(resolved_url)?; - // TODO(bartlomieju): skipped checking errors for commonJS resolution and - // "preserveSymlinksMain"/"preserveSymlinks" options. - Ok(resolve_response) - } - - pub fn resolve_binary_commands( - &self, - package_folder: &Path, - ) -> Result<Vec<String>, ResolveBinaryCommandsError> { - let pkg_json_path = package_folder.join("package.json"); - let Some(package_json) = self.load_package_json(&pkg_json_path)? else { - return Ok(Vec::new()); - }; - - Ok(match &package_json.bin { - Some(Value::String(_)) => { - let Some(name) = &package_json.name else { - return Err(ResolveBinaryCommandsError::MissingPkgJsonName { - pkg_json_path, - }); - }; - let name = name.split("/").last().unwrap(); - vec![name.to_string()] - } - Some(Value::Object(o)) => { - o.iter().map(|(key, _)| key.clone()).collect::<Vec<_>>() - } - _ => Vec::new(), - }) - } - - pub fn resolve_binary_export( - &self, - package_folder: &Path, - sub_path: Option<&str>, - ) -> Result<NodeResolution, ResolvePkgJsonBinExportError> { - let pkg_json_path = package_folder.join("package.json"); - let Some(package_json) = self.load_package_json(&pkg_json_path)? else { - return Err(ResolvePkgJsonBinExportError::MissingPkgJson { - pkg_json_path, - }); - }; - let bin_entry = - resolve_bin_entry_value(&package_json, sub_path).map_err(|err| { - ResolvePkgJsonBinExportError::InvalidBinProperty { - message: err.to_string(), - } - })?; - let url = to_file_specifier(&package_folder.join(bin_entry)); - - let resolve_response = self.url_to_node_resolution(url)?; - // TODO(bartlomieju): skipped checking errors for commonJS resolution and - // "preserveSymlinksMain"/"preserveSymlinks" options. - Ok(resolve_response) - } - - pub fn url_to_node_resolution( - &self, - url: Url, - ) -> Result<NodeResolution, UrlToNodeResolutionError> { - let url_str = url.as_str().to_lowercase(); - if url_str.starts_with("http") || url_str.ends_with(".json") { - Ok(NodeResolution::Esm(url)) - } else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") { - let maybe_package_config = self.get_closest_package_json(&url)?; - match maybe_package_config { - Some(c) if c.typ == "module" => Ok(NodeResolution::Esm(url)), - Some(_) => Ok(NodeResolution::CommonJs(url)), - None => Ok(NodeResolution::Esm(url)), - } - } else if url_str.ends_with(".mjs") || url_str.ends_with(".d.mts") { - Ok(NodeResolution::Esm(url)) - } else if url_str.ends_with(".ts") || url_str.ends_with(".mts") { - if self.in_npm_package(&url) { - Err(TypeScriptNotSupportedInNpmError { specifier: url }.into()) - } else { - Ok(NodeResolution::Esm(url)) - } - } else { - Ok(NodeResolution::CommonJs(url)) - } - } - - /// Checks if the resolved file has a corresponding declaration file. - fn path_to_declaration_url( - &self, - path: &Path, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - ) -> Result<Url, TypesNotFoundError> { - fn probe_extensions<TEnv: NodeResolverEnv>( - fs: &TEnv, - path: &Path, - lowercase_path: &str, - referrer_kind: NodeModuleKind, - ) -> Option<PathBuf> { - let mut searched_for_d_mts = false; - let mut searched_for_d_cts = false; - if lowercase_path.ends_with(".mjs") { - let d_mts_path = with_known_extension(path, "d.mts"); - if fs.exists_sync(&d_mts_path) { - return Some(d_mts_path); - } - searched_for_d_mts = true; - } else if lowercase_path.ends_with(".cjs") { - let d_cts_path = with_known_extension(path, "d.cts"); - if fs.exists_sync(&d_cts_path) { - return Some(d_cts_path); - } - searched_for_d_cts = true; - } - - let dts_path = with_known_extension(path, "d.ts"); - if fs.exists_sync(&dts_path) { - return Some(dts_path); - } - - let specific_dts_path = match referrer_kind { - NodeModuleKind::Cjs if !searched_for_d_cts => { - Some(with_known_extension(path, "d.cts")) - } - NodeModuleKind::Esm if !searched_for_d_mts => { - Some(with_known_extension(path, "d.mts")) - } - _ => None, // already searched above - }; - if let Some(specific_dts_path) = specific_dts_path { - if fs.exists_sync(&specific_dts_path) { - return Some(specific_dts_path); - } - } - None - } - - let lowercase_path = path.to_string_lossy().to_lowercase(); - if lowercase_path.ends_with(".d.ts") - || lowercase_path.ends_with(".d.cts") - || lowercase_path.ends_with(".d.mts") - { - return Ok(to_file_specifier(path)); - } - if let Some(path) = - probe_extensions(&self.env, path, &lowercase_path, referrer_kind) - { - return Ok(to_file_specifier(&path)); - } - if self.env.is_dir_sync(path) { - let resolution_result = self.resolve_package_dir_subpath( - path, - /* sub path */ ".", - maybe_referrer, - referrer_kind, - match referrer_kind { - NodeModuleKind::Esm => DEFAULT_CONDITIONS, - NodeModuleKind::Cjs => REQUIRE_CONDITIONS, - }, - NodeResolutionMode::Types, - ); - if let Ok(resolution) = resolution_result { - return Ok(resolution); - } - let index_path = path.join("index.js"); - if let Some(path) = probe_extensions( - &self.env, - &index_path, - &index_path.to_string_lossy().to_lowercase(), - referrer_kind, - ) { - return Ok(to_file_specifier(&path)); - } - } - // allow resolving .css files for types resolution - if lowercase_path.ends_with(".css") { - return Ok(to_file_specifier(path)); - } - Err(TypesNotFoundError(Box::new(TypesNotFoundErrorData { - code_specifier: to_file_specifier(path), - maybe_referrer: maybe_referrer.cloned(), - }))) - } - - #[allow(clippy::too_many_arguments)] - pub fn package_imports_resolve( - &self, - name: &str, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - referrer_pkg_json: Option<&PackageJson>, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageImportsResolveError> { - if name == "#" || name.starts_with("#/") || name.ends_with('/') { - let reason = "is not a valid internal imports specifier name"; - return Err( - errors::InvalidModuleSpecifierError { - request: name.to_string(), - reason: Cow::Borrowed(reason), - maybe_referrer: maybe_referrer.map(to_specifier_display_string), - } - .into(), - ); - } - - let mut package_json_path = None; - if let Some(pkg_json) = &referrer_pkg_json { - package_json_path = Some(pkg_json.path.clone()); - if let Some(imports) = &pkg_json.imports { - if imports.contains_key(name) && !name.contains('*') { - let target = imports.get(name).unwrap(); - let maybe_resolved = self.resolve_package_target( - package_json_path.as_ref().unwrap(), - target, - "", - name, - maybe_referrer, - referrer_kind, - false, - true, - conditions, - mode, - )?; - 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())], - ); - } - } - } - } - - if !best_match.is_empty() { - let target = imports.get(best_match).unwrap(); - let maybe_resolved = self.resolve_package_target( - package_json_path.as_ref().unwrap(), - target, - best_match_subpath.unwrap(), - best_match, - maybe_referrer, - referrer_kind, - true, - true, - conditions, - mode, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } - } - } - } - } - - Err( - PackageImportNotDefinedError { - name: name.to_string(), - package_json_path, - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - } - .into(), - ) - } - - #[allow(clippy::too_many_arguments)] - fn resolve_package_target_string( - &self, - target: &str, - subpath: &str, - match_: &str, - package_json_path: &Path, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - pattern: bool, - internal: bool, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageTargetResolveError> { - if !subpath.is_empty() && !pattern && !target.ends_with('/') { - return Err( - InvalidPackageTargetError { - pkg_json_path: package_json_path.to_path_buf(), - sub_path: match_.to_string(), - target: target.to_string(), - is_import: internal, - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - } - .into(), - ); - } - let invalid_segment_re = - lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)"); - let pattern_re = lazy_regex::regex!(r"\*"); - if !target.starts_with("./") { - if internal && !target.starts_with("../") && !target.starts_with('/') { - let target_url = Url::parse(target); - match target_url { - Ok(url) => { - if get_module_name_from_builtin_node_module_specifier(&url) - .is_some() - { - return Ok(url); - } - } - Err(_) => { - let export_target = if pattern { - pattern_re - .replace(target, |_caps: ®ex::Captures| subpath) - .to_string() - } else { - format!("{target}{subpath}") - }; - let package_json_url = to_file_specifier(package_json_path); - let result = match self.package_resolve( - &export_target, - &package_json_url, - referrer_kind, - conditions, - mode, - ) { - Ok(url) => Ok(url), - Err(err) => match err.code() { - NodeJsErrorCode::ERR_INVALID_MODULE_SPECIFIER - | NodeJsErrorCode::ERR_INVALID_PACKAGE_CONFIG - | NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET - | NodeJsErrorCode::ERR_PACKAGE_IMPORT_NOT_DEFINED - | NodeJsErrorCode::ERR_PACKAGE_PATH_NOT_EXPORTED - | NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION - | NodeJsErrorCode::ERR_UNSUPPORTED_DIR_IMPORT - | NodeJsErrorCode::ERR_UNSUPPORTED_ESM_URL_SCHEME - | NodeJsErrorCode::ERR_TYPES_NOT_FOUND => { - Err(PackageTargetResolveErrorKind::PackageResolve(err).into()) - } - NodeJsErrorCode::ERR_MODULE_NOT_FOUND => Err( - PackageTargetResolveErrorKind::NotFound( - PackageTargetNotFoundError { - pkg_json_path: package_json_path.to_path_buf(), - target: export_target.to_string(), - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - referrer_kind, - mode, - }, - ) - .into(), - ), - }, - }; - - return match result { - Ok(url) => Ok(url), - Err(err) => { - if self.env.is_builtin_node_module(target) { - Ok(Url::parse(&format!("node:{}", target)).unwrap()) - } else { - Err(err) - } - } - }; - } - } - } - return Err( - InvalidPackageTargetError { - pkg_json_path: package_json_path.to_path_buf(), - sub_path: match_.to_string(), - target: target.to_string(), - is_import: internal, - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - } - .into(), - ); - } - if invalid_segment_re.is_match(&target[2..]) { - return Err( - InvalidPackageTargetError { - pkg_json_path: package_json_path.to_path_buf(), - sub_path: match_.to_string(), - target: target.to_string(), - is_import: internal, - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - } - .into(), - ); - } - let package_path = package_json_path.parent().unwrap(); - let resolved_path = package_path.join(target).clean(); - if !resolved_path.starts_with(package_path) { - return Err( - InvalidPackageTargetError { - pkg_json_path: package_json_path.to_path_buf(), - sub_path: match_.to_string(), - target: target.to_string(), - is_import: internal, - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - } - .into(), - ); - } - if subpath.is_empty() { - return Ok(to_file_specifier(&resolved_path)); - } - 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_path, - internal, - maybe_referrer, - ) - .into(), - ); - } - if pattern { - let resolved_path_str = resolved_path.to_string_lossy(); - let replaced = pattern_re - .replace(&resolved_path_str, |_caps: ®ex::Captures| subpath); - return Ok(to_file_specifier(&PathBuf::from(replaced.to_string()))); - } - Ok(to_file_specifier(&resolved_path.join(subpath).clean())) - } - - #[allow(clippy::too_many_arguments)] - fn resolve_package_target( - &self, - package_json_path: &Path, - target: &Value, - subpath: &str, - package_subpath: &str, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - pattern: bool, - internal: bool, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Option<Url>, PackageTargetResolveError> { - let result = self.resolve_package_target_inner( - package_json_path, - target, - subpath, - package_subpath, - maybe_referrer, - referrer_kind, - pattern, - internal, - conditions, - mode, - ); - match result { - Ok(maybe_resolved) => Ok(maybe_resolved), - Err(err) => { - if mode.is_types() - && err.code() == NodeJsErrorCode::ERR_TYPES_NOT_FOUND - && conditions != TYPES_ONLY_CONDITIONS - { - // try resolving with just "types" conditions for when someone misconfigures - // and puts the "types" condition in the wrong place - if let Ok(Some(resolved)) = self.resolve_package_target_inner( - package_json_path, - target, - subpath, - package_subpath, - maybe_referrer, - referrer_kind, - pattern, - internal, - TYPES_ONLY_CONDITIONS, - mode, - ) { - return Ok(Some(resolved)); - } - } - - Err(err) - } - } - } - - #[allow(clippy::too_many_arguments)] - fn resolve_package_target_inner( - &self, - package_json_path: &Path, - target: &Value, - subpath: &str, - package_subpath: &str, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - pattern: bool, - internal: bool, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Option<Url>, PackageTargetResolveError> { - if let Some(target) = target.as_str() { - let url = self.resolve_package_target_string( - target, - subpath, - package_subpath, - package_json_path, - maybe_referrer, - referrer_kind, - pattern, - internal, - conditions, - mode, - )?; - if mode.is_types() && url.scheme() == "file" { - let path = url.to_file_path().unwrap(); - return Ok(Some(self.path_to_declaration_url( - &path, - maybe_referrer, - referrer_kind, - )?)); - } else { - return Ok(Some(url)); - } - } 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 = self.resolve_package_target( - package_json_path, - target_item, - subpath, - package_subpath, - maybe_referrer, - referrer_kind, - pattern, - internal, - conditions, - mode, - ); - - match resolved_result { - Ok(Some(resolved)) => return Ok(Some(resolved)), - Ok(None) => { - last_error = None; - continue; - } - Err(e) => { - if e.code() == NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET { - last_error = Some(e); - continue; - } else { - return Err(e); - } - } - } - } - 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()) - || mode.is_types() && key.as_str() == "types" - { - let condition_target = target_obj.get(key).unwrap(); - - let resolved = self.resolve_package_target( - package_json_path, - condition_target, - subpath, - package_subpath, - maybe_referrer, - referrer_kind, - pattern, - internal, - conditions, - mode, - )?; - match resolved { - Some(resolved) => return Ok(Some(resolved)), - None => { - continue; - } - } - } - } - } else if target.is_null() { - return Ok(None); - } - - Err( - InvalidPackageTargetError { - pkg_json_path: package_json_path.to_path_buf(), - sub_path: package_subpath.to_string(), - target: target.to_string(), - is_import: internal, - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - } - .into(), - ) - } - - #[allow(clippy::too_many_arguments)] - pub fn package_exports_resolve( - &self, - package_json_path: &Path, - package_subpath: &str, - package_exports: &Map<String, Value>, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageExportsResolveError> { - if package_exports.contains_key(package_subpath) - && package_subpath.find('*').is_none() - && !package_subpath.ends_with('/') - { - let target = package_exports.get(package_subpath).unwrap(); - let resolved = self.resolve_package_target( - package_json_path, - target, - "", - package_subpath, - maybe_referrer, - referrer_kind, - false, - false, - conditions, - mode, - )?; - return match resolved { - Some(resolved) => Ok(resolved), - None => Err( - PackagePathNotExportedError { - pkg_json_path: package_json_path.to_path_buf(), - subpath: package_subpath.to_string(), - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - mode, - } - .into(), - ), - }; - } - - let mut best_match = ""; - let mut best_match_subpath = None; - for key in package_exports.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 = package_exports.get(best_match).unwrap(); - let maybe_resolved = self.resolve_package_target( - package_json_path, - target, - &best_match_subpath.unwrap(), - best_match, - maybe_referrer, - referrer_kind, - true, - false, - conditions, - mode, - )?; - if let Some(resolved) = maybe_resolved { - return Ok(resolved); - } else { - return Err( - PackagePathNotExportedError { - pkg_json_path: package_json_path.to_path_buf(), - subpath: package_subpath.to_string(), - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - mode, - } - .into(), - ); - } - } - - Err( - PackagePathNotExportedError { - pkg_json_path: package_json_path.to_path_buf(), - subpath: package_subpath.to_string(), - maybe_referrer: maybe_referrer.map(ToOwned::to_owned), - mode, - } - .into(), - ) - } - - pub(super) fn package_resolve( - &self, - specifier: &str, - referrer: &Url, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageResolveError> { - let (package_name, package_subpath, _is_scoped) = - parse_npm_pkg_name(specifier, referrer)?; - - if let Some(package_config) = self.get_closest_package_json(referrer)? { - // ResolveSelf - if package_config.name.as_ref() == Some(&package_name) { - if let Some(exports) = &package_config.exports { - return self - .package_exports_resolve( - &package_config.path, - &package_subpath, - exports, - Some(referrer), - referrer_kind, - conditions, - mode, - ) - .map_err(|err| err.into()); - } - } - } - - self.resolve_package_subpath_for_package( - &package_name, - &package_subpath, - referrer, - referrer_kind, - conditions, - mode, - ) - } - - #[allow(clippy::too_many_arguments)] - fn resolve_package_subpath_for_package( - &self, - package_name: &str, - package_subpath: &str, - referrer: &Url, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageResolveError> { - let result = self.resolve_package_subpath_for_package_inner( - package_name, - package_subpath, - referrer, - referrer_kind, - conditions, - mode, - ); - if mode.is_types() && !matches!(result, Ok(Url { .. })) { - // try to resolve with the @types package - let package_name = types_package_name(package_name); - if let Ok(result) = self.resolve_package_subpath_for_package_inner( - &package_name, - package_subpath, - referrer, - referrer_kind, - conditions, - mode, - ) { - return Ok(result); - } - } - result - } - - #[allow(clippy::too_many_arguments)] - fn resolve_package_subpath_for_package_inner( - &self, - package_name: &str, - package_subpath: &str, - referrer: &Url, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageResolveError> { - let package_dir_path = self - .npm_resolver - .resolve_package_folder_from_package(package_name, referrer)?; - - // todo: error with this instead when can't find package - // Err(errors::err_module_not_found( - // &package_json_url - // .join(".") - // .unwrap() - // .to_file_path() - // .unwrap() - // .display() - // .to_string(), - // &to_file_path_string(referrer), - // "package", - // )) - - // Package match. - self - .resolve_package_dir_subpath( - &package_dir_path, - package_subpath, - Some(referrer), - referrer_kind, - conditions, - mode, - ) - .map_err(|err| err.into()) - } - - #[allow(clippy::too_many_arguments)] - fn resolve_package_dir_subpath( - &self, - package_dir_path: &Path, - package_subpath: &str, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageSubpathResolveError> { - let package_json_path = package_dir_path.join("package.json"); - match self.load_package_json(&package_json_path)? { - Some(pkg_json) => self.resolve_package_subpath( - &pkg_json, - package_subpath, - maybe_referrer, - referrer_kind, - conditions, - mode, - ), - None => self - .resolve_package_subpath_no_pkg_json( - package_dir_path, - package_subpath, - maybe_referrer, - referrer_kind, - mode, - ) - .map_err(|err| { - PackageSubpathResolveErrorKind::LegacyResolve(err).into() - }), - } - } - - #[allow(clippy::too_many_arguments)] - fn resolve_package_subpath( - &self, - package_json: &PackageJson, - package_subpath: &str, - referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - conditions: &[&str], - mode: NodeResolutionMode, - ) -> Result<Url, PackageSubpathResolveError> { - if let Some(exports) = &package_json.exports { - let result = self.package_exports_resolve( - &package_json.path, - package_subpath, - exports, - referrer, - referrer_kind, - conditions, - mode, - ); - match result { - Ok(found) => return Ok(found), - Err(exports_err) => { - if mode.is_types() && package_subpath == "." { - return self - .legacy_main_resolve(package_json, referrer, referrer_kind, mode) - .map_err(|err| { - PackageSubpathResolveErrorKind::LegacyResolve(err).into() - }); - } - return Err( - PackageSubpathResolveErrorKind::Exports(exports_err).into(), - ); - } - } - } - - if package_subpath == "." { - return self - .legacy_main_resolve(package_json, referrer, referrer_kind, mode) - .map_err(|err| { - PackageSubpathResolveErrorKind::LegacyResolve(err).into() - }); - } - - self - .resolve_subpath_exact( - package_json.path.parent().unwrap(), - package_subpath, - referrer, - referrer_kind, - mode, - ) - .map_err(|err| { - PackageSubpathResolveErrorKind::LegacyResolve(err.into()).into() - }) - } - - fn resolve_subpath_exact( - &self, - directory: &Path, - package_subpath: &str, - referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result<Url, TypesNotFoundError> { - assert_ne!(package_subpath, "."); - let file_path = directory.join(package_subpath); - if mode.is_types() { - Ok(self.path_to_declaration_url(&file_path, referrer, referrer_kind)?) - } else { - Ok(to_file_specifier(&file_path)) - } - } - - fn resolve_package_subpath_no_pkg_json( - &self, - directory: &Path, - package_subpath: &str, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result<Url, LegacyResolveError> { - if package_subpath == "." { - self.legacy_index_resolve(directory, maybe_referrer, referrer_kind, mode) - } else { - self - .resolve_subpath_exact( - directory, - package_subpath, - maybe_referrer, - referrer_kind, - mode, - ) - .map_err(|err| err.into()) - } - } - - pub fn get_closest_package_json( - &self, - url: &Url, - ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> { - let Ok(file_path) = url.to_file_path() else { - return Ok(None); - }; - self.get_closest_package_json_from_path(&file_path) - } - - pub fn get_closest_package_json_from_path( - &self, - file_path: &Path, - ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> { - // we use this for deno compile using byonm because the script paths - // won't be in virtual file system, but the package.json paths will be - fn canonicalize_first_ancestor_exists( - dir_path: &Path, - env: &dyn NodeResolverEnv, - ) -> Result<Option<PathBuf>, std::io::Error> { - for ancestor in dir_path.ancestors() { - match env.realpath_sync(ancestor) { - Ok(dir_path) => return Ok(Some(dir_path)), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - // keep searching - } - Err(err) => return Err(err), - } - } - Ok(None) - } - - let parent_dir = file_path.parent().unwrap(); - let Some(start_dir) = canonicalize_first_ancestor_exists( - parent_dir, &self.env, - ) - .map_err(|source| CanonicalizingPkgJsonDirError { - dir_path: parent_dir.to_path_buf(), - source, - })? - else { - return Ok(None); - }; - let start_dir = strip_unc_prefix(start_dir); - for current_dir in start_dir.ancestors() { - let package_json_path = current_dir.join("package.json"); - if let Some(pkg_json) = self.load_package_json(&package_json_path)? { - return Ok(Some(pkg_json)); - } - } - - Ok(None) - } - - pub fn load_package_json( - &self, - package_json_path: &Path, - ) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> { - crate::package_json::load_pkg_json( - self.env.pkg_json_fs(), - package_json_path, - ) - } - - pub(super) fn legacy_main_resolve( - &self, - package_json: &PackageJson, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result<Url, LegacyResolveError> { - let maybe_main = if mode.is_types() { - match package_json.types.as_ref() { - Some(types) => Some(types.as_str()), - None => { - // fallback to checking the main entrypoint for - // a corresponding declaration file - if let Some(main) = package_json.main(referrer_kind) { - let main = package_json.path.parent().unwrap().join(main).clean(); - let decl_url_result = self.path_to_declaration_url( - &main, - maybe_referrer, - referrer_kind, - ); - // don't surface errors, fallback to checking the index now - if let Ok(url) = decl_url_result { - return Ok(url); - } - } - None - } - } - } else { - package_json.main(referrer_kind) - }; - - if let Some(main) = maybe_main { - let guess = package_json.path.parent().unwrap().join(main).clean(); - if self.env.is_file_sync(&guess) { - return Ok(to_file_specifier(&guess)); - } - - // todo(dsherret): investigate exactly how node and typescript handles this - let endings = if mode.is_types() { - match referrer_kind { - NodeModuleKind::Cjs => { - vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"] - } - NodeModuleKind::Esm => vec![ - ".d.ts", - ".d.mts", - "/index.d.ts", - "/index.d.mts", - ".d.cts", - "/index.d.cts", - ], - } - } else { - vec![".js", "/index.js"] - }; - for ending in endings { - let guess = package_json - .path - .parent() - .unwrap() - .join(format!("{main}{ending}")) - .clean(); - if self.env.is_file_sync(&guess) { - // TODO(bartlomieju): emitLegacyIndexDeprecation() - return Ok(to_file_specifier(&guess)); - } - } - } - - self.legacy_index_resolve( - package_json.path.parent().unwrap(), - maybe_referrer, - referrer_kind, - mode, - ) - } - - fn legacy_index_resolve( - &self, - directory: &Path, - maybe_referrer: Option<&Url>, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result<Url, LegacyResolveError> { - let index_file_names = if mode.is_types() { - // todo(dsherret): investigate exactly how typescript does this - match referrer_kind { - NodeModuleKind::Cjs => vec!["index.d.ts", "index.d.cts"], - NodeModuleKind::Esm => vec!["index.d.ts", "index.d.mts", "index.d.cts"], - } - } else { - vec!["index.js"] - }; - for index_file_name in index_file_names { - let guess = directory.join(index_file_name).clean(); - if self.env.is_file_sync(&guess) { - // TODO(bartlomieju): emitLegacyIndexDeprecation() - return Ok(to_file_specifier(&guess)); - } - } - - if mode.is_types() { - Err( - TypesNotFoundError(Box::new(TypesNotFoundErrorData { - code_specifier: to_file_specifier(&directory.join("index.js")), - maybe_referrer: maybe_referrer.cloned(), - })) - .into(), - ) - } else { - Err( - ModuleNotFoundError { - specifier: to_file_specifier(&directory.join("index.js")), - typ: "module", - maybe_referrer: maybe_referrer.cloned(), - } - .into(), - ) - } - } -} - -fn resolve_bin_entry_value<'a>( - package_json: &'a PackageJson, - bin_name: Option<&str>, -) -> Result<&'a str, AnyError> { - let bin = match &package_json.bin { - Some(bin) => bin, - None => bail!( - "'{}' did not have a bin property", - package_json.path.display(), - ), - }; - let bin_entry = match bin { - Value::String(_) => { - if bin_name.is_some() - && bin_name - != package_json - .name - .as_deref() - .map(|name| name.rsplit_once('/').map_or(name, |(_, name)| name)) - { - None - } else { - Some(bin) - } - } - Value::Object(o) => { - if let Some(bin_name) = bin_name { - o.get(bin_name) - } else if o.len() == 1 - || o.len() > 1 && o.values().all(|v| v == o.values().next().unwrap()) - { - o.values().next() - } else { - package_json.name.as_ref().and_then(|n| o.get(n)) - } - } - _ => bail!( - "'{}' did not have a bin property with a string or object value", - package_json.path.display() - ), - }; - let bin_entry = match bin_entry { - Some(e) => e, - None => { - let prefix = package_json - .name - .as_ref() - .map(|n| { - let mut prefix = format!("npm:{}", n); - if let Some(version) = &package_json.version { - prefix.push('@'); - prefix.push_str(version); - } - prefix.push('/'); - prefix - }) - .unwrap_or_default(); - let keys = bin - .as_object() - .map(|o| { - o.keys() - .map(|k| format!(" * {prefix}{k}")) - .collect::<Vec<_>>() - }) - .unwrap_or_default(); - bail!( - "'{}' did not have a bin entry{}{}", - package_json.path.display(), - bin_name - .or(package_json.name.as_deref()) - .map(|name| format!(" for '{}'", name)) - .unwrap_or_default(), - if keys.is_empty() { - "".to_string() - } else { - format!("\n\nPossibilities:\n{}", keys.join("\n")) - } - ) - } - }; - match bin_entry { - Value::String(s) => Ok(s), - _ => bail!( - "'{}' had a non-string sub property of bin", - package_json.path.display(), - ), - } -} - -fn to_file_path(url: &Url) -> PathBuf { - url - .to_file_path() - .unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {url}")) -} - -fn to_file_path_string(url: &Url) -> 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().take(3).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 -} - -/// Alternate `PathBuf::with_extension` that will handle known extensions -/// more intelligently. -fn with_known_extension(path: &Path, ext: &str) -> PathBuf { - const NON_DECL_EXTS: &[&str] = &[ - "cjs", "js", "json", "jsx", "mjs", "tsx", /* ex. types.d */ "d", - ]; - const DECL_EXTS: &[&str] = &["cts", "mts", "ts"]; - - let file_name = match path.file_name() { - Some(value) => value.to_string_lossy(), - None => return path.to_path_buf(), - }; - let lowercase_file_name = file_name.to_lowercase(); - let period_index = lowercase_file_name.rfind('.').and_then(|period_index| { - let ext = &lowercase_file_name[period_index + 1..]; - if DECL_EXTS.contains(&ext) { - if let Some(next_period_index) = - lowercase_file_name[..period_index].rfind('.') - { - if &lowercase_file_name[next_period_index + 1..period_index] == "d" { - Some(next_period_index) - } else { - Some(period_index) - } - } else { - Some(period_index) - } - } else if NON_DECL_EXTS.contains(&ext) { - Some(period_index) - } else { - None - } - }); - - let file_name = match period_index { - Some(period_index) => &file_name[..period_index], - None => &file_name, - }; - path.with_file_name(format!("{file_name}.{ext}")) -} - -fn to_specifier_display_string(url: &Url) -> String { - if let Ok(path) = url.to_file_path() { - path.display().to_string() - } else { - url.to_string() - } -} - -fn throw_invalid_subpath( - subpath: String, - package_json_path: &Path, - internal: bool, - maybe_referrer: Option<&Url>, -) -> InvalidModuleSpecifierError { - let ie = if internal { "imports" } else { "exports" }; - let reason = format!( - "request is not a valid subpath for the \"{}\" resolution of {}", - ie, - package_json_path.display(), - ); - InvalidModuleSpecifierError { - request: subpath, - reason: Cow::Owned(reason), - maybe_referrer: maybe_referrer.map(to_specifier_display_string), - } -} - -pub fn parse_npm_pkg_name( - specifier: &str, - referrer: &Url, -) -> Result<(String, String, bool), InvalidModuleSpecifierError> { - 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('/') - .map(|new_index| index + 1 + new_index); - } 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::InvalidModuleSpecifierError { - request: specifier.to_string(), - reason: Cow::Borrowed("is not a valid package name"), - maybe_referrer: Some(to_specifier_display_string(referrer)), - }); - } - - 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)) -} - -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 -} - -/// Gets the corresponding @types package for the provided package name. -fn types_package_name(package_name: &str) -> String { - debug_assert!(!package_name.starts_with("@types/")); - // Scoped packages will get two underscores for each slash - // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages - format!("@types/{}", package_name.replace('/', "__")) -} - -/// Ex. returns `fs` for `node:fs` -fn get_module_name_from_builtin_node_module_specifier( - specifier: &Url, -) -> Option<&str> { - if specifier.scheme() != "node" { - return None; - } - - let (_, specifier) = specifier.as_str().split_once(':')?; - Some(specifier) -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - fn build_package_json(json: Value) -> PackageJson { - PackageJson::load_from_value(PathBuf::from("/package.json"), json) - } - - #[test] - fn test_resolve_bin_entry_value() { - // should resolve the specified value - let pkg_json = build_package_json(json!({ - "name": "pkg", - "version": "1.1.1", - "bin": { - "bin1": "./value1", - "bin2": "./value2", - "pkg": "./value3", - } - })); - assert_eq!( - resolve_bin_entry_value(&pkg_json, Some("bin1")).unwrap(), - "./value1" - ); - - // should resolve the value with the same name when not specified - assert_eq!( - resolve_bin_entry_value(&pkg_json, None).unwrap(), - "./value3" - ); - - // should not resolve when specified value does not exist - assert_eq!( - resolve_bin_entry_value(&pkg_json, Some("other"),) - .err() - .unwrap() - .to_string(), - concat!( - "'/package.json' did not have a bin entry for 'other'\n", - "\n", - "Possibilities:\n", - " * npm:pkg@1.1.1/bin1\n", - " * npm:pkg@1.1.1/bin2\n", - " * npm:pkg@1.1.1/pkg" - ) - ); - - // should not resolve when default value can't be determined - let pkg_json = build_package_json(json!({ - "name": "pkg", - "version": "1.1.1", - "bin": { - "bin": "./value1", - "bin2": "./value2", - } - })); - assert_eq!( - resolve_bin_entry_value(&pkg_json, None) - .err() - .unwrap() - .to_string(), - concat!( - "'/package.json' did not have a bin entry for 'pkg'\n", - "\n", - "Possibilities:\n", - " * npm:pkg@1.1.1/bin\n", - " * npm:pkg@1.1.1/bin2", - ) - ); - - // should resolve since all the values are the same - let pkg_json = build_package_json(json!({ - "name": "pkg", - "version": "1.2.3", - "bin": { - "bin1": "./value", - "bin2": "./value", - } - })); - assert_eq!( - resolve_bin_entry_value(&pkg_json, None,).unwrap(), - "./value" - ); - - // should not resolve when specified and is a string - let pkg_json = build_package_json(json!({ - "name": "pkg", - "version": "1.2.3", - "bin": "./value", - })); - assert_eq!( - resolve_bin_entry_value(&pkg_json, Some("path"),) - .err() - .unwrap() - .to_string(), - "'/package.json' did not have a bin entry for 'path'" - ); - - // no version in the package.json - let pkg_json = build_package_json(json!({ - "name": "pkg", - "bin": { - "bin1": "./value1", - "bin2": "./value2", - } - })); - assert_eq!( - resolve_bin_entry_value(&pkg_json, None) - .err() - .unwrap() - .to_string(), - concat!( - "'/package.json' did not have a bin entry for 'pkg'\n", - "\n", - "Possibilities:\n", - " * npm:pkg/bin1\n", - " * npm:pkg/bin2", - ) - ); - - // no name or version in the package.json - let pkg_json = build_package_json(json!({ - "bin": { - "bin1": "./value1", - "bin2": "./value2", - } - })); - assert_eq!( - resolve_bin_entry_value(&pkg_json, None) - .err() - .unwrap() - .to_string(), - concat!( - "'/package.json' did not have a bin entry\n", - "\n", - "Possibilities:\n", - " * bin1\n", - " * bin2", - ) - ); - } - - #[test] - fn test_parse_package_name() { - let dummy_referrer = Url::parse("http://example.com").unwrap(); - - assert_eq!( - parse_npm_pkg_name("fetch-blob", &dummy_referrer).unwrap(), - ("fetch-blob".to_string(), ".".to_string(), false) - ); - assert_eq!( - parse_npm_pkg_name("@vue/plugin-vue", &dummy_referrer).unwrap(), - ("@vue/plugin-vue".to_string(), ".".to_string(), true) - ); - assert_eq!( - parse_npm_pkg_name("@astrojs/prism/dist/highlighter", &dummy_referrer) - .unwrap(), - ( - "@astrojs/prism".to_string(), - "./dist/highlighter".to_string(), - true - ) - ); - } - - #[test] - fn test_with_known_extension() { - let cases = &[ - ("test", "d.ts", "test.d.ts"), - ("test.d.ts", "ts", "test.ts"), - ("test.worker", "d.ts", "test.worker.d.ts"), - ("test.d.mts", "js", "test.js"), - ]; - for (path, ext, expected) in cases { - let actual = with_known_extension(&PathBuf::from(path), ext); - assert_eq!(actual.to_string_lossy(), *expected); - } - } - - #[test] - fn test_types_package_name() { - assert_eq!(types_package_name("name"), "@types/name"); - assert_eq!( - types_package_name("@scoped/package"), - "@types/@scoped__package" - ); - } -} |