diff --git a/lib/lib.rs b/lib/lib.rs index b86d9ace6..2847a7455 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -296,6 +296,7 @@ pub fn js_parse_module( content.into(), maybe_resolver.as_ref().map(|r| r as &dyn Resolver), None, + None, ) { Ok(module) => { let serializer = diff --git a/src/graph.rs b/src/graph.rs index 82504f96e..523a812bd 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -822,6 +822,7 @@ impl GraphImport { referrer: &ModuleSpecifier, imports: Vec, maybe_resolver: Option<&dyn Resolver>, + maybe_npm_resolver: Option<&dyn NpmResolver>, ) -> Self { let dependencies = imports .into_iter() @@ -831,7 +832,8 @@ impl GraphImport { start: Position::zeroed(), end: Position::zeroed(), }; - let maybe_type = resolve(&import, referrer_range, maybe_resolver); + let maybe_type = + resolve(&import, referrer_range, maybe_resolver, maybe_npm_resolver); ( import, Dependency { @@ -1550,6 +1552,7 @@ fn resolve( specifier_text: &str, referrer_range: Range, maybe_resolver: Option<&dyn Resolver>, + maybe_npm_resolver: Option<&dyn NpmResolver>, ) -> Resolution { let response = if let Some(resolver) = maybe_resolver { resolver.resolve(specifier_text, &referrer_range.specifier) @@ -1557,6 +1560,24 @@ fn resolve( resolve_import(specifier_text, &referrer_range.specifier) .map_err(|err| err.into()) }; + use ResolveError::*; + use SpecifierError::*; + if let Err(Specifier(ImportPrefixMissing(_, _))) = response.as_ref() { + if let Some(npm_resolver) = maybe_npm_resolver { + if let Ok(specifier) = + ModuleSpecifier::parse(&format!("node:{}", specifier_text)) + { + if npm_resolver.resolve_builtin_node_module(&specifier).is_ok() { + npm_resolver.on_resolve_bare_builtin_node_module(specifier_text); + return Resolution::from_resolve_result( + Ok(specifier), + specifier_text, + referrer_range, + ); + } + } + } + } Resolution::from_resolve_result(response, specifier_text, referrer_range) } @@ -1620,6 +1641,7 @@ pub(crate) fn parse_module( module_analyzer: &dyn ModuleAnalyzer, is_root: bool, is_dynamic_branch: bool, + maybe_npm_resolver: Option<&dyn NpmResolver>, ) -> Result { let media_type = MediaType::from_specifier_and_headers(specifier, maybe_headers); @@ -1686,6 +1708,7 @@ pub(crate) fn parse_module( module_info, content, maybe_resolver, + maybe_npm_resolver, ))) } Err(diagnostic) => Err(ModuleGraphError::ModuleError( @@ -1708,6 +1731,7 @@ pub(crate) fn parse_module( module_info, content, maybe_resolver, + maybe_npm_resolver, ))) } Err(diagnostic) => Err(ModuleGraphError::ModuleError( @@ -1732,6 +1756,7 @@ pub(crate) fn parse_esm_module_from_module_info( module_info: ModuleInfo, source: Arc, maybe_resolver: Option<&dyn Resolver>, + maybe_npm_resolver: Option<&dyn NpmResolver>, ) -> EsmModule { let mut module = EsmModule::new(specifier.clone(), source); module.media_type = media_type; @@ -1747,8 +1772,12 @@ pub(crate) fn parse_esm_module_from_module_info( let range = Range::from_position_range(module.specifier.clone(), specifier.range); if dep.maybe_type.is_none() { - dep.maybe_type = - resolve(&specifier.text, range.clone(), maybe_resolver); + dep.maybe_type = resolve( + &specifier.text, + range.clone(), + maybe_resolver, + maybe_npm_resolver, + ); } dep.imports.push(Import { specifier: specifier.text, @@ -1761,8 +1790,12 @@ pub(crate) fn parse_esm_module_from_module_info( TypeScriptReference::Types(specifier) => { let range = Range::from_position_range(module.specifier.clone(), specifier.range); - let dep_resolution = - resolve(&specifier.text, range.clone(), maybe_resolver); + let dep_resolution = resolve( + &specifier.text, + range.clone(), + maybe_resolver, + maybe_npm_resolver, + ); if is_untyped(&module.media_type) { module.maybe_types_dependency = Some(TypesDependency { specifier: specifier.text.clone(), @@ -1823,8 +1856,12 @@ pub(crate) fn parse_esm_module_from_module_info( import_source.range, ); if dep.maybe_code.is_none() { - dep.maybe_code = - resolve(&specifier_text, range.clone(), maybe_resolver); + dep.maybe_code = resolve( + &specifier_text, + range.clone(), + maybe_resolver, + maybe_npm_resolver, + ); } dep.imports.push(Import { specifier: specifier_text, @@ -1845,7 +1882,12 @@ pub(crate) fn parse_esm_module_from_module_info( let range = Range::from_position_range(module.specifier.clone(), specifier.range); if dep.maybe_type.is_none() { - dep.maybe_type = resolve(&specifier.text, range.clone(), maybe_resolver); + dep.maybe_type = resolve( + &specifier.text, + range.clone(), + maybe_resolver, + maybe_npm_resolver, + ); } dep.imports.push(Import { specifier: specifier.text, @@ -1867,7 +1909,12 @@ pub(crate) fn parse_esm_module_from_module_info( }; module.maybe_types_dependency = Some(TypesDependency { specifier: types_header.to_string(), - dependency: resolve(types_header, range, maybe_resolver), + dependency: resolve( + types_header, + range, + maybe_resolver, + maybe_npm_resolver, + ), }); } } @@ -1929,8 +1976,12 @@ pub(crate) fn parse_esm_module_from_module_info( module.specifier.clone(), desc.specifier_range.clone(), ); - let dep_resolution = - resolve(&desc.specifier, range.clone(), maybe_resolver); + let dep_resolution = resolve( + &desc.specifier, + range.clone(), + maybe_resolver, + maybe_npm_resolver, + ); if matches!( desc.kind, DependencyKind::ImportType | DependencyKind::ExportType @@ -1973,6 +2024,7 @@ pub(crate) fn parse_esm_module_from_module_info( &pragma.specifier, Range::from_position_range(specifier, pragma.range), maybe_resolver, + maybe_npm_resolver, ) } else { Resolution::None @@ -2349,7 +2401,8 @@ impl<'a, 'graph> Builder<'a, 'graph> { for referrer_imports in imports { let referrer = referrer_imports.referrer; let imports = referrer_imports.imports; - let graph_import = GraphImport::new(&referrer, imports, self.resolver); + let graph_import = + GraphImport::new(&referrer, imports, self.resolver, self.npm_resolver); for dep in graph_import.dependencies.values() { if let Resolution::Ok(resolved) = &dep.maybe_type { self.load( @@ -3157,6 +3210,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { .unwrap_or(self.module_analyzer), is_root, self.in_dynamic_branch, + self.npm_resolver, ) { Ok(module) => ModuleSlot::Module(module), Err(err) => ModuleSlot::Err(err), @@ -3615,6 +3669,7 @@ mod tests { &module_analyzer, true, false, + None, ) .unwrap(); let module = module.esm().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 288ac1330..5336b97b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ pub mod packages; pub mod source; mod text_encoding; +use source::NpmResolver; use source::Resolver; use std::collections::HashMap; @@ -81,6 +82,7 @@ pub fn parse_module( content: Arc, maybe_resolver: Option<&dyn Resolver>, maybe_module_analyzer: Option<&dyn ModuleAnalyzer>, + maybe_npm_resolver: Option<&dyn NpmResolver>, ) -> Result { let default_module_analyzer = ast::DefaultModuleAnalyzer::default(); let module_analyzer = @@ -95,6 +97,7 @@ pub fn parse_module( module_analyzer, true, false, + maybe_npm_resolver, ) } @@ -104,6 +107,7 @@ pub fn parse_module_from_ast( maybe_headers: Option<&HashMap>, parsed_source: &deno_ast::ParsedSource, maybe_resolver: Option<&dyn Resolver>, + maybe_npm_resolver: Option<&dyn NpmResolver>, ) -> EsmModule { graph::parse_esm_module_from_module_info( specifier, @@ -112,6 +116,7 @@ pub fn parse_module_from_ast( DefaultModuleAnalyzer::module_info(parsed_source), parsed_source.text_info().text(), maybe_resolver, + maybe_npm_resolver, ) } @@ -1072,6 +1077,122 @@ console.log(a); } } + #[derive(Debug, Clone)] + struct MockNpmResolver {} + + impl NpmResolver for MockNpmResolver { + fn resolve_builtin_node_module( + &self, + specifier: &deno_ast::ModuleSpecifier, + ) -> anyhow::Result, source::UnknownBuiltInNodeModuleError> + { + if specifier.to_string() == "node:path" { + Ok(Some("path".to_string())) + } else { + Ok(None) + } + } + + fn on_resolve_bare_builtin_node_module(&self, module_name: &str) { + eprintln!( + "Warning: Resolving bare specifier \"{}\" to \"node:{}\".", + module_name, module_name + ); + } + + fn load_and_cache_npm_package_info( + &self, + _package_name: &str, + ) -> futures::future::LocalBoxFuture< + 'static, + anyhow::Result<(), anyhow::Error>, + > { + todo!(); + } + + fn resolve_npm( + &self, + _package_req: &deno_semver::package::PackageReq, + ) -> NpmPackageReqResolution { + todo!() + } + } + + #[tokio::test] + async fn test_builtin_node_module_as_bare_specifier() { + let mut loader = setup( + vec![( + "file:///a/test.ts", + Source::Module { + specifier: "file:///a/test.ts", + maybe_headers: None, + content: r#"import "path";"#, + }, + )], + vec![], + ); + let root_specifier = + ModuleSpecifier::parse("file:///a/test.ts").expect("bad url"); + let mock_npm_resolver = MockNpmResolver {}; + let mut graph = ModuleGraph::new(GraphKind::All); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + BuildOptions { + is_dynamic: Default::default(), + imports: Default::default(), + resolver: Default::default(), + npm_resolver: Some(&mock_npm_resolver), + module_analyzer: Default::default(), + reporter: Default::default(), + workspace_members: Default::default(), + }, + ) + .await; + assert!(graph.valid().is_ok()); + assert_eq!( + json!(graph), + json!({ + "roots": [ + "file:///a/test.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "path", + "code": { + "specifier": "node:path", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 13 + } + } + }, + } + ], + "size": 14, + "mediaType": "TypeScript", + "specifier": "file:///a/test.ts" + }, + { + "kind": "node", + "specifier": "node:path", + "moduleName": "path", + } + ], + "redirects": {} + }) + ); + } + #[tokio::test] async fn test_unsupported_media_type() { let mut loader = setup( @@ -2755,6 +2876,7 @@ export function a(a) { .into(), None, None, + None, ) .unwrap(); let actual = actual.esm().unwrap(); @@ -2776,6 +2898,7 @@ export function a(a) { .into(), None, None, + None, ) .unwrap(); assert_eq!( @@ -2842,6 +2965,7 @@ export function a(a) { .into(), None, None, + None, ) .unwrap(); let actual = actual.esm().unwrap(); @@ -2881,6 +3005,7 @@ export function a(a) { .into(), Some(&R), None, + None, ) .unwrap(); let actual = actual.esm().unwrap(); @@ -2916,6 +3041,7 @@ export function a(a) { .into(), None, None, + None, ); assert!(result.is_ok()); } @@ -2940,6 +3066,7 @@ export function a(a) { .into(), None, None, + None, ) .unwrap(); assert_eq!( @@ -3007,6 +3134,7 @@ export function a(a: A): B { .into(), None, None, + None, ) .unwrap(); assert_eq!( diff --git a/src/source.rs b/src/source.rs index 151ee4063..34c8dc482 100644 --- a/src/source.rs +++ b/src/source.rs @@ -221,6 +221,10 @@ pub trait NpmResolver: fmt::Debug { specifier: &ModuleSpecifier, ) -> Result, UnknownBuiltInNodeModuleError>; + /// The callback when a bare specifier is resolved to a builtin node module. + /// (Note: used for printing warnings to discourage that usage of bare specifiers) + fn on_resolve_bare_builtin_node_module(&self, module_name: &str); + /// This tells the implementation to asynchronously load within itself the /// npm registry package information so that synchronous resolution can occur /// afterwards. diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 4e251fbce..0d715a0f1 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -149,6 +149,8 @@ async fn test_npm_version_not_found_then_found() { Ok(None) } + fn on_resolve_bare_builtin_node_module(&self, _module_name: &str) {} + fn load_and_cache_npm_package_info( &self, _package_name: &str,