Skip to content

Commit

Permalink
feat: support node built-in module imports (#17264)
Browse files Browse the repository at this point in the history
Co-authored-by: David Sherret <dsherret@gmail.com>
  • Loading branch information
bartlomieju and dsherret authored Jan 24, 2023
1 parent cadeaae commit fc2e001
Show file tree
Hide file tree
Showing 32 changed files with 925 additions and 445 deletions.
7 changes: 7 additions & 0 deletions cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod ts {
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::OpState;
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use regex::Regex;
use serde::Deserialize;
use serde_json::json;
Expand Down Expand Up @@ -164,10 +165,16 @@ mod ts {
#[op]
fn op_build_info(state: &mut OpState) -> Value {
let build_specifier = "asset:///bootstrap.ts";

let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES
.iter()
.map(|s| s.name)
.collect::<Vec<&str>>();
let build_libs = state.borrow::<Vec<&str>>();
json!({
"buildSpecifier": build_specifier,
"libs": build_libs,
"nodeBuiltInModuleNames": node_built_in_module_names,
})
}

Expand Down
23 changes: 21 additions & 2 deletions cli/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl FetchCacher {

impl Loader for FetchCacher {
fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> {
if specifier.scheme() == "npm" {
if matches!(specifier.scheme(), "npm" | "node") {
return None;
}

Expand Down Expand Up @@ -101,7 +101,26 @@ impl Loader for FetchCacher {
));
}

let specifier = specifier.clone();
let specifier =
if let Some(module_name) = specifier.as_str().strip_prefix("node:") {
if module_name == "module" {
// the source code for "node:module" is built-in rather than
// being from deno_std like the other modules
return Box::pin(futures::future::ready(Ok(Some(
deno_graph::source::LoadResponse::External {
specifier: specifier.clone(),
},
))));
}

match crate::node::resolve_builtin_node_module(module_name) {
Ok(specifier) => specifier,
Err(err) => return Box::pin(futures::future::ready(Err(err))),
}
} else {
specifier.clone()
};

let permissions = if is_dynamic {
self.dynamic_permissions.clone()
} else {
Expand Down
56 changes: 44 additions & 12 deletions cli/graph_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::cache;
use crate::cache::TypeCheckCache;
use crate::colors;
use crate::errors::get_error_class_name;
use crate::npm::resolve_npm_package_reqs;
use crate::npm::resolve_graph_npm_info;
use crate::npm::NpmPackageReference;
use crate::npm::NpmPackageReq;
use crate::proc_state::ProcState;
Expand All @@ -25,6 +25,7 @@ use deno_graph::GraphImport;
use deno_graph::MediaType;
use deno_graph::ModuleGraph;
use deno_graph::ModuleGraphError;
use deno_graph::ModuleKind;
use deno_graph::Range;
use deno_graph::Resolved;
use deno_runtime::permissions::PermissionsContainer;
Expand Down Expand Up @@ -54,7 +55,10 @@ pub enum ModuleEntry {
#[derive(Debug, Default)]
pub struct GraphData {
modules: HashMap<ModuleSpecifier, ModuleEntry>,
/// Specifiers that are built-in or external.
external_specifiers: HashSet<ModuleSpecifier>,
npm_packages: Vec<NpmPackageReq>,
has_node_builtin_specifier: bool,
/// Map of first known referrer locations for each module. Used to enhance
/// error messages.
referrer_map: HashMap<ModuleSpecifier, Box<Range>>,
Expand Down Expand Up @@ -83,22 +87,32 @@ impl GraphData {
let mut has_npm_specifier_in_graph = false;

for (specifier, result) in graph.specifiers() {
if NpmPackageReference::from_specifier(specifier).is_ok() {
has_npm_specifier_in_graph = true;
if self.modules.contains_key(specifier) {
continue;
}

if self.modules.contains_key(specifier) {
continue;
if !self.has_node_builtin_specifier && specifier.scheme() == "node" {
self.has_node_builtin_specifier = true;
}

if let Some(found) = graph.redirects.get(specifier) {
let module_entry = ModuleEntry::Redirect(found.clone());
self.modules.insert(specifier.clone(), module_entry);
continue;
}

match result {
Ok((_, _, media_type)) => {
Ok((_, module_kind, media_type)) => {
if module_kind == ModuleKind::External {
if !has_npm_specifier_in_graph
&& NpmPackageReference::from_specifier(specifier).is_ok()
{
has_npm_specifier_in_graph = true;
}
self.external_specifiers.insert(specifier.clone());
continue; // ignore npm and node specifiers
}

let module = graph.get(specifier).unwrap();
let code = match &module.maybe_source {
Some(source) => source.clone(),
Expand Down Expand Up @@ -147,7 +161,9 @@ impl GraphData {
}

if has_npm_specifier_in_graph {
self.npm_packages.extend(resolve_npm_package_reqs(graph));
self
.npm_packages
.extend(resolve_graph_npm_info(graph).package_reqs);
}
}

Expand All @@ -157,6 +173,11 @@ impl GraphData {
self.modules.iter()
}

/// Gets if the graph had a "node:" specifier.
pub fn has_node_builtin_specifier(&self) -> bool {
self.has_node_builtin_specifier
}

/// Gets the npm package requirements from all the encountered graphs
/// in the order that they should be resolved.
pub fn npm_package_reqs(&self) -> &Vec<NpmPackageReq> {
Expand Down Expand Up @@ -195,13 +216,14 @@ impl GraphData {
}
}
while let Some(specifier) = visiting.pop_front() {
if NpmPackageReference::from_specifier(specifier).is_ok() {
continue; // skip analyzing npm specifiers
}

let (specifier, entry) = match self.modules.get_key_value(specifier) {
Some(pair) => pair,
None => return None,
None => {
if self.external_specifiers.contains(specifier) {
continue;
}
return None;
}
};
result.insert(specifier, entry);
match entry {
Expand Down Expand Up @@ -281,6 +303,8 @@ impl GraphData {
}
Some(Self {
modules,
external_specifiers: self.external_specifiers.clone(),
has_node_builtin_specifier: self.has_node_builtin_specifier,
npm_packages: self.npm_packages.clone(),
referrer_map,
graph_imports: self.graph_imports.to_vec(),
Expand Down Expand Up @@ -547,6 +571,14 @@ pub async fn create_graph_and_maybe_check(
}

if ps.options.type_check_mode() != TypeCheckMode::None {
// node built-in specifiers use the @types/node package to determine
// types, so inject that now after the lockfile has been written
if graph_data.has_node_builtin_specifier() {
ps.npm_resolver
.inject_synthetic_types_node_package()
.await?;
}

let ts_config_result =
ps.options.resolve_ts_config_for_emit(TsConfigType::Check {
lib: ps.options.ts_type_lib_window(),
Expand Down
2 changes: 1 addition & 1 deletion cli/lsp/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl CacheMetadata {
&self,
specifier: &ModuleSpecifier,
) -> Option<Arc<HashMap<MetadataKey, String>>> {
if specifier.scheme() == "file" || specifier.scheme() == "npm" {
if matches!(specifier.scheme(), "file" | "npm" | "node") {
return None;
}
let version = self
Expand Down
29 changes: 29 additions & 0 deletions cli/lsp/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use super::tsc;
use super::tsc::TsServer;

use crate::args::LintOptions;
use crate::node;
use crate::npm::NpmPackageReference;
use crate::tools::lint::get_configured_rules;

Expand Down Expand Up @@ -614,6 +615,8 @@ pub enum DenoDiagnostic {
},
/// An error occurred when resolving the specifier string.
ResolutionError(deno_graph::ResolutionError),
/// Invalid `node:` specifier.
InvalidNodeSpecifier(ModuleSpecifier),
}

impl DenoDiagnostic {
Expand Down Expand Up @@ -641,6 +644,7 @@ impl DenoDiagnostic {
},
ResolutionError::ResolverError { .. } => "resolver-error",
},
Self::InvalidNodeSpecifier(_) => "resolver-error",
}
}

Expand Down Expand Up @@ -791,6 +795,7 @@ impl DenoDiagnostic {
Self::NoLocal(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier), None),
Self::Redirect { from, to} => (lsp::DiagnosticSeverity::INFORMATION, format!("The import of \"{}\" was redirected to \"{}\".", from, to), Some(json!({ "specifier": from, "redirect": to }))),
Self::ResolutionError(err) => (lsp::DiagnosticSeverity::ERROR, err.to_string(), None),
Self::InvalidNodeSpecifier(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unknown Node built-in module: {}", specifier.path()), None),
};
lsp::Diagnostic {
range: *range,
Expand Down Expand Up @@ -872,6 +877,30 @@ fn diagnose_resolved(
);
}
}
} else if let Some(module_name) = specifier.as_str().strip_prefix("node:")
{
if node::resolve_builtin_node_module(module_name).is_err() {
diagnostics.push(
DenoDiagnostic::InvalidNodeSpecifier(specifier.clone())
.to_lsp_diagnostic(&range),
);
} else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver {
// check that a @types/node package exists in the resolver
let types_node_ref =
NpmPackageReference::from_str("npm:@types/node").unwrap();
if npm_resolver
.resolve_package_folder_from_deno_module(&types_node_ref.req)
.is_err()
{
diagnostics.push(
DenoDiagnostic::NoCacheNpm(
types_node_ref,
ModuleSpecifier::parse("npm:@types/node").unwrap(),
)
.to_lsp_diagnostic(&range),
);
}
}
} else {
// When the document is not available, it means that it cannot be found
// in the cache or locally on the disk, so we want to issue a diagnostic
Expand Down
Loading

0 comments on commit fc2e001

Please sign in to comment.