summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/build.rs7
-rw-r--r--cli/cache/mod.rs23
-rw-r--r--cli/graph_util.rs56
-rw-r--r--cli/lsp/cache.rs2
-rw-r--r--cli/lsp/diagnostics.rs29
-rw-r--r--cli/lsp/documents.rs69
-rw-r--r--cli/lsp/tsc.rs58
-rw-r--r--cli/node/mod.rs202
-rw-r--r--cli/npm/mod.rs2
-rw-r--r--cli/npm/resolution/graph.rs2
-rw-r--r--cli/npm/resolution/mod.rs2
-rw-r--r--cli/npm/resolution/specifier.rs43
-rw-r--r--cli/npm/resolvers/mod.rs94
-rw-r--r--cli/proc_state.rs52
-rw-r--r--cli/tests/integration/check_tests.rs12
-rw-r--r--cli/tests/integration/lsp_tests.rs184
-rw-r--r--cli/tests/integration/run_tests.rs20
-rw-r--r--cli/tests/testdata/check/node_builtin_modules/mod.js3
-rw-r--r--cli/tests/testdata/check/node_builtin_modules/mod.js.out5
-rw-r--r--cli/tests/testdata/check/node_builtin_modules/mod.ts9
-rw-r--r--cli/tests/testdata/check/node_builtin_modules/mod.ts.out13
-rw-r--r--cli/tests/testdata/run/node_builtin_modules/mod.js2
-rw-r--r--cli/tests/testdata/run/node_builtin_modules/mod.js.out1
-rw-r--r--cli/tests/testdata/run/node_builtin_modules/mod.ts2
-rw-r--r--cli/tests/testdata/run/node_builtin_modules/mod.ts.out1
-rw-r--r--cli/tools/check.rs19
-rw-r--r--cli/tsc/00_typescript.js15
-rw-r--r--cli/tsc/99_main_compiler.js26
-rw-r--r--cli/tsc/compiler.d.ts1
-rw-r--r--cli/tsc/diagnostics.rs8
-rw-r--r--cli/tsc/mod.rs214
-rw-r--r--ext/node/lib.rs194
32 files changed, 925 insertions, 445 deletions
diff --git a/cli/build.rs b/cli/build.rs
index 03aa47c51..131d404a7 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -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;
@@ -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,
})
}
diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs
index 4d255b014..c8fcaa223 100644
--- a/cli/cache/mod.rs
+++ b/cli/cache/mod.rs
@@ -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;
}
@@ -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 {
diff --git a/cli/graph_util.rs b/cli/graph_util.rs
index 22bddf137..243fd6eef 100644
--- a/cli/graph_util.rs
+++ b/cli/graph_util.rs
@@ -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;
@@ -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;
@@ -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>>,
@@ -83,13 +87,12 @@ 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) {
@@ -97,8 +100,19 @@ impl GraphData {
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(),
@@ -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);
}
}
@@ -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> {
@@ -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 {
@@ -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(),
@@ -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(),
diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs
index 47f6e44ba..23469a583 100644
--- a/cli/lsp/cache.rs
+++ b/cli/lsp/cache.rs
@@ -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
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index b4be63a55..4faff2fae 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -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;
@@ -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 {
@@ -641,6 +644,7 @@ impl DenoDiagnostic {
},
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::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,
@@ -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
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index bc1e4a808..b99b64bfe 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -770,6 +770,9 @@ pub struct Documents {
maybe_resolver: Option<CliResolver>,
/// The npm package requirements.
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.
specifier_resolver: Arc<SpecifierResolver>,
}
@@ -785,6 +788,7 @@ impl Documents {
imports: Default::default(),
maybe_resolver: None,
npm_reqs: Default::default(),
+ has_injected_types_node_package: false,
specifier_resolver: Arc::new(SpecifierResolver::new(location)),
}
}
@@ -925,6 +929,12 @@ impl Documents {
(*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.
pub fn get(&self, original_specifier: &ModuleSpecifier) -> Option<Document> {
let specifier = self.specifier_resolver.resolve(original_specifier)?;
@@ -985,11 +995,15 @@ impl Documents {
/// tsc when type checking.
pub fn resolve(
&self,
- specifiers: &[String],
- referrer: &ModuleSpecifier,
+ specifiers: Vec<String>,
+ referrer_doc: &AssetOrDocument,
maybe_npm_resolver: Option<&NpmPackageResolver>,
- ) -> Option<Vec<Option<(ModuleSpecifier, MediaType)>>> {
- let dependencies = self.get(referrer)?.0.dependencies.clone();
+ ) -> Vec<Option<(ModuleSpecifier, MediaType)>> {
+ 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();
for specifier in specifiers {
if let Some(npm_resolver) = maybe_npm_resolver {
@@ -997,7 +1011,7 @@ impl Documents {
// we're in an npm package, so use node resolution
results.push(Some(NodeResolution::into_specifier_and_media_type(
node::node_resolve(
- specifier,
+ &specifier,
referrer,
NodeResolutionMode::Types,
npm_resolver,
@@ -1009,15 +1023,28 @@ impl Documents {
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 let Ok(specifier) = ModuleSpecifier::parse(specifier) {
+ if let Ok(specifier) = ModuleSpecifier::parse(&specifier) {
let media_type = MediaType::from(&specifier);
results.push(Some((specifier, media_type)));
} else {
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 {
results.push(self.resolve_dependency(specifier, maybe_npm_resolver));
} else if let Resolved::Ok { specifier, .. } = &dep.maybe_code {
@@ -1026,12 +1053,12 @@ impl Documents {
results.push(None);
}
} 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
let specifier = specifier.clone();
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| {
NodeResolution::into_specifier_and_media_type(
node_resolve_npm_reference(
@@ -1048,7 +1075,7 @@ impl Documents {
results.push(None);
}
}
- Some(results)
+ results
}
/// Update the location of the on disk cache for the document store.
@@ -1125,6 +1152,7 @@ impl Documents {
analyzed_specifiers: HashSet<ModuleSpecifier>,
pending_specifiers: VecDeque<ModuleSpecifier>,
npm_reqs: HashSet<NpmPackageReq>,
+ has_node_builtin_specifier: bool,
}
impl DocAnalyzer {
@@ -1148,7 +1176,11 @@ impl Documents {
fn analyze_doc(&mut self, specifier: &ModuleSpecifier, doc: &Document) {
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() {
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.npm_reqs = Arc::new(doc_analyzer.npm_reqs);
+ self.npm_reqs = Arc::new(npm_reqs);
self.dirty = false;
file_system_docs.dirty = false;
}
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index ced8fb699..3619f529c 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -216,7 +216,7 @@ fn new_assets_map() -> Arc<Mutex<AssetsMap>> {
let asset = AssetDocument::new(specifier.clone(), v);
(specifier, asset)
})
- .collect();
+ .collect::<AssetsMap>();
Arc::new(Mutex::new(assets))
}
@@ -2728,28 +2728,29 @@ fn op_resolve(
let state = state.borrow_mut::<State>();
let mark = state.performance.mark("op_resolve", Some(&args));
let referrer = state.normalize_specifier(&args.base)?;
-
- let result = if let Some(resolved) = state.state_snapshot.documents.resolve(
- &args.specifiers,
- &referrer,
- state.state_snapshot.maybe_npm_resolver.as_ref(),
- ) {
- Ok(
- resolved
- .into_iter()
- .map(|o| {
- o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string()))
- })
- .collect(),
- )
- } else {
- Err(custom_error(
+ let result = match state.get_asset_or_document(&referrer) {
+ Some(referrer_doc) => {
+ let resolved = state.state_snapshot.documents.resolve(
+ args.specifiers,
+ &referrer_doc,
+ state.state_snapshot.maybe_npm_resolver.as_ref(),
+ );
+ Ok(
+ resolved
+ .into_iter()
+ .map(|o| {
+ o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string()))
+ })
+ .collect(),
+ )
+ }
+ None => Err(custom_error(
"NotFound",
format!(
"Error resolving. Referring specifier \"{}\" was not found.",
args.base
),
- ))
+ )),
};
state.performance.measure(mark);
@@ -2764,15 +2765,20 @@ fn op_respond(state: &mut OpState, args: Response) -> bool {
}
#[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>();
- state
- .state_snapshot
- .documents
- .documents(true, true)
- .into_iter()
- .map(|d| d.specifier().clone())
- .collect()
+ let documents = &state.state_snapshot.documents;
+ let open_docs = documents.documents(true, true);
+
+ let mut result = Vec::with_capacity(open_docs.len() + 1);
+
+ if documents.has_injected_types_node_package() {
+ // 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)]
diff --git a/cli/node/mod.rs b/cli/node/mod.rs
index aed639bc4..2125f670e 100644
--- a/cli/node/mod.rs
+++ b/cli/node/mod.rs
@@ -25,6 +25,7 @@ use deno_runtime::deno_node::package_imports_resolve;
use deno_runtime::deno_node::package_resolve;
use deno_runtime::deno_node::path_to_declaration_path;
use deno_runtime::deno_node::NodeModuleKind;
+use deno_runtime::deno_node::NodeModulePolyfill;
use deno_runtime::deno_node::NodePermissions;
use deno_runtime::deno_node::NodeResolutionMode;
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::DEFAULT_CONDITIONS;
use deno_runtime::deno_node::NODE_GLOBAL_THIS_NAME;
+use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use deno_runtime::permissions::PermissionsContainer;
use once_cell::sync::Lazy;
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(|| {
if let Ok(url_str) = std::env::var("DENO_NODE_COMPAT_URL") {
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());
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 {
@@ -336,7 +146,7 @@ pub fn resolve_builtin_node_module(specifier: &str) -> Result<Url, AnyError> {
}
Err(generic_error(format!(
- "Unknown built-in Node module: {}",
+ "Unknown built-in \"node:\" module: {}",
specifier
)))
}
diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs
index ce6fec5e5..9f41e508a 100644
--- a/cli/npm/mod.rs
+++ b/cli/npm/mod.rs
@@ -14,7 +14,7 @@ pub use cache::NpmCache;
pub use registry::NpmPackageVersionDistInfo;
pub use registry::NpmRegistryApi;
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::NpmPackageReference;
pub use resolution::NpmPackageReq;
diff --git a/cli/npm/resolution/graph.rs b/cli/npm/resolution/graph.rs
index 81ff58546..e21048149 100644
--- a/cli/npm/resolution/graph.rs
+++ b/cli/npm/resolution/graph.rs
@@ -1081,7 +1081,7 @@ fn tag_to_version_info<'a>(
// explicit version.
if tag == "latest" && info.name == "@types/node" {
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,
parent,
);
diff --git a/cli/npm/resolution/mod.rs b/cli/npm/resolution/mod.rs
index f10678cbe..ed194bbac 100644
--- a/cli/npm/resolution/mod.rs
+++ b/cli/npm/resolution/mod.rs
@@ -28,7 +28,7 @@ mod specifier;
use graph::Graph;
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::NpmPackageReq;
diff --git a/cli/npm/resolution/specifier.rs b/cli/npm/resolution/specifier.rs
index 9a7f67cf8..6667c60dd 100644
--- a/cli/npm/resolution/specifier.rs
+++ b/cli/npm/resolution/specifier.rs
@@ -168,8 +168,15 @@ impl NpmVersionMatcher for NpmPackageReq {
}
}
-/// Resolves the npm package requirements from the graph attempting. The order
-/// returned is the order they should be resolved in.
+pub struct GraphNpmInfo {
+ /// 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
/// 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
/// 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>(
graph: &'a ModuleGraph,
module: &'a deno_graph::Module,
@@ -248,6 +255,7 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
graph: &ModuleGraph,
specifier_graph: &mut SpecifierTree,
seen: &mut HashSet<ModuleSpecifier>,
+ has_node_builtin_specifier: &mut bool,
) {
if !seen.insert(module.specifier.clone()) {
return; // already visited
@@ -267,12 +275,22 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
.dependencies
.insert(get_folder_path_specifier(specifier));
}
+
+ if !*has_node_builtin_specifier && specifier.scheme() == "node" {
+ *has_node_builtin_specifier = true;
+ }
}
// now visit all the dependencies
for specifier in &specifiers {
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<_>>();
let mut seen = HashSet::new();
let mut specifier_graph = SpecifierTree::default();
+ let mut has_node_builtin_specifier = false;
for root in &root_specifiers {
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 {
@@ -979,7 +1007,8 @@ mod tests {
},
)
.await;
- let reqs = resolve_npm_package_reqs(&graph)
+ let reqs = resolve_graph_npm_info(&graph)
+ .package_reqs
.into_iter()
.map(|r| r.to_string())
.collect::<Vec<_>>();
diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs
index 767187f5e..4307f2b2e 100644
--- a/cli/npm/resolvers/mod.rs
+++ b/cli/npm/resolvers/mod.rs
@@ -95,59 +95,51 @@ impl NpmPackageResolver {
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
) -> 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,
api,
no_npm,
local_node_modules_path,
- None,
- )
+ maybe_snapshot,
+ maybe_lockfile,
+ ))
}
- /// This function will replace current resolver with a new one built from a
- /// 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(
+ fn new_inner(
cache: NpmCache,
api: RealNpmRegistryApi,
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
initial_snapshot: Option<NpmResolutionSnapshot>,
+ maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
) -> Self {
let process_npm_state = NpmProcessState::take();
let local_node_modules_path = local_node_modules_path.or_else(|| {
@@ -177,7 +169,7 @@ impl NpmPackageResolver {
local_node_modules_path,
api,
cache,
- maybe_lockfile: None,
+ maybe_lockfile,
}
}
@@ -320,12 +312,13 @@ impl NpmPackageResolver {
/// Gets a new resolver with a new snapshotted state.
pub fn snapshotted(&self) -> Self {
- Self::new_with_maybe_snapshot(
+ Self::new_inner(
self.cache.clone(),
self.api.clone(),
self.no_npm,
self.local_node_modules_path.clone(),
Some(self.snapshot()),
+ None,
)
}
@@ -336,6 +329,19 @@ impl NpmPackageResolver {
pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
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 {
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
index 16e80d6f6..fd5ad4840 100644
--- a/cli/proc_state.rs
+++ b/cli/proc_state.rs
@@ -23,7 +23,7 @@ use crate::graph_util::ModuleEntry;
use crate::http_util::HttpClient;
use crate::node;
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::NpmPackageReference;
use crate::npm::NpmPackageResolver;
@@ -261,20 +261,16 @@ impl ProcState {
http_client.clone(),
progress_bar.clone(),
);
- let maybe_lockfile = lockfile.as_ref().cloned();
- let mut npm_resolver = NpmPackageResolver::new(
+ let npm_resolver = NpmPackageResolver::new_with_maybe_lockfile(
npm_cache.clone(),
api,
cli_options.no_npm(),
cli_options
.resolve_local_node_modules_folder()
.with_context(|| "Resolving local node_modules folder.")?,
- );
- if let Some(lockfile) = maybe_lockfile {
- npm_resolver
- .add_lockfile_and_maybe_regenerate_snapshot(lockfile)
- .await?;
- }
+ lockfile.as_ref().cloned(),
+ )
+ .await?;
let node_analysis_cache =
NodeAnalysisCache::new(Some(dir.node_analysis_db_file_path()));
@@ -424,7 +420,7 @@ impl ProcState {
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();
graph_data.add_graph(&graph);
let check_js = self.options.check_js();
@@ -435,7 +431,10 @@ impl ProcState {
check_js,
)
.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() {
@@ -443,6 +442,15 @@ impl ProcState {
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);
// 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
// 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.
@@ -732,9 +745,20 @@ impl ProcState {
.await;
// add the found npm package requirements to the npm resolver and cache them
- let npm_package_reqs = resolve_npm_package_reqs(&graph);
- if !npm_package_reqs.is_empty() {
- self.npm_resolver.add_package_reqs(npm_package_reqs).await?;
+ let graph_npm_info = resolve_graph_npm_info(&graph);
+ if !graph_npm_info.package_reqs.is_empty() {
+ 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)
diff --git a/cli/tests/integration/check_tests.rs b/cli/tests/integration/check_tests.rs
index 38301f079..66433f81d 100644
--- a/cli/tests/integration/check_tests.rs
+++ b/cli/tests/integration/check_tests.rs
@@ -64,6 +64,18 @@ itest!(check_static_response_json {
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 {
args: "check --quiet check/no_error_truncation/main.ts --config check/no_error_truncation/deno.json",
output: "check/no_error_truncation/main.out",
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index 3fba65c46..4f08ad84b 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -4408,6 +4408,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]
fn lsp_completions_registry() {
let _g = http_server();
let mut client = init("initialize_params_registry.json");
diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs
index 564b7355f..f12282b9f 100644
--- a/cli/tests/integration/run_tests.rs
+++ b/cli/tests/integration/run_tests.rs
@@ -3743,3 +3743,23 @@ fn stdio_streams_are_locked_in_permission_prompt() {
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,
+});
diff --git a/cli/tests/testdata/check/node_builtin_modules/mod.js b/cli/tests/testdata/check/node_builtin_modules/mod.js
new file mode 100644
index 000000000..196fb9be9
--- /dev/null
+++ b/cli/tests/testdata/check/node_builtin_modules/mod.js
@@ -0,0 +1,3 @@
+// @ts-check
+import fs from "node:fs";
+const _data = fs.readFileSync("./node_builtin.js", 123);
diff --git a/cli/tests/testdata/check/node_builtin_modules/mod.js.out b/cli/tests/testdata/check/node_builtin_modules/mod.js.out
new file mode 100644
index 000000000..97786ebae
--- /dev/null
+++ b/cli/tests/testdata/check/node_builtin_modules/mod.js.out
@@ -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
diff --git a/cli/tests/testdata/check/node_builtin_modules/mod.ts b/cli/tests/testdata/check/node_builtin_modules/mod.ts
new file mode 100644
index 000000000..0e62353fe
--- /dev/null
+++ b/cli/tests/testdata/check/node_builtin_modules/mod.ts
@@ -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;
diff --git a/cli/tests/testdata/check/node_builtin_modules/mod.ts.out b/cli/tests/testdata/check/node_builtin_modules/mod.ts.out
new file mode 100644
index 000000000..49b762cff
--- /dev/null
+++ b/cli/tests/testdata/check/node_builtin_modules/mod.ts.out
@@ -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.
diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.js b/cli/tests/testdata/run/node_builtin_modules/mod.js
new file mode 100644
index 000000000..70e39be56
--- /dev/null
+++ b/cli/tests/testdata/run/node_builtin_modules/mod.js
@@ -0,0 +1,2 @@
+import process from "node:process";
+console.log(process.version);
diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.js.out b/cli/tests/testdata/run/node_builtin_modules/mod.js.out
new file mode 100644
index 000000000..9dc2247f4
--- /dev/null
+++ b/cli/tests/testdata/run/node_builtin_modules/mod.js.out
@@ -0,0 +1 @@
+v[WILDCARD].[WILDCARD].[WILDCARD]
diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.ts b/cli/tests/testdata/run/node_builtin_modules/mod.ts
new file mode 100644
index 000000000..70e39be56
--- /dev/null
+++ b/cli/tests/testdata/run/node_builtin_modules/mod.ts
@@ -0,0 +1,2 @@
+import process from "node:process";
+console.log(process.version);
diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.ts.out b/cli/tests/testdata/run/node_builtin_modules/mod.ts.out
new file mode 100644
index 000000000..9dc2247f4
--- /dev/null
+++ b/cli/tests/testdata/run/node_builtin_modules/mod.ts.out
@@ -0,0 +1 @@
+v[WILDCARD].[WILDCARD].[WILDCARD]
diff --git a/cli/tools/check.rs b/cli/tools/check.rs
index 2ab4fe498..d669a736f 100644
--- a/cli/tools/check.rs
+++ b/cli/tools/check.rs
@@ -232,10 +232,16 @@ fn get_tsc_roots(
graph_data: &GraphData,
check_js: bool,
) -> Vec<(ModuleSpecifier, MediaType)> {
- graph_data
- .entries()
- .into_iter()
- .filter_map(|(specifier, module_entry)| match module_entry {
+ let mut result = Vec::new();
+ if graph_data.has_node_builtin_specifier() {
+ // inject a specifier that will resolve node types
+ 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 {
media_type, code, ..
} => match media_type {
@@ -252,8 +258,9 @@ fn get_tsc_roots(
_ => None,
},
_ => None,
- })
- .collect()
+ },
+ ));
+ result
}
/// Matches the `@ts-check` pragma.
diff --git a/cli/tsc/00_typescript.js b/cli/tsc/00_typescript.js
index 789de3a66..3fbf4624a 100644
--- a/cli/tsc/00_typescript.js
+++ b/cli/tsc/00_typescript.js
@@ -91389,10 +91389,15 @@ var ts;
var deno;
(function (deno) {
var isNodeSourceFile = function () { return false; };
+ var nodeBuiltInModuleNames = new ts.Set();
function setIsNodeSourceFileCallback(callback) {
isNodeSourceFile = callback;
}
deno.setIsNodeSourceFileCallback = setIsNodeSourceFileCallback;
+ function setNodeBuiltInModuleNames(names) {
+ nodeBuiltInModuleNames = new ts.Set(names);
+ }
+ deno.setNodeBuiltInModuleNames = setNodeBuiltInModuleNames;
// When upgrading:
// 1. Inspect all usages of "globals" and "globalThisSymbol" in checker.ts
// - Beware that `globalThisType` might refer to the global `this` type
@@ -91452,8 +91457,16 @@ var ts;
function getGlobalsForName(id) {
// Node ambient modules are only accessible in the node code,
// 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 nodeOnlyGlobalNames.has(id) ? nodeGlobals : globals;
}
function mergeGlobalSymbolTable(node, source, unidirectional) {
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index a0219fe13..138b24ba0 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -855,25 +855,7 @@ delete Object.prototype.__proto__;
...program.getOptionsDiagnostics(),
...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics(),
- ].filter((diagnostic) => {
- 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;
- }
- });
+ ].filter((diagnostic) => !IGNORED_DIAGNOSTICS.includes(diagnostic.code));
// emit the tsbuildinfo file
// @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
// 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) {
const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into
diff --git a/cli/tsc/compiler.d.ts b/cli/tsc/compiler.d.ts
index ab6f95bd3..03784ae84 100644
--- a/cli/tsc/compiler.d.ts
+++ b/cli/tsc/compiler.d.ts
@@ -30,6 +30,7 @@ declare global {
function setIsNodeSourceFileCallback(
callback: (sourceFile: SourceFile) => boolean,
);
+ function setNodeBuiltInModuleNames(names: string[]);
}
}
diff --git a/cli/tsc/diagnostics.rs b/cli/tsc/diagnostics.rs
index fb7304c9b..b3026d934 100644
--- a/cli/tsc/diagnostics.rs
+++ b/cli/tsc/diagnostics.rs
@@ -299,9 +299,11 @@ impl Diagnostic {
fn fmt_related_information(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(related_information) = self.related_information.as_ref() {
- write!(f, "\n\n")?;
- for info in related_information {
- info.fmt_stack(f, 4)?;
+ if !related_information.is_empty() {
+ write!(f, "\n\n")?;
+ for info in related_information {
+ info.fmt_stack(f, 4)?;
+ }
}
}
diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs
index 3da2f5f25..439a0de20 100644
--- a/cli/tsc/mod.rs
+++ b/cli/tsc/mod.rs
@@ -165,6 +165,12 @@ pub static LAZILY_LOADED_STATIC_ASSETS: Lazy<
"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()
.cloned()
@@ -599,117 +605,133 @@ fn 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:///") {
- resolved.push((
- specifier.clone(),
- MediaType::from(specifier).as_ts_extension().to_string(),
- ));
- } else {
- let graph_data = state.graph_data.read();
- let resolved_dep = match graph_data.get_dependencies(&referrer) {
- Some(dependencies) => dependencies.get(specifier).map(|d| {
- if matches!(d.maybe_type, Resolved::Ok { .. }) {
- &d.maybe_type
- } else {
- &d.maybe_code
- }
- }),
- None => None,
- };
- let maybe_result = match resolved_dep {
- Some(Resolved::Ok { specifier, .. }) => {
- let specifier = graph_data.follow_redirect(specifier);
- match graph_data.get(&specifier) {
- Some(ModuleEntry::Module {
- media_type,
- maybe_types,
- ..
- }) => match maybe_types {
- Some(Resolved::Ok { specifier, .. }) => {
- let types = graph_data.follow_redirect(specifier);
- match graph_data.get(&types) {
- Some(ModuleEntry::Module { media_type, .. }) => {
- Some((types, *media_type))
- }
- _ => None,
+ let media_type =
+ MediaType::from(&specifier).as_ts_extension().to_string();
+ resolved.push((specifier, media_type));
+ continue;
+ }
+
+ let graph_data = state.graph_data.read();
+ let resolved_dep = match graph_data.get_dependencies(&referrer) {
+ Some(dependencies) => dependencies.get(&specifier).map(|d| {
+ if matches!(d.maybe_type, Resolved::Ok { .. }) {
+ &d.maybe_type
+ } else {
+ &d.maybe_code
+ }
+ }),
+ None => None,
+ };
+ let maybe_result = match resolved_dep {
+ Some(Resolved::Ok { specifier, .. }) => {
+ let specifier = graph_data.follow_redirect(specifier);
+ match graph_data.get(&specifier) {
+ Some(ModuleEntry::Module {
+ media_type,
+ maybe_types,
+ ..
+ }) => match maybe_types {
+ Some(Resolved::Ok { specifier, .. }) => {
+ let types = graph_data.follow_redirect(specifier);
+ match graph_data.get(&types) {
+ Some(ModuleEntry::Module { media_type, .. }) => {
+ Some((types, *media_type))
}
+ _ => None,
}
- _ => Some((specifier, *media_type)),
- },
- _ => {
- // handle npm:<package> urls
- if let Ok(npm_ref) =
- NpmPackageReference::from_specifier(&specifier)
- {
- if let Some(npm_resolver) = &state.maybe_npm_resolver {
- Some(resolve_npm_package_reference_types(
- &npm_ref,
- npm_resolver,
- )?)
- } else {
- None
- }
+ }
+ _ => Some((specifier, *media_type)),
+ },
+ _ => {
+ // handle npm:<package> urls
+ if let Ok(npm_ref) = NpmPackageReference::from_specifier(&specifier)
+ {
+ if let Some(npm_resolver) = &state.maybe_npm_resolver {
+ Some(resolve_npm_package_reference_types(
+ &npm_ref,
+ npm_resolver,
+ )?)
} else {
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 {
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);
+ }
+ _ => {
+ if let Some(npm_resolver) = state.maybe_npm_resolver.as_ref() {
+ 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 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
.remapped_specifiers
.insert(specifier_str.clone(), specifier);
specifier_str
+ } else {
+ specifier.to_string()
}
- _ => {
- if let Some(specifier_str) =
- maybe_remap_specifier(&specifier, media_type)
- {
- state
- .remapped_specifiers
- .insert(specifier_str.clone(), specifier);
- specifier_str
- } else {
- specifier.to_string()
- }
- }
- };
- (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);
- }
+ }
+ };
+ (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)
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 8a36e95fa..3eda18958 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -675,3 +675,197 @@ fn op_require_break_on_next_statement(state: &mut OpState) {
.borrow_mut()
.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",
+ },
+];