#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
use std::path::PathBuf;
use std::sync::Arc;
use boxed_error::Boxed;
use deno_config::workspace::MappedResolution;
use deno_config::workspace::MappedResolutionDiagnostic;
use deno_config::workspace::MappedResolutionError;
use deno_config::workspace::WorkspaceResolvePkgJsonFolderError;
use deno_config::workspace::WorkspaceResolver;
use deno_package_json::PackageJsonDepValue;
use deno_package_json::PackageJsonDepValueParseError;
use deno_semver::npm::NpmPackageReqReference;
use fs::DenoResolverFs;
use lib_node_resolver::env::NodeResolverEnv;
use lib_node_resolver::errors::NodeResolveError;
use lib_node_resolver::errors::PackageSubpathResolveError;
use lib_node_resolver::InNpmPackageChecker;
use lib_node_resolver::NodeModuleKind;
use lib_node_resolver::NodeResolution;
use lib_node_resolver::NodeResolutionMode;
use lib_node_resolver::NodeResolver;
use npm::MissingPackageNodeModulesFolderError;
use npm::NodeModulesOutOfDateError;
use npm::NpmReqResolver;
use npm::ResolveIfForNpmPackageErrorKind;
use npm::ResolvePkgFolderFromDenoReqError;
use npm::ResolveReqWithSubPathErrorKind;
use sloppy_imports::SloppyImportResolverFs;
use sloppy_imports::SloppyImportsResolutionMode;
use sloppy_imports::SloppyImportsResolver;
use thiserror::Error;
use url::Url;
pub mod cjs;
pub mod fs;
pub mod npm;
pub mod sloppy_imports;
#[derive(Debug, Clone)]
pub struct DenoResolution {
pub url: Url,
pub maybe_diagnostic: Option<Box<MappedResolutionDiagnostic>>,
pub found_package_json_dep: bool,
}
#[derive(Debug, Boxed)]
pub struct DenoResolveError(pub Box<DenoResolveErrorKind>);
#[derive(Debug, Error)]
pub enum DenoResolveErrorKind {
#[error("Importing from the vendor directory is not permitted. Use a remote specifier instead or disable vendoring.")]
InvalidVendorFolderImport,
#[error(transparent)]
MappedResolution(#[from] MappedResolutionError),
#[error(transparent)]
MissingPackageNodeModulesFolder(#[from] MissingPackageNodeModulesFolderError),
#[error(transparent)]
Node(#[from] NodeResolveError),
#[error(transparent)]
NodeModulesOutOfDate(#[from] NodeModulesOutOfDateError),
#[error(transparent)]
PackageJsonDepValueParse(#[from] PackageJsonDepValueParseError),
#[error(transparent)]
PackageJsonDepValueUrlParse(url::ParseError),
#[error(transparent)]
PackageSubpathResolve(#[from] PackageSubpathResolveError),
#[error(transparent)]
ResolvePkgFolderFromDenoReq(#[from] ResolvePkgFolderFromDenoReqError),
#[error(transparent)]
WorkspaceResolvePkgJsonFolder(#[from] WorkspaceResolvePkgJsonFolderError),
}
#[derive(Debug)]
pub struct NodeAndNpmReqResolver<
Fs: DenoResolverFs,
TNodeResolverEnv: NodeResolverEnv,
> {
pub lib_node_resolver: Arc<NodeResolver<TNodeResolverEnv>>,
pub npm_req_resolver: Arc<NpmReqResolver<Fs, TNodeResolverEnv>>,
}
pub struct DenoResolverOptions<
'a,
Fs: DenoResolverFs,
TNodeResolverEnv: NodeResolverEnv,
TSloppyImportResolverFs: SloppyImportResolverFs,
> {
pub in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pub node_and_req_resolver:
Option<NodeAndNpmReqResolver<Fs, TNodeResolverEnv>>,
pub sloppy_imports_resolver:
Option<Arc<SloppyImportsResolver<TSloppyImportResolverFs>>>,
pub workspace_resolver: Arc<WorkspaceResolver>,
pub is_byonm: bool,
pub maybe_vendor_dir: Option<&'a PathBuf>,
}
#[derive(Debug)]
pub struct DenoResolver<
Fs: DenoResolverFs,
TNodeResolverEnv: NodeResolverEnv,
TSloppyImportResolverFs: SloppyImportResolverFs,
> {
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
node_and_npm_resolver: Option<NodeAndNpmReqResolver<Fs, TNodeResolverEnv>>,
sloppy_imports_resolver:
Option<Arc<SloppyImportsResolver<TSloppyImportResolverFs>>>,
workspace_resolver: Arc<WorkspaceResolver>,
is_byonm: bool,
maybe_vendor_specifier: Option<Url>,
}
impl<
Fs: DenoResolverFs,
TNodeResolverEnv: NodeResolverEnv,
TSloppyImportResolverFs: SloppyImportResolverFs,
> DenoResolver<Fs, TNodeResolverEnv, TSloppyImportResolverFs>
{
pub fn new(
options: DenoResolverOptions<Fs, TNodeResolverEnv, TSloppyImportResolverFs>,
) -> Self {
Self {
in_npm_pkg_checker: options.in_npm_pkg_checker,
node_and_npm_resolver: options.node_and_req_resolver,
sloppy_imports_resolver: options.sloppy_imports_resolver,
workspace_resolver: options.workspace_resolver,
is_byonm: options.is_byonm,
maybe_vendor_specifier: options
.maybe_vendor_dir
.and_then(|v| deno_path_util::url_from_directory_path(v).ok()),
}
}
pub fn resolve(
&self,
raw_specifier: &str,
referrer: &Url,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
) -> Result<DenoResolution, DenoResolveError> {
let mut found_package_json_dep = false;
let mut maybe_diagnostic = None;
if let Some(node_and_npm_resolver) = self.node_and_npm_resolver.as_ref() {
let lib_node_resolver = &node_and_npm_resolver.lib_node_resolver;
if referrer.scheme() == "file"
&& self.in_npm_pkg_checker.in_npm_package(referrer)
{
return lib_node_resolver
.resolve(raw_specifier, referrer, referrer_kind, mode)
.map(|res| DenoResolution {
url: res.into_url(),
found_package_json_dep,
maybe_diagnostic,
})
.map_err(|e| e.into());
}
}
let result: Result<_, DenoResolveError> = self
.workspace_resolver
.resolve(raw_specifier, referrer)
.map_err(|err| err.into());
let result = match result {
Ok(resolution) => match resolution {
MappedResolution::Normal {
specifier,
maybe_diagnostic: current_diagnostic,
}
| MappedResolution::ImportMap {
specifier,
maybe_diagnostic: current_diagnostic,
} => {
maybe_diagnostic = current_diagnostic;
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
Ok(
sloppy_imports_resolver
.resolve(
&specifier,
match mode {
NodeResolutionMode::Execution => {
SloppyImportsResolutionMode::Execution
}
NodeResolutionMode::Types => {
SloppyImportsResolutionMode::Types
}
},
)
.map(|s| s.into_specifier())
.unwrap_or(specifier),
)
} else {
Ok(specifier)
}
}
MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
Ok(specifier)
}
MappedResolution::WorkspaceNpmPackage {
target_pkg_json: pkg_json,
sub_path,
..
} => self
.node_and_npm_resolver
.as_ref()
.unwrap()
.lib_node_resolver
.resolve_package_subpath_from_deno_module(
pkg_json.dir_path(),
sub_path.as_deref(),
Some(referrer),
referrer_kind,
mode,
)
.map_err(|e| e.into()),
MappedResolution::PackageJson {
dep_result,
alias,
sub_path,
..
} => {
found_package_json_dep = true;
dep_result
.as_ref()
.map_err(|e| {
DenoResolveErrorKind::PackageJsonDepValueParse(e.clone())
.into_box()
})
.and_then(|dep| match dep {
PackageJsonDepValue::Req(req) => Url::parse(&format!(
"npm:{}{}",
req,
sub_path.map(|s| format!("/{}", s)).unwrap_or_default()
))
.map_err(|e| {
DenoResolveErrorKind::PackageJsonDepValueUrlParse(e).into_box()
}),
PackageJsonDepValue::Workspace(version_req) => self
.workspace_resolver
.resolve_workspace_pkg_json_folder_for_pkg_json_dep(
alias,
version_req,
)
.map_err(|e| {
DenoResolveErrorKind::WorkspaceResolvePkgJsonFolder(e)
.into_box()
})
.and_then(|pkg_folder| {
self
.node_and_npm_resolver
.as_ref()
.unwrap()
.lib_node_resolver
.resolve_package_subpath_from_deno_module(
pkg_folder,
sub_path.as_deref(),
Some(referrer),
referrer_kind,
mode,
)
.map_err(|e| {
DenoResolveErrorKind::PackageSubpathResolve(e).into_box()
})
}),
})
}
},
Err(err) => Err(err),
};
if let Some(vendor_specifier) = &self.maybe_vendor_specifier {
if let Ok(specifier) = &result {
if specifier.as_str().starts_with(vendor_specifier.as_str()) {
return Err(
DenoResolveErrorKind::InvalidVendorFolderImport.into_box(),
);
}
}
}
let Some(NodeAndNpmReqResolver {
lib_node_resolver,
npm_req_resolver,
}) = &self.node_and_npm_resolver
else {
return Ok(DenoResolution {
url: result?,
maybe_diagnostic,
found_package_json_dep,
});
};
match result {
Ok(specifier) => {
if let Ok(npm_req_ref) =
NpmPackageReqReference::from_specifier(&specifier)
{
if let Some(pkg_folder) = self
.workspace_resolver
.resolve_workspace_pkg_json_folder_for_npm_specifier(
npm_req_ref.req(),
)
{
return lib_node_resolver
.resolve_package_subpath_from_deno_module(
pkg_folder,
npm_req_ref.sub_path(),
Some(referrer),
referrer_kind,
mode,
)
.map(|url| DenoResolution {
url,
maybe_diagnostic,
found_package_json_dep,
})
.map_err(|e| e.into());
}
if self.is_byonm {
return npm_req_resolver
.resolve_req_reference(
&npm_req_ref,
referrer,
referrer_kind,
mode,
)
.map(|url| DenoResolution {
url,
maybe_diagnostic,
found_package_json_dep,
})
.map_err(|err| {
match err.into_kind() {
ResolveReqWithSubPathErrorKind::MissingPackageNodeModulesFolder(
err,
) => err.into(),
ResolveReqWithSubPathErrorKind::ResolvePkgFolderFromDenoReq(
err,
) => err.into(),
ResolveReqWithSubPathErrorKind::PackageSubpathResolve(err) => {
err.into()
}
}
});
}
}
Ok(DenoResolution {
url: lib_node_resolver
.handle_if_in_node_modules(&specifier)
.unwrap_or(specifier),
maybe_diagnostic,
found_package_json_dep,
})
}
Err(err) => {
if self.is_byonm && referrer.scheme() == "file" {
let maybe_resolution = npm_req_resolver
.resolve_if_for_npm_pkg(
raw_specifier,
referrer,
referrer_kind,
mode,
)
.map_err(|e| match e.into_kind() {
ResolveIfForNpmPackageErrorKind::NodeResolve(e) => {
DenoResolveErrorKind::Node(e).into_box()
}
ResolveIfForNpmPackageErrorKind::NodeModulesOutOfDate(e) => {
e.into()
}
})?;
if let Some(res) = maybe_resolution {
match res {
NodeResolution::Module(url) => {
return Ok(DenoResolution {
url,
maybe_diagnostic,
found_package_json_dep,
})
}
NodeResolution::BuiltIn(_) => {
}
}
}
}
Err(err)
}
}
}
}