summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-11-07 09:56:06 -0500
committerGitHub <noreply@github.com>2023-11-07 09:56:06 -0500
commit9201198efd6fb116585d4c26111669f4c1006e5d (patch)
tree746b2283da7edb457cea5eced2bc6cd7b42b8067
parent50e4806a2db66be289aa123a0bfd8dd8688712ba (diff)
fix(node): inspect ancestor directories when resolving cjs re-exports during analysis (#21104)
If a CJS re-export can't be resolved, it will check the ancestor directories, which is more similar to what `require` does at runtime.
-rw-r--r--cli/args/mod.rs2
-rw-r--r--cli/tests/integration/npm_tests.rs67
-rw-r--r--ext/node/analyze.rs48
-rw-r--r--ext/node/errors.rs1
-rw-r--r--ext/node/lib.rs11
-rw-r--r--ext/node/ops/require.rs4
-rw-r--r--ext/node/package_json.rs9
7 files changed, 119 insertions, 23 deletions
diff --git a/cli/args/mod.rs b/cli/args/mod.rs
index 4acbb1763..9c113acd2 100644
--- a/cli/args/mod.rs
+++ b/cli/args/mod.rs
@@ -924,7 +924,7 @@ impl CliOptions {
}
pub fn has_node_modules_dir(&self) -> bool {
- self.maybe_node_modules_folder.is_some()
+ self.maybe_node_modules_folder.is_some() || self.unstable_byonm()
}
pub fn node_modules_dir_path(&self) -> Option<PathBuf> {
diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs
index 92af166e0..006180843 100644
--- a/cli/tests/integration/npm_tests.rs
+++ b/cli/tests/integration/npm_tests.rs
@@ -2483,6 +2483,73 @@ console.log(add(1, 2));
output.assert_matches_text("Check file:///[WILDCARD]/project-b/main.ts\n");
}
+#[test]
+pub fn byonm_cjs_export_analysis_require_re_export() {
+ let test_context = TestContextBuilder::for_npm().use_temp_cwd().build();
+ let dir = test_context.temp_dir();
+ dir.write(
+ "deno.json",
+ r#"{
+ "unstable": [
+ "byonm"
+ ]
+ }"#,
+ );
+
+ dir.write(
+ "package.json",
+ r#"{
+ "name": "test",
+ "packages": {
+ "my-package": "1.0.0"
+ }
+}
+"#,
+ );
+ dir.write(
+ "main.js",
+ "import { value1, value2 } from 'my-package';\nconsole.log(value1);\nconsole.log(value2)\n",
+ );
+
+ let node_modules_dir = dir.path().join("node_modules");
+
+ // create a package at node_modules/.multipart/name/nested without a package.json
+ {
+ let pkg_dir = node_modules_dir
+ .join(".multipart")
+ .join("name")
+ .join("nested");
+ pkg_dir.create_dir_all();
+ pkg_dir.join("index.js").write("module.exports.value1 = 5;");
+ }
+ // create a package at node_modules/.multipart/other with a package.json
+ {
+ let pkg_dir = node_modules_dir.join(".multipart").join("other");
+ pkg_dir.create_dir_all();
+ pkg_dir.join("index.js").write("module.exports.value2 = 6;");
+ }
+ // create a package at node_modules/my-package that requires them both
+ {
+ let pkg_dir = node_modules_dir.join("my-package");
+ pkg_dir.create_dir_all();
+ pkg_dir.join("package.json").write_json(&json!({
+ "name": "my-package",
+ "version": "1.0.0",
+ }));
+ pkg_dir
+ .join("index.js")
+ .write("module.exports = { ...require('.multipart/name/nested/index'), ...require('.multipart/other/index.js') }");
+ }
+
+ // the cjs export analysis was preivously failing, but it should
+ // resolve these exports similar to require
+ let output = test_context
+ .new_command()
+ .args("run --allow-read main.js")
+ .run();
+ output.assert_matches_text("5\n6\n");
+}
+
itest!(imports_package_json {
args: "run --node-modules-dir=false npm/imports_package_json/main.js",
output: "npm/imports_package_json/main.out",
diff --git a/ext/node/analyze.rs b/ext/node/analyze.rs
index c9b23211e..994295578 100644
--- a/ext/node/analyze.rs
+++ b/ext/node/analyze.rs
@@ -193,7 +193,6 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
}
// We've got a bare specifier or maybe bare_specifier/blah.js"
-
let (package_specifier, package_subpath) =
parse_specifier(specifier).unwrap();
@@ -205,14 +204,13 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
)?;
let package_json_path = module_dir.join("package.json");
- if self.fs.exists_sync(&package_json_path) {
- let package_json = PackageJson::load(
- &*self.fs,
- &*self.npm_resolver,
- permissions,
- package_json_path.clone(),
- )?;
-
+ let package_json = PackageJson::load(
+ &*self.fs,
+ &*self.npm_resolver,
+ permissions,
+ package_json_path.clone(),
+ )?;
+ if package_json.exists {
if let Some(exports) = &package_json.exports {
return self.node_resolver.package_exports_resolve(
&package_json_path,
@@ -232,13 +230,13 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
if self.fs.is_dir_sync(&d) {
// subdir might have a package.json that specifies the entrypoint
let package_json_path = d.join("package.json");
- if self.fs.exists_sync(&package_json_path) {
- let package_json = PackageJson::load(
- &*self.fs,
- &*self.npm_resolver,
- permissions,
- package_json_path,
- )?;
+ let package_json = PackageJson::load(
+ &*self.fs,
+ &*self.npm_resolver,
+ permissions,
+ package_json_path,
+ )?;
+ if package_json.exists {
if let Some(main) = package_json.main(NodeModuleKind::Cjs) {
return Ok(d.join(main).clean());
}
@@ -253,6 +251,24 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
return Ok(module_dir.join("index.js").clean());
}
}
+
+ // as a fallback, attempt to resolve it via the ancestor directories
+ let mut last = referrer_path.as_path();
+ while let Some(parent) = last.parent() {
+ if !self.npm_resolver.in_npm_package_at_dir_path(parent) {
+ break;
+ }
+ let path = if parent.ends_with("node_modules") {
+ parent.join(specifier)
+ } else {
+ parent.join("node_modules").join(specifier)
+ };
+ if let Ok(path) = self.file_extension_probe(path, &referrer_path) {
+ return Ok(path);
+ }
+ last = parent;
+ }
+
Err(not_found(specifier, &referrer_path))
}
diff --git a/ext/node/errors.rs b/ext/node/errors.rs
index e07a8347a..f34707c66 100644
--- a/ext/node/errors.rs
+++ b/ext/node/errors.rs
@@ -45,7 +45,6 @@ pub fn err_invalid_package_config(
generic_error(msg)
}
-#[allow(unused)]
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}\""
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 7a43fc4d4..cb395ad9a 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -83,7 +83,16 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool;
- fn in_npm_package_at_path(&self, path: &Path) -> bool {
+ fn in_npm_package_at_dir_path(&self, path: &Path) -> bool {
+ let specifier =
+ match ModuleSpecifier::from_directory_path(path.to_path_buf().clean()) {
+ Ok(p) => p,
+ Err(_) => return false,
+ };
+ self.in_npm_package(&specifier)
+ }
+
+ fn in_npm_package_at_file_path(&self, path: &Path) -> bool {
let specifier =
match ModuleSpecifier::from_file_path(path.to_path_buf().clean()) {
Ok(p) => p,
diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs
index ceb771bf9..8010558c5 100644
--- a/ext/node/ops/require.rs
+++ b/ext/node/ops/require.rs
@@ -209,7 +209,7 @@ pub fn op_require_is_deno_dir_package(
#[string] path: String,
) -> bool {
let resolver = state.borrow::<NpmResolverRc>();
- resolver.in_npm_package_at_path(&PathBuf::from(path))
+ resolver.in_npm_package_at_file_path(&PathBuf::from(path))
}
#[op2]
@@ -484,7 +484,7 @@ where
let permissions = state.borrow::<P>();
let pkg_path = if npm_resolver
- .in_npm_package_at_path(&PathBuf::from(&modules_path))
+ .in_npm_package_at_file_path(&PathBuf::from(&modules_path))
&& !uses_local_node_modules_dir
{
modules_path
diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs
index 104c87390..035d26ed8 100644
--- a/ext/node/package_json.rs
+++ b/ext/node/package_json.rs
@@ -110,8 +110,13 @@ impl PackageJson {
path: PathBuf,
source: String,
) -> Result<PackageJson, AnyError> {
- let package_json: Value = serde_json::from_str(&source)
- .map_err(|err| anyhow::anyhow!("malformed package.json {}", err))?;
+ let package_json: Value = serde_json::from_str(&source).map_err(|err| {
+ anyhow::anyhow!(
+ "malformed package.json: {}\n at {}",
+ err,
+ path.display()
+ )
+ })?;
Self::load_from_value(path, package_json)
}