0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

feat: support node built-in module imports (#17264)

Co-authored-by: David Sherret <dsherret@gmail.com>
This commit is contained in:
Bartek Iwańczuk 2023-01-24 15:05:54 +01:00 committed by GitHub
parent cadeaae045
commit fc2e00152b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 925 additions and 445 deletions

View file

@ -17,6 +17,7 @@ mod ts {
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::op; use deno_core::op;
use deno_core::OpState; use deno_core::OpState;
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
@ -164,10 +165,16 @@ mod ts {
#[op] #[op]
fn op_build_info(state: &mut OpState) -> Value { fn op_build_info(state: &mut OpState) -> Value {
let build_specifier = "asset:///bootstrap.ts"; 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>>(); let build_libs = state.borrow::<Vec<&str>>();
json!({ json!({
"buildSpecifier": build_specifier, "buildSpecifier": build_specifier,
"libs": build_libs, "libs": build_libs,
"nodeBuiltInModuleNames": node_built_in_module_names,
}) })
} }

23
cli/cache/mod.rs vendored
View file

@ -65,7 +65,7 @@ impl FetchCacher {
impl Loader for FetchCacher { impl Loader for FetchCacher {
fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> { fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> {
if specifier.scheme() == "npm" { if matches!(specifier.scheme(), "npm" | "node") {
return None; return None;
} }
@ -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 { let permissions = if is_dynamic {
self.dynamic_permissions.clone() self.dynamic_permissions.clone()
} else { } else {

View file

@ -8,7 +8,7 @@ use crate::cache;
use crate::cache::TypeCheckCache; use crate::cache::TypeCheckCache;
use crate::colors; use crate::colors;
use crate::errors::get_error_class_name; 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::NpmPackageReference;
use crate::npm::NpmPackageReq; use crate::npm::NpmPackageReq;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
@ -25,6 +25,7 @@ use deno_graph::GraphImport;
use deno_graph::MediaType; use deno_graph::MediaType;
use deno_graph::ModuleGraph; use deno_graph::ModuleGraph;
use deno_graph::ModuleGraphError; use deno_graph::ModuleGraphError;
use deno_graph::ModuleKind;
use deno_graph::Range; use deno_graph::Range;
use deno_graph::Resolved; use deno_graph::Resolved;
use deno_runtime::permissions::PermissionsContainer; use deno_runtime::permissions::PermissionsContainer;
@ -54,7 +55,10 @@ pub enum ModuleEntry {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct GraphData { pub struct GraphData {
modules: HashMap<ModuleSpecifier, ModuleEntry>, modules: HashMap<ModuleSpecifier, ModuleEntry>,
/// Specifiers that are built-in or external.
external_specifiers: HashSet<ModuleSpecifier>,
npm_packages: Vec<NpmPackageReq>, npm_packages: Vec<NpmPackageReq>,
has_node_builtin_specifier: bool,
/// Map of first known referrer locations for each module. Used to enhance /// Map of first known referrer locations for each module. Used to enhance
/// error messages. /// error messages.
referrer_map: HashMap<ModuleSpecifier, Box<Range>>, referrer_map: HashMap<ModuleSpecifier, Box<Range>>,
@ -83,13 +87,12 @@ impl GraphData {
let mut has_npm_specifier_in_graph = false; let mut has_npm_specifier_in_graph = false;
for (specifier, result) in graph.specifiers() { for (specifier, result) in graph.specifiers() {
if NpmPackageReference::from_specifier(specifier).is_ok() { if self.modules.contains_key(specifier) {
has_npm_specifier_in_graph = true;
continue; continue;
} }
if self.modules.contains_key(specifier) { if !self.has_node_builtin_specifier && specifier.scheme() == "node" {
continue; self.has_node_builtin_specifier = true;
} }
if let Some(found) = graph.redirects.get(specifier) { if let Some(found) = graph.redirects.get(specifier) {
@ -97,8 +100,19 @@ impl GraphData {
self.modules.insert(specifier.clone(), module_entry); self.modules.insert(specifier.clone(), module_entry);
continue; continue;
} }
match result { 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 module = graph.get(specifier).unwrap();
let code = match &module.maybe_source { let code = match &module.maybe_source {
Some(source) => source.clone(), Some(source) => source.clone(),
@ -147,7 +161,9 @@ impl GraphData {
} }
if has_npm_specifier_in_graph { 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);
} }
} }
@ -157,6 +173,11 @@ impl GraphData {
self.modules.iter() 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 /// Gets the npm package requirements from all the encountered graphs
/// in the order that they should be resolved. /// in the order that they should be resolved.
pub fn npm_package_reqs(&self) -> &Vec<NpmPackageReq> { pub fn npm_package_reqs(&self) -> &Vec<NpmPackageReq> {
@ -195,13 +216,14 @@ impl GraphData {
} }
} }
while let Some(specifier) = visiting.pop_front() { 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) { let (specifier, entry) = match self.modules.get_key_value(specifier) {
Some(pair) => pair, Some(pair) => pair,
None => return None, None => {
if self.external_specifiers.contains(specifier) {
continue;
}
return None;
}
}; };
result.insert(specifier, entry); result.insert(specifier, entry);
match entry { match entry {
@ -281,6 +303,8 @@ impl GraphData {
} }
Some(Self { Some(Self {
modules, modules,
external_specifiers: self.external_specifiers.clone(),
has_node_builtin_specifier: self.has_node_builtin_specifier,
npm_packages: self.npm_packages.clone(), npm_packages: self.npm_packages.clone(),
referrer_map, referrer_map,
graph_imports: self.graph_imports.to_vec(), graph_imports: self.graph_imports.to_vec(),
@ -547,6 +571,14 @@ pub async fn create_graph_and_maybe_check(
} }
if ps.options.type_check_mode() != TypeCheckMode::None { 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 = let ts_config_result =
ps.options.resolve_ts_config_for_emit(TsConfigType::Check { ps.options.resolve_ts_config_for_emit(TsConfigType::Check {
lib: ps.options.ts_type_lib_window(), lib: ps.options.ts_type_lib_window(),

View file

@ -68,7 +68,7 @@ impl CacheMetadata {
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<Arc<HashMap<MetadataKey, String>>> { ) -> Option<Arc<HashMap<MetadataKey, String>>> {
if specifier.scheme() == "file" || specifier.scheme() == "npm" { if matches!(specifier.scheme(), "file" | "npm" | "node") {
return None; return None;
} }
let version = self let version = self

View file

@ -13,6 +13,7 @@ use super::tsc;
use super::tsc::TsServer; use super::tsc::TsServer;
use crate::args::LintOptions; use crate::args::LintOptions;
use crate::node;
use crate::npm::NpmPackageReference; use crate::npm::NpmPackageReference;
use crate::tools::lint::get_configured_rules; use crate::tools::lint::get_configured_rules;
@ -614,6 +615,8 @@ pub enum DenoDiagnostic {
}, },
/// An error occurred when resolving the specifier string. /// An error occurred when resolving the specifier string.
ResolutionError(deno_graph::ResolutionError), ResolutionError(deno_graph::ResolutionError),
/// Invalid `node:` specifier.
InvalidNodeSpecifier(ModuleSpecifier),
} }
impl DenoDiagnostic { impl DenoDiagnostic {
@ -641,6 +644,7 @@ impl DenoDiagnostic {
}, },
ResolutionError::ResolverError { .. } => "resolver-error", ResolutionError::ResolverError { .. } => "resolver-error",
}, },
Self::InvalidNodeSpecifier(_) => "resolver-error",
} }
} }
@ -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::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::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::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 { lsp::Diagnostic {
range: *range, range: *range,
@ -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 { } else {
// When the document is not available, it means that it cannot be found // 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 // in the cache or locally on the disk, so we want to issue a diagnostic

View file

@ -770,6 +770,9 @@ pub struct Documents {
maybe_resolver: Option<CliResolver>, maybe_resolver: Option<CliResolver>,
/// The npm package requirements. /// The npm package requirements.
npm_reqs: Arc<HashSet<NpmPackageReq>>, npm_reqs: Arc<HashSet<NpmPackageReq>>,
/// Gets if any document had a node: specifier such that a @types/node package
/// should be injected.
has_injected_types_node_package: bool,
/// Resolves a specifier to its final redirected to specifier. /// Resolves a specifier to its final redirected to specifier.
specifier_resolver: Arc<SpecifierResolver>, specifier_resolver: Arc<SpecifierResolver>,
} }
@ -785,6 +788,7 @@ impl Documents {
imports: Default::default(), imports: Default::default(),
maybe_resolver: None, maybe_resolver: None,
npm_reqs: Default::default(), npm_reqs: Default::default(),
has_injected_types_node_package: false,
specifier_resolver: Arc::new(SpecifierResolver::new(location)), specifier_resolver: Arc::new(SpecifierResolver::new(location)),
} }
} }
@ -925,6 +929,12 @@ impl Documents {
(*self.npm_reqs).clone() (*self.npm_reqs).clone()
} }
/// Returns if a @types/node package was injected into the npm
/// resolver based on the state of the documents.
pub fn has_injected_types_node_package(&self) -> bool {
self.has_injected_types_node_package
}
/// Return a document for the specifier. /// Return a document for the specifier.
pub fn get(&self, original_specifier: &ModuleSpecifier) -> Option<Document> { pub fn get(&self, original_specifier: &ModuleSpecifier) -> Option<Document> {
let specifier = self.specifier_resolver.resolve(original_specifier)?; let specifier = self.specifier_resolver.resolve(original_specifier)?;
@ -985,11 +995,15 @@ impl Documents {
/// tsc when type checking. /// tsc when type checking.
pub fn resolve( pub fn resolve(
&self, &self,
specifiers: &[String], specifiers: Vec<String>,
referrer: &ModuleSpecifier, referrer_doc: &AssetOrDocument,
maybe_npm_resolver: Option<&NpmPackageResolver>, maybe_npm_resolver: Option<&NpmPackageResolver>,
) -> Option<Vec<Option<(ModuleSpecifier, MediaType)>>> { ) -> Vec<Option<(ModuleSpecifier, MediaType)>> {
let dependencies = self.get(referrer)?.0.dependencies.clone(); let referrer = referrer_doc.specifier();
let dependencies = match referrer_doc {
AssetOrDocument::Asset(_) => None,
AssetOrDocument::Document(doc) => Some(doc.0.dependencies.clone()),
};
let mut results = Vec::new(); let mut results = Vec::new();
for specifier in specifiers { for specifier in specifiers {
if let Some(npm_resolver) = maybe_npm_resolver { if let Some(npm_resolver) = maybe_npm_resolver {
@ -997,7 +1011,7 @@ impl Documents {
// we're in an npm package, so use node resolution // we're in an npm package, so use node resolution
results.push(Some(NodeResolution::into_specifier_and_media_type( results.push(Some(NodeResolution::into_specifier_and_media_type(
node::node_resolve( node::node_resolve(
specifier, &specifier,
referrer, referrer,
NodeResolutionMode::Types, NodeResolutionMode::Types,
npm_resolver, npm_resolver,
@ -1009,15 +1023,28 @@ impl Documents {
continue; continue;
} }
} }
// handle npm:<package> urls if let Some(module_name) = specifier.strip_prefix("node:") {
if crate::node::resolve_builtin_node_module(module_name).is_ok() {
// return itself for node: specifiers because during type checking
// we resolve to the ambient modules in the @types/node package
// rather than deno_std/node
results.push(Some((
ModuleSpecifier::parse(&specifier).unwrap(),
MediaType::Dts,
)));
continue;
}
}
if specifier.starts_with("asset:") { if specifier.starts_with("asset:") {
if let Ok(specifier) = ModuleSpecifier::parse(specifier) { if let Ok(specifier) = ModuleSpecifier::parse(&specifier) {
let media_type = MediaType::from(&specifier); let media_type = MediaType::from(&specifier);
results.push(Some((specifier, media_type))); results.push(Some((specifier, media_type)));
} else { } else {
results.push(None); results.push(None);
} }
} else if let Some(dep) = dependencies.deps.get(specifier) { } else if let Some(dep) =
dependencies.as_ref().and_then(|d| d.deps.get(&specifier))
{
if let Resolved::Ok { specifier, .. } = &dep.maybe_type { if let Resolved::Ok { specifier, .. } = &dep.maybe_type {
results.push(self.resolve_dependency(specifier, maybe_npm_resolver)); results.push(self.resolve_dependency(specifier, maybe_npm_resolver));
} else if let Resolved::Ok { specifier, .. } = &dep.maybe_code { } else if let Resolved::Ok { specifier, .. } = &dep.maybe_code {
@ -1026,12 +1053,12 @@ impl Documents {
results.push(None); results.push(None);
} }
} else if let Some(Resolved::Ok { specifier, .. }) = } else if let Some(Resolved::Ok { specifier, .. }) =
self.resolve_imports_dependency(specifier) self.resolve_imports_dependency(&specifier)
{ {
// clone here to avoid double borrow of self // clone here to avoid double borrow of self
let specifier = specifier.clone(); let specifier = specifier.clone();
results.push(self.resolve_dependency(&specifier, maybe_npm_resolver)); results.push(self.resolve_dependency(&specifier, maybe_npm_resolver));
} else if let Ok(npm_ref) = NpmPackageReference::from_str(specifier) { } else if let Ok(npm_ref) = NpmPackageReference::from_str(&specifier) {
results.push(maybe_npm_resolver.map(|npm_resolver| { results.push(maybe_npm_resolver.map(|npm_resolver| {
NodeResolution::into_specifier_and_media_type( NodeResolution::into_specifier_and_media_type(
node_resolve_npm_reference( node_resolve_npm_reference(
@ -1048,7 +1075,7 @@ impl Documents {
results.push(None); results.push(None);
} }
} }
Some(results) results
} }
/// Update the location of the on disk cache for the document store. /// Update the location of the on disk cache for the document store.
@ -1125,6 +1152,7 @@ impl Documents {
analyzed_specifiers: HashSet<ModuleSpecifier>, analyzed_specifiers: HashSet<ModuleSpecifier>,
pending_specifiers: VecDeque<ModuleSpecifier>, pending_specifiers: VecDeque<ModuleSpecifier>,
npm_reqs: HashSet<NpmPackageReq>, npm_reqs: HashSet<NpmPackageReq>,
has_node_builtin_specifier: bool,
} }
impl DocAnalyzer { impl DocAnalyzer {
@ -1148,7 +1176,11 @@ impl Documents {
fn analyze_doc(&mut self, specifier: &ModuleSpecifier, doc: &Document) { fn analyze_doc(&mut self, specifier: &ModuleSpecifier, doc: &Document) {
self.analyzed_specifiers.insert(specifier.clone()); self.analyzed_specifiers.insert(specifier.clone());
for dependency in doc.dependencies().values() { for (name, dependency) in doc.dependencies() {
if !self.has_node_builtin_specifier && name.starts_with("node:") {
self.has_node_builtin_specifier = true;
}
if let Some(dep) = dependency.get_code() { if let Some(dep) = dependency.get_code() {
self.add(dep, specifier); self.add(dep, specifier);
} }
@ -1185,8 +1217,19 @@ impl Documents {
} }
} }
let mut npm_reqs = doc_analyzer.npm_reqs;
// Ensure a @types/node package exists when any module uses a node: specifier.
// Unlike on the command line, here we just add @types/node to the npm package
// requirements since this won't end up in the lockfile.
self.has_injected_types_node_package = doc_analyzer
.has_node_builtin_specifier
&& !npm_reqs.iter().any(|r| r.name == "@types/node");
if self.has_injected_types_node_package {
npm_reqs.insert(NpmPackageReq::from_str("@types/node").unwrap());
}
self.dependents_map = Arc::new(doc_analyzer.dependents_map); self.dependents_map = Arc::new(doc_analyzer.dependents_map);
self.npm_reqs = Arc::new(doc_analyzer.npm_reqs); self.npm_reqs = Arc::new(npm_reqs);
self.dirty = false; self.dirty = false;
file_system_docs.dirty = false; file_system_docs.dirty = false;
} }

View file

@ -216,7 +216,7 @@ fn new_assets_map() -> Arc<Mutex<AssetsMap>> {
let asset = AssetDocument::new(specifier.clone(), v); let asset = AssetDocument::new(specifier.clone(), v);
(specifier, asset) (specifier, asset)
}) })
.collect(); .collect::<AssetsMap>();
Arc::new(Mutex::new(assets)) Arc::new(Mutex::new(assets))
} }
@ -2728,28 +2728,29 @@ fn op_resolve(
let state = state.borrow_mut::<State>(); let state = state.borrow_mut::<State>();
let mark = state.performance.mark("op_resolve", Some(&args)); let mark = state.performance.mark("op_resolve", Some(&args));
let referrer = state.normalize_specifier(&args.base)?; let referrer = state.normalize_specifier(&args.base)?;
let result = match state.get_asset_or_document(&referrer) {
let result = if let Some(resolved) = state.state_snapshot.documents.resolve( Some(referrer_doc) => {
&args.specifiers, let resolved = state.state_snapshot.documents.resolve(
&referrer, args.specifiers,
state.state_snapshot.maybe_npm_resolver.as_ref(), &referrer_doc,
) { state.state_snapshot.maybe_npm_resolver.as_ref(),
Ok( );
resolved Ok(
.into_iter() resolved
.map(|o| { .into_iter()
o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string())) .map(|o| {
}) o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string()))
.collect(), })
) .collect(),
} else { )
Err(custom_error( }
None => Err(custom_error(
"NotFound", "NotFound",
format!( format!(
"Error resolving. Referring specifier \"{}\" was not found.", "Error resolving. Referring specifier \"{}\" was not found.",
args.base args.base
), ),
)) )),
}; };
state.performance.measure(mark); state.performance.measure(mark);
@ -2764,15 +2765,20 @@ fn op_respond(state: &mut OpState, args: Response) -> bool {
} }
#[op] #[op]
fn op_script_names(state: &mut OpState) -> Vec<ModuleSpecifier> { fn op_script_names(state: &mut OpState) -> Vec<String> {
let state = state.borrow_mut::<State>(); let state = state.borrow_mut::<State>();
state let documents = &state.state_snapshot.documents;
.state_snapshot let open_docs = documents.documents(true, true);
.documents
.documents(true, true) let mut result = Vec::with_capacity(open_docs.len() + 1);
.into_iter()
.map(|d| d.specifier().clone()) if documents.has_injected_types_node_package() {
.collect() // ensure this is first so it resolves the node types first
result.push("asset:///node_types.d.ts".to_string());
}
result.extend(open_docs.into_iter().map(|d| d.specifier().to_string()));
result
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]

View file

@ -25,6 +25,7 @@ use deno_runtime::deno_node::package_imports_resolve;
use deno_runtime::deno_node::package_resolve; use deno_runtime::deno_node::package_resolve;
use deno_runtime::deno_node::path_to_declaration_path; use deno_runtime::deno_node::path_to_declaration_path;
use deno_runtime::deno_node::NodeModuleKind; use deno_runtime::deno_node::NodeModuleKind;
use deno_runtime::deno_node::NodeModulePolyfill;
use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodePermissions;
use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_node::PackageJson;
@ -32,6 +33,7 @@ use deno_runtime::deno_node::PathClean;
use deno_runtime::deno_node::RequireNpmResolver; use deno_runtime::deno_node::RequireNpmResolver;
use deno_runtime::deno_node::DEFAULT_CONDITIONS; use deno_runtime::deno_node::DEFAULT_CONDITIONS;
use deno_runtime::deno_node::NODE_GLOBAL_THIS_NAME; use deno_runtime::deno_node::NODE_GLOBAL_THIS_NAME;
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use deno_runtime::permissions::PermissionsContainer; use deno_runtime::permissions::PermissionsContainer;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
@ -106,200 +108,6 @@ impl NodeResolution {
} }
} }
struct NodeModulePolyfill {
/// Name of the module like "assert" or "timers/promises"
name: &'static str,
/// Specifier relative to the root of `deno_std` repo, like "node/asser.ts"
specifier: &'static str,
}
static SUPPORTED_MODULES: &[NodeModulePolyfill] = &[
NodeModulePolyfill {
name: "assert",
specifier: "node/assert.ts",
},
NodeModulePolyfill {
name: "assert/strict",
specifier: "node/assert/strict.ts",
},
NodeModulePolyfill {
name: "async_hooks",
specifier: "node/async_hooks.ts",
},
NodeModulePolyfill {
name: "buffer",
specifier: "node/buffer.ts",
},
NodeModulePolyfill {
name: "child_process",
specifier: "node/child_process.ts",
},
NodeModulePolyfill {
name: "cluster",
specifier: "node/cluster.ts",
},
NodeModulePolyfill {
name: "console",
specifier: "node/console.ts",
},
NodeModulePolyfill {
name: "constants",
specifier: "node/constants.ts",
},
NodeModulePolyfill {
name: "crypto",
specifier: "node/crypto.ts",
},
NodeModulePolyfill {
name: "dgram",
specifier: "node/dgram.ts",
},
NodeModulePolyfill {
name: "dns",
specifier: "node/dns.ts",
},
NodeModulePolyfill {
name: "dns/promises",
specifier: "node/dns/promises.ts",
},
NodeModulePolyfill {
name: "domain",
specifier: "node/domain.ts",
},
NodeModulePolyfill {
name: "events",
specifier: "node/events.ts",
},
NodeModulePolyfill {
name: "fs",
specifier: "node/fs.ts",
},
NodeModulePolyfill {
name: "fs/promises",
specifier: "node/fs/promises.ts",
},
NodeModulePolyfill {
name: "http",
specifier: "node/http.ts",
},
NodeModulePolyfill {
name: "https",
specifier: "node/https.ts",
},
NodeModulePolyfill {
name: "module",
// NOTE(bartlomieju): `module` is special, because we don't want to use
// `deno_std/node/module.ts`, but instead use a special shim that we
// provide in `ext/node`.
specifier: "[USE `deno_node::MODULE_ES_SHIM` to get this module]",
},
NodeModulePolyfill {
name: "net",
specifier: "node/net.ts",
},
NodeModulePolyfill {
name: "os",
specifier: "node/os.ts",
},
NodeModulePolyfill {
name: "path",
specifier: "node/path.ts",
},
NodeModulePolyfill {
name: "path/posix",
specifier: "node/path/posix.ts",
},
NodeModulePolyfill {
name: "path/win32",
specifier: "node/path/win32.ts",
},
NodeModulePolyfill {
name: "perf_hooks",
specifier: "node/perf_hooks.ts",
},
NodeModulePolyfill {
name: "process",
specifier: "node/process.ts",
},
NodeModulePolyfill {
name: "querystring",
specifier: "node/querystring.ts",
},
NodeModulePolyfill {
name: "readline",
specifier: "node/readline.ts",
},
NodeModulePolyfill {
name: "stream",
specifier: "node/stream.ts",
},
NodeModulePolyfill {
name: "stream/consumers",
specifier: "node/stream/consumers.mjs",
},
NodeModulePolyfill {
name: "stream/promises",
specifier: "node/stream/promises.mjs",
},
NodeModulePolyfill {
name: "stream/web",
specifier: "node/stream/web.ts",
},
NodeModulePolyfill {
name: "string_decoder",
specifier: "node/string_decoder.ts",
},
NodeModulePolyfill {
name: "sys",
specifier: "node/sys.ts",
},
NodeModulePolyfill {
name: "timers",
specifier: "node/timers.ts",
},
NodeModulePolyfill {
name: "timers/promises",
specifier: "node/timers/promises.ts",
},
NodeModulePolyfill {
name: "tls",
specifier: "node/tls.ts",
},
NodeModulePolyfill {
name: "tty",
specifier: "node/tty.ts",
},
NodeModulePolyfill {
name: "url",
specifier: "node/url.ts",
},
NodeModulePolyfill {
name: "util",
specifier: "node/util.ts",
},
NodeModulePolyfill {
name: "util/types",
specifier: "node/util/types.ts",
},
NodeModulePolyfill {
name: "v8",
specifier: "node/v8.ts",
},
NodeModulePolyfill {
name: "vm",
specifier: "node/vm.ts",
},
NodeModulePolyfill {
name: "worker_threads",
specifier: "node/worker_threads.ts",
},
NodeModulePolyfill {
name: "zlib",
specifier: "node/zlib.ts",
},
];
static NODE_COMPAT_URL: Lazy<Url> = Lazy::new(|| { static NODE_COMPAT_URL: Lazy<Url> = Lazy::new(|| {
if let Ok(url_str) = std::env::var("DENO_NODE_COMPAT_URL") { if let Ok(url_str) = std::env::var("DENO_NODE_COMPAT_URL") {
let url = Url::parse(&url_str).expect( let url = Url::parse(&url_str).expect(
@ -315,7 +123,9 @@ pub static MODULE_ALL_URL: Lazy<Url> =
Lazy::new(|| NODE_COMPAT_URL.join("node/module_all.ts").unwrap()); Lazy::new(|| NODE_COMPAT_URL.join("node/module_all.ts").unwrap());
fn find_builtin_node_module(specifier: &str) -> Option<&NodeModulePolyfill> { fn find_builtin_node_module(specifier: &str) -> Option<&NodeModulePolyfill> {
SUPPORTED_MODULES.iter().find(|m| m.name == specifier) SUPPORTED_BUILTIN_NODE_MODULES
.iter()
.find(|m| m.name == specifier)
} }
fn is_builtin_node_module(specifier: &str) -> bool { fn is_builtin_node_module(specifier: &str) -> bool {
@ -336,7 +146,7 @@ pub fn resolve_builtin_node_module(specifier: &str) -> Result<Url, AnyError> {
} }
Err(generic_error(format!( Err(generic_error(format!(
"Unknown built-in Node module: {}", "Unknown built-in \"node:\" module: {}",
specifier specifier
))) )))
} }

View file

@ -14,7 +14,7 @@ pub use cache::NpmCache;
pub use registry::NpmPackageVersionDistInfo; pub use registry::NpmPackageVersionDistInfo;
pub use registry::NpmRegistryApi; pub use registry::NpmRegistryApi;
pub use registry::RealNpmRegistryApi; pub use registry::RealNpmRegistryApi;
pub use resolution::resolve_npm_package_reqs; pub use resolution::resolve_graph_npm_info;
pub use resolution::NpmPackageId; pub use resolution::NpmPackageId;
pub use resolution::NpmPackageReference; pub use resolution::NpmPackageReference;
pub use resolution::NpmPackageReq; pub use resolution::NpmPackageReq;

View file

@ -1081,7 +1081,7 @@ fn tag_to_version_info<'a>(
// explicit version. // explicit version.
if tag == "latest" && info.name == "@types/node" { if tag == "latest" && info.name == "@types/node" {
return get_resolved_package_version_and_info( return get_resolved_package_version_and_info(
&NpmVersionReq::parse("18.0.0 - 18.8.2").unwrap(), &NpmVersionReq::parse("18.0.0 - 18.11.18").unwrap(),
info, info,
parent, parent,
); );

View file

@ -28,7 +28,7 @@ mod specifier;
use graph::Graph; use graph::Graph;
pub use snapshot::NpmResolutionSnapshot; pub use snapshot::NpmResolutionSnapshot;
pub use specifier::resolve_npm_package_reqs; pub use specifier::resolve_graph_npm_info;
pub use specifier::NpmPackageReference; pub use specifier::NpmPackageReference;
pub use specifier::NpmPackageReq; pub use specifier::NpmPackageReq;

View file

@ -168,8 +168,15 @@ impl NpmVersionMatcher for NpmPackageReq {
} }
} }
/// Resolves the npm package requirements from the graph attempting. The order pub struct GraphNpmInfo {
/// returned is the order they should be resolved in. /// The order of these package requirements is the order they
/// should be resolved in.
pub package_reqs: Vec<NpmPackageReq>,
/// Gets if the graph had a built-in node specifier (ex. `node:fs`).
pub has_node_builtin_specifier: bool,
}
/// Resolves npm specific information from the graph.
/// ///
/// This function will analyze the module graph for parent-most folder /// This function will analyze the module graph for parent-most folder
/// specifiers of all modules, then group npm specifiers together as found in /// specifiers of all modules, then group npm specifiers together as found in
@ -211,7 +218,7 @@ impl NpmVersionMatcher for NpmPackageReq {
/// ///
/// Then it would resolve the npm specifiers in each of those groups according /// Then it would resolve the npm specifiers in each of those groups according
/// to that tree going by tree depth. /// to that tree going by tree depth.
pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> { pub fn resolve_graph_npm_info(graph: &ModuleGraph) -> GraphNpmInfo {
fn collect_specifiers<'a>( fn collect_specifiers<'a>(
graph: &'a ModuleGraph, graph: &'a ModuleGraph,
module: &'a deno_graph::Module, module: &'a deno_graph::Module,
@ -248,6 +255,7 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
graph: &ModuleGraph, graph: &ModuleGraph,
specifier_graph: &mut SpecifierTree, specifier_graph: &mut SpecifierTree,
seen: &mut HashSet<ModuleSpecifier>, seen: &mut HashSet<ModuleSpecifier>,
has_node_builtin_specifier: &mut bool,
) { ) {
if !seen.insert(module.specifier.clone()) { if !seen.insert(module.specifier.clone()) {
return; // already visited return; // already visited
@ -267,12 +275,22 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
.dependencies .dependencies
.insert(get_folder_path_specifier(specifier)); .insert(get_folder_path_specifier(specifier));
} }
if !*has_node_builtin_specifier && specifier.scheme() == "node" {
*has_node_builtin_specifier = true;
}
} }
// now visit all the dependencies // now visit all the dependencies
for specifier in &specifiers { for specifier in &specifiers {
if let Some(module) = graph.get(specifier) { if let Some(module) = graph.get(specifier) {
analyze_module(module, graph, specifier_graph, seen); analyze_module(
module,
graph,
specifier_graph,
seen,
has_node_builtin_specifier,
);
} }
} }
} }
@ -284,9 +302,16 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut seen = HashSet::new(); let mut seen = HashSet::new();
let mut specifier_graph = SpecifierTree::default(); let mut specifier_graph = SpecifierTree::default();
let mut has_node_builtin_specifier = false;
for root in &root_specifiers { for root in &root_specifiers {
if let Some(module) = graph.get(root) { if let Some(module) = graph.get(root) {
analyze_module(module, graph, &mut specifier_graph, &mut seen); analyze_module(
module,
graph,
&mut specifier_graph,
&mut seen,
&mut has_node_builtin_specifier,
);
} }
} }
@ -324,7 +349,10 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
} }
} }
result GraphNpmInfo {
has_node_builtin_specifier,
package_reqs: result,
}
} }
fn get_folder_path_specifier(specifier: &ModuleSpecifier) -> ModuleSpecifier { fn get_folder_path_specifier(specifier: &ModuleSpecifier) -> ModuleSpecifier {
@ -979,7 +1007,8 @@ mod tests {
}, },
) )
.await; .await;
let reqs = resolve_npm_package_reqs(&graph) let reqs = resolve_graph_npm_info(&graph)
.package_reqs
.into_iter() .into_iter()
.map(|r| r.to_string()) .map(|r| r.to_string())
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -95,59 +95,51 @@ impl NpmPackageResolver {
no_npm: bool, no_npm: bool,
local_node_modules_path: Option<PathBuf>, local_node_modules_path: Option<PathBuf>,
) -> Self { ) -> Self {
Self::new_with_maybe_snapshot( Self::new_inner(cache, api, no_npm, local_node_modules_path, None, None)
}
pub async fn new_with_maybe_lockfile(
cache: NpmCache,
api: RealNpmRegistryApi,
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
) -> Result<Self, AnyError> {
let maybe_snapshot = if let Some(lockfile) = &maybe_lockfile {
if lockfile.lock().overwrite {
None
} else {
Some(
NpmResolutionSnapshot::from_lockfile(lockfile.clone(), &api)
.await
.with_context(|| {
format!(
"failed reading lockfile '{}'",
lockfile.lock().filename.display()
)
})?,
)
}
} else {
None
};
Ok(Self::new_inner(
cache, cache,
api, api,
no_npm, no_npm,
local_node_modules_path, local_node_modules_path,
None, maybe_snapshot,
) maybe_lockfile,
))
} }
/// This function will replace current resolver with a new one built from a fn new_inner(
/// snapshot created out of the lockfile.
pub async fn add_lockfile_and_maybe_regenerate_snapshot(
&mut self,
lockfile: Arc<Mutex<Lockfile>>,
) -> Result<(), AnyError> {
self.maybe_lockfile = Some(lockfile.clone());
if lockfile.lock().overwrite {
return Ok(());
}
let snapshot =
NpmResolutionSnapshot::from_lockfile(lockfile.clone(), &self.api)
.await
.with_context(|| {
format!(
"failed reading lockfile '{}'",
lockfile.lock().filename.display()
)
})?;
if let Some(node_modules_folder) = &self.local_node_modules_path {
self.inner = Arc::new(LocalNpmPackageResolver::new(
self.cache.clone(),
self.api.clone(),
node_modules_folder.clone(),
Some(snapshot),
));
} else {
self.inner = Arc::new(GlobalNpmPackageResolver::new(
self.cache.clone(),
self.api.clone(),
Some(snapshot),
));
}
Ok(())
}
fn new_with_maybe_snapshot(
cache: NpmCache, cache: NpmCache,
api: RealNpmRegistryApi, api: RealNpmRegistryApi,
no_npm: bool, no_npm: bool,
local_node_modules_path: Option<PathBuf>, local_node_modules_path: Option<PathBuf>,
initial_snapshot: Option<NpmResolutionSnapshot>, initial_snapshot: Option<NpmResolutionSnapshot>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
) -> Self { ) -> Self {
let process_npm_state = NpmProcessState::take(); let process_npm_state = NpmProcessState::take();
let local_node_modules_path = local_node_modules_path.or_else(|| { let local_node_modules_path = local_node_modules_path.or_else(|| {
@ -177,7 +169,7 @@ impl NpmPackageResolver {
local_node_modules_path, local_node_modules_path,
api, api,
cache, cache,
maybe_lockfile: None, maybe_lockfile,
} }
} }
@ -320,12 +312,13 @@ impl NpmPackageResolver {
/// Gets a new resolver with a new snapshotted state. /// Gets a new resolver with a new snapshotted state.
pub fn snapshotted(&self) -> Self { pub fn snapshotted(&self) -> Self {
Self::new_with_maybe_snapshot( Self::new_inner(
self.cache.clone(), self.cache.clone(),
self.api.clone(), self.api.clone(),
self.no_npm, self.no_npm,
self.local_node_modules_path.clone(), self.local_node_modules_path.clone(),
Some(self.snapshot()), Some(self.snapshot()),
None,
) )
} }
@ -336,6 +329,19 @@ impl NpmPackageResolver {
pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> { pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
self.inner.lock(lockfile) self.inner.lock(lockfile)
} }
pub async fn inject_synthetic_types_node_package(
&self,
) -> Result<(), AnyError> {
// add and ensure this isn't added to the lockfile
self
.inner
.add_package_reqs(vec![NpmPackageReq::from_str("@types/node").unwrap()])
.await?;
self.inner.cache_packages().await?;
Ok(())
}
} }
impl RequireNpmResolver for NpmPackageResolver { impl RequireNpmResolver for NpmPackageResolver {

View file

@ -23,7 +23,7 @@ use crate::graph_util::ModuleEntry;
use crate::http_util::HttpClient; use crate::http_util::HttpClient;
use crate::node; use crate::node;
use crate::node::NodeResolution; use crate::node::NodeResolution;
use crate::npm::resolve_npm_package_reqs; use crate::npm::resolve_graph_npm_info;
use crate::npm::NpmCache; use crate::npm::NpmCache;
use crate::npm::NpmPackageReference; use crate::npm::NpmPackageReference;
use crate::npm::NpmPackageResolver; use crate::npm::NpmPackageResolver;
@ -261,20 +261,16 @@ impl ProcState {
http_client.clone(), http_client.clone(),
progress_bar.clone(), progress_bar.clone(),
); );
let maybe_lockfile = lockfile.as_ref().cloned(); let npm_resolver = NpmPackageResolver::new_with_maybe_lockfile(
let mut npm_resolver = NpmPackageResolver::new(
npm_cache.clone(), npm_cache.clone(),
api, api,
cli_options.no_npm(), cli_options.no_npm(),
cli_options cli_options
.resolve_local_node_modules_folder() .resolve_local_node_modules_folder()
.with_context(|| "Resolving local node_modules folder.")?, .with_context(|| "Resolving local node_modules folder.")?,
); lockfile.as_ref().cloned(),
if let Some(lockfile) = maybe_lockfile { )
npm_resolver .await?;
.add_lockfile_and_maybe_regenerate_snapshot(lockfile)
.await?;
}
let node_analysis_cache = let node_analysis_cache =
NodeAnalysisCache::new(Some(dir.node_analysis_db_file_path())); NodeAnalysisCache::new(Some(dir.node_analysis_db_file_path()));
@ -424,7 +420,7 @@ impl ProcState {
graph_data.entries().map(|(s, _)| s).cloned().collect() graph_data.entries().map(|(s, _)| s).cloned().collect()
}; };
let npm_package_reqs = { let (npm_package_reqs, has_node_builtin_specifier) = {
let mut graph_data = self.graph_data.write(); let mut graph_data = self.graph_data.write();
graph_data.add_graph(&graph); graph_data.add_graph(&graph);
let check_js = self.options.check_js(); let check_js = self.options.check_js();
@ -435,7 +431,10 @@ impl ProcState {
check_js, check_js,
) )
.unwrap()?; .unwrap()?;
graph_data.npm_package_reqs().clone() (
graph_data.npm_package_reqs().clone(),
graph_data.has_node_builtin_specifier(),
)
}; };
if !npm_package_reqs.is_empty() { if !npm_package_reqs.is_empty() {
@ -443,6 +442,15 @@ impl ProcState {
self.prepare_node_std_graph().await?; self.prepare_node_std_graph().await?;
} }
if has_node_builtin_specifier
&& self.options.type_check_mode() != TypeCheckMode::None
{
self
.npm_resolver
.inject_synthetic_types_node_package()
.await?;
}
drop(_pb_clear_guard); drop(_pb_clear_guard);
// type check if necessary // type check if necessary
@ -614,6 +622,11 @@ impl ProcState {
} }
} }
// Built-in Node modules
if let Some(module_name) = specifier.strip_prefix("node:") {
return node::resolve_builtin_node_module(module_name);
}
// FIXME(bartlomieju): this is a hacky way to provide compatibility with REPL // FIXME(bartlomieju): this is a hacky way to provide compatibility with REPL
// and `Deno.core.evalContext` API. Ideally we should always have a referrer filled // and `Deno.core.evalContext` API. Ideally we should always have a referrer filled
// but sadly that's not the case due to missing APIs in V8. // but sadly that's not the case due to missing APIs in V8.
@ -732,9 +745,20 @@ impl ProcState {
.await; .await;
// add the found npm package requirements to the npm resolver and cache them // add the found npm package requirements to the npm resolver and cache them
let npm_package_reqs = resolve_npm_package_reqs(&graph); let graph_npm_info = resolve_graph_npm_info(&graph);
if !npm_package_reqs.is_empty() { if !graph_npm_info.package_reqs.is_empty() {
self.npm_resolver.add_package_reqs(npm_package_reqs).await?; self
.npm_resolver
.add_package_reqs(graph_npm_info.package_reqs)
.await?;
}
if graph_npm_info.has_node_builtin_specifier
&& self.options.type_check_mode() != TypeCheckMode::None
{
self
.npm_resolver
.inject_synthetic_types_node_package()
.await?;
} }
Ok(graph) Ok(graph)

View file

@ -64,6 +64,18 @@ itest!(check_static_response_json {
exit_code: 0, exit_code: 0,
}); });
itest!(check_node_builtin_modules_ts {
args: "check --quiet check/node_builtin_modules/mod.ts",
output: "check/node_builtin_modules/mod.ts.out",
exit_code: 1,
});
itest!(check_node_builtin_modules_js {
args: "check --quiet check/node_builtin_modules/mod.js",
output: "check/node_builtin_modules/mod.js.out",
exit_code: 1,
});
itest!(check_no_error_truncation { itest!(check_no_error_truncation {
args: "check --quiet check/no_error_truncation/main.ts --config check/no_error_truncation/deno.json", args: "check --quiet check/no_error_truncation/main.ts --config check/no_error_truncation/deno.json",
output: "check/no_error_truncation/main.out", output: "check/no_error_truncation/main.out",

View file

@ -4407,6 +4407,190 @@ fn lsp_npm_specifier_unopened_file() {
} }
} }
#[test]
fn lsp_completions_node_specifier() {
let _g = http_server();
let mut client = init("initialize_params.json");
let diagnostics = CollectedDiagnostics(did_open(
&mut client,
json!({
"textDocument": {
"uri": "file:///a/file.ts",
"languageId": "typescript",
"version": 1,
"text": "import fs from 'node:non-existent';\n\n",
}
}),
));
let non_existent_diagnostics = diagnostics
.with_file_and_source("file:///a/file.ts", "deno")
.diagnostics
.into_iter()
.filter(|d| {
d.code == Some(lsp::NumberOrString::String("resolver-error".to_string()))
})
.collect::<Vec<_>>();
assert_eq!(
json!(non_existent_diagnostics),
json!([
{
"range": {
"start": {
"line": 0,
"character": 15
},
"end": {
"line": 0,
"character": 34
}
},
"severity": 1,
"code": "resolver-error",
"source": "deno",
"message": "Unknown Node built-in module: non-existent"
}
])
);
// update to have node:fs import
client
.write_notification(
"textDocument/didChange",
json!({
"textDocument": {
"uri": "file:///a/file.ts",
"version": 2
},
"contentChanges": [
{
"range": {
"start": {
"line": 0,
"character": 16
},
"end": {
"line": 0,
"character": 33
}
},
"text": "node:fs"
}
]
}),
)
.unwrap();
let diagnostics = read_diagnostics(&mut client);
let cache_diagnostics = diagnostics
.with_file_and_source("file:///a/file.ts", "deno")
.diagnostics
.into_iter()
.filter(|d| {
d.code == Some(lsp::NumberOrString::String("no-cache-npm".to_string()))
})
.collect::<Vec<_>>();
assert_eq!(
json!(cache_diagnostics),
json!([
{
"range": {
"start": {
"line": 0,
"character": 15
},
"end": {
"line": 0,
"character": 24
}
},
"data": {
"specifier": "npm:@types/node",
},
"severity": 1,
"code": "no-cache-npm",
"source": "deno",
"message": "Uncached or missing npm package: \"@types/node\"."
}
])
);
let (maybe_res, maybe_err) = client
.write_request::<_, _, Value>(
"deno/cache",
json!({
"referrer": {
"uri": "file:///a/file.ts",
},
"uris": [
{
"uri": "npm:@types/node",
}
]
}),
)
.unwrap();
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
client
.write_notification(
"textDocument/didChange",
json!({
"textDocument": {
"uri": "file:///a/file.ts",
"version": 2
},
"contentChanges": [
{
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 0
}
},
"text": "fs."
}
]
}),
)
.unwrap();
read_diagnostics(&mut client);
let (maybe_res, maybe_err) = client
.write_request(
"textDocument/completion",
json!({
"textDocument": {
"uri": "file:///a/file.ts"
},
"position": {
"line": 2,
"character": 3
},
"context": {
"triggerKind": 2,
"triggerCharacter": "."
}
}),
)
.unwrap();
assert!(maybe_err.is_none());
if let Some(lsp::CompletionResponse::List(list)) = maybe_res {
assert!(!list.is_incomplete);
assert!(list.items.iter().any(|i| i.label == "writeFile"));
assert!(list.items.iter().any(|i| i.label == "writeFileSync"));
} else {
panic!("unexpected response");
}
shutdown(&mut client);
}
#[test] #[test]
fn lsp_completions_registry() { fn lsp_completions_registry() {
let _g = http_server(); let _g = http_server();

View file

@ -3743,3 +3743,23 @@ fn stdio_streams_are_locked_in_permission_prompt() {
assert_eq!(output, expected_output); assert_eq!(output, expected_output);
}); });
} }
itest!(run_node_builtin_modules_ts {
args: "run --quiet run/node_builtin_modules/mod.ts",
output: "run/node_builtin_modules/mod.ts.out",
envs: vec![(
"DENO_NODE_COMPAT_URL".to_string(),
test_util::std_file_url()
)],
exit_code: 0,
});
itest!(run_node_builtin_modules_js {
args: "run --quiet run/node_builtin_modules/mod.js",
output: "run/node_builtin_modules/mod.js.out",
envs: vec![(
"DENO_NODE_COMPAT_URL".to_string(),
test_util::std_file_url()
)],
exit_code: 0,
});

View file

@ -0,0 +1,3 @@
// @ts-check
import fs from "node:fs";
const _data = fs.readFileSync("./node_builtin.js", 123);

View file

@ -0,0 +1,5 @@
error: TS2769 [ERROR]: No overload matches this call.
[WILDCARD]
const _data = fs.readFileSync("./node_builtin.js", 123);
~~~
at file:///[WILDCARD]/node_builtin_modules/mod.js:3:52

View file

@ -0,0 +1,9 @@
import fs from "node:fs";
const _data = fs.readFileSync("./node_builtin.js", 123);
// check node:module specifically because for deno check it should
// resolve to the @types/node package, but at runtime it uses a different
// builtin object than deno_std
import { builtinModules } from "node:module";
// should error about being string[]
const _testString: number[] = builtinModules;

View file

@ -0,0 +1,13 @@
error: TS2769 [ERROR]: No overload matches this call.
[WILDCARD]
const _data = fs.readFileSync("./node_builtin.js", 123);
~~~
at file:///[WILDCARD]/node_builtin_modules/mod.ts:2:52
TS2322 [ERROR]: Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'.
const _testString: number[] = builtinModules;
~~~~~~~~~~~
at file:///[WILDCARD]/node_builtin_modules/mod.ts:9:7
Found 2 errors.

View file

@ -0,0 +1,2 @@
import process from "node:process";
console.log(process.version);

View file

@ -0,0 +1 @@
v[WILDCARD].[WILDCARD].[WILDCARD]

View file

@ -0,0 +1,2 @@
import process from "node:process";
console.log(process.version);

View file

@ -0,0 +1 @@
v[WILDCARD].[WILDCARD].[WILDCARD]

View file

@ -232,10 +232,16 @@ fn get_tsc_roots(
graph_data: &GraphData, graph_data: &GraphData,
check_js: bool, check_js: bool,
) -> Vec<(ModuleSpecifier, MediaType)> { ) -> Vec<(ModuleSpecifier, MediaType)> {
graph_data let mut result = Vec::new();
.entries() if graph_data.has_node_builtin_specifier() {
.into_iter() // inject a specifier that will resolve node types
.filter_map(|(specifier, module_entry)| match module_entry { result.push((
ModuleSpecifier::parse("asset:///node_types.d.ts").unwrap(),
MediaType::Dts,
));
}
result.extend(graph_data.entries().into_iter().filter_map(
|(specifier, module_entry)| match module_entry {
ModuleEntry::Module { ModuleEntry::Module {
media_type, code, .. media_type, code, ..
} => match media_type { } => match media_type {
@ -252,8 +258,9 @@ fn get_tsc_roots(
_ => None, _ => None,
}, },
_ => None, _ => None,
}) },
.collect() ));
result
} }
/// Matches the `@ts-check` pragma. /// Matches the `@ts-check` pragma.

View file

@ -91389,10 +91389,15 @@ var ts;
var deno; var deno;
(function (deno) { (function (deno) {
var isNodeSourceFile = function () { return false; }; var isNodeSourceFile = function () { return false; };
var nodeBuiltInModuleNames = new ts.Set();
function setIsNodeSourceFileCallback(callback) { function setIsNodeSourceFileCallback(callback) {
isNodeSourceFile = callback; isNodeSourceFile = callback;
} }
deno.setIsNodeSourceFileCallback = setIsNodeSourceFileCallback; deno.setIsNodeSourceFileCallback = setIsNodeSourceFileCallback;
function setNodeBuiltInModuleNames(names) {
nodeBuiltInModuleNames = new ts.Set(names);
}
deno.setNodeBuiltInModuleNames = setNodeBuiltInModuleNames;
// When upgrading: // When upgrading:
// 1. Inspect all usages of "globals" and "globalThisSymbol" in checker.ts // 1. Inspect all usages of "globals" and "globalThisSymbol" in checker.ts
// - Beware that `globalThisType` might refer to the global `this` type // - Beware that `globalThisType` might refer to the global `this` type
@ -91452,8 +91457,16 @@ var ts;
function getGlobalsForName(id) { function getGlobalsForName(id) {
// Node ambient modules are only accessible in the node code, // Node ambient modules are only accessible in the node code,
// so put them on the node globals // so put them on the node globals
if (ambientModuleSymbolRegex.test(id)) if (ambientModuleSymbolRegex.test(id)) {
if (id.startsWith('"node:')) {
// check if it's a node specifier that we support
var name = id.slice(6, -1);
if (nodeBuiltInModuleNames.has(name)) {
return globals;
}
}
return nodeGlobals; return nodeGlobals;
}
return nodeOnlyGlobalNames.has(id) ? nodeGlobals : globals; return nodeOnlyGlobalNames.has(id) ? nodeGlobals : globals;
} }
function mergeGlobalSymbolTable(node, source, unidirectional) { function mergeGlobalSymbolTable(node, source, unidirectional) {

View file

@ -855,25 +855,7 @@ delete Object.prototype.__proto__;
...program.getOptionsDiagnostics(), ...program.getOptionsDiagnostics(),
...program.getGlobalDiagnostics(), ...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics(), ...program.getSemanticDiagnostics(),
].filter((diagnostic) => { ].filter((diagnostic) => !IGNORED_DIAGNOSTICS.includes(diagnostic.code));
if (IGNORED_DIAGNOSTICS.includes(diagnostic.code)) {
return false;
} else if (
diagnostic.code === 1259 &&
typeof diagnostic.messageText === "string" &&
diagnostic.messageText.startsWith(
"Module '\"deno:///missing_dependency.d.ts\"' can only be default-imported using the 'allowSyntheticDefaultImports' flag",
)
) {
// For now, ignore diagnostics like:
// > TS1259 [ERROR]: Module '"deno:///missing_dependency.d.ts"' can only be default-imported using the 'allowSyntheticDefaultImports' flag
// This diagnostic has surfaced due to supporting node cjs imports because this module does `export =`.
// See discussion in https://github.com/microsoft/TypeScript/pull/51136
return false;
} else {
return true;
}
});
// emit the tsbuildinfo file // emit the tsbuildinfo file
// @ts-ignore: emitBuildInfo is not exposed (https://github.com/microsoft/TypeScript/issues/49871) // @ts-ignore: emitBuildInfo is not exposed (https://github.com/microsoft/TypeScript/issues/49871)
@ -1273,9 +1255,11 @@ delete Object.prototype.__proto__;
// A build time only op that provides some setup information that is used to // A build time only op that provides some setup information that is used to
// ensure the snapshot is setup properly. // ensure the snapshot is setup properly.
/** @type {{ buildSpecifier: string; libs: string[] }} */ /** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
const { buildSpecifier, libs, nodeBuiltInModuleNames } = ops.op_build_info();
ts.deno.setNodeBuiltInModuleNames(nodeBuiltInModuleNames);
const { buildSpecifier, libs } = ops.op_build_info();
for (const lib of libs) { for (const lib of libs) {
const specifier = `lib.${lib}.d.ts`; const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into // we are using internal APIs here to "inject" our custom libraries into

View file

@ -30,6 +30,7 @@ declare global {
function setIsNodeSourceFileCallback( function setIsNodeSourceFileCallback(
callback: (sourceFile: SourceFile) => boolean, callback: (sourceFile: SourceFile) => boolean,
); );
function setNodeBuiltInModuleNames(names: string[]);
} }
} }

View file

@ -299,9 +299,11 @@ impl Diagnostic {
fn fmt_related_information(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt_related_information(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(related_information) = self.related_information.as_ref() { if let Some(related_information) = self.related_information.as_ref() {
write!(f, "\n\n")?; if !related_information.is_empty() {
for info in related_information { write!(f, "\n\n")?;
info.fmt_stack(f, 4)?; for info in related_information {
info.fmt_stack(f, 4)?;
}
} }
} }

View file

@ -165,6 +165,12 @@ pub static LAZILY_LOADED_STATIC_ASSETS: Lazy<
"lib.webworker.iterable.d.ts", "lib.webworker.iterable.d.ts",
inc!("lib.webworker.iterable.d.ts"), inc!("lib.webworker.iterable.d.ts"),
), ),
(
// Special file that can be used to inject the @types/node package.
// This is used for `node:` specifiers.
"node_types.d.ts",
"/// <reference types=\"npm:@types/node\" />\n",
),
]) ])
.iter() .iter()
.cloned() .cloned()
@ -599,117 +605,133 @@ fn op_resolve(
"Error converting a string module specifier for \"op_resolve\".", "Error converting a string module specifier for \"op_resolve\".",
)? )?
}; };
for specifier in &args.specifiers { for specifier in args.specifiers {
if let Some(module_name) = specifier.strip_prefix("node:") {
if crate::node::resolve_builtin_node_module(module_name).is_ok() {
// return itself for node: specifiers because during type checking
// we resolve to the ambient modules in the @types/node package
// rather than deno_std/node
resolved.push((specifier, MediaType::Dts.to_string()));
continue;
}
}
if specifier.starts_with("asset:///") { if specifier.starts_with("asset:///") {
resolved.push(( let media_type =
specifier.clone(), MediaType::from(&specifier).as_ts_extension().to_string();
MediaType::from(specifier).as_ts_extension().to_string(), resolved.push((specifier, media_type));
)); continue;
} else { }
let graph_data = state.graph_data.read();
let resolved_dep = match graph_data.get_dependencies(&referrer) { let graph_data = state.graph_data.read();
Some(dependencies) => dependencies.get(specifier).map(|d| { let resolved_dep = match graph_data.get_dependencies(&referrer) {
if matches!(d.maybe_type, Resolved::Ok { .. }) { Some(dependencies) => dependencies.get(&specifier).map(|d| {
&d.maybe_type if matches!(d.maybe_type, Resolved::Ok { .. }) {
} else { &d.maybe_type
&d.maybe_code } else {
} &d.maybe_code
}), }
None => None, }),
}; None => None,
let maybe_result = match resolved_dep { };
Some(Resolved::Ok { specifier, .. }) => { let maybe_result = match resolved_dep {
let specifier = graph_data.follow_redirect(specifier); Some(Resolved::Ok { specifier, .. }) => {
match graph_data.get(&specifier) { let specifier = graph_data.follow_redirect(specifier);
Some(ModuleEntry::Module { match graph_data.get(&specifier) {
media_type, Some(ModuleEntry::Module {
maybe_types, media_type,
.. maybe_types,
}) => match maybe_types { ..
Some(Resolved::Ok { specifier, .. }) => { }) => match maybe_types {
let types = graph_data.follow_redirect(specifier); Some(Resolved::Ok { specifier, .. }) => {
match graph_data.get(&types) { let types = graph_data.follow_redirect(specifier);
Some(ModuleEntry::Module { media_type, .. }) => { match graph_data.get(&types) {
Some((types, *media_type)) Some(ModuleEntry::Module { media_type, .. }) => {
} Some((types, *media_type))
_ => None,
} }
_ => None,
} }
_ => Some((specifier, *media_type)), }
}, _ => Some((specifier, *media_type)),
_ => { },
// handle npm:<package> urls _ => {
if let Ok(npm_ref) = // handle npm:<package> urls
NpmPackageReference::from_specifier(&specifier) if let Ok(npm_ref) = NpmPackageReference::from_specifier(&specifier)
{ {
if let Some(npm_resolver) = &state.maybe_npm_resolver { if let Some(npm_resolver) = &state.maybe_npm_resolver {
Some(resolve_npm_package_reference_types( Some(resolve_npm_package_reference_types(
&npm_ref, &npm_ref,
npm_resolver, npm_resolver,
)?) )?)
} else {
None
}
} else { } else {
None None
} }
}
}
}
_ => {
state.maybe_npm_resolver.as_ref().and_then(|npm_resolver| {
if npm_resolver.in_npm_package(&referrer) {
// we're in an npm package, so use node resolution
Some(NodeResolution::into_specifier_and_media_type(
node::node_resolve(
specifier,
&referrer,
NodeResolutionMode::Types,
npm_resolver,
&mut PermissionsContainer::allow_all(),
)
.ok()
.flatten(),
))
} else { } else {
None None
} }
}) }
} }
}; }
let result = match maybe_result { _ => {
Some((specifier, media_type)) => { if let Some(npm_resolver) = state.maybe_npm_resolver.as_ref() {
let specifier_str = match specifier.scheme() { if npm_resolver.in_npm_package(&referrer) {
"data" | "blob" => { // we're in an npm package, so use node resolution
let specifier_str = hash_url(&specifier, media_type); Some(NodeResolution::into_specifier_and_media_type(
node::node_resolve(
&specifier,
&referrer,
NodeResolutionMode::Types,
npm_resolver,
&mut PermissionsContainer::allow_all(),
)
.ok()
.flatten(),
))
} else if let Ok(npm_ref) = NpmPackageReference::from_str(&specifier)
{
// this could occur when resolving npm:@types/node when it is
// injected and not part of the graph
Some(resolve_npm_package_reference_types(&npm_ref, npm_resolver)?)
} else {
None
}
} else {
None
}
}
};
let result = match maybe_result {
Some((specifier, media_type)) => {
let specifier_str = match specifier.scheme() {
"data" | "blob" => {
let specifier_str = hash_url(&specifier, media_type);
state
.remapped_specifiers
.insert(specifier_str.clone(), specifier);
specifier_str
}
_ => {
if let Some(specifier_str) =
maybe_remap_specifier(&specifier, media_type)
{
state state
.remapped_specifiers .remapped_specifiers
.insert(specifier_str.clone(), specifier); .insert(specifier_str.clone(), specifier);
specifier_str specifier_str
} else {
specifier.to_string()
} }
_ => { }
if let Some(specifier_str) = };
maybe_remap_specifier(&specifier, media_type) (specifier_str, media_type.as_ts_extension().into())
{ }
state None => (
.remapped_specifiers "deno:///missing_dependency.d.ts".to_string(),
.insert(specifier_str.clone(), specifier); ".d.ts".to_string(),
specifier_str ),
} else { };
specifier.to_string() log::debug!("Resolved {} to {:?}", specifier, result);
} resolved.push(result);
}
};
(specifier_str, media_type.as_ts_extension().into())
}
None => (
"deno:///missing_dependency.d.ts".to_string(),
".d.ts".to_string(),
),
};
log::debug!("Resolved {} to {:?}", specifier, result);
resolved.push(result);
}
} }
Ok(resolved) Ok(resolved)

View file

@ -675,3 +675,197 @@ fn op_require_break_on_next_statement(state: &mut OpState) {
.borrow_mut() .borrow_mut()
.wait_for_session_and_break_on_next_statement() .wait_for_session_and_break_on_next_statement()
} }
pub struct NodeModulePolyfill {
/// Name of the module like "assert" or "timers/promises"
pub name: &'static str,
/// Specifier relative to the root of `deno_std` repo, like "node/assert.ts"
pub specifier: &'static str,
}
pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[
NodeModulePolyfill {
name: "assert",
specifier: "node/assert.ts",
},
NodeModulePolyfill {
name: "assert/strict",
specifier: "node/assert/strict.ts",
},
NodeModulePolyfill {
name: "async_hooks",
specifier: "node/async_hooks.ts",
},
NodeModulePolyfill {
name: "buffer",
specifier: "node/buffer.ts",
},
NodeModulePolyfill {
name: "child_process",
specifier: "node/child_process.ts",
},
NodeModulePolyfill {
name: "cluster",
specifier: "node/cluster.ts",
},
NodeModulePolyfill {
name: "console",
specifier: "node/console.ts",
},
NodeModulePolyfill {
name: "constants",
specifier: "node/constants.ts",
},
NodeModulePolyfill {
name: "crypto",
specifier: "node/crypto.ts",
},
NodeModulePolyfill {
name: "dgram",
specifier: "node/dgram.ts",
},
NodeModulePolyfill {
name: "dns",
specifier: "node/dns.ts",
},
NodeModulePolyfill {
name: "dns/promises",
specifier: "node/dns/promises.ts",
},
NodeModulePolyfill {
name: "domain",
specifier: "node/domain.ts",
},
NodeModulePolyfill {
name: "events",
specifier: "node/events.ts",
},
NodeModulePolyfill {
name: "fs",
specifier: "node/fs.ts",
},
NodeModulePolyfill {
name: "fs/promises",
specifier: "node/fs/promises.ts",
},
NodeModulePolyfill {
name: "http",
specifier: "node/http.ts",
},
NodeModulePolyfill {
name: "https",
specifier: "node/https.ts",
},
NodeModulePolyfill {
name: "module",
// NOTE(bartlomieju): `module` is special, because we don't want to use
// `deno_std/node/module.ts`, but instead use a special shim that we
// provide in `ext/node`.
specifier: "[USE `deno_node::MODULE_ES_SHIM` to get this module]",
},
NodeModulePolyfill {
name: "net",
specifier: "node/net.ts",
},
NodeModulePolyfill {
name: "os",
specifier: "node/os.ts",
},
NodeModulePolyfill {
name: "path",
specifier: "node/path.ts",
},
NodeModulePolyfill {
name: "path/posix",
specifier: "node/path/posix.ts",
},
NodeModulePolyfill {
name: "path/win32",
specifier: "node/path/win32.ts",
},
NodeModulePolyfill {
name: "perf_hooks",
specifier: "node/perf_hooks.ts",
},
NodeModulePolyfill {
name: "process",
specifier: "node/process.ts",
},
NodeModulePolyfill {
name: "querystring",
specifier: "node/querystring.ts",
},
NodeModulePolyfill {
name: "readline",
specifier: "node/readline.ts",
},
NodeModulePolyfill {
name: "stream",
specifier: "node/stream.ts",
},
NodeModulePolyfill {
name: "stream/consumers",
specifier: "node/stream/consumers.mjs",
},
NodeModulePolyfill {
name: "stream/promises",
specifier: "node/stream/promises.mjs",
},
NodeModulePolyfill {
name: "stream/web",
specifier: "node/stream/web.ts",
},
NodeModulePolyfill {
name: "string_decoder",
specifier: "node/string_decoder.ts",
},
NodeModulePolyfill {
name: "sys",
specifier: "node/sys.ts",
},
NodeModulePolyfill {
name: "timers",
specifier: "node/timers.ts",
},
NodeModulePolyfill {
name: "timers/promises",
specifier: "node/timers/promises.ts",
},
NodeModulePolyfill {
name: "tls",
specifier: "node/tls.ts",
},
NodeModulePolyfill {
name: "tty",
specifier: "node/tty.ts",
},
NodeModulePolyfill {
name: "url",
specifier: "node/url.ts",
},
NodeModulePolyfill {
name: "util",
specifier: "node/util.ts",
},
NodeModulePolyfill {
name: "util/types",
specifier: "node/util/types.ts",
},
NodeModulePolyfill {
name: "v8",
specifier: "node/v8.ts",
},
NodeModulePolyfill {
name: "vm",
specifier: "node/vm.ts",
},
NodeModulePolyfill {
name: "worker_threads",
specifier: "node/worker_threads.ts",
},
NodeModulePolyfill {
name: "zlib",
specifier: "node/zlib.ts",
},
];