diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2023-11-07 09:56:06 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-07 09:56:06 -0500 |
commit | 9201198efd6fb116585d4c26111669f4c1006e5d (patch) | |
tree | 746b2283da7edb457cea5eced2bc6cd7b42b8067 | |
parent | 50e4806a2db66be289aa123a0bfd8dd8688712ba (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.rs | 2 | ||||
-rw-r--r-- | cli/tests/integration/npm_tests.rs | 67 | ||||
-rw-r--r-- | ext/node/analyze.rs | 48 | ||||
-rw-r--r-- | ext/node/errors.rs | 1 | ||||
-rw-r--r-- | ext/node/lib.rs | 11 | ||||
-rw-r--r-- | ext/node/ops/require.rs | 4 | ||||
-rw-r--r-- | ext/node/package_json.rs | 9 |
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) } |