diff options
Diffstat (limited to 'cli/resolver.rs')
-rw-r--r-- | cli/resolver.rs | 341 |
1 files changed, 144 insertions, 197 deletions
diff --git a/cli/resolver.rs b/cli/resolver.rs index 9305cd1c9..26cf16ba9 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -4,7 +4,10 @@ use async_trait::async_trait; use dashmap::DashMap; use dashmap::DashSet; use deno_ast::MediaType; -use deno_config::package_json::PackageJsonDeps; +use deno_config::package_json::PackageJsonDepValue; +use deno_config::workspace::MappedResolution; +use deno_config::workspace::MappedResolutionError; +use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -30,14 +33,12 @@ use deno_runtime::deno_node::PackageJson; use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; -use import_map::ImportMap; use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use crate::args::JsxImportSourceConfig; -use crate::args::PackageJsonDepsProvider; use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::colors; use crate::node::CliNodeCodeTranslator; @@ -128,15 +129,31 @@ impl CliNodeResolver { referrer: &ModuleSpecifier, mode: NodeResolutionMode, ) -> Result<NodeResolution, AnyError> { - let package_folder = self - .npm_resolver - .resolve_pkg_folder_from_deno_module_req(req_ref.req(), referrer)?; - let maybe_resolution = self.resolve_package_sub_path_from_deno_module( - &package_folder, + self.resolve_req_with_sub_path( + req_ref.req(), req_ref.sub_path(), referrer, mode, - )?; + ) + } + + pub fn resolve_req_with_sub_path( + &self, + req: &PackageReq, + sub_path: Option<&str>, + referrer: &ModuleSpecifier, + mode: NodeResolutionMode, + ) -> Result<NodeResolution, AnyError> { + let package_folder = self + .npm_resolver + .resolve_pkg_folder_from_deno_module_req(req, referrer)?; + let maybe_resolution = self + .maybe_resolve_package_sub_path_from_deno_module( + &package_folder, + sub_path, + referrer, + mode, + )?; match maybe_resolution { Some(resolution) => Ok(resolution), None => { @@ -150,8 +167,9 @@ impl CliNodeResolver { } } Err(anyhow!( - "Failed resolving package subpath for '{}' in '{}'.", - req_ref, + "Failed resolving '{}{}' in '{}'.", + req, + sub_path.map(|s| format!("/{}", s)).unwrap_or_default(), package_folder.display() )) } @@ -164,6 +182,31 @@ impl CliNodeResolver { sub_path: Option<&str>, referrer: &ModuleSpecifier, mode: NodeResolutionMode, + ) -> Result<NodeResolution, AnyError> { + self + .maybe_resolve_package_sub_path_from_deno_module( + package_folder, + sub_path, + referrer, + mode, + )? + .ok_or_else(|| { + anyhow!( + "Failed resolving '{}' in '{}'.", + sub_path + .map(|s| format!("/{}", s)) + .unwrap_or_else(|| ".".to_string()), + package_folder.display(), + ) + }) + } + + pub fn maybe_resolve_package_sub_path_from_deno_module( + &self, + package_folder: &Path, + sub_path: Option<&str>, + referrer: &ModuleSpecifier, + mode: NodeResolutionMode, ) -> Result<Option<NodeResolution>, AnyError> { self.handle_node_resolve_result( self.node_resolver.resolve_package_subpath_from_deno_module( @@ -350,120 +393,39 @@ impl CjsResolutionStore { } } -/// Result of checking if a specifier is mapped via -/// an import map or package.json. -pub enum MappedResolution { - None, - PackageJson(ModuleSpecifier), - ImportMap(ModuleSpecifier), -} - -impl MappedResolution { - pub fn into_specifier(self) -> Option<ModuleSpecifier> { - match self { - MappedResolution::None => Option::None, - MappedResolution::PackageJson(specifier) => Some(specifier), - MappedResolution::ImportMap(specifier) => Some(specifier), - } - } -} - -/// Resolver for specifiers that could be mapped via an -/// import map or package.json. -#[derive(Debug)] -pub struct MappedSpecifierResolver { - maybe_import_map: Option<Arc<ImportMap>>, - package_json_deps_provider: Arc<PackageJsonDepsProvider>, -} - -impl MappedSpecifierResolver { - pub fn new( - maybe_import_map: Option<Arc<ImportMap>>, - package_json_deps_provider: Arc<PackageJsonDepsProvider>, - ) -> Self { - Self { - maybe_import_map, - package_json_deps_provider, - } - } - - pub fn resolve( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - ) -> Result<MappedResolution, AnyError> { - // attempt to resolve with the import map first - let maybe_import_map_err = match self - .maybe_import_map - .as_ref() - .map(|import_map| import_map.resolve(specifier, referrer)) - { - Some(Ok(value)) => return Ok(MappedResolution::ImportMap(value)), - Some(Err(err)) => Some(err), - None => None, - }; - - // then with package.json - if let Some(deps) = self.package_json_deps_provider.deps() { - if let Some(specifier) = resolve_package_json_dep(specifier, deps)? { - return Ok(MappedResolution::PackageJson(specifier)); - } - } - - // otherwise, surface the import map error or try resolving when has no import map - if let Some(err) = maybe_import_map_err { - Err(err.into()) - } else { - Ok(MappedResolution::None) - } - } -} - /// A resolver that takes care of resolution, taking into account loaded /// import map, JSX settings. #[derive(Debug)] pub struct CliGraphResolver { + node_resolver: Option<Arc<CliNodeResolver>>, + npm_resolver: Option<Arc<dyn CliNpmResolver>>, sloppy_imports_resolver: Option<SloppyImportsResolver>, - mapped_specifier_resolver: MappedSpecifierResolver, + workspace_resolver: Arc<WorkspaceResolver>, maybe_default_jsx_import_source: Option<String>, maybe_default_jsx_import_source_types: Option<String>, maybe_jsx_import_source_module: Option<String>, maybe_vendor_specifier: Option<ModuleSpecifier>, - node_resolver: Option<Arc<CliNodeResolver>>, - npm_resolver: Option<Arc<dyn CliNpmResolver>>, found_package_json_dep_flag: AtomicFlag, bare_node_builtins_enabled: bool, } pub struct CliGraphResolverOptions<'a> { - pub sloppy_imports_resolver: Option<SloppyImportsResolver>, pub node_resolver: Option<Arc<CliNodeResolver>>, pub npm_resolver: Option<Arc<dyn CliNpmResolver>>, - pub package_json_deps_provider: Arc<PackageJsonDepsProvider>, + pub sloppy_imports_resolver: Option<SloppyImportsResolver>, + pub workspace_resolver: Arc<WorkspaceResolver>, + pub bare_node_builtins_enabled: bool, pub maybe_jsx_import_source_config: Option<JsxImportSourceConfig>, - pub maybe_import_map: Option<Arc<ImportMap>>, pub maybe_vendor_dir: Option<&'a PathBuf>, - pub bare_node_builtins_enabled: bool, } impl CliGraphResolver { pub fn new(options: CliGraphResolverOptions) -> Self { - let is_byonm = options - .npm_resolver - .as_ref() - .map(|n| n.as_byonm().is_some()) - .unwrap_or(false); Self { + node_resolver: options.node_resolver, + npm_resolver: options.npm_resolver, sloppy_imports_resolver: options.sloppy_imports_resolver, - mapped_specifier_resolver: MappedSpecifierResolver::new( - options.maybe_import_map, - if is_byonm { - // don't resolve from the root package.json deps for byonm - Arc::new(PackageJsonDepsProvider::new(None)) - } else { - options.package_json_deps_provider - }, - ), + workspace_resolver: options.workspace_resolver, maybe_default_jsx_import_source: options .maybe_jsx_import_source_config .as_ref() @@ -478,8 +440,6 @@ impl CliGraphResolver { maybe_vendor_specifier: options .maybe_vendor_dir .and_then(|v| ModuleSpecifier::from_directory_path(v).ok()), - node_resolver: options.node_resolver, - npm_resolver: options.npm_resolver, found_package_json_dep_flag: Default::default(), bare_node_builtins_enabled: options.bare_node_builtins_enabled, } @@ -497,6 +457,7 @@ impl CliGraphResolver { } } + // todo(dsherret): if we returned structured errors from the NodeResolver we wouldn't need this fn check_surface_byonm_node_error( &self, specifier: &str, @@ -561,22 +522,92 @@ impl Resolver for CliGraphResolver { let referrer = &referrer_range.specifier; let result: Result<_, ResolveError> = self - .mapped_specifier_resolver + .workspace_resolver .resolve(specifier, referrer) - .map_err(|err| err.into()) - .and_then(|resolution| match resolution { - MappedResolution::ImportMap(specifier) => Ok(specifier), - MappedResolution::PackageJson(specifier) => { + .map_err(|err| match err { + MappedResolutionError::Specifier(err) => ResolveError::Specifier(err), + MappedResolutionError::ImportMap(err) => { + ResolveError::Other(err.into()) + } + }); + let result = match result { + Ok(resolution) => match resolution { + MappedResolution::Normal(specifier) + | MappedResolution::ImportMap(specifier) => Ok(specifier), + // todo(dsherret): for byonm it should do resolution solely based on + // the referrer and not the package.json + MappedResolution::PackageJson { + dep_result, + alias, + sub_path, + .. + } => { // found a specifier in the package.json, so mark that // we need to do an "npm install" later self.found_package_json_dep_flag.raise(); - Ok(specifier) + + dep_result + .as_ref() + .map_err(|e| ResolveError::Other(e.clone().into())) + .and_then(|dep| match dep { + PackageJsonDepValue::Req(req) => { + ModuleSpecifier::parse(&format!( + "npm:{}{}", + req, + sub_path.map(|s| format!("/{}", s)).unwrap_or_default() + )) + .map_err(|e| ResolveError::Other(e.into())) + } + PackageJsonDepValue::Workspace(version_req) => self + .workspace_resolver + .resolve_workspace_pkg_json_folder_for_pkg_json_dep( + alias, + version_req, + ) + .map_err(|e| ResolveError::Other(e.into())) + .and_then(|pkg_folder| { + Ok( + self + .node_resolver + .as_ref() + .unwrap() + .resolve_package_sub_path_from_deno_module( + pkg_folder, + sub_path.as_deref(), + referrer, + to_node_mode(mode), + )? + .into_url(), + ) + }), + }) } - MappedResolution::None => { - deno_graph::resolve_import(specifier, &referrer_range.specifier) - .map_err(|err| err.into()) + }, + Err(err) => Err(err), + }; + + // check if it's an npm specifier that resolves to a workspace member + if let Some(node_resolver) = &self.node_resolver { + if let Ok(specifier) = &result { + if let Ok(req_ref) = NpmPackageReqReference::from_specifier(specifier) { + if let Some(pkg_folder) = self + .workspace_resolver + .resolve_workspace_pkg_json_folder_for_npm_specifier(req_ref.req()) + { + return Ok( + node_resolver + .resolve_package_sub_path_from_deno_module( + pkg_folder, + req_ref.sub_path(), + referrer, + to_node_mode(mode), + )? + .into_url(), + ); + } } - }); + } + } // do sloppy imports resolution if enabled let result = @@ -733,28 +764,6 @@ fn sloppy_imports_resolve( resolution.into_specifier().into_owned() } -fn resolve_package_json_dep( - specifier: &str, - deps: &PackageJsonDeps, -) -> Result<Option<ModuleSpecifier>, AnyError> { - for (bare_specifier, req_result) in deps { - if specifier.starts_with(bare_specifier) { - let path = &specifier[bare_specifier.len()..]; - if path.is_empty() || path.starts_with('/') { - let req = req_result.as_ref().map_err(|err| { - anyhow!( - "Parsing version constraints in the application-level package.json is more strict at the moment.\n\n{:#}", - err.clone() - ) - })?; - return Ok(Some(ModuleSpecifier::parse(&format!("npm:{req}{path}"))?)); - } - } - } - - Ok(None) -} - #[derive(Debug)] pub struct WorkerCliNpmGraphResolver<'a> { npm_resolver: Option<&'a Arc<dyn CliNpmResolver>>, @@ -1266,73 +1275,11 @@ impl SloppyImportsResolver { #[cfg(test)] mod test { - use std::collections::BTreeMap; - use test_util::TestContext; use super::*; #[test] - fn test_resolve_package_json_dep() { - fn resolve( - specifier: &str, - deps: &BTreeMap<String, PackageReq>, - ) -> Result<Option<String>, String> { - let deps = deps - .iter() - .map(|(key, value)| (key.to_string(), Ok(value.clone()))) - .collect(); - resolve_package_json_dep(specifier, &deps) - .map(|s| s.map(|s| s.to_string())) - .map_err(|err| err.to_string()) - } - - let deps = BTreeMap::from([ - ( - "package".to_string(), - PackageReq::from_str("package@1.0").unwrap(), - ), - ( - "package-alias".to_string(), - PackageReq::from_str("package@^1.2").unwrap(), - ), - ( - "@deno/test".to_string(), - PackageReq::from_str("@deno/test@~0.2").unwrap(), - ), - ]); - - assert_eq!( - resolve("package", &deps).unwrap(), - Some("npm:package@1.0".to_string()), - ); - assert_eq!( - resolve("package/some_path.ts", &deps).unwrap(), - Some("npm:package@1.0/some_path.ts".to_string()), - ); - - assert_eq!( - resolve("@deno/test", &deps).unwrap(), - Some("npm:@deno/test@~0.2".to_string()), - ); - assert_eq!( - resolve("@deno/test/some_path.ts", &deps).unwrap(), - Some("npm:@deno/test@~0.2/some_path.ts".to_string()), - ); - // matches the start, but doesn't have the same length or a path - assert_eq!(resolve("@deno/testing", &deps).unwrap(), None,); - - // alias - assert_eq!( - resolve("package-alias", &deps).unwrap(), - Some("npm:package@^1.2".to_string()), - ); - - // non-existent bare specifier - assert_eq!(resolve("non-existent", &deps).unwrap(), None); - } - - #[test] fn test_unstable_sloppy_imports() { fn resolve(specifier: &ModuleSpecifier) -> SloppyImportsResolution { SloppyImportsResolver::new(Arc::new(deno_fs::RealFs)) |