summaryrefslogtreecommitdiff
path: root/ext/node/resolver.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/resolver.rs')
-rw-r--r--ext/node/resolver.rs686
1 files changed, 0 insertions, 686 deletions
diff --git a/ext/node/resolver.rs b/ext/node/resolver.rs
deleted file mode 100644
index 41e1cf4d4..000000000
--- a/ext/node/resolver.rs
+++ /dev/null
@@ -1,686 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-
-use std::path::Path;
-use std::path::PathBuf;
-
-use deno_core::anyhow::bail;
-use deno_core::anyhow::Context;
-use deno_core::error::generic_error;
-use deno_core::error::AnyError;
-use deno_core::serde_json::Value;
-use deno_core::url::Url;
-use deno_core::ModuleSpecifier;
-use deno_media_type::MediaType;
-use deno_semver::npm::NpmPackageNv;
-use deno_semver::npm::NpmPackageNvReference;
-use deno_semver::npm::NpmPackageReqReference;
-
-use crate::errors;
-use crate::get_closest_package_json;
-use crate::legacy_main_resolve;
-use crate::package_exports_resolve;
-use crate::package_imports_resolve;
-use crate::package_resolve;
-use crate::path_to_declaration_path;
-use crate::AllowAllNodePermissions;
-use crate::NodeFs;
-use crate::NodeModuleKind;
-use crate::NodePermissions;
-use crate::NodeResolutionMode;
-use crate::NpmResolver;
-use crate::PackageJson;
-use crate::DEFAULT_CONDITIONS;
-
-#[derive(Debug)]
-pub enum NodeResolution {
- Esm(ModuleSpecifier),
- CommonJs(ModuleSpecifier),
- BuiltIn(String),
-}
-
-impl NodeResolution {
- pub fn into_url(self) -> ModuleSpecifier {
- match self {
- Self::Esm(u) => u,
- Self::CommonJs(u) => u,
- Self::BuiltIn(specifier) => {
- if specifier.starts_with("node:") {
- ModuleSpecifier::parse(&specifier).unwrap()
- } else {
- ModuleSpecifier::parse(&format!("node:{specifier}")).unwrap()
- }
- }
- }
- }
-
- pub fn into_specifier_and_media_type(
- resolution: Option<Self>,
- ) -> (ModuleSpecifier, 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 => (
- ModuleSpecifier::parse("internal:///missing_dependency.d.ts").unwrap(),
- MediaType::Dts,
- ),
- }
- }
-}
-
-#[derive(Debug)]
-pub struct NodeResolver<TRequireNpmResolver: NpmResolver> {
- npm_resolver: TRequireNpmResolver,
-}
-
-impl<TRequireNpmResolver: NpmResolver> NodeResolver<TRequireNpmResolver> {
- pub fn new(require_npm_resolver: TRequireNpmResolver) -> Self {
- Self {
- npm_resolver: require_npm_resolver,
- }
- }
-
- pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> 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<Fs: NodeFs>(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- mode: NodeResolutionMode,
- permissions: &mut dyn NodePermissions,
- ) -> Result<Option<NodeResolution>, AnyError> {
- // 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 crate::is_builtin_node_module(specifier) {
- return Ok(Some(NodeResolution::BuiltIn(specifier.to_string())));
- }
-
- if let Ok(url) = Url::parse(specifier) {
- if url.scheme() == "data" {
- return Ok(Some(NodeResolution::Esm(url)));
- }
-
- let protocol = url.scheme();
-
- if protocol == "node" {
- let split_specifier = url.as_str().split(':');
- let specifier = split_specifier.skip(1).collect::<String>();
-
- if crate::is_builtin_node_module(&specifier) {
- return Ok(Some(NodeResolution::BuiltIn(specifier)));
- }
- }
-
- if protocol != "file" && protocol != "data" {
- return Err(errors::err_unsupported_esm_url_scheme(&url));
- }
-
- // todo(dsherret): this seems wrong
- if referrer.scheme() == "data" {
- let url = referrer.join(specifier).map_err(AnyError::from)?;
- return Ok(Some(NodeResolution::Esm(url)));
- }
- }
-
- let url = self.module_resolve::<Fs>(
- specifier,
- referrer,
- DEFAULT_CONDITIONS,
- mode,
- permissions,
- )?;
- let url = match 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 path =
- match path_to_declaration_path::<Fs>(path, NodeModuleKind::Esm) {
- Some(path) => path,
- None => return Ok(None),
- };
- ModuleSpecifier::from_file_path(path).unwrap()
- }
- };
-
- let resolve_response = self.url_to_node_resolution::<Fs>(url)?;
- // TODO(bartlomieju): skipped checking errors for commonJS resolution and
- // "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(Some(resolve_response))
- }
-
- fn module_resolve<Fs: NodeFs>(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- conditions: &[&str],
- mode: NodeResolutionMode,
- permissions: &mut dyn NodePermissions,
- ) -> Result<Option<ModuleSpecifier>, AnyError> {
- // 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
- let declaration_path =
- path_to_declaration_path::<Fs>(file_path, NodeModuleKind::Esm);
- declaration_path.map(|declaration_path| {
- ModuleSpecifier::from_file_path(declaration_path).unwrap()
- })
- } else {
- Some(resolved_specifier)
- }
- } else if specifier.starts_with('#') {
- Some(
- package_imports_resolve::<Fs>(
- specifier,
- referrer,
- NodeModuleKind::Esm,
- conditions,
- mode,
- &self.npm_resolver,
- permissions,
- )
- .map(|p| ModuleSpecifier::from_file_path(p).unwrap())?,
- )
- } else if let Ok(resolved) = Url::parse(specifier) {
- Some(resolved)
- } else {
- package_resolve::<Fs>(
- specifier,
- referrer,
- NodeModuleKind::Esm,
- conditions,
- mode,
- &self.npm_resolver,
- permissions,
- )?
- .map(|p| ModuleSpecifier::from_file_path(p).unwrap())
- };
- Ok(match url {
- Some(url) => Some(finalize_resolution::<Fs>(url, referrer)?),
- None => None,
- })
- }
-
- pub fn resolve_npm_req_reference<Fs: NodeFs>(
- &self,
- reference: &NpmPackageReqReference,
- mode: NodeResolutionMode,
- permissions: &mut dyn NodePermissions,
- ) -> Result<Option<NodeResolution>, AnyError> {
- let reference = self
- .npm_resolver
- .resolve_nv_ref_from_pkg_req_ref(reference)?;
- self.resolve_npm_reference::<Fs>(&reference, mode, permissions)
- }
-
- pub fn resolve_npm_reference<Fs: NodeFs>(
- &self,
- reference: &NpmPackageNvReference,
- mode: NodeResolutionMode,
- permissions: &mut dyn NodePermissions,
- ) -> Result<Option<NodeResolution>, AnyError> {
- let package_folder = self
- .npm_resolver
- .resolve_package_folder_from_deno_module(&reference.nv)?;
- let node_module_kind = NodeModuleKind::Esm;
- let maybe_resolved_path = package_config_resolve::<Fs>(
- &reference
- .sub_path
- .as_ref()
- .map(|s| format!("./{s}"))
- .unwrap_or_else(|| ".".to_string()),
- &package_folder,
- node_module_kind,
- DEFAULT_CONDITIONS,
- mode,
- &self.npm_resolver,
- permissions,
- )
- .with_context(|| {
- format!("Error resolving package config for '{reference}'")
- })?;
- let resolved_path = match maybe_resolved_path {
- Some(resolved_path) => resolved_path,
- None => return Ok(None),
- };
- let resolved_path = match mode {
- NodeResolutionMode::Execution => resolved_path,
- NodeResolutionMode::Types => {
- match path_to_declaration_path::<Fs>(resolved_path, node_module_kind) {
- Some(path) => path,
- None => return Ok(None),
- }
- }
- };
- let url = ModuleSpecifier::from_file_path(resolved_path).unwrap();
- let resolve_response = self.url_to_node_resolution::<Fs>(url)?;
- // TODO(bartlomieju): skipped checking errors for commonJS resolution and
- // "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(Some(resolve_response))
- }
-
- pub fn resolve_binary_commands<Fs: NodeFs>(
- &self,
- pkg_nv: &NpmPackageNv,
- ) -> Result<Vec<String>, AnyError> {
- let package_folder = self
- .npm_resolver
- .resolve_package_folder_from_deno_module(pkg_nv)?;
- let package_json_path = package_folder.join("package.json");
- let package_json = PackageJson::load::<Fs>(
- &self.npm_resolver,
- &mut AllowAllNodePermissions,
- package_json_path,
- )?;
-
- Ok(match package_json.bin {
- Some(Value::String(_)) => vec![pkg_nv.name.to_string()],
- Some(Value::Object(o)) => {
- o.into_iter().map(|(key, _)| key).collect::<Vec<_>>()
- }
- _ => Vec::new(),
- })
- }
-
- pub fn resolve_binary_export<Fs: NodeFs>(
- &self,
- pkg_ref: &NpmPackageReqReference,
- ) -> Result<NodeResolution, AnyError> {
- let pkg_nv = self
- .npm_resolver
- .resolve_pkg_id_from_pkg_req(&pkg_ref.req)?
- .nv;
- let bin_name = pkg_ref.sub_path.as_deref();
- let package_folder = self
- .npm_resolver
- .resolve_package_folder_from_deno_module(&pkg_nv)?;
- let package_json_path = package_folder.join("package.json");
- let package_json = PackageJson::load::<Fs>(
- &self.npm_resolver,
- &mut AllowAllNodePermissions,
- package_json_path,
- )?;
- let bin = match &package_json.bin {
- Some(bin) => bin,
- None => bail!(
- "package '{}' did not have a bin property in its package.json",
- &pkg_nv.name,
- ),
- };
- let bin_entry = resolve_bin_entry_value(&pkg_nv, bin_name, bin)?;
- let url =
- ModuleSpecifier::from_file_path(package_folder.join(bin_entry)).unwrap();
-
- let resolve_response = self.url_to_node_resolution::<Fs>(url)?;
- // TODO(bartlomieju): skipped checking errors for commonJS resolution and
- // "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(resolve_response)
- }
-
- pub fn url_to_node_resolution<Fs: NodeFs>(
- &self,
- url: ModuleSpecifier,
- ) -> Result<NodeResolution, AnyError> {
- let url_str = url.as_str().to_lowercase();
- if url_str.starts_with("http") {
- Ok(NodeResolution::Esm(url))
- } else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") {
- let package_config = get_closest_package_json::<Fs>(
- &url,
- &self.npm_resolver,
- &mut AllowAllNodePermissions,
- )?;
- if package_config.typ == "module" {
- Ok(NodeResolution::Esm(url))
- } else {
- Ok(NodeResolution::CommonJs(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") {
- Err(generic_error(format!(
- "TypeScript files are not supported in npm packages: {url}"
- )))
- } else {
- Ok(NodeResolution::CommonJs(url))
- }
- }
-}
-
-fn resolve_bin_entry_value<'a>(
- pkg_nv: &NpmPackageNv,
- bin_name: Option<&str>,
- bin: &'a Value,
-) -> Result<&'a str, AnyError> {
- let bin_entry = match bin {
- Value::String(_) => {
- if bin_name.is_some() && bin_name.unwrap() != pkg_nv.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 {
- o.get(&pkg_nv.name)
- }
- },
- _ => bail!("package '{}' did not have a bin property with a string or object value in its package.json", pkg_nv),
- };
- let bin_entry = match bin_entry {
- Some(e) => e,
- None => {
- let keys = bin
- .as_object()
- .map(|o| {
- o.keys()
- .map(|k| format!(" * npm:{pkg_nv}/{k}"))
- .collect::<Vec<_>>()
- })
- .unwrap_or_default();
- bail!(
- "package '{}' did not have a bin entry for '{}' in its package.json{}",
- pkg_nv,
- bin_name.unwrap_or(&pkg_nv.name),
- if keys.is_empty() {
- "".to_string()
- } else {
- format!("\n\nPossibilities:\n{}", keys.join("\n"))
- }
- )
- }
- };
- match bin_entry {
- Value::String(s) => Ok(s),
- _ => bail!(
- "package '{}' had a non-string sub property of bin in its package.json",
- pkg_nv,
- ),
- }
-}
-
-fn package_config_resolve<Fs: NodeFs>(
- package_subpath: &str,
- package_dir: &Path,
- referrer_kind: NodeModuleKind,
- conditions: &[&str],
- mode: NodeResolutionMode,
- npm_resolver: &dyn NpmResolver,
- permissions: &mut dyn NodePermissions,
-) -> Result<Option<PathBuf>, AnyError> {
- let package_json_path = package_dir.join("package.json");
- let referrer = ModuleSpecifier::from_directory_path(package_dir).unwrap();
- let package_config = PackageJson::load::<Fs>(
- npm_resolver,
- permissions,
- package_json_path.clone(),
- )?;
- if let Some(exports) = &package_config.exports {
- let result = package_exports_resolve::<Fs>(
- &package_json_path,
- package_subpath.to_string(),
- exports,
- &referrer,
- referrer_kind,
- conditions,
- mode,
- npm_resolver,
- permissions,
- );
- match result {
- Ok(found) => return Ok(Some(found)),
- Err(exports_err) => {
- if mode.is_types() && package_subpath == "." {
- if let Ok(Some(path)) =
- legacy_main_resolve::<Fs>(&package_config, referrer_kind, mode)
- {
- return Ok(Some(path));
- } else {
- return Ok(None);
- }
- }
- return Err(exports_err);
- }
- }
- }
- if package_subpath == "." {
- return legacy_main_resolve::<Fs>(&package_config, referrer_kind, mode);
- }
-
- Ok(Some(package_dir.join(package_subpath)))
-}
-
-fn finalize_resolution<Fs: NodeFs>(
- resolved: ModuleSpecifier,
- base: &ModuleSpecifier,
-) -> Result<ModuleSpecifier, AnyError> {
- 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)),
- ));
- }
-
- 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) = Fs::metadata(p) {
- (stats.is_dir, stats.is_file)
- } else {
- (false, false)
- };
- if is_dir {
- return Err(errors::err_unsupported_dir_import(
- resolved.as_str(),
- base.as_str(),
- ));
- } else if !is_file {
- return Err(errors::err_module_not_found(
- resolved.as_str(),
- base.as_str(),
- "module",
- ));
- }
-
- Ok(resolved)
-}
-
-fn to_file_path(url: &ModuleSpecifier) -> PathBuf {
- url
- .to_file_path()
- .unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {url}"))
-}
-
-fn to_file_path_string(url: &ModuleSpecifier) -> String {
- to_file_path(url).display().to_string()
-}
-
-fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool {
- if specifier.is_empty() {
- return false;
- }
-
- if specifier.starts_with('/') {
- return true;
- }
-
- is_relative_specifier(specifier)
-}
-
-// TODO(ry) We very likely have this utility function elsewhere in Deno.
-fn is_relative_specifier(specifier: &str) -> bool {
- let specifier_len = specifier.len();
- let specifier_chars: Vec<_> = specifier.chars().collect();
-
- if !specifier_chars.is_empty() && specifier_chars[0] == '.' {
- if specifier_len == 1 || specifier_chars[1] == '/' {
- return true;
- }
- if specifier_chars[1] == '.'
- && (specifier_len == 2 || specifier_chars[2] == '/')
- {
- return true;
- }
- }
- false
-}
-
-#[cfg(test)]
-mod tests {
- use deno_core::serde_json::json;
-
- use super::*;
-
- #[test]
- fn test_resolve_bin_entry_value() {
- // should resolve the specified value
- let value = json!({
- "bin1": "./value1",
- "bin2": "./value2",
- "test": "./value3",
- });
- assert_eq!(
- resolve_bin_entry_value(
- &NpmPackageNv::from_str("test@1.1.1").unwrap(),
- Some("bin1"),
- &value
- )
- .unwrap(),
- "./value1"
- );
-
- // should resolve the value with the same name when not specified
- assert_eq!(
- resolve_bin_entry_value(
- &NpmPackageNv::from_str("test@1.1.1").unwrap(),
- None,
- &value
- )
- .unwrap(),
- "./value3"
- );
-
- // should not resolve when specified value does not exist
- assert_eq!(
- resolve_bin_entry_value(
- &NpmPackageNv::from_str("test@1.1.1").unwrap(),
- Some("other"),
- &value
- )
- .err()
- .unwrap()
- .to_string(),
- concat!(
- "package 'test@1.1.1' did not have a bin entry for 'other' in its package.json\n",
- "\n",
- "Possibilities:\n",
- " * npm:test@1.1.1/bin1\n",
- " * npm:test@1.1.1/bin2\n",
- " * npm:test@1.1.1/test"
- )
- );
-
- // should not resolve when default value can't be determined
- assert_eq!(
- resolve_bin_entry_value(
- &NpmPackageNv::from_str("asdf@1.2.3").unwrap(),
- None,
- &value
- )
- .err()
- .unwrap()
- .to_string(),
- concat!(
- "package 'asdf@1.2.3' did not have a bin entry for 'asdf' in its package.json\n",
- "\n",
- "Possibilities:\n",
- " * npm:asdf@1.2.3/bin1\n",
- " * npm:asdf@1.2.3/bin2\n",
- " * npm:asdf@1.2.3/test"
- )
- );
-
- // should resolve since all the values are the same
- let value = json!({
- "bin1": "./value",
- "bin2": "./value",
- });
- assert_eq!(
- resolve_bin_entry_value(
- &NpmPackageNv::from_str("test@1.2.3").unwrap(),
- None,
- &value
- )
- .unwrap(),
- "./value"
- );
-
- // should not resolve when specified and is a string
- let value = json!("./value");
- assert_eq!(
- resolve_bin_entry_value(
- &NpmPackageNv::from_str("test@1.2.3").unwrap(),
- Some("path"),
- &value
- )
- .err()
- .unwrap()
- .to_string(),
- "package 'test@1.2.3' did not have a bin entry for 'path' in its package.json"
- );
- }
-}