diff options
Diffstat (limited to 'ext/node')
-rw-r--r-- | ext/node/Cargo.toml | 1 | ||||
-rw-r--r-- | ext/node/analyze.rs | 22 | ||||
-rw-r--r-- | ext/node/errors.rs | 562 | ||||
-rw-r--r-- | ext/node/lib.rs | 2 | ||||
-rw-r--r-- | ext/node/ops/require.rs | 7 | ||||
-rw-r--r-- | ext/node/resolution.rs | 670 |
6 files changed, 806 insertions, 458 deletions
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index b9228dfdd..fcbd07903 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -78,6 +78,7 @@ signature.workspace = true simd-json = "0.13.4" sm3 = "0.4.2" spki.workspace = true +thiserror.workspace = true tokio.workspace = true url.workspace = true winapi.workspace = true diff --git a/ext/node/analyze.rs b/ext/node/analyze.rs index 0a4ff8dac..3513a8105 100644 --- a/ext/node/analyze.rs +++ b/ext/node/analyze.rs @@ -278,6 +278,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> { } } + // todo(dsherret): what is going on here? Isn't this a bunch of duplicate code? fn resolve( &self, specifier: &str, @@ -314,15 +315,18 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> { let maybe_package_json = load_pkg_json(&*self.fs, &package_json_path)?; if let Some(package_json) = maybe_package_json { if let Some(exports) = &package_json.exports { - return self.node_resolver.package_exports_resolve( - &package_json_path, - &package_subpath, - exports, - referrer, - NodeModuleKind::Esm, - conditions, - mode, - ); + return self + .node_resolver + .package_exports_resolve( + &package_json_path, + &package_subpath, + exports, + Some(referrer), + NodeModuleKind::Esm, + conditions, + mode, + ) + .map_err(AnyError::from); } // old school diff --git a/ext/node/errors.rs b/ext/node/errors.rs index 45ae0b778..560336d68 100644 --- a/ext/node/errors.rs +++ b/ext/node/errors.rs @@ -1,170 +1,462 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::path::PathBuf; use deno_core::error::generic_error; -use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::url::Url; +use deno_core::ModuleSpecifier; +use thiserror::Error; +use crate::NodeModuleKind; use crate::NodeResolutionMode; -pub fn err_invalid_module_specifier( - request: &str, - reason: &str, - maybe_base: Option<String>, -) -> AnyError { - let mut msg = format!( - "[ERR_INVALID_MODULE_SPECIFIER] Invalid module \"{request}\" {reason}" - ); +macro_rules! kinded_err { + ($name:ident, $kind_name:ident) => { + #[derive(Error, Debug)] + #[error(transparent)] + pub struct $name(pub Box<$kind_name>); - if let Some(base) = maybe_base { - msg = format!("{msg} imported from {base}"); - } + impl $name { + pub fn as_kind(&self) -> &$kind_name { + &self.0 + } - type_error(msg) + pub fn into_kind(self) -> $kind_name { + *self.0 + } + } + + impl<E> From<E> for $name + where + $kind_name: From<E>, + { + fn from(err: E) -> Self { + $name(Box::new($kind_name::from(err))) + } + } + }; } -#[allow(unused)] -pub fn err_invalid_package_config( - path: &str, - maybe_base: Option<String>, - maybe_message: Option<String>, -) -> AnyError { - let mut msg = - format!("[ERR_INVALID_PACKAGE_CONFIG] Invalid package config {path}"); +kinded_err!( + ResolvePkgSubpathFromDenoModuleError, + ResolvePkgSubpathFromDenoModuleErrorKind +); - if let Some(base) = maybe_base { - msg = format!("{msg} while importing {base}"); - } +#[derive(Debug, Error)] +pub enum ResolvePkgSubpathFromDenoModuleErrorKind { + #[error(transparent)] + PackageSubpathResolve(#[from] PackageSubpathResolveError), + #[error(transparent)] + UrlToNodeResolution(#[from] UrlToNodeResolutionError), +} - if let Some(message) = maybe_message { - msg = format!("{msg}. {message}"); - } +// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError +#[derive(Debug, Clone, Error)] +#[error( + "[ERR_INVALID_MODULE_SPECIFIER] Invalid module '{}' {}{}", + request, + reason, + maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default() +)] +pub struct InvalidModuleSpecifierError { + pub request: String, + pub reason: Cow<'static, str>, + pub maybe_referrer: Option<String>, +} - generic_error(msg) +#[derive(Debug, Error)] +pub enum LegacyMainResolveError { + #[error(transparent)] + PathToDeclarationUrl(PathToDeclarationUrlError), } -pub fn err_module_not_found(path: &str, base: &str, typ: &str) -> AnyError { - generic_error(format!( - "[ERR_MODULE_NOT_FOUND] Cannot find {typ} \"{path}\" imported from \"{base}\"" - )) +kinded_err!(PackageFolderResolveError, PackageFolderResolveErrorKind); + +#[derive(Debug, Error)] +pub enum PackageFolderResolveErrorKind { + #[error( + "Could not find package '{}' from referrer '{}'{}.", + package_name, + referrer, + referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default() + )] + NotFoundPackage { + package_name: String, + referrer: ModuleSpecifier, + /// Extra information about the referrer. + referrer_extra: Option<String>, + }, + #[error( + "Could not find referrer npm package '{}'{}.", + referrer, + referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default() + )] + NotFoundReferrer { + referrer: ModuleSpecifier, + /// Extra information about the referrer. + referrer_extra: Option<String>, + }, + #[error("Failed resolving '{package_name}' from referrer '{referrer}'.")] + Io { + package_name: String, + referrer: ModuleSpecifier, + #[source] + source: std::io::Error, + }, } -pub fn err_invalid_package_target( - pkg_path: &str, - key: &str, - target: &str, - is_import: bool, - maybe_referrer: Option<String>, -) -> AnyError { - let rel_error = !is_import && !target.is_empty() && !target.starts_with("./"); - let mut msg = "[ERR_INVALID_PACKAGE_TARGET]".to_string(); - let pkg_json_path = PathBuf::from(pkg_path).join("package.json"); - - if key == "." { - assert!(!is_import); - msg = format!( - "{} Invalid \"exports\" main target {} defined in the package config {}", - msg, - target, - pkg_json_path.display() - ) - } else { - let ie = if is_import { "imports" } else { "exports" }; - msg = format!( - "{} Invalid \"{}\" target {} defined for '{}' in the package config {}", - msg, - ie, - target, - key, - pkg_json_path.display() - ) - }; +kinded_err!(PackageSubpathResolveError, PackageSubpathResolveErrorKind); - if let Some(base) = maybe_referrer { - msg = format!("{msg} imported from {base}"); - }; - if rel_error { - msg = format!("{msg}; target must start with \"./\""); +#[derive(Debug, Error)] +pub enum PackageSubpathResolveErrorKind { + #[error(transparent)] + PkgJsonLoad(#[from] deno_config::package_json::PackageJsonLoadError), + #[error(transparent)] + PackageFolderResolve(#[from] PackageFolderResolveError), + #[error(transparent)] + DirNotFound(AnyError), + #[error(transparent)] + Exports(PackageExportsResolveError), + #[error(transparent)] + LegacyMain(LegacyMainResolveError), + #[error(transparent)] + LegacyExact(PathToDeclarationUrlError), +} + +#[derive(Debug, Error)] +#[error( + "Target '{}' not found from '{}'{}{}.", + target, + pkg_json_path.display(), + maybe_referrer.as_ref().map(|r| + format!( + " from{} referrer {}", + match referrer_kind { + NodeModuleKind::Esm => "", + NodeModuleKind::Cjs => " cjs", + }, + r + ) + ).unwrap_or_default(), + match mode { + NodeResolutionMode::Execution => "", + NodeResolutionMode::Types => " for types", } +)] +pub struct PackageTargetNotFoundError { + pub pkg_json_path: PathBuf, + pub target: String, + pub maybe_referrer: Option<ModuleSpecifier>, + pub referrer_kind: NodeModuleKind, + pub mode: NodeResolutionMode, +} - generic_error(msg) +kinded_err!(PackageTargetResolveError, PackageTargetResolveErrorKind); + +#[derive(Debug, Error)] +pub enum PackageTargetResolveErrorKind { + #[error(transparent)] + NotFound(#[from] PackageTargetNotFoundError), + #[error(transparent)] + InvalidPackageTarget(#[from] InvalidPackageTargetError), + #[error(transparent)] + InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError), + #[error(transparent)] + PackageResolve(#[from] PackageResolveError), + #[error(transparent)] + PathToDeclarationUrl(#[from] PathToDeclarationUrlError), } -pub fn err_package_path_not_exported( - mut pkg_path: String, - subpath: &str, - maybe_referrer: Option<String>, - mode: NodeResolutionMode, -) -> AnyError { - let mut msg = "[ERR_PACKAGE_PATH_NOT_EXPORTED]".to_string(); +kinded_err!(PackageExportsResolveError, PackageExportsResolveErrorKind); + +#[derive(Debug, Error)] +pub enum PackageExportsResolveErrorKind { + #[error(transparent)] + PackagePathNotExported(#[from] PackagePathNotExportedError), + #[error(transparent)] + PackageTargetResolve(#[from] PackageTargetResolveError), +} + +#[derive(Debug, Error)] +pub enum PathToDeclarationUrlError { + #[error(transparent)] + SubPath(#[from] PackageSubpathResolveError), +} + +kinded_err!(ClosestPkgJsonError, ClosestPkgJsonErrorKind); - #[cfg(windows)] - { - if !pkg_path.ends_with('\\') { - pkg_path.push('\\'); +#[derive(Debug, Error)] +pub enum ClosestPkgJsonErrorKind { + #[error("Failed canonicalizing package.json directory '{dir_path}'.")] + CanonicalizingDir { + dir_path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error(transparent)] + Load(#[from] deno_config::package_json::PackageJsonLoadError), +} + +#[derive(Debug, Error)] +#[error("TypeScript files are not supported in npm packages: {specifier}")] +pub struct TypeScriptNotSupportedInNpmError { + pub specifier: ModuleSpecifier, +} + +kinded_err!(UrlToNodeResolutionError, UrlToNodeResolutionErrorKind); + +#[derive(Debug, Error)] +pub enum UrlToNodeResolutionErrorKind { + #[error(transparent)] + TypeScriptNotSupported(#[from] TypeScriptNotSupportedInNpmError), + #[error(transparent)] + ClosestPkgJson(#[from] ClosestPkgJsonError), +} + +// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError +#[derive(Debug, Error)] +#[error( + "[ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier \"{}\" is not defined{}{}", + name, + package_json_path.as_ref().map(|p| format!(" in package {}", p.display())).unwrap_or_default(), + maybe_referrer.as_ref().map(|r| format!(" imported from '{}'", r)).unwrap_or_default(), +)] +pub struct PackageImportNotDefinedError { + pub name: String, + pub package_json_path: Option<PathBuf>, + pub maybe_referrer: Option<ModuleSpecifier>, +} + +kinded_err!(PackageImportsResolveError, PackageImportsResolveErrorKind); + +#[derive(Debug, Error)] +pub enum PackageImportsResolveErrorKind { + #[error(transparent)] + ClosestPkgJson(ClosestPkgJsonError), + #[error(transparent)] + InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError), + #[error(transparent)] + NotDefined(#[from] PackageImportNotDefinedError), + #[error(transparent)] + Target(#[from] PackageTargetResolveError), +} + +kinded_err!(PackageResolveError, PackageResolveErrorKind); + +#[derive(Debug, Error)] +pub enum PackageResolveErrorKind { + #[error(transparent)] + ClosestPkgJson(#[from] ClosestPkgJsonError), + #[error(transparent)] + InvalidModuleSpecifier(#[from] InvalidModuleSpecifierError), + #[error(transparent)] + ExportsResolve(#[from] PackageExportsResolveError), + #[error(transparent)] + SubpathResolve(#[from] PackageSubpathResolveError), +} + +#[derive(Debug, Error)] +pub enum NodeResolveError { + #[error("Failed joining '{path}' from '{base}'.")] + RelativeJoinError { + path: String, + base: ModuleSpecifier, + #[source] + source: url::ParseError, + }, + #[error(transparent)] + PackageImportsResolve(#[from] PackageImportsResolveError), + #[error(transparent)] + UnsupportedEsmUrlScheme(#[from] UnsupportedEsmUrlSchemeError), + #[error("Failed resolving specifier from data url referrer.")] + DataUrlReferrerFailed { + #[source] + source: url::ParseError, + }, + #[error(transparent)] + PackageResolve(#[from] PackageResolveError), + #[error(transparent)] + PathToDeclarationUrl(#[from] PathToDeclarationUrlError), + #[error(transparent)] + UrlToNodeResolution(#[from] UrlToNodeResolutionError), + #[error(transparent)] + FinalizeResolution(#[from] FinalizeResolutionError), +} + +kinded_err!(FinalizeResolutionError, FinalizeResolutionErrorKind); + +#[derive(Debug, Error)] +pub enum FinalizeResolutionErrorKind { + #[error(transparent)] + InvalidModuleSpecifierError(#[from] InvalidModuleSpecifierError), + #[error(transparent)] + ModuleNotFound(#[from] ModuleNotFoundError), + #[error(transparent)] + UnsupportedDirImport(#[from] UnsupportedDirImportError), +} + +#[derive(Debug, Error)] +#[error( + "[ERR_MODULE_NOT_FOUND] Cannot find {} '{}'{}", + typ, + specifier, + maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default() +)] +pub struct ModuleNotFoundError { + pub specifier: ModuleSpecifier, + pub maybe_referrer: Option<ModuleSpecifier>, + pub typ: &'static str, +} + +#[derive(Debug, Error)] +#[error( + "[ERR_UNSUPPORTED_DIR_IMPORT] Directory import '{}' is not supported resolving ES modules{}", + dir_url, + maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default(), +)] +pub struct UnsupportedDirImportError { + pub dir_url: ModuleSpecifier, + pub maybe_referrer: Option<ModuleSpecifier>, +} + +#[derive(Debug)] +pub struct InvalidPackageTargetError { + pub pkg_json_path: PathBuf, + pub sub_path: String, + pub target: String, + pub is_import: bool, + pub maybe_referrer: Option<ModuleSpecifier>, +} + +impl std::error::Error for InvalidPackageTargetError {} + +impl std::fmt::Display for InvalidPackageTargetError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let rel_error = !self.is_import + && !self.target.is_empty() + && !self.target.starts_with("./"); + f.write_str("[ERR_INVALID_PACKAGE_TARGET]")?; + + if self.sub_path == "." { + assert!(!self.is_import); + write!( + f, + " Invalid \"exports\" main target {} defined in the package config {}", + self.target, + self.pkg_json_path.display() + )?; + } else { + let ie = if self.is_import { "imports" } else { "exports" }; + write!( + f, + " Invalid \"{}\" target {} defined for '{}' in the package config {}", + ie, + self.target, + self.sub_path, + self.pkg_json_path.display() + )?; + }; + + if let Some(referrer) = &self.maybe_referrer { + write!(f, " imported from '{}'", referrer)?; } - } - #[cfg(not(windows))] - { - if !pkg_path.ends_with('/') { - pkg_path.push('/'); + if rel_error { + write!(f, "; target must start with \"./\"")?; } + Ok(()) } +} - let types_msg = match mode { - NodeResolutionMode::Execution => String::new(), - NodeResolutionMode::Types => " for types".to_string(), - }; - if subpath == "." { - msg = - format!("{msg} No \"exports\" main defined{types_msg} in '{pkg_path}package.json'"); - } else { - msg = format!("{msg} Package subpath '{subpath}' is not defined{types_msg} by \"exports\" in '{pkg_path}package.json'"); - }; +#[derive(Debug)] +pub struct PackagePathNotExportedError { + pub pkg_json_path: PathBuf, + pub subpath: String, + pub maybe_referrer: Option<ModuleSpecifier>, + pub mode: NodeResolutionMode, +} - if let Some(referrer) = maybe_referrer { - msg = format!("{msg} imported from '{referrer}'"); - } +impl std::error::Error for PackagePathNotExportedError {} - generic_error(msg) -} +impl std::fmt::Display for PackagePathNotExportedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("[ERR_PACKAGE_PATH_NOT_EXPORTED]")?; -pub fn err_package_import_not_defined( - specifier: &str, - package_path: Option<String>, - base: &str, -) -> AnyError { - let mut msg = format!( - "[ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier \"{specifier}\" is not defined" - ); + let types_msg = match self.mode { + NodeResolutionMode::Execution => String::new(), + NodeResolutionMode::Types => " for types".to_string(), + }; + if self.subpath == "." { + write!( + f, + " No \"exports\" main defined{} in '{}'", + types_msg, + self.pkg_json_path.display() + )?; + } else { + write!( + f, + " Package subpath '{}' is not defined{} by \"exports\" in '{}'", + self.subpath, + types_msg, + self.pkg_json_path.display() + )?; + }; - if let Some(package_path) = package_path { - let pkg_json_path = PathBuf::from(package_path).join("package.json"); - msg = format!("{} in package {}", msg, pkg_json_path.display()); + if let Some(referrer) = &self.maybe_referrer { + write!(f, " imported from '{}'", referrer)?; + } + Ok(()) } +} - msg = format!("{msg} imported from {base}"); +#[derive(Debug, Clone, Error)] +#[error( + "[ERR_UNSUPPORTED_ESM_URL_SCHEME] Only file and data URLS are supported by the default ESM loader.{} Received protocol '{}'", + if cfg!(windows) && url_scheme.len() == 2 { " On Windows, absolute path must be valid file:// URLS."} else { "" }, + url_scheme +)] +pub struct UnsupportedEsmUrlSchemeError { + pub url_scheme: String, +} - type_error(msg) +#[derive(Debug, Error)] +pub enum ResolvePkgJsonBinExportError { + #[error(transparent)] + PkgJsonLoad(#[from] deno_config::package_json::PackageJsonLoadError), + #[error("Failed resolving binary export. '{}' did not exist", pkg_json_path.display())] + MissingPkgJson { pkg_json_path: PathBuf }, + #[error("Failed resolving binary export. {message}")] + InvalidBinProperty { message: String }, + #[error(transparent)] + UrlToNodeResolution(#[from] UrlToNodeResolutionError), } -pub fn err_unsupported_dir_import(path: &str, base: &str) -> AnyError { - generic_error(format!("[ERR_UNSUPPORTED_DIR_IMPORT] Directory import '{path}' is not supported resolving ES modules imported from {base}")) +#[derive(Debug, Error)] +pub enum ResolveBinaryCommandsError { + #[error(transparent)] + PkgJsonLoad(#[from] deno_config::package_json::PackageJsonLoadError), + #[error("'{}' did not have a name", pkg_json_path.display())] + MissingPkgJsonName { pkg_json_path: PathBuf }, } -pub fn err_unsupported_esm_url_scheme(url: &Url) -> AnyError { +#[allow(unused)] +pub fn err_invalid_package_config( + path: &str, + maybe_base: Option<String>, + maybe_message: Option<String>, +) -> AnyError { let mut msg = - "[ERR_UNSUPPORTED_ESM_URL_SCHEME] Only file and data URLS are supported by the default ESM loader" - .to_string(); + format!("[ERR_INVALID_PACKAGE_CONFIG] Invalid package config {path}"); - if cfg!(window) && url.scheme().len() == 2 { - msg = - format!("{msg}. On Windows, absolute path must be valid file:// URLs"); + if let Some(base) = maybe_base { + msg = format!("{msg} while importing {base}"); + } + + if let Some(message) = maybe_message { + msg = format!("{msg}. {message}"); } - msg = format!("{}. Received protocol '{}'", msg, url.scheme()); generic_error(msg) } @@ -176,23 +468,21 @@ mod test { fn types_resolution_package_path_not_exported() { let separator_char = if cfg!(windows) { '\\' } else { '/' }; assert_eq!( - err_package_path_not_exported( - "test_path".to_string(), - "./jsx-runtime", - None, - NodeResolutionMode::Types, - ) - .to_string(), + PackagePathNotExportedError { + pkg_json_path: PathBuf::from("test_path").join("package.json"), + subpath: "./jsx-runtime".to_string(), + maybe_referrer: None, + mode: NodeResolutionMode::Types + }.to_string(), format!("[ERR_PACKAGE_PATH_NOT_EXPORTED] Package subpath './jsx-runtime' is not defined for types by \"exports\" in 'test_path{separator_char}package.json'") ); assert_eq!( - err_package_path_not_exported( - "test_path".to_string(), - ".", - None, - NodeResolutionMode::Types, - ) - .to_string(), + PackagePathNotExportedError { + pkg_json_path: PathBuf::from("test_path").join("package.json"), + subpath: ".".to_string(), + maybe_referrer: None, + mode: NodeResolutionMode::Types + }.to_string(), format!("[ERR_PACKAGE_PATH_NOT_EXPORTED] No \"exports\" main defined for types in 'test_path{separator_char}package.json'") ); } diff --git a/ext/node/lib.rs b/ext/node/lib.rs index e86413346..13f9abc60 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -168,7 +168,7 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync { &self, specifier: &str, referrer: &ModuleSpecifier, - ) -> Result<PathBuf, AnyError>; + ) -> Result<PathBuf, errors::PackageFolderResolveError>; fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool; diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs index 3702a9047..d03b3dd9c 100644 --- a/ext/node/ops/require.rs +++ b/ext/node/ops/require.rs @@ -421,7 +421,7 @@ where &pkg.path, &expansion, exports, - &referrer, + Some(&referrer), NodeModuleKind::Cjs, resolution::REQUIRE_CONDITIONS, NodeResolutionMode::Execution, @@ -509,7 +509,7 @@ where &pkg.path, &format!(".{expansion}"), exports, - &referrer, + Some(&referrer), NodeModuleKind::Cjs, resolution::REQUIRE_CONDITIONS, NodeResolutionMode::Execution, @@ -538,6 +538,7 @@ where node_resolver .get_closest_package_json(&Url::from_file_path(filename).unwrap()) .map(|maybe_pkg| maybe_pkg.map(|pkg| (*pkg).clone())) + .map_err(AnyError::from) } #[op2] @@ -586,7 +587,7 @@ where deno_core::url::Url::from_file_path(&referrer_filename).unwrap(); let url = node_resolver.package_imports_resolve( &request, - &referrer_url, + Some(&referrer_url), NodeModuleKind::Cjs, Some(&pkg), resolution::REQUIRE_CONDITIONS, diff --git a/ext/node/resolution.rs b/ext/node/resolution.rs index 1b9a20012..5b61c1642 100644 --- a/ext/node/resolution.rs +++ b/ext/node/resolution.rs @@ -7,7 +7,6 @@ use std::path::PathBuf; use deno_config::package_json::PackageJsonRc; use deno_core::anyhow::bail; -use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::serde_json::Map; use deno_core::serde_json::Value; @@ -17,6 +16,33 @@ use deno_fs::FileSystemRc; use deno_media_type::MediaType; use crate::errors; +use crate::errors::ClosestPkgJsonError; +use crate::errors::ClosestPkgJsonErrorKind; +use crate::errors::FinalizeResolutionError; +use crate::errors::InvalidModuleSpecifierError; +use crate::errors::InvalidPackageTargetError; +use crate::errors::LegacyMainResolveError; +use crate::errors::ModuleNotFoundError; +use crate::errors::NodeResolveError; +use crate::errors::PackageExportsResolveError; +use crate::errors::PackageImportNotDefinedError; +use crate::errors::PackageImportsResolveError; +use crate::errors::PackageImportsResolveErrorKind; +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::PathToDeclarationUrlError; +use crate::errors::ResolveBinaryCommandsError; +use crate::errors::ResolvePkgJsonBinExportError; +use crate::errors::ResolvePkgSubpathFromDenoModuleError; +use crate::errors::TypeScriptNotSupportedInNpmError; +use crate::errors::UnsupportedDirImportError; +use crate::errors::UnsupportedEsmUrlSchemeError; +use crate::errors::UrlToNodeResolutionError; use crate::is_builtin_node_module; use crate::path::to_file_specifier; use crate::polyfill::get_module_name_from_builtin_node_module_specifier; @@ -147,7 +173,7 @@ impl NodeResolver { specifier: &str, referrer: &ModuleSpecifier, mode: NodeResolutionMode, - ) -> Result<Option<NodeResolution>, AnyError> { + ) -> Result<Option<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 @@ -169,36 +195,29 @@ impl NodeResolver { let protocol = url.scheme(); if protocol != "file" && protocol != "data" { - return Err(errors::err_unsupported_esm_url_scheme(&url)); + 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(AnyError::from)?; + let url = referrer.join(specifier).map_err(|source| { + NodeResolveError::DataUrlReferrerFailed { source } + })?; return Ok(Some(NodeResolution::Esm(url))); } } - let url = + let maybe_url = self.module_resolve(specifier, referrer, DEFAULT_CONDITIONS, mode)?; - let url = match url { + let url = match maybe_url { Some(url) => url, None => return Ok(None), }; - let url = match mode { - NodeResolutionMode::Execution => url, - NodeResolutionMode::Types => { - let path = url.to_file_path().unwrap(); - // todo(16370): the module kind is not correct here. I think we need - // typescript to tell us if the referrer is esm or cjs - let maybe_decl_url = - self.path_to_declaration_url(path, referrer, NodeModuleKind::Esm)?; - match maybe_decl_url { - Some(url) => url, - None => return Ok(None), - } - } - }; let resolve_response = self.url_to_node_resolution(url)?; // TODO(bartlomieju): skipped checking errors for commonJS resolution and @@ -212,27 +231,25 @@ impl NodeResolver { referrer: &ModuleSpecifier, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, NodeResolveError> { // note: if we're here, the referrer is an esm module - let url = if should_be_treated_as_relative_or_absolute_path(specifier) { - let resolved_specifier = referrer.join(specifier)?; - if mode.is_types() { - let file_path = to_file_path(&resolved_specifier); - // todo(dsherret): the node module kind is not correct and we - // should use the value provided by typescript instead - self.path_to_declaration_url( - file_path, - referrer, - NodeModuleKind::Esm, - )? - } else { - Some(resolved_specifier) - } + let maybe_url = if should_be_treated_as_relative_or_absolute_path(specifier) + { + Some(referrer.join(specifier).map_err(|err| { + NodeResolveError::RelativeJoinError { + path: specifier.to_string(), + base: referrer.clone(), + source: err, + } + })?) } else if specifier.starts_with('#') { - let pkg_config = self.get_closest_package_json(referrer)?; + let pkg_config = self + .get_closest_package_json(referrer) + .map_err(PackageImportsResolveErrorKind::ClosestPkgJson) + .map_err(|err| PackageImportsResolveError(Box::new(err)))?; Some(self.package_imports_resolve( specifier, - referrer, + Some(referrer), NodeModuleKind::Esm, pkg_config.as_deref(), conditions, @@ -249,8 +266,26 @@ impl NodeResolver { mode, )? }; - Ok(match url { - Some(url) => Some(self.finalize_resolution(url, referrer)?), + + let Some(url) = maybe_url else { + return Ok(None); + }; + + let maybe_url = if mode.is_types() { + let file_path = to_file_path(&url); + // todo(16370): the referrer module kind is not correct here. I think we need + // typescript to tell us if the referrer is esm or cjs + self.path_to_declaration_url( + file_path, + Some(referrer), + NodeModuleKind::Esm, + )? + } else { + Some(url) + }; + + Ok(match maybe_url { + Some(url) => Some(self.finalize_resolution(url, Some(referrer))?), None => None, }) } @@ -258,16 +293,21 @@ impl NodeResolver { fn finalize_resolution( &self, resolved: ModuleSpecifier, - base: &ModuleSpecifier, - ) -> Result<ModuleSpecifier, AnyError> { + maybe_referrer: Option<&ModuleSpecifier>, + ) -> Result<ModuleSpecifier, FinalizeResolutionError> { let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C"); 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)), - )); + 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" { @@ -295,16 +335,22 @@ impl NodeResolver { (false, false) }; if is_dir { - return Err(errors::err_unsupported_dir_import( - resolved.as_str(), - base.as_str(), - )); + return Err( + UnsupportedDirImportError { + dir_url: resolved.clone(), + maybe_referrer: maybe_referrer.map(ToOwned::to_owned), + } + .into(), + ); } else if !is_file { - return Err(errors::err_module_not_found( - resolved.as_str(), - base.as_str(), - "module", - )); + return Err( + ModuleNotFoundError { + specifier: resolved, + maybe_referrer: maybe_referrer.map(ToOwned::to_owned), + typ: "module", + } + .into(), + ); } Ok(resolved) @@ -314,9 +360,9 @@ impl NodeResolver { &self, package_dir: &Path, package_subpath: Option<&str>, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, mode: NodeResolutionMode, - ) -> Result<Option<NodeResolution>, AnyError> { + ) -> Result<Option<NodeResolution>, ResolvePkgSubpathFromDenoModuleError> { let node_module_kind = NodeModuleKind::Esm; let package_subpath = package_subpath .map(|s| format!("./{s}")) @@ -324,33 +370,15 @@ impl NodeResolver { let maybe_resolved_url = self.resolve_package_dir_subpath( package_dir, &package_subpath, - referrer, + maybe_referrer, node_module_kind, DEFAULT_CONDITIONS, mode, )?; let resolved_url = match maybe_resolved_url { - Some(resolved_path) => resolved_path, + Some(resolved_url) => resolved_url, None => return Ok(None), }; - let resolved_url = match mode { - NodeResolutionMode::Execution => resolved_url, - NodeResolutionMode::Types => { - if resolved_url.scheme() == "file" { - let path = resolved_url.to_file_path().unwrap(); - match self.path_to_declaration_url( - path, - referrer, - node_module_kind, - )? { - Some(url) => url, - None => return Ok(None), - } - } else { - resolved_url - } - } - }; let resolve_response = self.url_to_node_resolution(resolved_url)?; // TODO(bartlomieju): skipped checking errors for commonJS resolution and // "preserveSymlinksMain"/"preserveSymlinks" options. @@ -360,16 +388,18 @@ impl NodeResolver { pub fn resolve_binary_commands( &self, package_folder: &Path, - ) -> Result<Vec<String>, AnyError> { - let package_json_path = package_folder.join("package.json"); - let Some(package_json) = self.load_package_json(&package_json_path)? else { + ) -> 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 { - bail!("'{}' did not have a name", package_json_path.display()); + return Err(ResolveBinaryCommandsError::MissingPkgJsonName { + pkg_json_path, + }); }; vec![name.to_string()] } @@ -384,15 +414,19 @@ impl NodeResolver { &self, package_folder: &Path, sub_path: Option<&str>, - ) -> Result<NodeResolution, AnyError> { - let package_json_path = package_folder.join("package.json"); - let Some(package_json) = self.load_package_json(&package_json_path)? else { - bail!( - "Failed resolving binary export. '{}' did not exist", - package_json_path.display(), - ) + ) -> 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)?; + 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)?; @@ -404,7 +438,7 @@ impl NodeResolver { pub fn url_to_node_resolution( &self, url: ModuleSpecifier, - ) -> Result<NodeResolution, AnyError> { + ) -> 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)) @@ -419,9 +453,7 @@ impl NodeResolver { Ok(NodeResolution::Esm(url)) } else if url_str.ends_with(".ts") || url_str.ends_with(".mts") { if self.in_npm_package(&url) { - Err(generic_error(format!( - "TypeScript files are not supported in npm packages: {url}" - ))) + Err(TypeScriptNotSupportedInNpmError { specifier: url }.into()) } else { Ok(NodeResolution::Esm(url)) } @@ -434,9 +466,9 @@ impl NodeResolver { fn path_to_declaration_url( &self, path: PathBuf, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PathToDeclarationUrlError> { fn probe_extensions( fs: &dyn deno_fs::FileSystem, path: &Path, @@ -497,7 +529,7 @@ impl NodeResolver { let maybe_resolution = self.resolve_package_dir_subpath( &path, /* sub path */ ".", - referrer, + maybe_referrer, referrer_kind, match referrer_kind { NodeModuleKind::Esm => DEFAULT_CONDITIONS, @@ -529,19 +561,22 @@ impl NodeResolver { pub(super) fn package_imports_resolve( &self, name: &str, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, referrer_pkg_json: Option<&PackageJson>, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<ModuleSpecifier, AnyError> { + ) -> Result<ModuleSpecifier, PackageImportsResolveError> { 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_specifier_display_string(referrer)), - )); + 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; @@ -555,7 +590,7 @@ impl NodeResolver { target, "", name, - referrer, + maybe_referrer, referrer_kind, false, true, @@ -596,7 +631,7 @@ impl NodeResolver { target, &best_match_subpath.unwrap(), best_match, - referrer, + maybe_referrer, referrer_kind, true, true, @@ -611,11 +646,14 @@ impl NodeResolver { } } - Err(throw_import_not_defined( - name, - package_json_path.as_deref(), - referrer, - )) + Err( + PackageImportNotDefinedError { + name: name.to_string(), + package_json_path, + maybe_referrer: maybe_referrer.map(ToOwned::to_owned), + } + .into(), + ) } #[allow(clippy::too_many_arguments)] @@ -625,21 +663,24 @@ impl NodeResolver { subpath: &str, match_: &str, package_json_path: &Path, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, pattern: bool, internal: bool, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<ModuleSpecifier, AnyError> { + ) -> Result<ModuleSpecifier, PackageTargetResolveError> { if !subpath.is_empty() && !pattern && !target.ends_with('/') { - return Err(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); + 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)(\\|/|$)"); @@ -672,8 +713,21 @@ impl NodeResolver { mode, ) { Ok(Some(url)) => Ok(url), - Ok(None) => Err(generic_error("not found")), - Err(err) => Err(err), + Ok(None) => 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(), + ), + Err(err) => { + Err(PackageTargetResolveErrorKind::PackageResolve(err).into()) + } }; return match result { @@ -692,33 +746,42 @@ impl NodeResolver { } } } - return Err(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); + 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(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); + 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(throw_invalid_package_target( - match_, - target, - package_json_path, - internal, - referrer, - )); + 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)); @@ -729,12 +792,15 @@ impl NodeResolver { } else { format!("{match_}{subpath}") }; - return Err(throw_invalid_subpath( - request, - package_json_path, - internal, - referrer, - )); + return Err( + throw_invalid_subpath( + request, + package_json_path, + internal, + maybe_referrer, + ) + .into(), + ); } if pattern { let resolved_path_str = resolved_path.to_string_lossy(); @@ -752,20 +818,20 @@ impl NodeResolver { target: &Value, subpath: &str, package_subpath: &str, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, pattern: bool, internal: bool, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PackageTargetResolveError> { if let Some(target) = target.as_str() { let url = self.resolve_package_target_string( target, subpath, package_subpath, package_json_path, - referrer, + maybe_referrer, referrer_kind, pattern, internal, @@ -774,7 +840,11 @@ impl NodeResolver { )?; if mode.is_types() && url.scheme() == "file" { let path = url.to_file_path().unwrap(); - return self.path_to_declaration_url(path, referrer, referrer_kind); + return Ok(self.path_to_declaration_url( + path, + maybe_referrer, + referrer_kind, + )?); } else { return Ok(Some(url)); } @@ -790,7 +860,7 @@ impl NodeResolver { target_item, subpath, package_subpath, - referrer, + maybe_referrer, referrer_kind, pattern, internal, @@ -804,14 +874,15 @@ impl NodeResolver { last_error = None; continue; } - Err(e) => { - let err_string = e.to_string(); - last_error = Some(e); - if err_string.starts_with("[ERR_INVALID_PACKAGE_TARGET]") { + Err(e) => match e.as_kind() { + PackageTargetResolveErrorKind::InvalidPackageTarget(_) => { + last_error = Some(e); continue; } - return Err(last_error.unwrap()); - } + _ => { + return Err(e); + } + }, } } if last_error.is_none() { @@ -838,7 +909,7 @@ impl NodeResolver { condition_target, subpath, package_subpath, - referrer, + maybe_referrer, referrer_kind, pattern, internal, @@ -857,13 +928,16 @@ impl NodeResolver { return Ok(None); } - Err(throw_invalid_package_target( - package_subpath, - &target.to_string(), - package_json_path, - internal, - referrer, - )) + 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)] @@ -872,11 +946,11 @@ impl NodeResolver { package_json_path: &Path, package_subpath: &str, package_exports: &Map<String, Value>, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<ModuleSpecifier, AnyError> { + ) -> Result<ModuleSpecifier, PackageExportsResolveError> { if package_exports.contains_key(package_subpath) && package_subpath.find('*').is_none() && !package_subpath.ends_with('/') @@ -887,7 +961,7 @@ impl NodeResolver { target, "", package_subpath, - referrer, + maybe_referrer, referrer_kind, false, false, @@ -896,12 +970,15 @@ impl NodeResolver { )?; return match resolved { Some(resolved) => Ok(resolved), - None => Err(throw_exports_not_found( - package_subpath, - package_json_path, - referrer, - mode, - )), + 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(), + ), }; } @@ -946,7 +1023,7 @@ impl NodeResolver { target, &best_match_subpath.unwrap(), best_match, - referrer, + maybe_referrer, referrer_kind, true, false, @@ -956,21 +1033,27 @@ impl NodeResolver { if let Some(resolved) = maybe_resolved { return Ok(resolved); } else { - return Err(throw_exports_not_found( - package_subpath, - package_json_path, - referrer, - mode, - )); + 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(throw_exports_not_found( - package_subpath, - package_json_path, - referrer, - mode, - )) + 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( @@ -980,7 +1063,7 @@ impl NodeResolver { referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PackageResolveError> { let (package_name, package_subpath, _is_scoped) = parse_npm_pkg_name(specifier, referrer)?; @@ -995,23 +1078,26 @@ impl NodeResolver { &package_config.path, &package_subpath, exports, - referrer, + Some(referrer), referrer_kind, conditions, mode, ) - .map(Some); + .map(Some) + .map_err(|err| err.into()); } } - self.resolve_package_subpath_for_package( - &package_name, - &package_subpath, - referrer, - referrer_kind, - conditions, - mode, - ) + self + .resolve_package_subpath_for_package( + &package_name, + &package_subpath, + referrer, + referrer_kind, + conditions, + mode, + ) + .map_err(|err| err.into()) } #[allow(clippy::too_many_arguments)] @@ -1023,7 +1109,7 @@ impl NodeResolver { referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PackageSubpathResolveError> { let result = self.resolve_package_subpath_for_package_inner( package_name, package_subpath, @@ -1058,7 +1144,7 @@ impl NodeResolver { referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PackageSubpathResolveError> { let package_dir_path = self .npm_resolver .resolve_package_folder_from_package(package_name, referrer)?; @@ -1080,7 +1166,7 @@ impl NodeResolver { self.resolve_package_dir_subpath( &package_dir_path, package_subpath, - referrer, + Some(referrer), referrer_kind, conditions, mode, @@ -1092,28 +1178,30 @@ impl NodeResolver { &self, package_dir_path: &Path, package_subpath: &str, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, 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, - referrer, + maybe_referrer, referrer_kind, conditions, mode, ), - None => self.resolve_package_subpath_no_pkg_json( - package_dir_path, - package_subpath, - referrer, - referrer_kind, - mode, - ), + None => self + .resolve_package_subpath_no_pkg_json( + package_dir_path, + package_subpath, + maybe_referrer, + referrer_kind, + mode, + ) + .map_err(|err| PackageSubpathResolveErrorKind::LegacyExact(err).into()), } } @@ -1122,11 +1210,11 @@ impl NodeResolver { &self, package_json: &PackageJson, package_subpath: &str, - referrer: &ModuleSpecifier, + referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PackageSubpathResolveError> { if let Some(exports) = &package_json.exports { let result = self.package_exports_resolve( &package_json.path, @@ -1141,48 +1229,48 @@ impl NodeResolver { Ok(found) => return Ok(Some(found)), Err(exports_err) => { if mode.is_types() && package_subpath == "." { - return self.legacy_main_resolve( - package_json, - referrer, - referrer_kind, - mode, - ); + return self + .legacy_main_resolve(package_json, referrer, referrer_kind, mode) + .map_err(|err| { + PackageSubpathResolveErrorKind::LegacyMain(err).into() + }); } - return Err(exports_err); + return Err( + PackageSubpathResolveErrorKind::Exports(exports_err).into(), + ); } } } if package_subpath == "." { - return self.legacy_main_resolve( - package_json, + return self + .legacy_main_resolve(package_json, referrer, referrer_kind, mode) + .map_err(|err| PackageSubpathResolveErrorKind::LegacyMain(err).into()); + } + + self + .resolve_subpath_exact( + package_json.path.parent().unwrap(), + package_subpath, referrer, referrer_kind, mode, - ); - } - - self.resolve_subpath_exact( - package_json.path.parent().unwrap(), - package_subpath, - referrer, - referrer_kind, - mode, - ) + ) + .map_err(|err| PackageSubpathResolveErrorKind::LegacyExact(err).into()) } fn resolve_subpath_exact( &self, directory: &Path, package_subpath: &str, - referrer: &ModuleSpecifier, + referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PathToDeclarationUrlError> { assert_ne!(package_subpath, "."); let file_path = directory.join(package_subpath); if mode.is_types() { - self.path_to_declaration_url(file_path, referrer, referrer_kind) + Ok(self.path_to_declaration_url(file_path, referrer, referrer_kind)?) } else { Ok(Some(to_file_specifier(&file_path))) } @@ -1192,12 +1280,12 @@ impl NodeResolver { &self, directory: &Path, package_subpath: &str, - referrer: &ModuleSpecifier, + referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, PathToDeclarationUrlError> { if package_subpath == "." { - self.legacy_index_resolve(directory, referrer_kind, mode) + Ok(self.legacy_index_resolve(directory, referrer_kind, mode)) } else { self.resolve_subpath_exact( directory, @@ -1212,7 +1300,7 @@ impl NodeResolver { pub fn get_closest_package_json( &self, url: &ModuleSpecifier, - ) -> Result<Option<PackageJsonRc>, AnyError> { + ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> { let Ok(file_path) = url.to_file_path() else { return Ok(None); }; @@ -1222,10 +1310,15 @@ impl NodeResolver { pub fn get_closest_package_json_from_path( &self, file_path: &Path, - ) -> Result<Option<PackageJsonRc>, AnyError> { - let current_dir = deno_core::strip_unc_prefix( - self.fs.realpath_sync(file_path.parent().unwrap())?, - ); + ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> { + let parent_dir = file_path.parent().unwrap(); + let current_dir = + deno_core::strip_unc_prefix(self.fs.realpath_sync(parent_dir).map_err( + |source| ClosestPkgJsonErrorKind::CanonicalizingDir { + dir_path: parent_dir.to_path_buf(), + source: source.into_io_error(), + }, + )?); let mut current_dir = current_dir.as_path(); let package_json_path = current_dir.join("package.json"); if let Some(pkg_json) = self.load_package_json(&package_json_path)? { @@ -1255,10 +1348,10 @@ impl NodeResolver { pub(super) fn legacy_main_resolve( &self, package_json: &PackageJson, - referrer: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Result<Option<ModuleSpecifier>, LegacyMainResolveError> { let maybe_main = if mode.is_types() { match package_json.types.as_ref() { Some(types) => Some(types.as_str()), @@ -1267,8 +1360,9 @@ impl NodeResolver { // a corresponding declaration file if let Some(main) = package_json.main(referrer_kind) { let main = package_json.path.parent().unwrap().join(main).clean(); - let maybe_decl_url = - self.path_to_declaration_url(main, referrer, referrer_kind)?; + let maybe_decl_url = self + .path_to_declaration_url(main, maybe_referrer, referrer_kind) + .map_err(LegacyMainResolveError::PathToDeclarationUrl)?; if let Some(path) = maybe_decl_url { return Ok(Some(path)); } @@ -1318,11 +1412,11 @@ impl NodeResolver { } } - self.legacy_index_resolve( + Ok(self.legacy_index_resolve( package_json.path.parent().unwrap(), referrer_kind, mode, - ) + )) } fn legacy_index_resolve( @@ -1330,7 +1424,7 @@ impl NodeResolver { directory: &Path, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result<Option<ModuleSpecifier>, AnyError> { + ) -> Option<ModuleSpecifier> { let index_file_names = if mode.is_types() { // todo(dsherret): investigate exactly how typescript does this match referrer_kind { @@ -1344,11 +1438,11 @@ impl NodeResolver { let guess = directory.join(index_file_name).clean(); if self.fs.is_file_sync(&guess) { // TODO(bartlomieju): emitLegacyIndexDeprecation() - return Ok(Some(to_file_specifier(&guess))); + return Some(to_file_specifier(&guess)); } } - Ok(None) + None } } @@ -1524,71 +1618,29 @@ fn to_specifier_display_string(url: &ModuleSpecifier) -> String { } } -fn throw_import_not_defined( - specifier: &str, - package_json_path: Option<&Path>, - base: &ModuleSpecifier, -) -> AnyError { - errors::err_package_import_not_defined( - specifier, - package_json_path.map(|p| p.parent().unwrap().display().to_string()), - &to_specifier_display_string(base), - ) -} - -fn throw_invalid_package_target( - subpath: &str, - target: &str, - package_json_path: &Path, - internal: bool, - referrer: &ModuleSpecifier, -) -> AnyError { - errors::err_invalid_package_target( - &package_json_path.parent().unwrap().display().to_string(), - subpath, - target, - internal, - Some(referrer.to_string()), - ) -} - fn throw_invalid_subpath( subpath: String, package_json_path: &Path, internal: bool, - referrer: &ModuleSpecifier, -) -> AnyError { + maybe_referrer: Option<&ModuleSpecifier>, +) -> 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(), ); - errors::err_invalid_module_specifier( - &subpath, - &reason, - Some(to_specifier_display_string(referrer)), - ) -} - -fn throw_exports_not_found( - subpath: &str, - package_json_path: &Path, - referrer: &ModuleSpecifier, - mode: NodeResolutionMode, -) -> AnyError { - errors::err_package_path_not_exported( - package_json_path.parent().unwrap().display().to_string(), - subpath, - Some(to_specifier_display_string(referrer)), - mode, - ) + 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: &ModuleSpecifier, -) -> Result<(String, String, bool), AnyError> { +) -> Result<(String, String, bool), InvalidModuleSpecifierError> { let mut separator_index = specifier.find('/'); let mut valid_package_name = true; let mut is_scoped = false; @@ -1620,11 +1672,11 @@ pub fn parse_npm_pkg_name( } if !valid_package_name { - return Err(errors::err_invalid_module_specifier( - specifier, - "is not a valid package name", - Some(to_specifier_display_string(referrer)), - )); + 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 { |