1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

refactor(node): combine node resolution code for resolving a package subpath from external code (#20791)

We had two methods that did the same functionality.
This commit is contained in:
David Sherret 2023-10-04 23:05:12 -04:00 committed by GitHub
parent 64f9155126
commit 1ff525e25b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 119 additions and 126 deletions

View file

@ -1,7 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -13,6 +11,7 @@ use deno_runtime::deno_node::PackageJson;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use deno_semver::VersionReq; use deno_semver::VersionReq;
use deno_semver::VersionReqSpecifierParseError; use deno_semver::VersionReqSpecifierParseError;
use indexmap::IndexMap;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error, Clone)] #[derive(Debug, Error, Clone)]
@ -26,7 +25,7 @@ pub enum PackageJsonDepValueParseError {
} }
pub type PackageJsonDeps = pub type PackageJsonDeps =
BTreeMap<String, Result<PackageReq, PackageJsonDepValueParseError>>; IndexMap<String, Result<PackageReq, PackageJsonDepValueParseError>>;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct PackageJsonDepsProvider(Option<PackageJsonDeps>); pub struct PackageJsonDepsProvider(Option<PackageJsonDeps>);
@ -92,24 +91,25 @@ pub fn get_local_package_json_version_reqs(
} }
fn insert_deps( fn insert_deps(
deps: Option<&HashMap<String, String>>, deps: Option<&IndexMap<String, String>>,
result: &mut PackageJsonDeps, result: &mut PackageJsonDeps,
) { ) {
if let Some(deps) = deps { if let Some(deps) = deps {
for (key, value) in deps { for (key, value) in deps {
result.insert(key.to_string(), parse_entry(key, value)); result
.entry(key.to_string())
.or_insert_with(|| parse_entry(key, value));
} }
} }
} }
let deps = package_json.dependencies.as_ref(); let deps = package_json.dependencies.as_ref();
let dev_deps = package_json.dev_dependencies.as_ref(); let dev_deps = package_json.dev_dependencies.as_ref();
let mut result = BTreeMap::new(); let mut result = IndexMap::new();
// insert the dev dependencies first so the dependencies will // favors the deps over dev_deps
// take priority and overwrite any collisions
insert_deps(dev_deps, &mut result);
insert_deps(deps, &mut result); insert_deps(deps, &mut result);
insert_deps(dev_deps, &mut result);
result result
} }
@ -182,7 +182,7 @@ mod test {
fn get_local_package_json_version_reqs_for_tests( fn get_local_package_json_version_reqs_for_tests(
package_json: &PackageJson, package_json: &PackageJson,
) -> BTreeMap<String, Result<PackageReq, String>> { ) -> IndexMap<String, Result<PackageReq, String>> {
get_local_package_json_version_reqs(package_json) get_local_package_json_version_reqs(package_json)
.into_iter() .into_iter()
.map(|(k, v)| { .map(|(k, v)| {
@ -194,17 +194,17 @@ mod test {
}, },
) )
}) })
.collect::<BTreeMap<_, _>>() .collect::<IndexMap<_, _>>()
} }
#[test] #[test]
fn test_get_local_package_json_version_reqs() { fn test_get_local_package_json_version_reqs() {
let mut package_json = PackageJson::empty(PathBuf::from("/package.json")); let mut package_json = PackageJson::empty(PathBuf::from("/package.json"));
package_json.dependencies = Some(HashMap::from([ package_json.dependencies = Some(IndexMap::from([
("test".to_string(), "^1.2".to_string()), ("test".to_string(), "^1.2".to_string()),
("other".to_string(), "npm:package@~1.3".to_string()), ("other".to_string(), "npm:package@~1.3".to_string()),
])); ]));
package_json.dev_dependencies = Some(HashMap::from([ package_json.dev_dependencies = Some(IndexMap::from([
("package_b".to_string(), "~2.2".to_string()), ("package_b".to_string(), "~2.2".to_string()),
// should be ignored // should be ignored
("other".to_string(), "^3.2".to_string()), ("other".to_string(), "^3.2".to_string()),
@ -212,7 +212,7 @@ mod test {
let deps = get_local_package_json_version_reqs_for_tests(&package_json); let deps = get_local_package_json_version_reqs_for_tests(&package_json);
assert_eq!( assert_eq!(
deps, deps,
BTreeMap::from([ IndexMap::from([
( (
"test".to_string(), "test".to_string(),
Ok(PackageReq::from_str("test@^1.2").unwrap()) Ok(PackageReq::from_str("test@^1.2").unwrap())
@ -232,14 +232,14 @@ mod test {
#[test] #[test]
fn test_get_local_package_json_version_reqs_errors_non_npm_specifier() { fn test_get_local_package_json_version_reqs_errors_non_npm_specifier() {
let mut package_json = PackageJson::empty(PathBuf::from("/package.json")); let mut package_json = PackageJson::empty(PathBuf::from("/package.json"));
package_json.dependencies = Some(HashMap::from([( package_json.dependencies = Some(IndexMap::from([(
"test".to_string(), "test".to_string(),
"1.x - 1.3".to_string(), "1.x - 1.3".to_string(),
)])); )]));
let map = get_local_package_json_version_reqs_for_tests(&package_json); let map = get_local_package_json_version_reqs_for_tests(&package_json);
assert_eq!( assert_eq!(
map, map,
BTreeMap::from([( IndexMap::from([(
"test".to_string(), "test".to_string(),
Err( Err(
concat!( concat!(
@ -256,7 +256,7 @@ mod test {
#[test] #[test]
fn test_get_local_package_json_version_reqs_skips_certain_specifiers() { fn test_get_local_package_json_version_reqs_skips_certain_specifiers() {
let mut package_json = PackageJson::empty(PathBuf::from("/package.json")); let mut package_json = PackageJson::empty(PathBuf::from("/package.json"));
package_json.dependencies = Some(HashMap::from([ package_json.dependencies = Some(IndexMap::from([
("test".to_string(), "1".to_string()), ("test".to_string(), "1".to_string()),
("work-test".to_string(), "workspace:1.1.1".to_string()), ("work-test".to_string(), "workspace:1.1.1".to_string()),
("file-test".to_string(), "file:something".to_string()), ("file-test".to_string(), "file:something".to_string()),
@ -267,7 +267,7 @@ mod test {
let result = get_local_package_json_version_reqs_for_tests(&package_json); let result = get_local_package_json_version_reqs_for_tests(&package_json);
assert_eq!( assert_eq!(
result, result,
BTreeMap::from([ IndexMap::from([
( (
"file-test".to_string(), "file-test".to_string(),
Err("Not implemented scheme 'file'".to_string()), Err("Not implemented scheme 'file'".to_string()),

View file

@ -47,7 +47,6 @@ use indexmap::IndexMap;
use lsp::Url; use lsp::Url;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use package_json::PackageJsonDepsProvider; use package_json::PackageJsonDepsProvider;
use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::collections::VecDeque; use std::collections::VecDeque;
@ -1267,8 +1266,8 @@ impl Documents {
if let Some(package_json_deps) = &maybe_package_json_deps { if let Some(package_json_deps) = &maybe_package_json_deps {
// We need to ensure the hashing is deterministic so explicitly type // We need to ensure the hashing is deterministic so explicitly type
// this in order to catch if the type of package_json_deps ever changes // this in order to catch if the type of package_json_deps ever changes
// from a sorted/deterministic BTreeMap to something else. // from a sorted/deterministic IndexMap to something else.
let package_json_deps: &BTreeMap<_, _> = *package_json_deps; let package_json_deps: &IndexMap<_, _> = *package_json_deps;
for (key, value) in package_json_deps { for (key, value) in package_json_deps {
hasher.write_hashable(key); hasher.write_hashable(key);
match value { match value {

View file

@ -216,7 +216,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
if let Some(exports) = &package_json.exports { if let Some(exports) = &package_json.exports {
return self.node_resolver.package_exports_resolve( return self.node_resolver.package_exports_resolve(
&package_json_path, &package_json_path,
package_subpath, &package_subpath,
exports, exports,
referrer, referrer,
NodeModuleKind::Esm, NodeModuleKind::Esm,

View file

@ -53,8 +53,8 @@ pub fn err_module_not_found(path: &str, base: &str, typ: &str) -> AnyError {
} }
pub fn err_invalid_package_target( pub fn err_invalid_package_target(
pkg_path: String, pkg_path: &str,
key: String, key: &str,
target: String, target: String,
is_import: bool, is_import: bool,
maybe_referrer: Option<String>, maybe_referrer: Option<String>,
@ -95,7 +95,7 @@ pub fn err_invalid_package_target(
pub fn err_package_path_not_exported( pub fn err_package_path_not_exported(
mut pkg_path: String, mut pkg_path: String,
subpath: String, subpath: &str,
maybe_referrer: Option<String>, maybe_referrer: Option<String>,
mode: NodeResolutionMode, mode: NodeResolutionMode,
) -> AnyError { ) -> AnyError {
@ -178,7 +178,7 @@ mod test {
assert_eq!( assert_eq!(
err_package_path_not_exported( err_package_path_not_exported(
"test_path".to_string(), "test_path".to_string(),
"./jsx-runtime".to_string(), "./jsx-runtime",
None, None,
NodeResolutionMode::Types, NodeResolutionMode::Types,
) )
@ -188,7 +188,7 @@ mod test {
assert_eq!( assert_eq!(
err_package_path_not_exported( err_package_path_not_exported(
"test_path".to_string(), "test_path".to_string(),
".".to_string(), ".",
None, None,
NodeResolutionMode::Types, NodeResolutionMode::Types,
) )

View file

@ -423,7 +423,7 @@ where
node_resolver node_resolver
.package_exports_resolve( .package_exports_resolve(
&pkg.path, &pkg.path,
expansion, &expansion,
exports, exports,
&referrer, &referrer,
NodeModuleKind::Cjs, NodeModuleKind::Cjs,
@ -507,7 +507,7 @@ where
node_resolver node_resolver
.package_exports_resolve( .package_exports_resolve(
&pkg.path, &pkg.path,
format!(".{expansion}"), &format!(".{expansion}"),
exports, exports,
&referrer, &referrer,
NodeModuleKind::Cjs, NodeModuleKind::Cjs,

View file

@ -36,8 +36,8 @@ pub struct PackageJson {
pub path: PathBuf, pub path: PathBuf,
pub typ: String, pub typ: String,
pub types: Option<String>, pub types: Option<String>,
pub dependencies: Option<HashMap<String, String>>, pub dependencies: Option<IndexMap<String, String>>,
pub dev_dependencies: Option<HashMap<String, String>>, pub dev_dependencies: Option<IndexMap<String, String>>,
pub scripts: Option<IndexMap<String, String>>, pub scripts: Option<IndexMap<String, String>>,
} }
@ -146,7 +146,7 @@ impl PackageJson {
let dependencies = package_json.get("dependencies").and_then(|d| { let dependencies = package_json.get("dependencies").and_then(|d| {
if d.is_object() { if d.is_object() {
let deps: HashMap<String, String> = let deps: IndexMap<String, String> =
serde_json::from_value(d.to_owned()).unwrap(); serde_json::from_value(d.to_owned()).unwrap();
Some(deps) Some(deps)
} else { } else {
@ -155,7 +155,7 @@ impl PackageJson {
}); });
let dev_dependencies = package_json.get("devDependencies").and_then(|d| { let dev_dependencies = package_json.get("devDependencies").and_then(|d| {
if d.is_object() { if d.is_object() {
let deps: HashMap<String, String> = let deps: IndexMap<String, String> =
serde_json::from_value(d.to_owned()).unwrap(); serde_json::from_value(d.to_owned()).unwrap();
Some(deps) Some(deps)
} else { } else {

View file

@ -327,18 +327,24 @@ impl NodeResolver {
pub fn resolve_npm_reference( pub fn resolve_npm_reference(
&self, &self,
package_folder: &Path, package_dir: &Path,
sub_path: Option<&str>, package_subpath: Option<&str>,
mode: NodeResolutionMode, mode: NodeResolutionMode,
permissions: &dyn NodePermissions, permissions: &dyn NodePermissions,
) -> Result<Option<NodeResolution>, AnyError> { ) -> Result<Option<NodeResolution>, AnyError> {
let package_json_path = package_dir.join("package.json");
let referrer = ModuleSpecifier::from_directory_path(package_dir).unwrap();
let package_json =
self.load_package_json(permissions, package_json_path.clone())?;
let node_module_kind = NodeModuleKind::Esm; let node_module_kind = NodeModuleKind::Esm;
let maybe_resolved_path = self let package_subpath = package_subpath
.package_config_resolve(
&sub_path
.map(|s| format!("./{s}")) .map(|s| format!("./{s}"))
.unwrap_or_else(|| ".".to_string()), .unwrap_or_else(|| ".".to_string());
package_folder, let maybe_resolved_path = self
.resolve_package_subpath(
&package_json,
&package_subpath,
&referrer,
node_module_kind, node_module_kind,
DEFAULT_CONDITIONS, DEFAULT_CONDITIONS,
mode, mode,
@ -346,8 +352,9 @@ impl NodeResolver {
) )
.with_context(|| { .with_context(|| {
format!( format!(
"Failed resolving package config for '{}'", "Failed resolving package subpath '{}' for '{}'",
package_folder.join("package.json").display() package_subpath,
package_json.path.display()
) )
})?; })?;
let resolved_path = match maybe_resolved_path { let resolved_path = match maybe_resolved_path {
@ -440,53 +447,6 @@ impl NodeResolver {
} }
} }
fn package_config_resolve(
&self,
package_subpath: &str,
package_dir: &Path,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
permissions: &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 =
self.load_package_json(permissions, package_json_path.clone())?;
if let Some(exports) = &package_config.exports {
let result = self.package_exports_resolve(
&package_json_path,
package_subpath.to_string(),
exports,
&referrer,
referrer_kind,
conditions,
mode,
permissions,
);
match result {
Ok(found) => return Ok(Some(found)),
Err(exports_err) => {
if mode.is_types() && package_subpath == "." {
if let Ok(Some(path)) =
self.legacy_main_resolve(&package_config, referrer_kind, mode)
{
return Ok(Some(path));
} else {
return Ok(None);
}
}
return Err(exports_err);
}
}
}
if package_subpath == "." {
return self.legacy_main_resolve(&package_config, referrer_kind, mode);
}
Ok(Some(package_dir.join(package_subpath)))
}
/// Checks if the resolved file has a corresponding declaration file. /// Checks if the resolved file has a corresponding declaration file.
pub(super) fn path_to_declaration_path( pub(super) fn path_to_declaration_path(
&self, &self,
@ -592,8 +552,8 @@ impl NodeResolver {
let maybe_resolved = self.resolve_package_target( let maybe_resolved = self.resolve_package_target(
package_json_path.as_ref().unwrap(), package_json_path.as_ref().unwrap(),
imports.get(name).unwrap().to_owned(), imports.get(name).unwrap().to_owned(),
"".to_string(), "",
name.to_string(), name,
referrer, referrer,
referrer_kind, referrer_kind,
false, false,
@ -635,8 +595,8 @@ impl NodeResolver {
let maybe_resolved = self.resolve_package_target( let maybe_resolved = self.resolve_package_target(
package_json_path.as_ref().unwrap(), package_json_path.as_ref().unwrap(),
target, target,
best_match_subpath.unwrap(), &best_match_subpath.unwrap(),
best_match.to_string(), best_match,
referrer, referrer,
referrer_kind, referrer_kind,
true, true,
@ -665,8 +625,8 @@ impl NodeResolver {
fn resolve_package_target_string( fn resolve_package_target_string(
&self, &self,
target: String, target: String,
subpath: String, subpath: &str,
match_: String, match_: &str,
package_json_path: &Path, package_json_path: &Path,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, referrer_kind: NodeModuleKind,
@ -746,9 +706,9 @@ impl NodeResolver {
if subpath.is_empty() { if subpath.is_empty() {
return Ok(resolved_path); return Ok(resolved_path);
} }
if invalid_segment_re.is_match(&subpath) { if invalid_segment_re.is_match(subpath) {
let request = if pattern { let request = if pattern {
match_.replace('*', &subpath) match_.replace('*', subpath)
} else { } else {
format!("{match_}{subpath}") format!("{match_}{subpath}")
}; };
@ -767,7 +727,7 @@ impl NodeResolver {
}); });
return Ok(PathBuf::from(replaced.to_string())); return Ok(PathBuf::from(replaced.to_string()));
} }
Ok(resolved_path.join(&subpath).clean()) Ok(resolved_path.join(subpath).clean())
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -775,8 +735,8 @@ impl NodeResolver {
&self, &self,
package_json_path: &Path, package_json_path: &Path,
target: Value, target: Value,
subpath: String, subpath: &str,
package_subpath: String, package_subpath: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, referrer_kind: NodeModuleKind,
pattern: bool, pattern: bool,
@ -901,7 +861,7 @@ impl NodeResolver {
pub fn package_exports_resolve( pub fn package_exports_resolve(
&self, &self,
package_json_path: &Path, package_json_path: &Path,
package_subpath: String, package_subpath: &str,
package_exports: &Map<String, Value>, package_exports: &Map<String, Value>,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, referrer_kind: NodeModuleKind,
@ -909,16 +869,16 @@ impl NodeResolver {
mode: NodeResolutionMode, mode: NodeResolutionMode,
permissions: &dyn NodePermissions, permissions: &dyn NodePermissions,
) -> Result<PathBuf, AnyError> { ) -> Result<PathBuf, AnyError> {
if package_exports.contains_key(&package_subpath) if package_exports.contains_key(package_subpath)
&& package_subpath.find('*').is_none() && package_subpath.find('*').is_none()
&& !package_subpath.ends_with('/') && !package_subpath.ends_with('/')
{ {
let target = package_exports.get(&package_subpath).unwrap().to_owned(); let target = package_exports.get(package_subpath).unwrap().to_owned();
let resolved = self.resolve_package_target( let resolved = self.resolve_package_target(
package_json_path, package_json_path,
target, target,
"".to_string(), "",
package_subpath.to_string(), package_subpath,
referrer, referrer,
referrer_kind, referrer_kind,
false, false,
@ -977,8 +937,8 @@ impl NodeResolver {
let maybe_resolved = self.resolve_package_target( let maybe_resolved = self.resolve_package_target(
package_json_path, package_json_path,
target, target,
best_match_subpath.unwrap(), &best_match_subpath.unwrap(),
best_match.to_string(), best_match,
referrer, referrer,
referrer_kind, referrer_kind,
true, true,
@ -1032,7 +992,7 @@ impl NodeResolver {
return self return self
.package_exports_resolve( .package_exports_resolve(
&package_config.path, &package_config.path,
package_subpath, &package_subpath,
exports, exports,
referrer, referrer,
referrer_kind, referrer_kind,
@ -1065,9 +1025,30 @@ impl NodeResolver {
// Package match. // Package match.
let package_json = let package_json =
self.load_package_json(permissions, package_json_path)?; self.load_package_json(permissions, package_json_path)?;
self.resolve_package_subpath(
&package_json,
&package_subpath,
referrer,
referrer_kind,
conditions,
mode,
permissions,
)
}
#[allow(clippy::too_many_arguments)]
fn resolve_package_subpath(
&self,
package_json: &PackageJson,
package_subpath: &str,
referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
permissions: &dyn NodePermissions,
) -> Result<Option<PathBuf>, AnyError> {
if let Some(exports) = &package_json.exports { if let Some(exports) = &package_json.exports {
return self let result = self.package_exports_resolve(
.package_exports_resolve(
&package_json.path, &package_json.path,
package_subpath, package_subpath,
exports, exports,
@ -1076,15 +1057,28 @@ impl NodeResolver {
conditions, conditions,
mode, mode,
permissions, permissions,
) );
.map(Some); match result {
Ok(found) => return Ok(Some(found)),
Err(exports_err) => {
if mode.is_types() && package_subpath == "." {
if let Ok(Some(path)) =
self.legacy_main_resolve(package_json, referrer_kind, mode)
{
return Ok(Some(path));
} else {
return Ok(None);
}
}
return Err(exports_err);
}
}
} }
if package_subpath == "." { if package_subpath == "." {
return self.legacy_main_resolve(&package_json, referrer_kind, mode); return self.legacy_main_resolve(package_json, referrer_kind, mode);
} }
let file_path = package_json.path.parent().unwrap().join(&package_subpath); let file_path = package_json.path.parent().unwrap().join(package_subpath);
if mode.is_types() { if mode.is_types() {
let maybe_declaration_path = let maybe_declaration_path =
self.path_to_declaration_path(file_path, referrer_kind); self.path_to_declaration_path(file_path, referrer_kind);
@ -1443,14 +1437,14 @@ fn throw_import_not_defined(
} }
fn throw_invalid_package_target( fn throw_invalid_package_target(
subpath: String, subpath: &str,
target: String, target: String,
package_json_path: &Path, package_json_path: &Path,
internal: bool, internal: bool,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> AnyError { ) -> AnyError {
errors::err_invalid_package_target( errors::err_invalid_package_target(
package_json_path.parent().unwrap().display().to_string(), &package_json_path.parent().unwrap().display().to_string(),
subpath, subpath,
target, target,
internal, internal,
@ -1478,7 +1472,7 @@ fn throw_invalid_subpath(
} }
fn throw_exports_not_found( fn throw_exports_not_found(
subpath: String, subpath: &str,
package_json_path: &Path, package_json_path: &Path,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
mode: NodeResolutionMode, mode: NodeResolutionMode,