summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--cli/build.rs6
-rw-r--r--cli/dts/README.md18
-rw-r--r--cli/lsp/diagnostics.rs49
-rw-r--r--cli/lsp/documents.rs178
-rw-r--r--cli/lsp/language_server.rs50
-rw-r--r--cli/lsp/tsc.rs27
-rw-r--r--cli/main.rs1
-rw-r--r--cli/node/mod.rs180
-rw-r--r--cli/npm/cache.rs11
-rw-r--r--cli/npm/mod.rs1
-rw-r--r--cli/npm/resolution.rs73
-rw-r--r--cli/npm/resolvers/common.rs7
-rw-r--r--cli/npm/resolvers/global.rs60
-rw-r--r--cli/npm/resolvers/local.rs58
-rw-r--r--cli/npm/resolvers/mod.rs83
-rw-r--r--cli/proc_state.rs15
-rw-r--r--cli/tests/integration/check_tests.rs7
-rw-r--r--cli/tests/integration/lsp_tests.rs194
-rw-r--r--cli/tests/integration/npm_tests.rs49
-rw-r--r--cli/tests/testdata/check/npm_install_diagnostics/main.out11
-rw-r--r--cli/tests/testdata/check/npm_install_diagnostics/main.ts2
-rw-r--r--cli/tests/testdata/lsp/code_actions/cache_npm/cache_action.json41
-rw-r--r--cli/tests/testdata/lsp/code_actions/cache_npm/cache_response.json36
-rw-r--r--cli/tests/testdata/lsp/code_actions/cache_npm/diagnostics.json25
-rw-r--r--cli/tests/testdata/lsp/completions/npm/resolve_params.json14
-rw-r--r--cli/tests/testdata/lsp/completions/npm/resolve_response.json14
-rw-r--r--cli/tests/testdata/npm/check_errors/main.ts3
-rw-r--r--cli/tests/testdata/npm/check_errors/main_all.out19
-rw-r--r--cli/tests/testdata/npm/check_errors/main_local.out7
-rw-r--r--cli/tests/testdata/npm/compare_globals/main.js2
-rw-r--r--cli/tests/testdata/npm/compare_globals/main.out5
-rw-r--r--cli/tests/testdata/npm/compare_globals/main.ts14
-rw-r--r--cli/tests/testdata/npm/esm_import_cjs_default/main.ts (renamed from cli/tests/testdata/npm/esm_import_cjs_default/main.js)1
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.d.ts6
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.js6
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/package.json5
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/index.d.ts6
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/package.json3
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/esm-import-cjs-default/1.0.0/package.json1
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts13
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js1
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/package.json3
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.d.ts10
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.js3
-rw-r--r--cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/package.json5
-rw-r--r--cli/tests/testdata/npm/registry/@types/node/node-18.8.2.tgzbin0 -> 649087 bytes
-rw-r--r--cli/tests/testdata/npm/registry/@types/node/registry.json73
-rw-r--r--cli/tests/testdata/npm/types_ambient_module/import_map.json5
-rw-r--r--cli/tests/testdata/npm/types_ambient_module/main.out21
-rw-r--r--cli/tests/testdata/npm/types_ambient_module/main.ts7
-rw-r--r--cli/tests/testdata/npm/types_ambient_module/main_import_map.out9
-rw-r--r--cli/tests/testdata/npm/types_ambient_module/main_import_map.ts4
-rw-r--r--cli/tools/check.rs6
-rw-r--r--cli/tsc.rs132
-rw-r--r--cli/tsc/00_typescript.js384
-rw-r--r--cli/tsc/99_main_compiler.js316
-rw-r--r--cli/tsc/compiler.d.ts7
-rw-r--r--ext/node/lib.rs3
-rw-r--r--ext/node/package_json.rs6
-rw-r--r--ext/node/resolution.rs100
-rw-r--r--test_util/Cargo.toml1
-rw-r--r--test_util/src/lib.rs10
-rw-r--r--test_util/src/lsp.rs5
64 files changed, 2134 insertions, 279 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b1550a044..8de6a2fdb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4752,6 +4752,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tokio-tungstenite",
+ "url",
"winapi 0.3.9",
]
diff --git a/cli/build.rs b/cli/build.rs
index 98d044a3e..b72153871 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -202,6 +202,11 @@ fn create_compiler_snapshot(
}
#[op]
+ fn op_is_node_file() -> bool {
+ false
+ }
+
+ #[op]
fn op_script_version(
_state: &mut OpState,
_args: Value,
@@ -266,6 +271,7 @@ fn create_compiler_snapshot(
op_build_info::decl(),
op_cwd::decl(),
op_exists::decl(),
+ op_is_node_file::decl(),
op_load::decl(),
op_script_version::decl(),
])
diff --git a/cli/dts/README.md b/cli/dts/README.md
index 9d07188f7..14d937c88 100644
--- a/cli/dts/README.md
+++ b/cli/dts/README.md
@@ -4,16 +4,26 @@ The files in this directory are mostly from the TypeScript repository. We
currently (unfortunately) have a rather manual process for upgrading TypeScript.
It works like this currently:
-1. Checkout typescript repo in a separate directory.
-2. Copy typescript.js into Deno repo.
-3. Copy d.ts files into dts directory.
+1. Checkout denoland/TypeScript repo in a separate directory.
+1. Add Microsoft/TypeScript as a remote and fetch its latest tags
+1. Checkout a new branch based on this tag.
+1. Cherry pick the custom commit we made in a previous release to the new one.
+1. This commit has a "deno.ts" file in it. Read the instructions in it.
+1. Copy typescript.js into Deno repo.
+1. Copy d.ts files into dts directory.
So that might look something like this:
```
-git clone https://github.com/microsoft/TypeScript.git
+git clone https://github.com/denoland/TypeScript.git
cd typescript
+git remote add upstream https://github.com/Microsoft/TypeScript
+git fetch upstream
git checkout v3.9.7
+git checkout -b branch_v3.9.7
+git cherry pick <previous-release-branch-commit-we-did>
+npm install
+gulp local
rsync lib/typescript.js ~/src/deno/cli/tsc/00_typescript.js
rsync --exclude=protocol.d.ts --exclude=tsserverlibrary.d.ts --exclude=typescriptServices.d.ts lib/*.d.ts ~/src/deno/cli/dts/
```
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index aef5ae889..aac6e5862 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -458,6 +458,13 @@ async fn generate_lint_diagnostics(
break;
}
+ // ignore any npm package files
+ if let Some(npm_resolver) = &snapshot.maybe_npm_resolver {
+ if npm_resolver.in_npm_package(document.specifier()) {
+ continue;
+ }
+ }
+
let version = document.maybe_lsp_version();
diagnostics_vec.push((
document.specifier().clone(),
@@ -597,6 +604,8 @@ pub enum DenoDiagnostic {
NoCacheBlob,
/// A data module was not found in the cache.
NoCacheData(ModuleSpecifier),
+ /// A remote npm package reference was not found in the cache.
+ NoCacheNpm(NpmPackageReference, ModuleSpecifier),
/// A local module was not found on the local file system.
NoLocal(ModuleSpecifier),
/// The specifier resolved to a remote specifier that was redirected to
@@ -622,6 +631,7 @@ impl DenoDiagnostic {
Self::NoCache(_) => "no-cache",
Self::NoCacheBlob => "no-cache-blob",
Self::NoCacheData(_) => "no-cache-data",
+ Self::NoCacheNpm(_, _) => "no-cache-npm",
Self::NoLocal(_) => "no-local",
Self::Redirect { .. } => "redirect",
Self::ResolutionError(err) => match err {
@@ -690,16 +700,17 @@ impl DenoDiagnostic {
}),
..Default::default()
},
- "no-cache" | "no-cache-data" => {
+ "no-cache" | "no-cache-data" | "no-cache-npm" => {
let data = diagnostic
.data
.clone()
.ok_or_else(|| anyhow!("Diagnostic is missing data"))?;
let data: DiagnosticDataSpecifier = serde_json::from_value(data)?;
- let title = if code == "no-cache" {
- format!("Cache \"{}\" and its dependencies.", data.specifier)
- } else {
- "Cache the data URL and its dependencies.".to_string()
+ let title = match code.as_str() {
+ "no-cache" | "no-cache-npm" => {
+ format!("Cache \"{}\" and its dependencies.", data.specifier)
+ }
+ _ => "Cache the data URL and its dependencies.".to_string(),
};
lsp::CodeAction {
title,
@@ -757,6 +768,7 @@ impl DenoDiagnostic {
code.as_str(),
"import-map-remap"
| "no-cache"
+ | "no-cache-npm"
| "no-cache-data"
| "no-assert-type"
| "redirect"
@@ -777,6 +789,7 @@ impl DenoDiagnostic {
Self::NoCache(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing remote URL: \"{}\".", specifier), Some(json!({ "specifier": specifier }))),
Self::NoCacheBlob => (lsp::DiagnosticSeverity::ERROR, "Uncached blob URL.".to_string(), None),
Self::NoCacheData(specifier) => (lsp::DiagnosticSeverity::ERROR, "Uncached data URL.".to_string(), Some(json!({ "specifier": specifier }))),
+ Self::NoCacheNpm(pkg_ref, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing npm package: \"{}\".", pkg_ref.req), Some(json!({ "specifier": specifier }))),
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),
@@ -847,8 +860,20 @@ fn diagnose_resolved(
.push(DenoDiagnostic::NoAssertType.to_lsp_diagnostic(&range)),
}
}
- } else if NpmPackageReference::from_specifier(specifier).is_ok() {
- // ignore npm specifiers for now
+ } else if let Ok(pkg_ref) = NpmPackageReference::from_specifier(specifier)
+ {
+ if let Some(npm_resolver) = &snapshot.maybe_npm_resolver {
+ // show diagnostics for npm package references that aren't cached
+ if npm_resolver
+ .resolve_package_folder_from_deno_module(&pkg_ref.req)
+ .is_err()
+ {
+ diagnostics.push(
+ DenoDiagnostic::NoCacheNpm(pkg_ref, specifier.clone())
+ .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
@@ -882,6 +907,12 @@ fn diagnose_dependency(
dependency_key: &str,
dependency: &deno_graph::Dependency,
) {
+ if let Some(npm_resolver) = &snapshot.maybe_npm_resolver {
+ if npm_resolver.in_npm_package(referrer) {
+ return; // ignore, surface typescript errors instead
+ }
+ }
+
if let Some(import_map) = &snapshot.maybe_import_map {
if let Resolved::Ok {
specifier, range, ..
@@ -938,8 +969,8 @@ async fn generate_deno_diagnostics(
&mut diagnostics,
snapshot,
specifier,
- &dependency_key,
- &dependency,
+ dependency_key,
+ dependency,
);
}
}
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index 61db899e5..57216eecb 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -12,6 +12,13 @@ use crate::file_fetcher::SUPPORTED_SCHEMES;
use crate::fs_util::specifier_to_file_path;
use crate::http_cache;
use crate::http_cache::HttpCache;
+use crate::node;
+use crate::node::node_resolve_npm_reference;
+use crate::node::NodeResolution;
+use crate::node::NodeResolutionMode;
+use crate::npm::NpmPackageReference;
+use crate::npm::NpmPackageReq;
+use crate::npm::NpmPackageResolver;
use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
use crate::text_encoding;
@@ -209,6 +216,29 @@ impl AssetOrDocument {
}
}
+#[derive(Debug, Default)]
+struct DocumentDependencies {
+ deps: BTreeMap<String, deno_graph::Dependency>,
+ maybe_types_dependency: Option<(String, Resolved)>,
+}
+
+impl DocumentDependencies {
+ pub fn from_maybe_module(maybe_module: &MaybeModuleResult) -> Self {
+ if let Some(Ok(module)) = &maybe_module {
+ Self::from_module(module)
+ } else {
+ Self::default()
+ }
+ }
+
+ pub fn from_module(module: &deno_graph::Module) -> Self {
+ Self {
+ deps: module.dependencies.clone(),
+ maybe_types_dependency: module.maybe_types_dependency.clone(),
+ }
+ }
+}
+
type MaybeModuleResult =
Option<Result<deno_graph::Module, deno_graph::ModuleGraphError>>;
type MaybeParsedSourceResult =
@@ -217,7 +247,7 @@ type MaybeParsedSourceResult =
#[derive(Debug, Clone)]
struct DocumentInner {
/// contains the last-known-good set of dependencies from parsing the module
- dependencies: Arc<BTreeMap<String, deno_graph::Dependency>>,
+ dependencies: Arc<DocumentDependencies>,
fs_version: String,
line_index: Arc<LineIndex>,
maybe_language_id: Option<LanguageId>,
@@ -249,12 +279,9 @@ impl Document {
maybe_headers,
maybe_resolver,
);
- let dependencies = if let Some(Ok(module)) = &maybe_module {
- Arc::new(module.dependencies.clone())
- } else {
- Arc::new(BTreeMap::new())
- };
- // todo(dsherret): retrieve this from the parsed source if it
+ let dependencies =
+ Arc::new(DocumentDependencies::from_maybe_module(&maybe_module));
+ // todo(dsherret): retrieve this from the parsed source if it exists
let text_info = SourceTextInfo::new(content);
let line_index = Arc::new(LineIndex::new(text_info.text_str()));
Self(Arc::new(DocumentInner {
@@ -289,11 +316,8 @@ impl Document {
} else {
(None, None)
};
- let dependencies = if let Some(Ok(module)) = &maybe_module {
- Arc::new(module.dependencies.clone())
- } else {
- Arc::new(BTreeMap::new())
- };
+ let dependencies =
+ Arc::new(DocumentDependencies::from_maybe_module(&maybe_module));
let source = SourceTextInfo::new(content);
let line_index = Arc::new(LineIndex::new(source.text_str()));
Self(Arc::new(DocumentInner {
@@ -355,9 +379,9 @@ impl Document {
(None, None)
};
let dependencies = if let Some(Ok(module)) = &maybe_module {
- Arc::new(module.dependencies.clone())
+ Arc::new(DocumentDependencies::from_module(module))
} else {
- self.0.dependencies.clone()
+ self.0.dependencies.clone() // use the last known good
};
let text_info = SourceTextInfo::new(content);
let line_index = if index_valid == IndexValid::All {
@@ -435,15 +459,9 @@ impl Document {
}
pub fn maybe_types_dependency(&self) -> deno_graph::Resolved {
- let module_result = match self.0.maybe_module.as_ref() {
- Some(module_result) => module_result,
- _ => return deno_graph::Resolved::None,
- };
- let module = match module_result.as_ref() {
- Ok(module) => module,
- Err(_) => return deno_graph::Resolved::None,
- };
- if let Some((_, maybe_dep)) = module.maybe_types_dependency.as_ref() {
+ if let Some((_, maybe_dep)) =
+ self.0.dependencies.maybe_types_dependency.as_ref()
+ {
maybe_dep.clone()
} else {
deno_graph::Resolved::None
@@ -479,13 +497,8 @@ impl Document {
self.0.maybe_navigation_tree.clone()
}
- pub fn dependencies(&self) -> Vec<(String, deno_graph::Dependency)> {
- self
- .0
- .dependencies
- .iter()
- .map(|(s, d)| (s.clone(), d.clone()))
- .collect()
+ pub fn dependencies(&self) -> &BTreeMap<String, deno_graph::Dependency> {
+ &self.0.dependencies.deps
}
/// If the supplied position is within a dependency range, return the resolved
@@ -698,6 +711,8 @@ pub struct Documents {
maybe_import_map: Option<ImportMapResolver>,
/// The optional JSX resolver, which is used when JSX imports are configured.
maybe_jsx_resolver: Option<JsxResolver>,
+ /// The npm package requirements.
+ npm_reqs: HashSet<NpmPackageReq>,
/// Resolves a specifier to its final redirected to specifier.
specifier_resolver: Arc<SpecifierResolver>,
}
@@ -713,6 +728,7 @@ impl Documents {
imports: Default::default(),
maybe_import_map: None,
maybe_jsx_resolver: None,
+ npm_reqs: HashSet::new(),
specifier_resolver: Arc::new(SpecifierResolver::new(location)),
}
}
@@ -847,6 +863,12 @@ impl Documents {
}
}
+ /// Returns a collection of npm package requirements.
+ pub fn npm_package_reqs(&mut self) -> HashSet<NpmPackageReq> {
+ self.calculate_dependents_if_dirty();
+ self.npm_reqs.clone()
+ }
+
/// Return a document for the specifier.
pub fn get(&self, original_specifier: &ModuleSpecifier) -> Option<Document> {
let specifier = self.specifier_resolver.resolve(original_specifier)?;
@@ -921,10 +943,28 @@ impl Documents {
&self,
specifiers: Vec<String>,
referrer: &ModuleSpecifier,
+ maybe_npm_resolver: Option<&NpmPackageResolver>,
) -> Option<Vec<Option<(ModuleSpecifier, MediaType)>>> {
let dependencies = self.get(referrer)?.0.dependencies.clone();
let mut results = Vec::new();
for specifier in specifiers {
+ if let Some(npm_resolver) = maybe_npm_resolver {
+ if npm_resolver.in_npm_package(referrer) {
+ // we're in an npm package, so use node resolution
+ results.push(Some(NodeResolution::into_specifier_and_media_type(
+ node::node_resolve(
+ &specifier,
+ referrer,
+ node::NodeResolutionMode::Types,
+ npm_resolver,
+ )
+ .ok()
+ .flatten(),
+ )));
+ continue;
+ }
+ }
+ // handle npm:<package> urls
if specifier.starts_with("asset:") {
if let Ok(specifier) = ModuleSpecifier::parse(&specifier) {
let media_type = MediaType::from(&specifier);
@@ -932,11 +972,11 @@ impl Documents {
} else {
results.push(None);
}
- } else if let Some(dep) = dependencies.get(&specifier) {
+ } else if let Some(dep) = dependencies.deps.get(&specifier) {
if let Resolved::Ok { specifier, .. } = &dep.maybe_type {
- results.push(self.resolve_dependency(specifier));
+ results.push(self.resolve_dependency(specifier, maybe_npm_resolver));
} else if let Resolved::Ok { specifier, .. } = &dep.maybe_code {
- results.push(self.resolve_dependency(specifier));
+ results.push(self.resolve_dependency(specifier, maybe_npm_resolver));
} else {
results.push(None);
}
@@ -945,7 +985,19 @@ impl Documents {
{
// clone here to avoid double borrow of self
let specifier = specifier.clone();
- results.push(self.resolve_dependency(&specifier));
+ results.push(self.resolve_dependency(&specifier, maybe_npm_resolver));
+ } 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(
+ &npm_ref,
+ NodeResolutionMode::Types,
+ npm_resolver,
+ )
+ .ok()
+ .flatten(),
+ )
+ }));
} else {
results.push(None);
}
@@ -1038,32 +1090,36 @@ impl Documents {
// favour documents that are open in case a document exists in both collections
let documents = file_system_docs.docs.iter().chain(self.open_docs.iter());
for (specifier, doc) in documents {
- if let Some(Ok(module)) = doc.maybe_module() {
- for dependency in module.dependencies.values() {
- if let Some(dep) = dependency.get_code() {
- dependents_map
- .entry(dep.clone())
- .or_default()
- .insert(specifier.clone());
- }
- if let Some(dep) = dependency.get_type() {
- dependents_map
- .entry(dep.clone())
- .or_default()
- .insert(specifier.clone());
- }
+ for dependency in doc.dependencies().values() {
+ if let Some(dep) = dependency.get_code() {
+ dependents_map
+ .entry(dep.clone())
+ .or_default()
+ .insert(specifier.clone());
}
- if let Some((_, Resolved::Ok { specifier: dep, .. })) =
- &module.maybe_types_dependency
- {
+ if let Some(dep) = dependency.get_type() {
dependents_map
.entry(dep.clone())
.or_default()
.insert(specifier.clone());
}
}
+ if let Resolved::Ok { specifier: dep, .. } = doc.maybe_types_dependency()
+ {
+ dependents_map
+ .entry(dep.clone())
+ .or_default()
+ .insert(specifier.clone());
+ }
+ }
+ let mut npm_reqs = HashSet::new();
+ for specifier in dependents_map.keys() {
+ if let Ok(reference) = NpmPackageReference::from_specifier(specifier) {
+ npm_reqs.insert(reference.req);
+ }
}
self.dependents_map = Arc::new(dependents_map);
+ self.npm_reqs = npm_reqs;
self.dirty = false;
file_system_docs.dirty = false;
}
@@ -1079,7 +1135,21 @@ impl Documents {
fn resolve_dependency(
&self,
specifier: &ModuleSpecifier,
+ maybe_npm_resolver: Option<&NpmPackageResolver>,
) -> Option<(ModuleSpecifier, MediaType)> {
+ if let Ok(npm_ref) = NpmPackageReference::from_specifier(specifier) {
+ return maybe_npm_resolver.map(|npm_resolver| {
+ NodeResolution::into_specifier_and_media_type(
+ node_resolve_npm_reference(
+ &npm_ref,
+ NodeResolutionMode::Types,
+ npm_resolver,
+ )
+ .ok()
+ .flatten(),
+ )
+ });
+ }
let doc = self.get(specifier)?;
let maybe_module = doc.maybe_module().and_then(|r| r.as_ref().ok());
let maybe_types_dependency = maybe_module.and_then(|m| {
@@ -1088,7 +1158,7 @@ impl Documents {
.map(|(_, resolved)| resolved.clone())
});
if let Some(Resolved::Ok { specifier, .. }) = maybe_types_dependency {
- self.resolve_dependency(&specifier)
+ self.resolve_dependency(&specifier, maybe_npm_resolver)
} else {
let media_type = doc.media_type();
Some((specifier.clone(), media_type))
@@ -1113,12 +1183,12 @@ impl Documents {
}
/// Loader that will look at the open documents.
-pub struct DocumentsDenoGraphLoader<'a> {
+pub struct OpenDocumentsGraphLoader<'a> {
pub inner_loader: &'a mut dyn deno_graph::source::Loader,
pub open_docs: &'a HashMap<ModuleSpecifier, Document>,
}
-impl<'a> deno_graph::source::Loader for DocumentsDenoGraphLoader<'a> {
+impl<'a> deno_graph::source::Loader for OpenDocumentsGraphLoader<'a> {
fn load(
&mut self,
specifier: &ModuleSpecifier,
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 27d69127c..a3f516615 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -66,10 +66,15 @@ use crate::args::LintConfig;
use crate::args::TsConfig;
use crate::deno_dir;
use crate::file_fetcher::get_source_from_data_url;
+use crate::file_fetcher::CacheSetting;
use crate::fs_util;
use crate::graph_util::graph_valid;
+use crate::npm::NpmCache;
+use crate::npm::NpmPackageResolver;
+use crate::npm::NpmRegistryApi;
use crate::proc_state::import_map_from_text;
use crate::proc_state::ProcState;
+use crate::progress_bar::ProgressBar;
use crate::tools::fmt::format_file;
use crate::tools::fmt::format_parsed_source;
@@ -87,6 +92,7 @@ pub struct StateSnapshot {
pub documents: Documents,
pub maybe_import_map: Option<Arc<ImportMap>>,
pub root_uri: Option<Url>,
+ pub maybe_npm_resolver: Option<NpmPackageResolver>,
}
#[derive(Debug)]
@@ -125,6 +131,8 @@ pub struct Inner {
pub maybe_lint_config: Option<LintConfig>,
/// A lazily create "server" for handling test run requests.
maybe_testing_server: Option<testing::TestServer>,
+ /// Resolver for npm packages.
+ npm_resolver: NpmPackageResolver,
/// A collection of measurements which instrument that performance of the LSP.
performance: Arc<Performance>,
/// A memoized version of fixable diagnostic codes retrieved from TypeScript.
@@ -250,6 +258,26 @@ impl Inner {
ts_server.clone(),
);
let assets = Assets::new(ts_server.clone());
+ let registry_url = NpmRegistryApi::default_url();
+ // Use an "only" cache setting in order to make the
+ // user do an explicit "cache" command and prevent
+ // the cache from being filled with lots of packages while
+ // the user is typing.
+ let cache_setting = CacheSetting::Only;
+ let progress_bar = ProgressBar::default();
+ let npm_cache = NpmCache::from_deno_dir(
+ &dir,
+ cache_setting.clone(),
+ progress_bar.clone(),
+ );
+ let api = NpmRegistryApi::new(
+ registry_url,
+ npm_cache.clone(),
+ cache_setting,
+ progress_bar,
+ );
+ let npm_resolver =
+ NpmPackageResolver::new(npm_cache, api, true, false, None);
Self {
assets,
@@ -267,6 +295,7 @@ impl Inner {
maybe_testing_server: None,
module_registries,
module_registries_location,
+ npm_resolver,
performance,
ts_fixable_diagnostics: Default::default(),
ts_server,
@@ -435,6 +464,7 @@ impl Inner {
cache_metadata: self.cache_metadata.clone(),
documents: self.documents.clone(),
maybe_import_map: self.maybe_import_map.clone(),
+ maybe_npm_resolver: Some(self.npm_resolver.snapshotted()),
root_uri: self.config.root_uri.clone(),
})
}
@@ -828,7 +858,7 @@ impl Inner {
if let Err(err) =
self.client.register_capability(vec![registration]).await
{
- warn!("Client errored on capabilities.\n{}", err);
+ warn!("Client errored on capabilities.\n{:#}", err);
}
}
self.config.update_enabled_paths(self.client.clone()).await;
@@ -891,6 +921,7 @@ impl Inner {
) {
Ok(document) => {
if document.is_diagnosable() {
+ self.refresh_npm_specifiers().await;
self
.diagnostics_server
.invalidate(&self.documents.dependents(&specifier));
@@ -903,6 +934,13 @@ impl Inner {
self.performance.measure(mark);
}
+ async fn refresh_npm_specifiers(&mut self) {
+ let package_reqs = self.documents.npm_package_reqs();
+ if let Err(err) = self.npm_resolver.set_package_reqs(package_reqs).await {
+ warn!("Could not set npm package requirements. {:#}", err);
+ }
+ }
+
async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
let mark = self.performance.mark("did_close", Some(&params));
if params.text_document.uri.scheme() == "deno" {
@@ -917,6 +955,7 @@ impl Inner {
error!("{}", err);
}
if self.is_diagnosable(&specifier) {
+ self.refresh_npm_specifiers().await;
let mut specifiers = self.documents.dependents(&specifier);
specifiers.push(specifier.clone());
self.diagnostics_server.invalidate(&specifiers);
@@ -1135,7 +1174,7 @@ impl Inner {
Ok(None) => Some(Vec::new()),
Err(err) => {
// TODO(lucacasonato): handle error properly
- warn!("Format error: {}", err);
+ warn!("Format error: {:#}", err);
None
}
}
@@ -2476,6 +2515,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
let has_specifier_settings =
inner.config.has_specifier_settings(&specifier);
if document.is_diagnosable() {
+ inner.refresh_npm_specifiers().await;
let specifiers = inner.documents.dependents(&specifier);
inner.diagnostics_server.invalidate(&specifiers);
// don't send diagnostics yet if we don't have the specifier settings
@@ -2834,7 +2874,7 @@ impl Inner {
.collect::<HashMap<_, _>>();
let ps = ProcState::from_options(Arc::new(cli_options)).await?;
let mut inner_loader = ps.create_graph_loader();
- let mut loader = crate::lsp::documents::DocumentsDenoGraphLoader {
+ let mut loader = crate::lsp::documents::OpenDocumentsGraphLoader {
inner_loader: &mut inner_loader,
open_docs: &open_docs,
};
@@ -2870,6 +2910,9 @@ impl Inner {
ca_stores: None,
ca_file: None,
unsafely_ignore_certificate_errors: None,
+ // this is to allow loading npm specifiers, so we can remove this
+ // once stabilizing them
+ unstable: true,
..Default::default()
},
self.maybe_config_file.clone(),
@@ -2892,6 +2935,7 @@ impl Inner {
// For that we're invalidating all the existing diagnostics and restarting
// the language server for TypeScript (as it might hold to some stale
// documents).
+ self.refresh_npm_specifiers().await;
self.diagnostics_server.invalidate_all();
let _: bool = self
.ts_server
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 6c2136990..bed06e088 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -2679,6 +2679,20 @@ fn op_is_cancelled(state: &mut OpState) -> bool {
}
#[op]
+fn op_is_node_file(state: &mut OpState, path: String) -> bool {
+ let state = state.borrow::<State>();
+ match ModuleSpecifier::parse(&path) {
+ Ok(specifier) => state
+ .state_snapshot
+ .maybe_npm_resolver
+ .as_ref()
+ .map(|r| r.in_npm_package(&specifier))
+ .unwrap_or(false),
+ Err(_) => false,
+ }
+}
+
+#[op]
fn op_load(
state: &mut OpState,
args: SpecifierArgs,
@@ -2692,7 +2706,7 @@ fn op_load(
Some(doc) => {
json!({
"data": doc.text(),
- "scriptKind": crate::tsc::as_ts_script_kind(&doc.media_type()),
+ "scriptKind": crate::tsc::as_ts_script_kind(doc.media_type()),
"version": state.script_version(&specifier),
})
}
@@ -2709,11 +2723,11 @@ fn op_resolve(
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)
- {
+ 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()
@@ -2789,6 +2803,7 @@ fn init_extension(performance: Arc<Performance>) -> Extension {
.ops(vec![
op_exists::decl(),
op_is_cancelled::decl(),
+ op_is_node_file::decl(),
op_load::decl(),
op_resolve::decl(),
op_respond::decl(),
diff --git a/cli/main.rs b/cli/main.rs
index f192bea5e..3a4cc3c37 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -525,6 +525,7 @@ async fn create_graph_and_maybe_check(
&graph.roots,
Arc::new(RwLock::new(graph.as_ref().into())),
&cache,
+ ps.npm_resolver.clone(),
check::CheckOptions {
type_check_mode: ps.options.type_check_mode(),
debug,
diff --git a/cli/node/mod.rs b/cli/node/mod.rs
index 97240cfe7..56f228d3f 100644
--- a/cli/node/mod.rs
+++ b/cli/node/mod.rs
@@ -31,6 +31,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::TYPES_CONDITIONS;
use once_cell::sync::Lazy;
use regex::Regex;
@@ -55,9 +56,61 @@ impl NodeResolution {
match self {
Self::Esm(u) => u,
Self::CommonJs(u) => u,
- _ => unreachable!(),
+ Self::BuiltIn(specifier) => {
+ if specifier.starts_with("node:") {
+ ModuleSpecifier::parse(&specifier).unwrap()
+ } else {
+ ModuleSpecifier::parse(&format!("node:{}", specifier)).unwrap()
+ }
+ }
}
}
+
+ pub fn into_specifier_and_media_type(
+ resolution: Option<Self>,
+ ) -> (ModuleSpecifier, MediaType) {
+ match resolution {
+ Some(NodeResolution::CommonJs(specifier)) => {
+ let media_type = MediaType::from(&specifier);
+ (
+ specifier,
+ match media_type {
+ MediaType::JavaScript | MediaType::Jsx => MediaType::Cjs,
+ MediaType::TypeScript | MediaType::Tsx => MediaType::Cts,
+ MediaType::Dts => MediaType::Dcts,
+ _ => media_type,
+ },
+ )
+ }
+ Some(NodeResolution::Esm(specifier)) => {
+ let media_type = MediaType::from(&specifier);
+ (
+ specifier,
+ match media_type {
+ MediaType::JavaScript | MediaType::Jsx => MediaType::Mjs,
+ MediaType::TypeScript | MediaType::Tsx => MediaType::Mts,
+ MediaType::Dts => MediaType::Dmts,
+ _ => media_type,
+ },
+ )
+ }
+ maybe_response => {
+ let specifier = match maybe_response {
+ Some(response) => response.into_url(),
+ None => {
+ ModuleSpecifier::parse("deno:///missing_dependency.d.ts").unwrap()
+ }
+ };
+ (specifier, MediaType::Dts)
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum NodeResolutionMode {
+ Execution,
+ Types,
}
struct NodeModulePolyfill {
@@ -389,6 +442,7 @@ pub async fn initialize_binary_command(
pub fn node_resolve(
specifier: &str,
referrer: &ModuleSpecifier,
+ mode: NodeResolutionMode,
npm_resolver: &dyn RequireNpmResolver,
) -> Result<Option<NodeResolution>, AnyError> {
// Note: if we are here, then the referrer is an esm module
@@ -425,12 +479,22 @@ pub fn node_resolve(
}
}
- let conditions = DEFAULT_CONDITIONS;
+ let conditions = mode_conditions(mode);
let url = module_resolve(specifier, referrer, conditions, npm_resolver)?;
let url = match url {
Some(url) => url,
None => return Ok(None),
};
+ let url = match mode {
+ NodeResolutionMode::Execution => url,
+ NodeResolutionMode::Types => {
+ let path = url.to_file_path().unwrap();
+ // todo(16370): the module kind is not correct here. I think we need
+ // typescript to tell us if the referrer is esm or cjs
+ let path = path_to_declaration_path(path, NodeModuleKind::Esm);
+ ModuleSpecifier::from_file_path(path).unwrap()
+ }
+ };
let resolve_response = url_to_node_resolution(url, npm_resolver)?;
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
@@ -440,24 +504,36 @@ pub fn node_resolve(
pub fn node_resolve_npm_reference(
reference: &NpmPackageReference,
+ mode: NodeResolutionMode,
npm_resolver: &NpmPackageResolver,
) -> Result<Option<NodeResolution>, AnyError> {
let package_folder =
npm_resolver.resolve_package_folder_from_deno_module(&reference.req)?;
- let resolved_path = package_config_resolve(
+ let node_module_kind = NodeModuleKind::Esm;
+ let maybe_resolved_path = package_config_resolve(
&reference
.sub_path
.as_ref()
.map(|s| format!("./{}", s))
.unwrap_or_else(|| ".".to_string()),
&package_folder,
+ node_module_kind,
+ mode_conditions(mode),
npm_resolver,
- NodeModuleKind::Esm,
)
.with_context(|| {
format!("Error resolving package config for '{}'.", reference)
})?;
-
+ let resolved_path = match maybe_resolved_path {
+ Some(resolved_path) => resolved_path,
+ None => return Ok(None),
+ };
+ let resolved_path = match mode {
+ NodeResolutionMode::Execution => resolved_path,
+ NodeResolutionMode::Types => {
+ path_to_declaration_path(resolved_path, node_module_kind)
+ }
+ };
let url = ModuleSpecifier::from_file_path(resolved_path).unwrap();
let resolve_response = url_to_node_resolution(url, npm_resolver)?;
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
@@ -465,6 +541,41 @@ pub fn node_resolve_npm_reference(
Ok(Some(resolve_response))
}
+fn mode_conditions(mode: NodeResolutionMode) -> &'static [&'static str] {
+ match mode {
+ NodeResolutionMode::Execution => DEFAULT_CONDITIONS,
+ NodeResolutionMode::Types => TYPES_CONDITIONS,
+ }
+}
+
+/// Checks if the resolved file has a corresponding declaration file.
+fn path_to_declaration_path(
+ path: PathBuf,
+ referrer_kind: NodeModuleKind,
+) -> PathBuf {
+ let lowercase_path = path.to_string_lossy().to_lowercase();
+ if lowercase_path.ends_with(".d.ts")
+ || lowercase_path.ends_with(".d.cts")
+ || lowercase_path.ends_with(".d.ts")
+ {
+ return path;
+ }
+ let specific_dts_path = match referrer_kind {
+ NodeModuleKind::Cjs => path.with_extension("d.cts"),
+ NodeModuleKind::Esm => path.with_extension("d.mts"),
+ };
+ if specific_dts_path.exists() {
+ specific_dts_path
+ } else {
+ let dts_path = path.with_extension("d.ts");
+ if dts_path.exists() {
+ dts_path
+ } else {
+ path
+ }
+ }
+}
+
pub fn node_resolve_binary_export(
pkg_req: &NpmPackageReq,
bin_name: Option<&str>,
@@ -562,45 +673,56 @@ pub fn load_cjs_module_from_ext_node(
fn package_config_resolve(
package_subpath: &str,
package_dir: &Path,
- npm_resolver: &dyn RequireNpmResolver,
referrer_kind: NodeModuleKind,
-) -> Result<PathBuf, AnyError> {
+ conditions: &[&str],
+ npm_resolver: &dyn RequireNpmResolver,
+) -> Result<Option<PathBuf>, AnyError> {
let package_json_path = package_dir.join("package.json");
let referrer = ModuleSpecifier::from_directory_path(package_dir).unwrap();
let package_config =
PackageJson::load(npm_resolver, package_json_path.clone())?;
if let Some(exports) = &package_config.exports {
+ let is_types = conditions == TYPES_CONDITIONS;
+ if is_types && package_subpath == "." {
+ if let Ok(Some(path)) =
+ legacy_main_resolve(&package_config, referrer_kind, conditions)
+ {
+ return Ok(Some(path));
+ }
+ }
return package_exports_resolve(
&package_json_path,
package_subpath.to_string(),
exports,
&referrer,
referrer_kind,
- DEFAULT_CONDITIONS,
+ conditions,
npm_resolver,
- );
+ )
+ .map(Some);
}
if package_subpath == "." {
- return legacy_main_resolve(&package_config, referrer_kind);
+ return legacy_main_resolve(&package_config, referrer_kind, conditions);
}
- Ok(package_dir.join(package_subpath))
+ Ok(Some(package_dir.join(package_subpath)))
}
pub fn url_to_node_resolution(
url: ModuleSpecifier,
npm_resolver: &dyn RequireNpmResolver,
) -> Result<NodeResolution, AnyError> {
- Ok(if url.as_str().starts_with("http") {
+ let url_str = url.as_str().to_lowercase();
+ Ok(if url_str.starts_with("http") {
NodeResolution::Esm(url)
- } else if url.as_str().ends_with(".js") {
+ } else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") {
let package_config = get_closest_package_json(&url, npm_resolver)?;
if package_config.typ == "module" {
NodeResolution::Esm(url)
} else {
NodeResolution::CommonJs(url)
}
- } else if url.as_str().ends_with(".mjs") {
+ } else if url_str.ends_with(".mjs") || url_str.ends_with(".d.mts") {
NodeResolution::Esm(url)
} else {
NodeResolution::CommonJs(url)
@@ -666,7 +788,16 @@ fn module_resolve(
// note: if we're here, the referrer is an esm module
let url = if should_be_treated_as_relative_or_absolute_path(specifier) {
let resolved_specifier = referrer.join(specifier)?;
- Some(resolved_specifier)
+ if conditions == TYPES_CONDITIONS {
+ let file_path = to_file_path(&resolved_specifier);
+ // todo(dsherret): the node module kind is not correct and we
+ // should use the value provided by typescript instead
+ let declaration_path =
+ path_to_declaration_path(file_path, NodeModuleKind::Esm);
+ Some(ModuleSpecifier::from_file_path(declaration_path).unwrap())
+ } else {
+ Some(resolved_specifier)
+ }
} else if specifier.starts_with('#') {
Some(
package_imports_resolve(
@@ -681,16 +812,14 @@ fn module_resolve(
} else if let Ok(resolved) = Url::parse(specifier) {
Some(resolved)
} else {
- Some(
- package_resolve(
- specifier,
- referrer,
- NodeModuleKind::Esm,
- conditions,
- npm_resolver,
- )
- .map(|p| ModuleSpecifier::from_file_path(p).unwrap())?,
- )
+ package_resolve(
+ specifier,
+ referrer,
+ NodeModuleKind::Esm,
+ conditions,
+ npm_resolver,
+ )?
+ .map(|p| ModuleSpecifier::from_file_path(p).unwrap())
};
Ok(match url {
Some(url) => Some(finalize_resolution(url, referrer)?),
@@ -913,6 +1042,7 @@ fn resolve(
let module_dir = npm_resolver.resolve_package_folder_from_package(
package_specifier.as_str(),
&referrer_path,
+ conditions,
)?;
let package_json_path = module_dir.join("package.json");
diff --git a/cli/npm/cache.rs b/cli/npm/cache.rs
index 77ecf1228..6a0d72b3a 100644
--- a/cli/npm/cache.rs
+++ b/cli/npm/cache.rs
@@ -160,13 +160,14 @@ impl ReadonlyNpmCache {
.take(if is_scoped_package { 3 } else { 2 })
.map(|(_, part)| part)
.collect::<Vec<_>>();
+ if parts.len() < 2 {
+ return None;
+ }
let version = parts.pop().unwrap();
let name = parts.join("/");
-
- Some(NpmPackageId {
- name,
- version: NpmVersion::parse(version).unwrap(),
- })
+ NpmVersion::parse(version)
+ .ok()
+ .map(|version| NpmPackageId { name, version })
}
pub fn get_cache_location(&self) -> PathBuf {
diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs
index d60c06f14..738e1766f 100644
--- a/cli/npm/mod.rs
+++ b/cli/npm/mod.rs
@@ -13,4 +13,5 @@ pub use resolution::NpmPackageId;
pub use resolution::NpmPackageReference;
pub use resolution::NpmPackageReq;
pub use resolution::NpmResolutionPackage;
+pub use resolution::NpmResolutionSnapshot;
pub use resolvers::NpmPackageResolver;
diff --git a/cli/npm/resolution.rs b/cli/npm/resolution.rs
index 2ceaa86ab..8656d8a3c 100644
--- a/cli/npm/resolution.rs
+++ b/cli/npm/resolution.rs
@@ -21,6 +21,7 @@ use super::registry::NpmPackageVersionDistInfo;
use super::registry::NpmPackageVersionInfo;
use super::registry::NpmRegistryApi;
use super::semver::NpmVersion;
+use super::semver::NpmVersionReq;
use super::semver::SpecifierVersionReq;
/// The version matcher used for npm schemed urls is more strict than
@@ -375,15 +376,57 @@ impl NpmResolution {
pub async fn add_package_reqs(
&self,
- mut package_reqs: Vec<NpmPackageReq>,
+ package_reqs: Vec<NpmPackageReq>,
) -> Result<(), AnyError> {
- // multiple packages are resolved in alphabetical order
- package_reqs.sort_by(|a, b| a.name.cmp(&b.name));
+ // only allow one thread in here at a time
+ let _permit = self.update_sempahore.acquire().await.unwrap();
+ let snapshot = self.snapshot.read().clone();
+
+ let snapshot = self
+ .add_package_reqs_to_snapshot(package_reqs, snapshot)
+ .await?;
+
+ *self.snapshot.write() = snapshot;
+ Ok(())
+ }
+ pub async fn set_package_reqs(
+ &self,
+ package_reqs: HashSet<NpmPackageReq>,
+ ) -> Result<(), AnyError> {
// only allow one thread in here at a time
let _permit = self.update_sempahore.acquire().await.unwrap();
- let mut snapshot = self.snapshot.read().clone();
- let mut pending_dependencies = VecDeque::new();
+ let snapshot = self.snapshot.read().clone();
+
+ let has_removed_package = !snapshot
+ .package_reqs
+ .keys()
+ .all(|req| package_reqs.contains(req));
+ // if any packages were removed, we need to completely recreate the npm resolution snapshot
+ let snapshot = if has_removed_package {
+ NpmResolutionSnapshot::default()
+ } else {
+ snapshot
+ };
+ let snapshot = self
+ .add_package_reqs_to_snapshot(
+ package_reqs.into_iter().collect(),
+ snapshot,
+ )
+ .await?;
+
+ *self.snapshot.write() = snapshot;
+
+ Ok(())
+ }
+
+ async fn add_package_reqs_to_snapshot(
+ &self,
+ mut package_reqs: Vec<NpmPackageReq>,
+ mut snapshot: NpmResolutionSnapshot,
+ ) -> Result<NpmResolutionSnapshot, AnyError> {
+ // multiple packages are resolved in alphabetical order
+ package_reqs.sort_by(|a, b| a.name.cmp(&b.name));
// go over the top level packages first, then down the
// tree one level at a time through all the branches
@@ -418,6 +461,7 @@ impl NpmResolution {
}));
}
+ let mut pending_dependencies = VecDeque::new();
for result in futures::future::join_all(unresolved_tasks).await {
let (package_req, info) = result??;
let version_and_info = get_resolved_package_version_and_info(
@@ -546,8 +590,7 @@ impl NpmResolution {
}
}
- *self.snapshot.write() = snapshot;
- Ok(())
+ Ok(snapshot)
}
pub fn resolve_package_from_package(
@@ -601,6 +644,22 @@ fn get_resolved_package_version_and_info(
) -> Result<VersionAndInfo, AnyError> {
let mut maybe_best_version: Option<VersionAndInfo> = None;
if let Some(tag) = version_matcher.tag() {
+ // For when someone just specifies @types/node, we want to pull in a
+ // "known good" version of @types/node that works well with Deno and
+ // not necessarily the latest version. For example, we might only be
+ // compatible with Node vX, but then Node vY is published so we wouldn't
+ // want to pull that in.
+ // Note: If the user doesn't want this behavior, then they can specify an
+ // explicit version.
+ if tag == "latest" && pkg_name == "@types/node" {
+ return get_resolved_package_version_and_info(
+ pkg_name,
+ &NpmVersionReq::parse("18.0.0 - 18.8.2").unwrap(),
+ info,
+ parent,
+ );
+ }
+
if let Some(version) = info.dist_tags.get(tag) {
match info.versions.get(version) {
Some(info) => {
diff --git a/cli/npm/resolvers/common.rs b/cli/npm/resolvers/common.rs
index b91deae9e..4981d5613 100644
--- a/cli/npm/resolvers/common.rs
+++ b/cli/npm/resolvers/common.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+use std::collections::HashSet;
use std::io::ErrorKind;
use std::path::Path;
use std::path::PathBuf;
@@ -26,6 +27,7 @@ pub trait InnerNpmPackageResolver: Send + Sync {
&self,
name: &str,
referrer: &ModuleSpecifier,
+ conditions: &[&str],
) -> Result<PathBuf, AnyError>;
fn resolve_package_folder_from_specifier(
@@ -40,6 +42,11 @@ pub trait InnerNpmPackageResolver: Send + Sync {
packages: Vec<NpmPackageReq>,
) -> BoxFuture<'static, Result<(), AnyError>>;
+ fn set_package_reqs(
+ &self,
+ packages: HashSet<NpmPackageReq>,
+ ) -> BoxFuture<'static, Result<(), AnyError>>;
+
fn ensure_read_permission(&self, path: &Path) -> Result<(), AnyError>;
fn snapshot(&self) -> NpmResolutionSnapshot;
diff --git a/cli/npm/resolvers/global.rs b/cli/npm/resolvers/global.rs
index c1b6818fd..8eafc19f4 100644
--- a/cli/npm/resolvers/global.rs
+++ b/cli/npm/resolvers/global.rs
@@ -2,6 +2,7 @@
//! Code for global npm cache resolution.
+use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
@@ -11,6 +12,8 @@ use deno_core::error::AnyError;
use deno_core::futures::future::BoxFuture;
use deno_core::futures::FutureExt;
use deno_core::url::Url;
+use deno_runtime::deno_node::PackageJson;
+use deno_runtime::deno_node::TYPES_CONDITIONS;
use crate::npm::resolution::NpmResolution;
use crate::npm::resolution::NpmResolutionSnapshot;
@@ -65,14 +68,35 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver {
&self,
name: &str,
referrer: &ModuleSpecifier,
+ conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let referrer_pkg_id = self
.cache
.resolve_package_id_from_specifier(referrer, &self.registry_url)?;
- let pkg = self
+ let pkg_result = self
.resolution
- .resolve_package_from_package(name, &referrer_pkg_id)?;
- Ok(self.package_folder(&pkg.id))
+ .resolve_package_from_package(name, &referrer_pkg_id);
+ if conditions == TYPES_CONDITIONS && !name.starts_with("@types/") {
+ // When doing types resolution, the package must contain a "types"
+ // entry, or else it will then search for a @types package
+ if let Ok(pkg) = pkg_result {
+ let package_folder = self.package_folder(&pkg.id);
+ let package_json = PackageJson::load_skip_read_permission(
+ package_folder.join("package.json"),
+ )?;
+ if package_json.types.is_some() {
+ return Ok(package_folder);
+ }
+ }
+
+ let name = format!("@types/{}", name);
+ let pkg = self
+ .resolution
+ .resolve_package_from_package(&name, &referrer_pkg_id)?;
+ Ok(self.package_folder(&pkg.id))
+ } else {
+ Ok(self.package_folder(&pkg_result?.id))
+ }
}
fn resolve_package_folder_from_specifier(
@@ -96,12 +120,19 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver {
let resolver = self.clone();
async move {
resolver.resolution.add_package_reqs(packages).await?;
- cache_packages(
- resolver.resolution.all_packages(),
- &resolver.cache,
- &resolver.registry_url,
- )
- .await
+ cache_packages_in_resolver(&resolver).await
+ }
+ .boxed()
+ }
+
+ fn set_package_reqs(
+ &self,
+ packages: HashSet<NpmPackageReq>,
+ ) -> BoxFuture<'static, Result<(), AnyError>> {
+ let resolver = self.clone();
+ async move {
+ resolver.resolution.set_package_reqs(packages).await?;
+ cache_packages_in_resolver(&resolver).await
}
.boxed()
}
@@ -115,3 +146,14 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver {
self.resolution.snapshot()
}
}
+
+async fn cache_packages_in_resolver(
+ resolver: &GlobalNpmPackageResolver,
+) -> Result<(), AnyError> {
+ cache_packages(
+ resolver.resolution.all_packages(),
+ &resolver.cache,
+ &resolver.registry_url,
+ )
+ .await
+}
diff --git a/cli/npm/resolvers/local.rs b/cli/npm/resolvers/local.rs
index cd79320b7..b51593d4c 100644
--- a/cli/npm/resolvers/local.rs
+++ b/cli/npm/resolvers/local.rs
@@ -17,6 +17,8 @@ use deno_core::futures::future::BoxFuture;
use deno_core::futures::FutureExt;
use deno_core::url::Url;
use deno_runtime::deno_core::futures;
+use deno_runtime::deno_node::PackageJson;
+use deno_runtime::deno_node::TYPES_CONDITIONS;
use tokio::task::JoinHandle;
use crate::fs_util;
@@ -124,6 +126,7 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
&self,
name: &str,
referrer: &ModuleSpecifier,
+ conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let local_path = self.resolve_folder_for_specifier(referrer)?;
let package_root_path = self.resolve_package_root(&local_path);
@@ -132,8 +135,28 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
current_folder = get_next_node_modules_ancestor(current_folder);
let sub_dir = join_package_name(current_folder, name);
if sub_dir.is_dir() {
- return Ok(sub_dir);
+ // if doing types resolution, only resolve the package if it specifies a types property
+ if conditions == TYPES_CONDITIONS && !name.starts_with("@types/") {
+ let package_json = PackageJson::load_skip_read_permission(
+ sub_dir.join("package.json"),
+ )?;
+ if package_json.types.is_some() {
+ return Ok(sub_dir);
+ }
+ } else {
+ return Ok(sub_dir);
+ }
}
+
+ // if doing type resolution, check for the existance of a @types package
+ if conditions == TYPES_CONDITIONS && !name.starts_with("@types/") {
+ let sub_dir =
+ join_package_name(current_folder, &format!("@types/{}", name));
+ if sub_dir.is_dir() {
+ return Ok(sub_dir);
+ }
+ }
+
if current_folder == self.root_node_modules_path {
bail!(
"could not find package '{}' from referrer '{}'.",
@@ -164,15 +187,20 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
let resolver = self.clone();
async move {
resolver.resolution.add_package_reqs(packages).await?;
+ sync_resolver_with_fs(&resolver).await?;
+ Ok(())
+ }
+ .boxed()
+ }
- sync_resolution_with_fs(
- &resolver.resolution.snapshot(),
- &resolver.cache,
- &resolver.registry_url,
- &resolver.root_node_modules_path,
- )
- .await?;
-
+ fn set_package_reqs(
+ &self,
+ packages: HashSet<NpmPackageReq>,
+ ) -> BoxFuture<'static, Result<(), AnyError>> {
+ let resolver = self.clone();
+ async move {
+ resolver.resolution.set_package_reqs(packages).await?;
+ sync_resolver_with_fs(&resolver).await?;
Ok(())
}
.boxed()
@@ -187,6 +215,18 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
}
}
+async fn sync_resolver_with_fs(
+ resolver: &LocalNpmPackageResolver,
+) -> Result<(), AnyError> {
+ sync_resolution_with_fs(
+ &resolver.resolution.snapshot(),
+ &resolver.cache,
+ &resolver.registry_url,
+ &resolver.root_node_modules_path,
+ )
+ .await
+}
+
/// Creates a pnpm style folder structure.
async fn sync_resolution_with_fs(
snapshot: &NpmResolutionSnapshot,
diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs
index d290a5569..5498bbf75 100644
--- a/cli/npm/resolvers/mod.rs
+++ b/cli/npm/resolvers/mod.rs
@@ -15,6 +15,7 @@ use global::GlobalNpmPackageResolver;
use once_cell::sync::Lazy;
use serde::Deserialize;
use serde::Serialize;
+use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
@@ -23,10 +24,10 @@ use crate::fs_util;
use self::common::InnerNpmPackageResolver;
use self::local::LocalNpmPackageResolver;
-use super::resolution::NpmResolutionSnapshot;
use super::NpmCache;
use super::NpmPackageReq;
use super::NpmRegistryApi;
+use super::NpmResolutionSnapshot;
const RESOLUTION_STATE_ENV_VAR_NAME: &str =
"DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE";
@@ -67,6 +68,19 @@ pub struct NpmPackageResolver {
no_npm: bool,
inner: Arc<dyn InnerNpmPackageResolver>,
local_node_modules_path: Option<PathBuf>,
+ api: NpmRegistryApi,
+ cache: NpmCache,
+}
+
+impl std::fmt::Debug for NpmPackageResolver {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("NpmPackageResolver")
+ .field("unstable", &self.unstable)
+ .field("no_npm", &self.no_npm)
+ .field("inner", &"<omitted>")
+ .field("local_node_modules_path", &self.local_node_modules_path)
+ .finish()
+ }
}
impl NpmPackageResolver {
@@ -77,30 +91,53 @@ impl NpmPackageResolver {
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
) -> Self {
+ Self::new_with_maybe_snapshot(
+ cache,
+ api,
+ unstable,
+ no_npm,
+ local_node_modules_path,
+ None,
+ )
+ }
+
+ fn new_with_maybe_snapshot(
+ cache: NpmCache,
+ api: NpmRegistryApi,
+ unstable: bool,
+ no_npm: bool,
+ local_node_modules_path: Option<PathBuf>,
+ initial_snapshot: Option<NpmResolutionSnapshot>,
+ ) -> Self {
let process_npm_state = NpmProcessState::take();
let local_node_modules_path = local_node_modules_path.or_else(|| {
process_npm_state
.as_ref()
.and_then(|s| s.local_node_modules_path.as_ref().map(PathBuf::from))
});
- let maybe_snapshot = process_npm_state.map(|s| s.snapshot);
+ let maybe_snapshot =
+ initial_snapshot.or_else(|| process_npm_state.map(|s| s.snapshot));
let inner: Arc<dyn InnerNpmPackageResolver> = match &local_node_modules_path
{
Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new(
- cache,
- api,
+ cache.clone(),
+ api.clone(),
node_modules_folder.clone(),
maybe_snapshot,
)),
- None => {
- Arc::new(GlobalNpmPackageResolver::new(cache, api, maybe_snapshot))
- }
+ None => Arc::new(GlobalNpmPackageResolver::new(
+ cache.clone(),
+ api.clone(),
+ maybe_snapshot,
+ )),
};
Self {
unstable,
no_npm,
inner,
local_node_modules_path,
+ api,
+ cache,
}
}
@@ -122,10 +159,11 @@ impl NpmPackageResolver {
&self,
name: &str,
referrer: &ModuleSpecifier,
+ conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let path = self
.inner
- .resolve_package_folder_from_package(name, referrer)?;
+ .resolve_package_folder_from_package(name, referrer, conditions)?;
log::debug!("Resolved {} from {} to {}", name, referrer, path.display());
Ok(path)
}
@@ -156,12 +194,14 @@ impl NpmPackageResolver {
self.inner.has_packages()
}
- /// Adds a package requirement to the resolver and ensures everything is setup.
+ /// Adds package requirements to the resolver and ensures everything is setup.
pub async fn add_package_reqs(
&self,
packages: Vec<NpmPackageReq>,
) -> Result<(), AnyError> {
- assert!(!packages.is_empty());
+ if packages.is_empty() {
+ return Ok(());
+ }
if !self.unstable {
bail!(
@@ -187,6 +227,14 @@ impl NpmPackageResolver {
self.inner.add_package_reqs(packages).await
}
+ /// Sets package requirements to the resolver, removing old requirements and adding new ones.
+ pub async fn set_package_reqs(
+ &self,
+ packages: HashSet<NpmPackageReq>,
+ ) -> Result<(), AnyError> {
+ self.inner.set_package_reqs(packages).await
+ }
+
// If the main module should be treated as being in an npm package.
// This is triggered via a secret environment variable which is used
// for functionality like child_process.fork. Users should NOT depend
@@ -206,6 +254,18 @@ impl NpmPackageResolver {
})
.unwrap()
}
+
+ /// Gets a new resolver with a new snapshotted state.
+ pub fn snapshotted(&self) -> Self {
+ Self::new_with_maybe_snapshot(
+ self.cache.clone(),
+ self.api.clone(),
+ self.unstable,
+ self.no_npm,
+ self.local_node_modules_path.clone(),
+ Some(self.inner.snapshot()),
+ )
+ }
}
impl RequireNpmResolver for NpmPackageResolver {
@@ -213,9 +273,10 @@ impl RequireNpmResolver for NpmPackageResolver {
&self,
specifier: &str,
referrer: &std::path::Path,
+ conditions: &[&str],
) -> Result<PathBuf, AnyError> {
let referrer = path_to_specifier(referrer)?;
- self.resolve_package_folder_from_package(specifier, &referrer)
+ self.resolve_package_folder_from_package(specifier, &referrer, conditions)
}
fn resolve_package_folder_from_path(
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
index 07a7c1f8a..95520ffde 100644
--- a/cli/proc_state.rs
+++ b/cli/proc_state.rs
@@ -34,9 +34,9 @@ use crate::tools::check;
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
-use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::custom_error;
+use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::futures;
use deno_core::parking_lot::Mutex;
@@ -433,8 +433,13 @@ impl ProcState {
let check_cache =
TypeCheckCache::new(&self.dir.type_checking_cache_db_file_path());
let graph_data = self.graph_data.clone();
- let check_result =
- check::check(&roots, graph_data, &check_cache, options)?;
+ let check_result = check::check(
+ &roots,
+ graph_data,
+ &check_cache,
+ self.npm_resolver.clone(),
+ options,
+ )?;
if !check_result.diagnostics.is_empty() {
return Err(anyhow!(check_result.diagnostics));
}
@@ -470,7 +475,7 @@ impl ProcState {
) -> Result<ModuleSpecifier, AnyError> {
let response = match result? {
Some(response) => response,
- None => bail!("Not found."),
+ None => return Err(generic_error("not found")),
};
if let NodeResolution::CommonJs(specifier) = &response {
// remember that this was a common js resolution
@@ -493,6 +498,7 @@ impl ProcState {
.handle_node_resolve_result(node::node_resolve(
specifier,
&referrer,
+ node::NodeResolutionMode::Execution,
&self.npm_resolver,
))
.with_context(|| {
@@ -516,6 +522,7 @@ impl ProcState {
return self
.handle_node_resolve_result(node::node_resolve_npm_reference(
&reference,
+ node::NodeResolutionMode::Execution,
&self.npm_resolver,
))
.with_context(|| format!("Could not resolve '{}'.", reference));
diff --git a/cli/tests/integration/check_tests.rs b/cli/tests/integration/check_tests.rs
index ab96670ef..f42cd4a7a 100644
--- a/cli/tests/integration/check_tests.rs
+++ b/cli/tests/integration/check_tests.rs
@@ -50,6 +50,13 @@ itest!(declaration_header_file_with_no_exports {
output_str: Some(""),
});
+itest!(check_npm_install_diagnostics {
+ args: "check --quiet check/npm_install_diagnostics/main.ts",
+ output: "check/npm_install_diagnostics/main.out",
+ envs: vec![("NO_COLOR".to_string(), "1".to_string())],
+ exit_code: 1,
+});
+
#[test]
fn cache_switching_config_then_no_config() {
let deno_dir = util::new_deno_dir();
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index 8390b0f6f..9a0d407df 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -3347,6 +3347,37 @@ fn lsp_code_actions_deno_cache() {
}
#[test]
+fn lsp_code_actions_deno_cache_npm() {
+ let mut session = TestSession::from_file("initialize_params.json");
+ let diagnostics = session.did_open(json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": "import chalk from \"npm:chalk\";\n\nconsole.log(chalk.green);\n"
+ }
+ }));
+ assert_eq!(
+ diagnostics.with_source("deno"),
+ load_fixture_as("code_actions/cache_npm/diagnostics.json")
+ );
+
+ let (maybe_res, maybe_err) = session
+ .client
+ .write_request(
+ "textDocument/codeAction",
+ load_fixture("code_actions/cache_npm/cache_action.json"),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(
+ maybe_res,
+ Some(load_fixture("code_actions/cache_npm/cache_response.json"))
+ );
+ session.shutdown_and_exit();
+}
+
+#[test]
fn lsp_code_actions_imports() {
let mut session = TestSession::from_file("initialize_params.json");
session.did_open(json!({
@@ -4047,6 +4078,169 @@ fn lsp_completions_no_snippet() {
}
#[test]
+fn lsp_completions_npm() {
+ let _g = http_server();
+ let mut client = init("initialize_params.json");
+ did_open(
+ &mut client,
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": "import cjsDefault from 'npm:@denotest/cjs-default-export';import chalk from 'npm:chalk';\n\n",
+ }
+ }),
+ );
+ let (maybe_res, maybe_err) = client
+ .write_request::<_, _, Value>(
+ "deno/cache",
+ json!({
+ "referrer": {
+ "uri": "file:///a/file.ts",
+ },
+ "uris": [
+ {
+ "uri": "npm:@denotest/cjs-default-export",
+ },
+ {
+ "uri": "npm:chalk",
+ }
+ ]
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert!(maybe_res.is_some());
+
+ // check importing a cjs default import
+ 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": "cjsDefault."
+ }
+ ]
+ }),
+ )
+ .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": 11
+ },
+ "context": {
+ "triggerKind": 2,
+ "triggerCharacter": "."
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ if let Some(lsp::CompletionResponse::List(list)) = maybe_res {
+ assert!(!list.is_incomplete);
+ assert_eq!(list.items.len(), 3);
+ assert!(list.items.iter().any(|i| i.label == "default"));
+ assert!(list.items.iter().any(|i| i.label == "MyClass"));
+ } else {
+ panic!("unexpected response");
+ }
+ let (maybe_res, maybe_err) = client
+ .write_request(
+ "completionItem/resolve",
+ load_fixture("completions/npm/resolve_params.json"),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(
+ maybe_res,
+ Some(load_fixture("completions/npm/resolve_response.json"))
+ );
+
+ // now check chalk, which is esm
+ client
+ .write_notification(
+ "textDocument/didChange",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "version": 3
+ },
+ "contentChanges": [
+ {
+ "range": {
+ "start": {
+ "line": 2,
+ "character": 0
+ },
+ "end": {
+ "line": 2,
+ "character": 11
+ }
+ },
+ "text": "chalk."
+ }
+ ]
+ }),
+ )
+ .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": 6
+ },
+ "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 == "green"));
+ assert!(list.items.iter().any(|i| i.label == "red"));
+ } 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/npm_tests.rs b/cli/tests/integration/npm_tests.rs
index bc19c613d..9fc817141 100644
--- a/cli/tests/integration/npm_tests.rs
+++ b/cli/tests/integration/npm_tests.rs
@@ -1,6 +1,5 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-use deno_core::url::Url;
use std::process::Stdio;
use test_util as util;
use util::assert_contains;
@@ -34,7 +33,7 @@ itest!(esm_module_deno_test {
});
itest!(esm_import_cjs_default {
- args: "run --allow-read --allow-env --unstable --quiet npm/esm_import_cjs_default/main.js",
+ args: "run --allow-read --allow-env --unstable --quiet --check=all npm/esm_import_cjs_default/main.ts",
output: "npm/esm_import_cjs_default/main.out",
envs: env_vars(),
http_server: true,
@@ -84,7 +83,7 @@ itest!(translate_cjs_to_esm {
});
itest!(compare_globals {
- args: "run --allow-read --unstable npm/compare_globals/main.js",
+ args: "run --allow-read --unstable --check=all npm/compare_globals/main.ts",
output: "npm/compare_globals/main.out",
envs: env_vars(),
http_server: true,
@@ -210,6 +209,38 @@ itest!(deno_cache {
http_server: true,
});
+itest!(check_all {
+ args: "check --unstable --remote npm/check_errors/main.ts",
+ output: "npm/check_errors/main_all.out",
+ envs: env_vars(),
+ http_server: true,
+ exit_code: 1,
+});
+
+itest!(check_local {
+ args: "check --unstable npm/check_errors/main.ts",
+ output: "npm/check_errors/main_local.out",
+ envs: env_vars(),
+ http_server: true,
+ exit_code: 1,
+});
+
+itest!(types_ambient_module {
+ args: "check --unstable --quiet npm/types_ambient_module/main.ts",
+ output: "npm/types_ambient_module/main.out",
+ envs: env_vars(),
+ http_server: true,
+ exit_code: 1,
+});
+
+itest!(types_ambient_module_import_map {
+ args: "check --unstable --quiet --import-map=npm/types_ambient_module/import_map.json npm/types_ambient_module/main_import_map.ts",
+ output: "npm/types_ambient_module/main_import_map.out",
+ envs: env_vars(),
+ http_server: true,
+ exit_code: 1,
+});
+
#[test]
fn parallel_downloading() {
let (out, _err) = util::run_and_collect_output_with_args(
@@ -672,18 +703,10 @@ fn ensure_registry_files_local() {
}
}
-fn std_file_url() -> String {
- let u = Url::from_directory_path(util::std_path()).unwrap();
- u.to_string()
-}
-
fn env_vars_no_sync_download() -> Vec<(String, String)> {
vec![
- ("DENO_NODE_COMPAT_URL".to_string(), std_file_url()),
- (
- "DENO_NPM_REGISTRY".to_string(),
- "http://localhost:4545/npm/registry/".to_string(),
- ),
+ ("DENO_NODE_COMPAT_URL".to_string(), util::std_file_url()),
+ ("DENO_NPM_REGISTRY".to_string(), util::npm_registry_url()),
("NO_COLOR".to_string(), "1".to_string()),
]
}
diff --git a/cli/tests/testdata/check/npm_install_diagnostics/main.out b/cli/tests/testdata/check/npm_install_diagnostics/main.out
new file mode 100644
index 000000000..fe46f0e42
--- /dev/null
+++ b/cli/tests/testdata/check/npm_install_diagnostics/main.out
@@ -0,0 +1,11 @@
+error: TS2581 [ERROR]: Cannot find name '$'. Did you mean to import jQuery? Try adding `import $ from "npm:jquery";`.
+$;
+^
+ at file:///[WILDCARD]/npm_install_diagnostics/main.ts:1:1
+
+TS2580 [ERROR]: Cannot find name 'process'.
+process;
+~~~~~~~
+ at file:///[WILDCARD]/npm_install_diagnostics/main.ts:2:1
+
+Found 2 errors.
diff --git a/cli/tests/testdata/check/npm_install_diagnostics/main.ts b/cli/tests/testdata/check/npm_install_diagnostics/main.ts
new file mode 100644
index 000000000..62c0c5619
--- /dev/null
+++ b/cli/tests/testdata/check/npm_install_diagnostics/main.ts
@@ -0,0 +1,2 @@
+$;
+process;
diff --git a/cli/tests/testdata/lsp/code_actions/cache_npm/cache_action.json b/cli/tests/testdata/lsp/code_actions/cache_npm/cache_action.json
new file mode 100644
index 000000000..b698df3bd
--- /dev/null
+++ b/cli/tests/testdata/lsp/code_actions/cache_npm/cache_action.json
@@ -0,0 +1,41 @@
+{
+ "textDocument": {
+ "uri": "file:///a/file.ts"
+ },
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 18
+ },
+ "end": {
+ "line": 0,
+ "character": 29
+ }
+ },
+ "context": {
+ "diagnostics": [
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 18
+ },
+ "end": {
+ "line": 0,
+ "character": 29
+ }
+ },
+ "severity": 1,
+ "code": "no-cache-npm",
+ "source": "deno",
+ "message": "Uncached or missing npm package: \"chalk\".",
+ "data": {
+ "specifier": "npm:chalk"
+ }
+ }
+ ],
+ "only": [
+ "quickfix"
+ ]
+ }
+}
diff --git a/cli/tests/testdata/lsp/code_actions/cache_npm/cache_response.json b/cli/tests/testdata/lsp/code_actions/cache_npm/cache_response.json
new file mode 100644
index 000000000..1b41babcb
--- /dev/null
+++ b/cli/tests/testdata/lsp/code_actions/cache_npm/cache_response.json
@@ -0,0 +1,36 @@
+[
+ {
+ "title": "Cache \"npm:chalk\" and its dependencies.",
+ "kind": "quickfix",
+ "diagnostics": [
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 18
+ },
+ "end": {
+ "line": 0,
+ "character": 29
+ }
+ },
+ "severity": 1,
+ "code": "no-cache-npm",
+ "source": "deno",
+ "message": "Uncached or missing npm package: \"chalk\".",
+ "data": {
+ "specifier": "npm:chalk"
+ }
+ }
+ ],
+ "command": {
+ "title": "",
+ "command": "deno.cache",
+ "arguments": [
+ [
+ "npm:chalk"
+ ]
+ ]
+ }
+ }
+]
diff --git a/cli/tests/testdata/lsp/code_actions/cache_npm/diagnostics.json b/cli/tests/testdata/lsp/code_actions/cache_npm/diagnostics.json
new file mode 100644
index 000000000..63c9d0029
--- /dev/null
+++ b/cli/tests/testdata/lsp/code_actions/cache_npm/diagnostics.json
@@ -0,0 +1,25 @@
+{
+ "uri": "file:///a/file.ts",
+ "diagnostics": [
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 18
+ },
+ "end": {
+ "line": 0,
+ "character": 29
+ }
+ },
+ "severity": 1,
+ "code": "no-cache-npm",
+ "source": "deno",
+ "message": "Uncached or missing npm package: \"chalk\".",
+ "data": {
+ "specifier": "npm:chalk"
+ }
+ }
+ ],
+ "version": 1
+}
diff --git a/cli/tests/testdata/lsp/completions/npm/resolve_params.json b/cli/tests/testdata/lsp/completions/npm/resolve_params.json
new file mode 100644
index 000000000..c83b8ce49
--- /dev/null
+++ b/cli/tests/testdata/lsp/completions/npm/resolve_params.json
@@ -0,0 +1,14 @@
+{
+ "label": "MyClass",
+ "kind": 6,
+ "sortText": "1",
+ "insertTextFormat": 1,
+ "data": {
+ "tsc": {
+ "specifier": "file:///a/file.ts",
+ "position": 69,
+ "name": "MyClass",
+ "useCodeSnippet": false
+ }
+ }
+}
diff --git a/cli/tests/testdata/lsp/completions/npm/resolve_response.json b/cli/tests/testdata/lsp/completions/npm/resolve_response.json
new file mode 100644
index 000000000..c83b8ce49
--- /dev/null
+++ b/cli/tests/testdata/lsp/completions/npm/resolve_response.json
@@ -0,0 +1,14 @@
+{
+ "label": "MyClass",
+ "kind": 6,
+ "sortText": "1",
+ "insertTextFormat": 1,
+ "data": {
+ "tsc": {
+ "specifier": "file:///a/file.ts",
+ "position": 69,
+ "name": "MyClass",
+ "useCodeSnippet": false
+ }
+ }
+}
diff --git a/cli/tests/testdata/npm/check_errors/main.ts b/cli/tests/testdata/npm/check_errors/main.ts
new file mode 100644
index 000000000..4b8684195
--- /dev/null
+++ b/cli/tests/testdata/npm/check_errors/main.ts
@@ -0,0 +1,3 @@
+import * as test from "npm:@denotest/check-error";
+
+console.log(test.Asdf); // should error
diff --git a/cli/tests/testdata/npm/check_errors/main_all.out b/cli/tests/testdata/npm/check_errors/main_all.out
new file mode 100644
index 000000000..96f16d0b9
--- /dev/null
+++ b/cli/tests/testdata/npm/check_errors/main_all.out
@@ -0,0 +1,19 @@
+Download http://localhost:4545/npm/registry/@denotest/check-error
+Download http://localhost:4545/npm/registry/@denotest/check-error/1.0.0.tgz
+Check file:///[WILDCARD]/check_errors/main.ts
+error: TS2506 [ERROR]: 'Class1' is referenced directly or indirectly in its own base expression.
+export class Class1 extends Class2 {
+ ~~~~~~
+ at file:///[WILDCARD]/check-error/1.0.0/index.d.ts:2:14
+
+TS2506 [ERROR]: 'Class2' is referenced directly or indirectly in its own base expression.
+export class Class2 extends Class1 {
+ ~~~~~~
+ at file:///[WILDCARD]/check-error/1.0.0/index.d.ts:5:14
+
+TS2339 [ERROR]: Property 'Asdf' does not exist on type 'typeof import("file:///[WILDCARD]/@denotest/check-error/1.0.0/index.d.ts")'.
+console.log(test.Asdf); // should error
+ ~~~~
+ at file:///[WILDCARD]/check_errors/main.ts:3:18
+
+Found 3 errors.
diff --git a/cli/tests/testdata/npm/check_errors/main_local.out b/cli/tests/testdata/npm/check_errors/main_local.out
new file mode 100644
index 000000000..1624b98bc
--- /dev/null
+++ b/cli/tests/testdata/npm/check_errors/main_local.out
@@ -0,0 +1,7 @@
+Download http://localhost:4545/npm/registry/@denotest/check-error
+Download http://localhost:4545/npm/registry/@denotest/check-error/1.0.0.tgz
+Check file:///[WILDCARD]/check_errors/main.ts
+error: TS2339 [ERROR]: Property 'Asdf' does not exist on type 'typeof import("file:///[WILDCARD]/@denotest/check-error/1.0.0/index.d.ts")'.
+console.log(test.Asdf); // should error
+ ~~~~
+ at file:///[WILDCARD]/npm/check_errors/main.ts:3:18
diff --git a/cli/tests/testdata/npm/compare_globals/main.js b/cli/tests/testdata/npm/compare_globals/main.js
deleted file mode 100644
index ce43e32b1..000000000
--- a/cli/tests/testdata/npm/compare_globals/main.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import * as globals from "npm:@denotest/globals";
-console.log(globals.global === globals.globalThis);
diff --git a/cli/tests/testdata/npm/compare_globals/main.out b/cli/tests/testdata/npm/compare_globals/main.out
index a1a5c0e8f..1b22fd318 100644
--- a/cli/tests/testdata/npm/compare_globals/main.out
+++ b/cli/tests/testdata/npm/compare_globals/main.out
@@ -1,3 +1,8 @@
Download http://localhost:4545/npm/registry/@denotest/globals
+Download http://localhost:4545/npm/registry/@types/node
Download http://localhost:4545/npm/registry/@denotest/globals/1.0.0.tgz
+Download http://localhost:4545/npm/registry/@types/node/node-18.8.2.tgz
+Check file:///[WILDCARD]/npm/compare_globals/main.ts
+Check file:///[WILDCARD]/std/node/module_all.ts
true
+[]
diff --git a/cli/tests/testdata/npm/compare_globals/main.ts b/cli/tests/testdata/npm/compare_globals/main.ts
new file mode 100644
index 000000000..5710d0bd5
--- /dev/null
+++ b/cli/tests/testdata/npm/compare_globals/main.ts
@@ -0,0 +1,14 @@
+/// <reference types="npm:@types/node" />
+
+import * as globals from "npm:@denotest/globals";
+console.log(globals.global === globals.globalThis);
+console.log(globals.process.execArgv);
+
+type AssertTrue<T extends true> = never;
+type _TestNoProcessGlobal = AssertTrue<
+ typeof globalThis extends { process: any } ? false : true
+>;
+type _TestHasNodeJsGlobal = NodeJS.Architecture;
+
+const controller = new AbortController();
+controller.abort("reason"); // in the NodeJS declaration it doesn't have a reason
diff --git a/cli/tests/testdata/npm/esm_import_cjs_default/main.js b/cli/tests/testdata/npm/esm_import_cjs_default/main.ts
index f405a5899..f9c3280e5 100644
--- a/cli/tests/testdata/npm/esm_import_cjs_default/main.js
+++ b/cli/tests/testdata/npm/esm_import_cjs_default/main.ts
@@ -1,3 +1,4 @@
+// @ts-check
import cjsDefault, {
MyClass as MyCjsClass,
} from "npm:@denotest/cjs-default-export";
diff --git a/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.d.ts b/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.d.ts
new file mode 100644
index 000000000..673c0035e
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.d.ts
@@ -0,0 +1,6 @@
+// intentional type checking errors
+export class Class1 extends Class2 {
+}
+
+export class Class2 extends Class1 {
+}
diff --git a/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.js
new file mode 100644
index 000000000..7eb6b784d
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/index.js
@@ -0,0 +1,6 @@
+module.exports = {
+ Class1: class {
+ },
+ Class2: class {
+ },
+};
diff --git a/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/package.json
new file mode 100644
index 000000000..295920a8f
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/check-error/1.0.0/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "@denotest/check-error",
+ "version": "1.0.0",
+ "types": "./index.d.ts"
+}
diff --git a/cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/index.d.ts b/cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/index.d.ts
new file mode 100644
index 000000000..90fdfe5f6
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/index.d.ts
@@ -0,0 +1,6 @@
+export default function (): number;
+export declare function named(): number;
+declare class MyClass {
+ static someStaticMethod(): string;
+}
+export { MyClass };
diff --git a/cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/package.json
index 4765d25d2..8da28b919 100644
--- a/cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/package.json
+++ b/cli/tests/testdata/npm/registry/@denotest/cjs-default-export/1.0.0/package.json
@@ -1,4 +1,5 @@
{
"name": "@denotest/cjs-default-export",
- "version": "1.0.0"
+ "version": "1.0.0",
+ "types": "./index.d.ts"
}
diff --git a/cli/tests/testdata/npm/registry/@denotest/esm-import-cjs-default/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/esm-import-cjs-default/1.0.0/package.json
index 184076799..f757a08fb 100644
--- a/cli/tests/testdata/npm/registry/@denotest/esm-import-cjs-default/1.0.0/package.json
+++ b/cli/tests/testdata/npm/registry/@denotest/esm-import-cjs-default/1.0.0/package.json
@@ -1,6 +1,7 @@
{
"name": "@denotest/esm-import-cjs-default",
"version": "1.0.0",
+ "main": "index.mjs",
"dependencies": {
"@denotest/cjs-default-export": "^1.0.0"
}
diff --git a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts
new file mode 100644
index 000000000..ee03712dd
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts
@@ -0,0 +1,13 @@
+declare const tempGlobalThis: typeof globalThis;
+declare const tempGlobal: typeof global;
+declare const tempProcess: typeof process;
+export {
+ tempGlobalThis as globalThis,
+ tempGlobal as global,
+ tempProcess as process,
+};
+
+type AssertTrue<T extends true> = never;
+type _TestHasProcessGlobal = AssertTrue<
+ typeof globalThis extends { process: any } ? true : false
+>;
diff --git a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js
index be5e6e5ac..50d2d3d2a 100644
--- a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js
+++ b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js
@@ -1,2 +1,3 @@
exports.globalThis = globalThis;
exports.global = global;
+exports.process = process;
diff --git a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/package.json
index cba0742c1..1ce42ded4 100644
--- a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/package.json
+++ b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/package.json
@@ -1,4 +1,5 @@
{
"name": "@denotest/globals",
- "version": "1.0.0"
+ "version": "1.0.0",
+ "types": "index.d.ts"
}
diff --git a/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.d.ts b/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.d.ts
new file mode 100644
index 000000000..fc2199884
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.d.ts
@@ -0,0 +1,10 @@
+// Some packages do this. It's really not ideal because instead of allowing
+// the package to be resolved at any specifier, it instead expects the package
+// to be resolved via a "@denotest/types-ambient" specifier. To make this work,
+// we've currently modified the typescript compiler to check for any "<package-name>"
+// ambient modules when resolving an npm specifier at "npm:<package-name>"
+declare module "@denotest/types-ambient" {
+ class Test {
+ prop: number;
+ }
+}
diff --git a/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.js
new file mode 100644
index 000000000..47ff7adb2
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/index.js
@@ -0,0 +1,3 @@
+export class Test {
+ prop = 5;
+}
diff --git a/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/package.json
new file mode 100644
index 000000000..ef927cbe3
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@denotest/types-ambient/1.0.0/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "@denotest/types-ambient",
+ "version": "1.0.0",
+ "types": "./index.d.ts"
+}
diff --git a/cli/tests/testdata/npm/registry/@types/node/node-18.8.2.tgz b/cli/tests/testdata/npm/registry/@types/node/node-18.8.2.tgz
new file mode 100644
index 000000000..8afc9d21d
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@types/node/node-18.8.2.tgz
Binary files differ
diff --git a/cli/tests/testdata/npm/registry/@types/node/registry.json b/cli/tests/testdata/npm/registry/@types/node/registry.json
new file mode 100644
index 000000000..3fff1578e
--- /dev/null
+++ b/cli/tests/testdata/npm/registry/@types/node/registry.json
@@ -0,0 +1,73 @@
+{
+ "_id": "@types/node",
+ "_rev": "8944-025a921c7561ec7339c70b87995cb358",
+ "name": "@types/node",
+ "description": "TypeScript definitions for Node.js",
+ "dist-tags": {
+ "latest": "18.8.2"
+ },
+ "versions": {
+ "18.8.2": {
+ "name": "@types/node",
+ "version": "18.8.2",
+ "description": "TypeScript definitions for Node.js",
+ "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node",
+ "license": "MIT",
+ "contributors": [
+ ],
+ "main": "",
+ "types": "index.d.ts",
+ "typesVersions": { "<4.9.0-0": { "*": ["ts4.8/*"] } },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
+ "directory": "types/node"
+ },
+ "scripts": {},
+ "dependencies": {},
+ "typesPublisherContentHash": "034172ea945b66afc6502e6be34d6fb957c596091e39cf43672e8aca563a8c66",
+ "typeScriptVersion": "4.1",
+ "_id": "@types/node@18.8.2",
+ "dist": {
+ "integrity": "sha512-cRMwIgdDN43GO4xMWAfJAecYn8wV4JbsOGHNfNUIDiuYkUYAR5ec4Rj7IO2SAhFPEfpPtLtUTbbny/TCT7aDwA==",
+ "shasum": "17d42c6322d917764dd3d2d3a10d7884925de067",
+ "tarball": "http://localhost:4545/npm/registry/@types/node/node-18.8.2.tgz",
+ "fileCount": 124,
+ "unpackedSize": 3524549,
+ "signatures": [
+ {
+ "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA",
+ "sig": "MEYCIQCAqI3XibndhBD647C/13AFb58Fhmg4WmfCoGrIYrgtzwIhAIB0b0D58Tigwb3qKaOVsKnuYOOr0strAmprZSCi/+oq"
+ }
+ ],
+ "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjPFItACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrKAg/+IwaUWPgePlO4IxW7CVhFEEFiyhjEH3FHe0ogC3YmreoBFv/A\r\nPwQrwObdskbGWrBzsAOVFvhzYktzP3kc857HtU2ia9FXeaEYvsSQBqh6jZfA\r\njR1+h+jn+W5JnmbnwRGfNn2riCo/un4tYoZ4o/bKiMdNd9WrdIs0Oii1Dd4N\r\nnsBXPb05UPPP4Uu8cz68u1bj+QQ6aslr6keGNyNeILf8bJsEfcfVkEO3l4cu\r\njyTIrxiD+tM8jrUE9CDeodF8CZNuvLh3hqdMPJeH3U47qkDtPDKEOvZTbyYm\r\ngodto6mcnuBr8F9vmikAQfGiXV0U2cg2XRjWc1lI8HT4X0kESTIo+nzNuliD\r\niTpfjyZHdKBGGIuHP1Ou9dVvx4t5XZ1JsK9EK5WTilvAlu/qZrynxXxAV3Rc\r\nvL9UhIacISprMWB3Sohl9ZtfcmGnV/KMRpM+yPZOWp39gQQCKaKF/j2f/mir\r\n8YFbFUnySZkXKzxgsgjrSsh9QiK2dYAzcqHlazITeFN9jOYRzYUjAFj3qOFm\r\n7o1eJpW0qM5vgR+fPq30WxcdcQ4PaWgHSlb/ll8hiwQG1ZUihO/1RU7FtDoc\r\n1KwcfrGOAJPL6vBSLPReUkhDIUTSBwfmvfMxzqD1aDp6YV5WX7h03B0dWbPJ\r\nmPJmJZjjZje4Trk9jBJ5/ZLpB8/zkrDKvhE=\r\n=LPWa\r\n-----END PGP SIGNATURE-----\r\n"
+ },
+ "_npmUser": { "name": "types", "email": "ts-npm-types@microsoft.com" },
+ "directories": {},
+ "maintainers": [
+ { "name": "types", "email": "ts-npm-types@microsoft.com" }
+ ],
+ "_npmOperationalInternal": {
+ "host": "s3://npm-registry-packages",
+ "tmp": "tmp/node_18.8.2_1664897581729_0.9746861340465625"
+ },
+ "_hasShrinkwrap": false
+ }
+ },
+ "readme": "[object Object]",
+ "maintainers": [{ "name": "types", "email": "ts-npm-types@microsoft.com" }],
+ "time": {
+ "18.8.2": "2022-10-04T15:33:01.913Z"
+ },
+ "license": "MIT",
+ "readmeFilename": "",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
+ "directory": "types/node"
+ },
+ "users": {
+ },
+ "contributors": [],
+ "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node"
+}
diff --git a/cli/tests/testdata/npm/types_ambient_module/import_map.json b/cli/tests/testdata/npm/types_ambient_module/import_map.json
new file mode 100644
index 000000000..f61d99b47
--- /dev/null
+++ b/cli/tests/testdata/npm/types_ambient_module/import_map.json
@@ -0,0 +1,5 @@
+{
+ "imports": {
+ "types-ambient": "npm:@denotest/types-ambient"
+ }
+}
diff --git a/cli/tests/testdata/npm/types_ambient_module/main.out b/cli/tests/testdata/npm/types_ambient_module/main.out
new file mode 100644
index 000000000..c84130707
--- /dev/null
+++ b/cli/tests/testdata/npm/types_ambient_module/main.out
@@ -0,0 +1,21 @@
+error: TS2551 [ERROR]: Property 'Test2' does not exist on type 'typeof import("@denotest/types-ambient")'. Did you mean 'Test'?
+console.log(import1.Test2); // should error
+ ~~~~~
+ at file:///[WILDCARD]/types_ambient_module/main.ts:5:21
+
+ 'Test' is declared here.
+ class Test {
+ ~~~~
+ at file:///[WILDCARD]/@denotest/types-ambient/1.0.0/index.d.ts:7:9
+
+TS2551 [ERROR]: Property 'Test2' does not exist on type 'typeof import("@denotest/types-ambient")'. Did you mean 'Test'?
+console.log(import2.Test2); // should error
+ ~~~~~
+ at file:///[WILDCARD]/types_ambient_module/main.ts:7:21
+
+ 'Test' is declared here.
+ class Test {
+ ~~~~
+ at file:///[WILDCARD]/@denotest/types-ambient/1.0.0/index.d.ts:7:9
+
+Found 2 errors.
diff --git a/cli/tests/testdata/npm/types_ambient_module/main.ts b/cli/tests/testdata/npm/types_ambient_module/main.ts
new file mode 100644
index 000000000..8f77cabe8
--- /dev/null
+++ b/cli/tests/testdata/npm/types_ambient_module/main.ts
@@ -0,0 +1,7 @@
+import * as import1 from "npm:@denotest/types-ambient";
+import * as import2 from "npm:@denotest/types-ambient@1";
+
+console.log(import1.Test);
+console.log(import1.Test2); // should error
+console.log(import2.Test);
+console.log(import2.Test2); // should error
diff --git a/cli/tests/testdata/npm/types_ambient_module/main_import_map.out b/cli/tests/testdata/npm/types_ambient_module/main_import_map.out
new file mode 100644
index 000000000..548f9b479
--- /dev/null
+++ b/cli/tests/testdata/npm/types_ambient_module/main_import_map.out
@@ -0,0 +1,9 @@
+error: TS2551 [ERROR]: Property 'Test2' does not exist on type 'typeof import("@denotest/types-ambient")'. Did you mean 'Test'?
+console.log(mod.Test2); // should error
+ ~~~~~
+ at file:///[WILDCARD]/main_import_map.ts:4:17
+
+ 'Test' is declared here.
+ class Test {
+ ~~~~
+ at file:///[WILDCARD]/@denotest/types-ambient/1.0.0/index.d.ts:7:9
diff --git a/cli/tests/testdata/npm/types_ambient_module/main_import_map.ts b/cli/tests/testdata/npm/types_ambient_module/main_import_map.ts
new file mode 100644
index 000000000..2694c94b7
--- /dev/null
+++ b/cli/tests/testdata/npm/types_ambient_module/main_import_map.ts
@@ -0,0 +1,4 @@
+import * as mod from "npm:@denotest/types-ambient";
+
+console.log(mod.Test);
+console.log(mod.Test2); // should error
diff --git a/cli/tools/check.rs b/cli/tools/check.rs
index bb0b873f4..88c05e130 100644
--- a/cli/tools/check.rs
+++ b/cli/tools/check.rs
@@ -18,6 +18,7 @@ use crate::cache::TypeCheckCache;
use crate::diagnostics::Diagnostics;
use crate::graph_util::GraphData;
use crate::graph_util::ModuleEntry;
+use crate::npm::NpmPackageResolver;
use crate::tsc;
use crate::tsc::Stats;
use crate::version;
@@ -57,6 +58,7 @@ pub fn check(
roots: &[(ModuleSpecifier, ModuleKind)],
graph_data: Arc<RwLock<GraphData>>,
cache: &TypeCheckCache,
+ npm_resolver: NpmPackageResolver,
options: CheckOptions,
) -> Result<CheckResult, AnyError> {
let check_js = options.ts_config.get_check_js();
@@ -106,6 +108,7 @@ pub fn check(
graph_data,
hash_data,
maybe_config_specifier: options.maybe_config_specifier,
+ maybe_npm_resolver: Some(npm_resolver.clone()),
maybe_tsbuildinfo,
root_names,
})?;
@@ -114,6 +117,9 @@ pub fn check(
response.diagnostics.filter(|d| {
if let Some(file_name) = &d.file_name {
!file_name.starts_with("http")
+ && ModuleSpecifier::parse(file_name)
+ .map(|specifier| !npm_resolver.in_npm_package(&specifier))
+ .unwrap_or(true)
} else {
true
}
diff --git a/cli/tsc.rs b/cli/tsc.rs
index e9800d9d2..90ae7334c 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -4,6 +4,12 @@ use crate::args::TsConfig;
use crate::diagnostics::Diagnostics;
use crate::graph_util::GraphData;
use crate::graph_util::ModuleEntry;
+use crate::node;
+use crate::node::node_resolve_npm_reference;
+use crate::node::NodeResolution;
+use crate::node::NodeResolutionMode;
+use crate::npm::NpmPackageReference;
+use crate::npm::NpmPackageResolver;
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
@@ -28,6 +34,7 @@ use deno_core::RuntimeOptions;
use deno_core::Snapshot;
use deno_graph::Resolved;
use once_cell::sync::Lazy;
+use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
@@ -171,7 +178,7 @@ fn get_maybe_hash(
}
/// Hash the URL so it can be sent to `tsc` in a supportable way
-fn hash_url(specifier: &ModuleSpecifier, media_type: &MediaType) -> String {
+fn hash_url(specifier: &ModuleSpecifier, media_type: MediaType) -> String {
let hash = crate::checksum::gen(&[specifier.path().as_bytes()]);
format!(
"{}:///{}{}",
@@ -187,7 +194,7 @@ fn hash_url(specifier: &ModuleSpecifier, media_type: &MediaType) -> String {
/// think a `.js` version exists, when it doesn't.
fn maybe_remap_specifier(
specifier: &ModuleSpecifier,
- media_type: &MediaType,
+ media_type: MediaType,
) -> Option<String> {
let path = if specifier.scheme() == "file" {
if let Ok(path) = specifier.to_file_path() {
@@ -279,6 +286,7 @@ pub struct Request {
pub graph_data: Arc<RwLock<GraphData>>,
pub hash_data: Vec<Vec<u8>>,
pub maybe_config_specifier: Option<ModuleSpecifier>,
+ pub maybe_npm_resolver: Option<NpmPackageResolver>,
pub maybe_tsbuildinfo: Option<String>,
/// A vector of strings that represent the root/entry point modules for the
/// program.
@@ -295,13 +303,14 @@ pub struct Response {
pub stats: Stats,
}
-#[derive(Debug)]
+#[derive(Debug, Default)]
struct State {
hash_data: Vec<Vec<u8>>,
graph_data: Arc<RwLock<GraphData>>,
maybe_config_specifier: Option<ModuleSpecifier>,
maybe_tsbuildinfo: Option<String>,
maybe_response: Option<RespondArgs>,
+ maybe_npm_resolver: Option<NpmPackageResolver>,
remapped_specifiers: HashMap<String, ModuleSpecifier>,
root_map: HashMap<String, ModuleSpecifier>,
}
@@ -311,6 +320,7 @@ impl State {
graph_data: Arc<RwLock<GraphData>>,
hash_data: Vec<Vec<u8>>,
maybe_config_specifier: Option<ModuleSpecifier>,
+ maybe_npm_resolver: Option<NpmPackageResolver>,
maybe_tsbuildinfo: Option<String>,
root_map: HashMap<String, ModuleSpecifier>,
remapped_specifiers: HashMap<String, ModuleSpecifier>,
@@ -319,6 +329,7 @@ impl State {
hash_data,
graph_data,
maybe_config_specifier,
+ maybe_npm_resolver,
maybe_tsbuildinfo,
maybe_response: None,
remapped_specifiers,
@@ -417,7 +428,7 @@ struct LoadArgs {
specifier: String,
}
-pub fn as_ts_script_kind(media_type: &MediaType) -> i32 {
+pub fn as_ts_script_kind(media_type: MediaType) -> i32 {
match media_type {
MediaType::JavaScript => 1,
MediaType::Jsx => 2,
@@ -431,7 +442,10 @@ pub fn as_ts_script_kind(media_type: &MediaType) -> i32 {
MediaType::Dcts => 3,
MediaType::Tsx => 4,
MediaType::Json => 6,
- _ => 0,
+ MediaType::SourceMap
+ | MediaType::TsBuildInfo
+ | MediaType::Wasm
+ | MediaType::Unknown => 0,
}
}
@@ -446,19 +460,19 @@ fn op_load(state: &mut OpState, args: Value) -> Result<Value, AnyError> {
let mut media_type = MediaType::Unknown;
let graph_data = state.graph_data.read();
let data = if &v.specifier == "deno:///.tsbuildinfo" {
- state.maybe_tsbuildinfo.as_deref()
+ state.maybe_tsbuildinfo.as_deref().map(Cow::Borrowed)
// in certain situations we return a "blank" module to tsc and we need to
// handle the request for that module here.
} else if &v.specifier == "deno:///missing_dependency.d.ts" {
hash = Some("1".to_string());
media_type = MediaType::Dts;
- Some("declare const __: any;\nexport = __;\n")
+ Some(Cow::Borrowed("declare const __: any;\nexport = __;\n"))
} else if v.specifier.starts_with("asset:///") {
let name = v.specifier.replace("asset:///", "");
let maybe_source = get_asset(&name);
hash = get_maybe_hash(maybe_source, &state.hash_data);
media_type = MediaType::from(&v.specifier);
- maybe_source
+ maybe_source.map(Cow::Borrowed)
} else {
let specifier = if let Some(remapped_specifier) =
state.remapped_specifiers.get(&v.specifier)
@@ -477,18 +491,31 @@ fn op_load(state: &mut OpState, args: Value) -> Result<Value, AnyError> {
graph_data.get(&graph_data.follow_redirect(&specifier))
{
media_type = *mt;
- Some(code as &str)
+ Some(Cow::Borrowed(code as &str))
+ } else if state
+ .maybe_npm_resolver
+ .as_ref()
+ .map(|resolver| resolver.in_npm_package(&specifier))
+ .unwrap_or(false)
+ {
+ media_type = MediaType::from(&specifier);
+ let file_path = specifier.to_file_path().unwrap();
+ let code = std::fs::read_to_string(&file_path)
+ .with_context(|| format!("Unable to load {}", file_path.display()))?;
+ Some(Cow::Owned(code))
} else {
media_type = MediaType::Unknown;
None
};
- hash = get_maybe_hash(maybe_source, &state.hash_data);
+ hash = get_maybe_hash(maybe_source.as_deref(), &state.hash_data);
maybe_source
};
- Ok(
- json!({ "data": data, "version": hash, "scriptKind": as_ts_script_kind(&media_type) }),
- )
+ Ok(json!({
+ "data": data,
+ "version": hash,
+ "scriptKind": as_ts_script_kind(media_type),
+ }))
}
#[derive(Debug, Deserialize, Serialize)]
@@ -550,17 +577,51 @@ fn op_resolve(
let types = graph_data.follow_redirect(specifier);
match graph_data.get(&types) {
Some(ModuleEntry::Module { media_type, .. }) => {
- Some((types, media_type))
+ Some((types, *media_type))
}
_ => None,
}
}
- _ => Some((specifier, media_type)),
+ _ => Some((specifier, *media_type)),
},
- _ => None,
+ _ => {
+ // 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
+ }
+ } 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,
+ node::NodeResolutionMode::Types,
+ npm_resolver,
+ )
+ .ok()
+ .flatten(),
+ ))
+ } else {
+ None
+ }
+ })
+ }
};
let result = match maybe_result {
Some((specifier, media_type)) => {
@@ -599,6 +660,33 @@ fn op_resolve(
Ok(resolved)
}
+pub fn resolve_npm_package_reference_types(
+ npm_ref: &NpmPackageReference,
+ npm_resolver: &NpmPackageResolver,
+) -> Result<(ModuleSpecifier, MediaType), AnyError> {
+ let maybe_resolution = node_resolve_npm_reference(
+ npm_ref,
+ NodeResolutionMode::Types,
+ npm_resolver,
+ )?;
+ Ok(NodeResolution::into_specifier_and_media_type(
+ maybe_resolution,
+ ))
+}
+
+#[op]
+fn op_is_node_file(state: &mut OpState, path: String) -> bool {
+ let state = state.borrow::<State>();
+ match ModuleSpecifier::parse(&path) {
+ Ok(specifier) => state
+ .maybe_npm_resolver
+ .as_ref()
+ .map(|r| r.in_npm_package(&specifier))
+ .unwrap_or(false),
+ Err(_) => false,
+ }
+}
+
#[derive(Debug, Deserialize, Eq, PartialEq)]
struct RespondArgs {
pub diagnostics: Diagnostics,
@@ -629,13 +717,13 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
.iter()
.map(|(s, mt)| match s.scheme() {
"data" | "blob" => {
- let specifier_str = hash_url(s, mt);
+ let specifier_str = hash_url(s, *mt);
remapped_specifiers.insert(specifier_str.clone(), s.clone());
specifier_str
}
_ => {
let ext_media_type = get_tsc_media_type(s);
- if mt != &ext_media_type {
+ if *mt != ext_media_type {
let new_specifier = format!("{}{}", s, mt.as_ts_extension());
root_map.insert(new_specifier.clone(), s.clone());
new_specifier
@@ -653,6 +741,7 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
op_create_hash::decl(),
op_emit::decl(),
op_exists::decl(),
+ op_is_node_file::decl(),
op_load::decl(),
op_resolve::decl(),
op_respond::decl(),
@@ -662,6 +751,7 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
request.graph_data.clone(),
request.hash_data.clone(),
request.maybe_config_specifier.clone(),
+ request.maybe_npm_resolver.clone(),
request.maybe_tsbuildinfo.clone(),
root_map.clone(),
remapped_specifiers.clone(),
@@ -771,6 +861,7 @@ mod tests {
Arc::new(RwLock::new((&graph).into())),
hash_data,
None,
+ None,
maybe_tsbuildinfo,
HashMap::new(),
HashMap::new(),
@@ -820,6 +911,7 @@ mod tests {
graph_data: Arc::new(RwLock::new((&graph).into())),
hash_data,
maybe_config_specifier: None,
+ maybe_npm_resolver: None,
maybe_tsbuildinfo: None,
root_names: vec![(specifier.clone(), MediaType::TypeScript)],
};
@@ -865,7 +957,7 @@ mod tests {
"data:application/javascript,console.log(\"Hello%20Deno\");",
)
.unwrap();
- assert_eq!(hash_url(&specifier, &MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js");
+ assert_eq!(hash_url(&specifier, MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js");
}
#[test]
diff --git a/cli/tsc/00_typescript.js b/cli/tsc/00_typescript.js
index 8b22dd451..c39e70e24 100644
--- a/cli/tsc/00_typescript.js
+++ b/cli/tsc/00_typescript.js
@@ -48949,12 +48949,23 @@ var ts;
var emitResolver = createResolver();
var nodeBuilder = createNodeBuilder();
var globals = ts.createSymbolTable();
+ var nodeGlobals = ts.createSymbolTable();
var undefinedSymbol = createSymbol(4 /* SymbolFlags.Property */, "undefined");
undefinedSymbol.declarations = [];
var globalThisSymbol = createSymbol(1536 /* SymbolFlags.Module */, "globalThis", 8 /* CheckFlags.Readonly */);
globalThisSymbol.exports = globals;
globalThisSymbol.declarations = [];
globals.set(globalThisSymbol.escapedName, globalThisSymbol);
+ var denoContext = ts.deno.createDenoForkContext({
+ globals: globals,
+ nodeGlobals: nodeGlobals,
+ mergeSymbol: mergeSymbol,
+ ambientModuleSymbolRegex: ambientModuleSymbolRegex,
+ });
+ var nodeGlobalThisSymbol = createSymbol(1536 /* SymbolFlags.Module */, "globalThis", 8 /* CheckFlags.Readonly */);
+ nodeGlobalThisSymbol.exports = denoContext.combinedGlobals;
+ nodeGlobalThisSymbol.declarations = [];
+ nodeGlobals.set(nodeGlobalThisSymbol.escapedName, nodeGlobalThisSymbol);
var argumentsSymbol = createSymbol(4 /* SymbolFlags.Property */, "arguments");
var requireSymbol = createSymbol(4 /* SymbolFlags.Property */, "require");
/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
@@ -49464,6 +49475,7 @@ var ts;
var reverseMappedCache = new ts.Map();
var inInferTypeForHomomorphicMappedType = false;
var ambientModulesCache;
+ var nodeAmbientModulesCache;
/**
* List of every ambient module with a "*" wildcard.
* Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches.
@@ -49851,7 +49863,7 @@ var ts;
// Do not report an error when merging `var globalThis` with the built-in `globalThis`,
// as we will already report a "Declaration name conflicts..." error, and this error
// won't make much sense.
- if (target !== globalThisSymbol) {
+ if (target !== globalThisSymbol && target !== nodeGlobalThisSymbol) {
error(source.declarations && ts.getNameOfDeclaration(source.declarations[0]), ts.Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target));
}
}
@@ -49950,7 +49962,7 @@ var ts;
return;
}
if (ts.isGlobalScopeAugmentation(moduleAugmentation)) {
- mergeSymbolTable(globals, moduleAugmentation.symbol.exports);
+ denoContext.mergeGlobalSymbolTable(moduleAugmentation, moduleAugmentation.symbol.exports);
}
else {
// find a module that about to be augmented
@@ -50631,7 +50643,12 @@ var ts;
}
}
if (!excludeGlobals) {
- result = lookup(globals, name, meaning);
+ if (denoContext.hasNodeSourceFile(lastLocation)) {
+ result = lookup(nodeGlobals, name, meaning);
+ }
+ if (!result) {
+ result = lookup(globals, name, meaning);
+ }
}
}
if (!result) {
@@ -51888,6 +51905,24 @@ var ts;
: undefined;
}
function resolveExternalModule(location, moduleReference, moduleNotFoundError, errorNode, isForAugmentation) {
+ var _a;
+ if (isForAugmentation === void 0) { isForAugmentation = false; }
+ var result = resolveExternalModuleInner(location, moduleReference, moduleNotFoundError, errorNode, isForAugmentation);
+ // deno: attempt to resolve an npm package reference to its bare specifier w/ path ambient module
+ // when not found and the symbol has zero exports
+ if (moduleReference.startsWith("npm:") && (result == null || ((_a = result === null || result === void 0 ? void 0 : result.exports) === null || _a === void 0 ? void 0 : _a.size) === 0)) {
+ var npmPackageRef = ts.deno.tryParseNpmPackageReference(moduleReference);
+ if (npmPackageRef) {
+ var bareSpecifier = npmPackageRef.name + (npmPackageRef.subPath == null ? "" : "/" + npmPackageRef.subPath);
+ var ambientModule = tryFindAmbientModule(bareSpecifier, /*withAugmentations*/ true);
+ if (ambientModule) {
+ return ambientModule;
+ }
+ }
+ }
+ return result;
+ }
+ function resolveExternalModuleInner(location, moduleReference, moduleNotFoundError, errorNode, isForAugmentation) {
var _a, _b, _c, _d, _e, _f, _g, _h;
if (isForAugmentation === void 0) { isForAugmentation = false; }
if (ts.startsWith(moduleReference, "@types/")) {
@@ -52630,6 +52665,12 @@ var ts;
if (typeof state_2 === "object")
return state_2.value;
}
+ if (denoContext.hasNodeSourceFile(enclosingDeclaration)) {
+ result = callback(nodeGlobals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true);
+ if (result) {
+ return result;
+ }
+ }
return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true);
}
function getQualifiedLeftMeaning(rightMeaning) {
@@ -52713,7 +52754,11 @@ var ts;
}
});
// If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that
- return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined);
+ if (result) {
+ return result;
+ }
+ var globalSymbol = symbols === nodeGlobals ? nodeGlobalThisSymbol : symbols === globals ? globalThisSymbol : undefined;
+ return globalSymbol != null ? getCandidateListForSymbol(globalSymbol, globalSymbol, ignoreQualification) : undefined;
}
function getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification) {
if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) {
@@ -59159,7 +59204,7 @@ var ts;
var indexInfos;
if (symbol.exports) {
members = getExportsOfSymbol(symbol);
- if (symbol === globalThisSymbol) {
+ if (symbol === globalThisSymbol || symbol === nodeGlobalThisSymbol) {
var varsOnly_1 = new ts.Map();
members.forEach(function (p) {
var _a;
@@ -60321,7 +60366,7 @@ var ts;
if (ts.isExternalModuleNameRelative(moduleName)) {
return undefined;
}
- var symbol = getSymbol(globals, '"' + moduleName + '"', 512 /* SymbolFlags.ValueModule */);
+ var symbol = getSymbol(denoContext.combinedGlobals, '"' + moduleName + '"', 512 /* SymbolFlags.ValueModule */);
// merged symbol is module declaration symbol combined with all augmentations
return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol;
}
@@ -62988,6 +63033,10 @@ var ts;
if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports.has(propName) && (globalThisSymbol.exports.get(propName).flags & 418 /* SymbolFlags.BlockScoped */)) {
error(accessExpression, ts.Diagnostics.Property_0_does_not_exist_on_type_1, ts.unescapeLeadingUnderscores(propName), typeToString(objectType));
}
+ // deno: ensure condition and body match the above
+ else if (objectType.symbol === nodeGlobalThisSymbol && propName !== undefined && nodeGlobalThisSymbol.exports.has(propName) && (nodeGlobalThisSymbol.exports.get(propName).flags & 418 /* SymbolFlags.BlockScoped */)) {
+ error(accessExpression, ts.Diagnostics.Property_0_does_not_exist_on_type_1, ts.unescapeLeadingUnderscores(propName), typeToString(objectType));
+ }
else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !(accessFlags & 128 /* AccessFlags.SuppressNoImplicitAnyError */)) {
if (propName !== undefined && typeHasStaticProperty(propName, objectType)) {
var typeName = typeToString(objectType);
@@ -71860,7 +71909,7 @@ var ts;
if (type.flags & 1048576 /* TypeFlags.Union */
|| type.flags & 524288 /* TypeFlags.Object */ && declaredType !== type && !(declaredType === unknownType && isEmptyAnonymousObjectType(type))
|| ts.isThisTypeParameter(type)
- || type.flags & 2097152 /* TypeFlags.Intersection */ && ts.every(type.types, function (t) { return t.symbol !== globalThisSymbol; })) {
+ || type.flags & 2097152 /* TypeFlags.Intersection */ && ts.every(type.types, function (t) { return t.symbol !== globalThisSymbol && t.symbol !== nodeGlobalThisSymbol; })) {
return filterType(type, function (t) { return isTypePresencePossible(t, name, assumeTrue); });
}
return type;
@@ -73019,6 +73068,9 @@ var ts;
return undefinedType;
}
else if (includeGlobalThis) {
+ if (denoContext.hasNodeSourceFile(container)) {
+ return getTypeOfSymbol(nodeGlobalThisSymbol);
+ }
return getTypeOfSymbol(globalThisSymbol);
}
}
@@ -75690,6 +75742,11 @@ var ts;
}
return anyType;
}
+ // deno: ensure condition matches above
+ if (leftType.symbol === nodeGlobalThisSymbol) {
+ // deno: don't bother with errors like above for simplicity
+ return anyType;
+ }
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
reportNonexistentProperty(right, ts.isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS);
}
@@ -75997,7 +76054,7 @@ var ts;
if (symbol)
return symbol;
var candidates;
- if (symbols === globals) {
+ if (symbols === globals || symbols === nodeGlobals) {
var primitives = ts.mapDefined(["string", "number", "boolean", "object", "bigint", "symbol"], function (s) { return symbols.has((s.charAt(0).toUpperCase() + s.slice(1)))
? createSymbol(524288 /* SymbolFlags.TypeAlias */, s)
: undefined; });
@@ -86656,7 +86713,7 @@ var ts;
// find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases)
var symbol = resolveName(exportedName, exportedName.escapedText, 111551 /* SymbolFlags.Value */ | 788968 /* SymbolFlags.Type */ | 1920 /* SymbolFlags.Namespace */ | 2097152 /* SymbolFlags.Alias */,
/*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
- if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
+ if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol === nodeGlobalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
error(exportedName, ts.Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, ts.idText(exportedName));
}
else {
@@ -87343,6 +87400,9 @@ var ts;
isStaticSymbol = ts.isStatic(location);
location = location.parent;
}
+ if (denoContext.hasNodeSourceFile(location)) {
+ copySymbols(nodeGlobals, meaning);
+ }
copySymbols(globals, meaning);
}
/**
@@ -88717,25 +88777,24 @@ var ts;
amalgamatedDuplicates = new ts.Map();
// Initialize global symbol table
var augmentations;
- for (var _b = 0, _c = host.getSourceFiles(); _b < _c.length; _b++) {
- var file = _c[_b];
+ var _loop_35 = function (file) {
if (file.redirectInfo) {
- continue;
+ return "continue";
}
if (!ts.isExternalOrCommonJsModule(file)) {
// It is an error for a non-external-module (i.e. script) to declare its own `globalThis`.
// We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files.
var fileGlobalThisSymbol = file.locals.get("globalThis");
if (fileGlobalThisSymbol === null || fileGlobalThisSymbol === void 0 ? void 0 : fileGlobalThisSymbol.declarations) {
- for (var _d = 0, _e = fileGlobalThisSymbol.declarations; _d < _e.length; _d++) {
- var declaration = _e[_d];
+ for (var _h = 0, _j = fileGlobalThisSymbol.declarations; _h < _j.length; _h++) {
+ var declaration = _j[_h];
diagnostics.add(ts.createDiagnosticForNode(declaration, ts.Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis"));
}
}
- mergeSymbolTable(globals, file.locals);
+ denoContext.mergeGlobalSymbolTable(file, file.locals);
}
if (file.jsGlobalAugmentations) {
- mergeSymbolTable(globals, file.jsGlobalAugmentations);
+ denoContext.mergeGlobalSymbolTable(file, file.jsGlobalAugmentations);
}
if (file.patternAmbientModules && file.patternAmbientModules.length) {
patternAmbientModules = ts.concatenate(patternAmbientModules, file.patternAmbientModules);
@@ -88746,12 +88805,18 @@ var ts;
if (file.symbol && file.symbol.globalExports) {
// Merge in UMD exports with first-in-wins semantics (see #9771)
var source = file.symbol.globalExports;
+ var isNodeFile_1 = denoContext.hasNodeSourceFile(file);
source.forEach(function (sourceSymbol, id) {
- if (!globals.has(id)) {
- globals.set(id, sourceSymbol);
+ var envGlobals = isNodeFile_1 ? denoContext.getGlobalsForName(id) : globals;
+ if (!envGlobals.has(id)) {
+ envGlobals.set(id, sourceSymbol);
}
});
}
+ };
+ for (var _b = 0, _c = host.getSourceFiles(); _b < _c.length; _b++) {
+ var file = _c[_b];
+ _loop_35(file);
}
// We do global augmentations separately from module augmentations (and before creating global types) because they
// 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also,
@@ -88762,10 +88827,10 @@ var ts;
if (augmentations) {
// merge _global_ module augmentations.
// this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed
- for (var _f = 0, augmentations_1 = augmentations; _f < augmentations_1.length; _f++) {
- var list = augmentations_1[_f];
- for (var _g = 0, list_1 = list; _g < list_1.length; _g++) {
- var augmentation = list_1[_g];
+ for (var _d = 0, augmentations_1 = augmentations; _d < augmentations_1.length; _d++) {
+ var list = augmentations_1[_d];
+ for (var _e = 0, list_1 = list; _e < list_1.length; _e++) {
+ var augmentation = list_1[_e];
if (!ts.isGlobalScopeAugmentation(augmentation.parent))
continue;
mergeModuleAugmentation(augmentation);
@@ -88778,6 +88843,7 @@ var ts;
getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments", /*arity*/ 0, /*reportErrors*/ true);
getSymbolLinks(unknownSymbol).type = errorType;
getSymbolLinks(globalThisSymbol).type = createObjectType(16 /* ObjectFlags.Anonymous */, globalThisSymbol);
+ getSymbolLinks(nodeGlobalThisSymbol).type = createObjectType(16 /* ObjectFlags.Anonymous */, nodeGlobalThisSymbol);
// Initialize special types
globalArrayType = getGlobalType("Array", /*arity*/ 1, /*reportErrors*/ true);
globalObjectType = getGlobalType("Object", /*arity*/ 0, /*reportErrors*/ true);
@@ -88800,10 +88866,10 @@ var ts;
if (augmentations) {
// merge _nonglobal_ module augmentations.
// this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed
- for (var _h = 0, augmentations_2 = augmentations; _h < augmentations_2.length; _h++) {
- var list = augmentations_2[_h];
- for (var _j = 0, list_2 = list; _j < list_2.length; _j++) {
- var augmentation = list_2[_j];
+ for (var _f = 0, augmentations_2 = augmentations; _f < augmentations_2.length; _f++) {
+ var list = augmentations_2[_f];
+ for (var _g = 0, list_2 = list; _g < list_2.length; _g++) {
+ var augmentation = list_2[_g];
if (ts.isGlobalScopeAugmentation(augmentation.parent))
continue;
mergeModuleAugmentation(augmentation);
@@ -90373,17 +90439,30 @@ var ts;
}
return false;
}
- function getAmbientModules() {
- if (!ambientModulesCache) {
- ambientModulesCache = [];
- globals.forEach(function (global, sym) {
+ function getAmbientModules(sourceFile) {
+ var isNode = denoContext.hasNodeSourceFile(sourceFile);
+ if (isNode) {
+ if (!nodeAmbientModulesCache) {
+ nodeAmbientModulesCache = getAmbientModules(denoContext.combinedGlobals);
+ }
+ return nodeAmbientModulesCache;
+ }
+ else {
+ if (!ambientModulesCache) {
+ ambientModulesCache = getAmbientModules(globals);
+ }
+ return ambientModulesCache;
+ }
+ function getAmbientModules(envGlobals) {
+ var cache = [];
+ envGlobals.forEach(function (global, sym) {
// No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module.
if (ambientModuleSymbolRegex.test(sym)) {
- ambientModulesCache.push(global);
+ cache.push(global);
}
});
+ return cache;
}
- return ambientModulesCache;
}
function checkGrammarImportClause(node) {
var _a;
@@ -90562,6 +90641,234 @@ var ts;
}
ts.signatureHasLiteralTypes = signatureHasLiteralTypes;
})(ts || (ts = {}));
+/* @internal */
+var ts;
+(function (ts) {
+ var deno;
+ (function (deno) {
+ var isNodeSourceFile = function () { return false; };
+ function setIsNodeSourceFileCallback(callback) {
+ isNodeSourceFile = callback;
+ }
+ deno.setIsNodeSourceFileCallback = setIsNodeSourceFileCallback;
+ // When upgrading:
+ // 1. Inspect all usages of "globals" and "globalThisSymbol" in checker.ts
+ // - Beware that `globalThisType` might refer to the global `this` type
+ // and not the global `globalThis` type
+ // 2. Inspect the types in @types/node for anything that might need to go below
+ // as well.
+ var nodeOnlyGlobalNames = new ts.Set([
+ "NodeRequire",
+ "RequireResolve",
+ "RequireResolve",
+ "process",
+ "console",
+ "__filename",
+ "__dirname",
+ "require",
+ "module",
+ "exports",
+ "gc",
+ "BufferEncoding",
+ "BufferConstructor",
+ "WithImplicitCoercion",
+ "Buffer",
+ "Console",
+ "ImportMeta",
+ "setTimeout",
+ "setInterval",
+ "setImmediate",
+ "Global",
+ "AbortController",
+ "AbortSignal",
+ "Blob",
+ "BroadcastChannel",
+ "MessageChannel",
+ "MessagePort",
+ "Event",
+ "EventTarget",
+ "performance",
+ "TextDecoder",
+ "TextEncoder",
+ "URL",
+ "URLSearchParams",
+ ]);
+ function createDenoForkContext(_a) {
+ var mergeSymbol = _a.mergeSymbol, globals = _a.globals, nodeGlobals = _a.nodeGlobals, ambientModuleSymbolRegex = _a.ambientModuleSymbolRegex;
+ return {
+ hasNodeSourceFile: hasNodeSourceFile,
+ getGlobalsForName: getGlobalsForName,
+ mergeGlobalSymbolTable: mergeGlobalSymbolTable,
+ combinedGlobals: createNodeGlobalsSymbolTable(),
+ };
+ function hasNodeSourceFile(node) {
+ if (!node)
+ return false;
+ var sourceFile = ts.getSourceFileOfNode(node);
+ return isNodeSourceFile(sourceFile);
+ }
+ function getGlobalsForName(id) {
+ // Node ambient modules are only accessible in the node code,
+ // so put them on the node globals
+ if (ambientModuleSymbolRegex.test(id))
+ return nodeGlobals;
+ return nodeOnlyGlobalNames.has(id) ? nodeGlobals : globals;
+ }
+ function mergeGlobalSymbolTable(node, source, unidirectional) {
+ if (unidirectional === void 0) { unidirectional = false; }
+ var sourceFile = ts.getSourceFileOfNode(node);
+ var isNodeFile = hasNodeSourceFile(sourceFile);
+ source.forEach(function (sourceSymbol, id) {
+ var target = isNodeFile ? getGlobalsForName(id) : globals;
+ var targetSymbol = target.get(id);
+ target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol);
+ });
+ }
+ function createNodeGlobalsSymbolTable() {
+ return new Proxy(globals, {
+ get: function (target, prop, receiver) {
+ if (prop === "get") {
+ return function (key) {
+ var _a;
+ return (_a = nodeGlobals.get(key)) !== null && _a !== void 0 ? _a : globals.get(key);
+ };
+ }
+ else if (prop === "has") {
+ return function (key) {
+ return nodeGlobals.has(key) || globals.has(key);
+ };
+ }
+ else if (prop === "size") {
+ var i_2 = 0;
+ forEachEntry(function () {
+ i_2++;
+ });
+ return i_2;
+ }
+ else if (prop === "forEach") {
+ return function (action) {
+ forEachEntry(function (_a) {
+ var key = _a[0], value = _a[1];
+ action(value, key);
+ });
+ };
+ }
+ else if (prop === "entries") {
+ return function () {
+ return getEntries(function (kv) { return kv; });
+ };
+ }
+ else if (prop === "keys") {
+ return function () {
+ return getEntries(function (kv) { return kv[0]; });
+ };
+ }
+ else if (prop === "values") {
+ return function () {
+ return getEntries(function (kv) { return kv[1]; });
+ };
+ }
+ else if (prop === Symbol.iterator) {
+ return function () {
+ // Need to convert this to an array since typescript targets ES5
+ // and providing back the iterator won't work here. I don't want
+ // to change the target to ES6 because I'm not sure if that would
+ // surface any issues.
+ return ts.arrayFrom(getEntries(function (kv) { return kv; }))[Symbol.iterator]();
+ };
+ }
+ else {
+ var value_3 = target[prop];
+ if (value_3 instanceof Function) {
+ return function () {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ return value_3.apply(this === receiver ? target : this, args);
+ };
+ }
+ return value_3;
+ }
+ },
+ });
+ function forEachEntry(action) {
+ var iterator = getEntries(function (entry) {
+ action(entry);
+ });
+ // drain the iterator to do the action
+ while (!iterator.next().done) { }
+ }
+ function getEntries(transform) {
+ var foundKeys, _i, _a, entries, next;
+ return __generator(this, function (_b) {
+ switch (_b.label) {
+ case 0:
+ foundKeys = new ts.Set();
+ _i = 0, _a = [nodeGlobals.entries(), globals.entries()];
+ _b.label = 1;
+ case 1:
+ if (!(_i < _a.length)) return [3 /*break*/, 6];
+ entries = _a[_i];
+ next = entries.next();
+ _b.label = 2;
+ case 2:
+ if (!!next.done) return [3 /*break*/, 5];
+ if (!!foundKeys.has(next.value[0])) return [3 /*break*/, 4];
+ return [4 /*yield*/, transform(next.value)];
+ case 3:
+ _b.sent();
+ foundKeys.add(next.value[0]);
+ _b.label = 4;
+ case 4:
+ next = entries.next();
+ return [3 /*break*/, 2];
+ case 5:
+ _i++;
+ return [3 /*break*/, 1];
+ case 6: return [2 /*return*/];
+ }
+ });
+ }
+ }
+ }
+ deno.createDenoForkContext = createDenoForkContext;
+ function tryParseNpmPackageReference(text) {
+ try {
+ return parseNpmPackageReference(text);
+ }
+ catch (_a) {
+ return undefined;
+ }
+ }
+ deno.tryParseNpmPackageReference = tryParseNpmPackageReference;
+ function parseNpmPackageReference(text) {
+ if (!text.startsWith("npm:")) {
+ throw new Error("Not an npm specifier: ".concat(text));
+ }
+ text = text.replace(/^npm:/, "");
+ var parts = text.split("/");
+ var namePartLen = text.startsWith("@") ? 2 : 1;
+ if (parts.length < namePartLen) {
+ throw new Error("Not a valid package: ".concat(text));
+ }
+ var nameParts = parts.slice(0, namePartLen);
+ var lastNamePart = nameParts.at(-1);
+ var lastAtIndex = lastNamePart.lastIndexOf("@");
+ var versionReq = undefined;
+ if (lastAtIndex > 0) {
+ versionReq = lastNamePart.substring(lastAtIndex + 1);
+ nameParts[nameParts.length - 1] = lastNamePart.substring(0, lastAtIndex);
+ }
+ return {
+ name: nameParts.join("/"),
+ versionReq: versionReq,
+ subPath: parts.length > nameParts.length ? parts.slice(nameParts.length).join("/") : undefined,
+ };
+ }
+ deno.parseNpmPackageReference = parseNpmPackageReference;
+ })(deno = ts.deno || (ts.deno = {}));
+})(ts || (ts = {}));
var ts;
(function (ts) {
function visitNode(node, visitor, test, lift) {
@@ -121271,7 +121578,7 @@ var ts;
}
}
// From ambient modules
- for (var _f = 0, _g = program.getTypeChecker().getAmbientModules(); _f < _g.length; _f++) {
+ for (var _f = 0, _g = program.getTypeChecker().getAmbientModules(sourceFile); _f < _g.length; _f++) {
var ambientModule = _g[_f];
if (ambientModule.declarations && ambientModule.declarations.length > 1) {
addReferenceFromAmbientModule(ambientModule);
@@ -124123,7 +124430,7 @@ var ts;
});
// Sort by paths closest to importing file Name directory
var sortedPaths = [];
- var _loop_35 = function (directory) {
+ var _loop_36 = function (directory) {
var directoryStart = ts.ensureTrailingDirectorySeparator(directory);
var pathsInDirectory;
allFileNames.forEach(function (_a, fileName) {
@@ -124147,7 +124454,7 @@ var ts;
};
var out_directory_1;
for (var directory = ts.getDirectoryPath(importingFileName); allFileNames.size !== 0;) {
- var state_11 = _loop_35(directory);
+ var state_11 = _loop_36(directory);
directory = out_directory_1;
if (state_11 === "break")
break;
@@ -124218,7 +124525,7 @@ var ts;
}
function tryGetModuleNameFromPaths(relativeToBaseUrl, paths, allowedEndings, host, compilerOptions) {
for (var key in paths) {
- var _loop_36 = function (patternText_1) {
+ var _loop_37 = function (patternText_1) {
var pattern = ts.normalizePath(patternText_1);
var indexOfStar = pattern.indexOf("*");
// In module resolution, if `pattern` itself has an extension, a file with that extension is looked up directly,
@@ -124285,7 +124592,7 @@ var ts;
};
for (var _i = 0, _a = paths[key]; _i < _a.length; _i++) {
var patternText_1 = _a[_i];
- var state_12 = _loop_36(patternText_1);
+ var state_12 = _loop_37(patternText_1);
if (typeof state_12 === "object")
return state_12.value;
}
@@ -137638,7 +137945,8 @@ var ts;
if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) {
return true;
}
- var globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, 111551 /* SymbolFlags.Value */, /*excludeGlobals*/ false);
+ // deno: provide sourceFile so that it can figure out if it's a node or deno globalThis
+ var globalThisSymbol = checker.resolveName("globalThis", /*location*/ sourceFile, 111551 /* SymbolFlags.Value */, /*excludeGlobals*/ false);
if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) {
return true;
}
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index b39f56cf6..7929d3b44 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -29,6 +29,7 @@ delete Object.prototype.__proto__;
// This map stores that relationship, and the original can be restored by the
// normalized specifier.
// See: https://github.com/denoland/deno/issues/9277#issuecomment-769653834
+ /** @type {Map<string, string>} */
const normalizedToOriginalMap = new Map();
/**
@@ -40,6 +41,16 @@ delete Object.prototype.__proto__;
"languageVersion" in value;
}
+ /**
+ * @param {ts.ScriptTarget | ts.CreateSourceFileOptions | undefined} versionOrOptions
+ * @returns {ts.CreateSourceFileOptions}
+ */
+ function getCreateSourceFileOptions(versionOrOptions) {
+ return isCreateSourceFileOptions(versionOrOptions)
+ ? versionOrOptions
+ : { languageVersion: versionOrOptions ?? ts.ScriptTarget.ESNext };
+ }
+
function setLogDebug(debug, source) {
logDebug = debug;
if (source) {
@@ -119,8 +130,23 @@ delete Object.prototype.__proto__;
return result;
}
- // In the case of the LSP, this is initialized with the assets
- // when snapshotting and never added to or removed after that.
+ class SpecifierIsCjsCache {
+ /** @type {Set<string>} */
+ #cache = new Set();
+
+ /** @param {[string, ts.Extension]} param */
+ add([specifier, ext]) {
+ if (ext === ".cjs" || ext === ".d.cts" || ext === ".cts") {
+ this.#cache.add(specifier);
+ }
+ }
+
+ has(specifier) {
+ return this.#cache.has(specifier);
+ }
+ }
+
+ // In the case of the LSP, this will only ever contain the assets.
/** @type {Map<string, ts.SourceFile>} */
const sourceFileCache = new Map();
@@ -130,6 +156,181 @@ delete Object.prototype.__proto__;
/** @type {Map<string, string>} */
const scriptVersionCache = new Map();
+ /** @type {Map<string, boolean>} */
+ const isNodeSourceFileCache = new Map();
+
+ const isCjsCache = new SpecifierIsCjsCache();
+
+ /**
+ * @param {ts.CompilerOptions | ts.MinimalResolutionCacheHost} settingsOrHost
+ * @returns {ts.CompilerOptions}
+ */
+ function getCompilationSettings(settingsOrHost) {
+ if (typeof settingsOrHost.getCompilationSettings === "function") {
+ return settingsOrHost.getCompilationSettings();
+ }
+ return /** @type {ts.CompilerOptions} */ (settingsOrHost);
+ }
+
+ // We need to use a custom document registry in order to provide source files
+ // with an impliedNodeFormat to the ts language service
+
+ /** @type {Map<string, ts.SourceFile} */
+ const documentRegistrySourceFileCache = new Map();
+ const { getKeyForCompilationSettings } = ts.createDocumentRegistry(); // reuse this code
+ /** @type {ts.DocumentRegistry} */
+ const documentRegistry = {
+ acquireDocument(
+ fileName,
+ compilationSettingsOrHost,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const key = getKeyForCompilationSettings(
+ getCompilationSettings(compilationSettingsOrHost),
+ );
+ return this.acquireDocumentWithKey(
+ fileName,
+ /** @type {ts.Path} */ (fileName),
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ );
+ },
+
+ acquireDocumentWithKey(
+ fileName,
+ path,
+ _compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const mapKey = path + key;
+ let sourceFile = documentRegistrySourceFileCache.get(mapKey);
+ if (!sourceFile || sourceFile.version !== version) {
+ sourceFile = ts.createLanguageServiceSourceFile(
+ fileName,
+ scriptSnapshot,
+ {
+ ...getCreateSourceFileOptions(sourceFileOptions),
+ impliedNodeFormat: isCjsCache.has(fileName)
+ ? ts.ModuleKind.CommonJS
+ : ts.ModuleKind.ESNext,
+ },
+ version,
+ true,
+ scriptKind,
+ );
+ documentRegistrySourceFileCache.set(mapKey, sourceFile);
+ }
+ return sourceFile;
+ },
+
+ updateDocument(
+ fileName,
+ compilationSettingsOrHost,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const key = getKeyForCompilationSettings(
+ getCompilationSettings(compilationSettingsOrHost),
+ );
+ return this.updateDocumentWithKey(
+ fileName,
+ /** @type {ts.Path} */ (fileName),
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ );
+ },
+
+ updateDocumentWithKey(
+ fileName,
+ path,
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ ) {
+ const mapKey = path + key;
+ let sourceFile = documentRegistrySourceFileCache.get(mapKey) ??
+ this.acquireDocumentWithKey(
+ fileName,
+ path,
+ compilationSettingsOrHost,
+ key,
+ scriptSnapshot,
+ version,
+ scriptKind,
+ sourceFileOptions,
+ );
+
+ if (sourceFile.version !== version) {
+ sourceFile = ts.updateLanguageServiceSourceFile(
+ sourceFile,
+ scriptSnapshot,
+ version,
+ scriptSnapshot.getChangeRange(sourceFile.scriptSnapShot),
+ );
+ }
+ return sourceFile;
+ },
+
+ getKeyForCompilationSettings(settings) {
+ return getKeyForCompilationSettings(settings);
+ },
+
+ releaseDocument(
+ fileName,
+ compilationSettings,
+ scriptKind,
+ impliedNodeFormat,
+ ) {
+ const key = getKeyForCompilationSettings(compilationSettings);
+ return this.releaseDocumentWithKey(
+ /** @type {ts.Path} */ (fileName),
+ key,
+ scriptKind,
+ impliedNodeFormat,
+ );
+ },
+
+ releaseDocumentWithKey(path, key, _scriptKind, _impliedNodeFormat) {
+ const mapKey = path + key;
+ documentRegistrySourceFileCache.remove(mapKey);
+ },
+
+ reportStats() {
+ return "[]";
+ },
+ };
+
+ ts.deno.setIsNodeSourceFileCallback((sourceFile) => {
+ const fileName = sourceFile.fileName;
+ let isNodeSourceFile = isNodeSourceFileCache.get(fileName);
+ if (isNodeSourceFile == null) {
+ const result = ops.op_is_node_file(fileName);
+ isNodeSourceFile = /** @type {boolean} */ (result);
+ isNodeSourceFileCache.set(fileName, isNodeSourceFile);
+ }
+ return isNodeSourceFile;
+ });
+
/** @param {ts.DiagnosticRelatedInformation} diagnostic */
function fromRelatedInformation({
start,
@@ -189,6 +390,10 @@ delete Object.prototype.__proto__;
/** Diagnostics that are intentionally ignored when compiling TypeScript in
* Deno, as they provide misleading or incorrect information. */
const IGNORED_DIAGNOSTICS = [
+ // TS1452: 'resolution-mode' assertions are only supported when `moduleResolution` is `node16` or `nodenext`.
+ // We specify the resolution mode to be CommonJS for some npm files and this
+ // diagnostic gets generated even though we're using custom module resolution.
+ 1452,
// TS2306: File '.../index.d.ts' is not a module.
// We get this for `x-typescript-types` declaration files which don't export
// anything. We prefer to treat these as modules with no exports.
@@ -228,10 +433,12 @@ delete Object.prototype.__proto__;
target: ts.ScriptTarget.ESNext,
};
+ // todo(dsherret): can we remove this and just use ts.OperationCanceledException?
/** Error thrown on cancellation. */
class OperationCanceledError extends Error {
}
+ // todo(dsherret): we should investigate if throttling is really necessary
/**
* Inspired by ThrottledCancellationToken in ts server.
*
@@ -291,13 +498,10 @@ delete Object.prototype.__proto__;
_onError,
_shouldCreateNewSourceFile,
) {
+ const createOptions = getCreateSourceFileOptions(languageVersion);
debug(
`host.getSourceFile("${specifier}", ${
- ts.ScriptTarget[
- isCreateSourceFileOptions(languageVersion)
- ? languageVersion.languageVersion
- : languageVersion
- ]
+ ts.ScriptTarget[createOptions.languageVersion]
})`,
);
@@ -320,7 +524,12 @@ delete Object.prototype.__proto__;
sourceFile = ts.createSourceFile(
specifier,
data,
- languageVersion,
+ {
+ ...createOptions,
+ impliedNodeFormat: isCjsCache.has(specifier)
+ ? ts.ModuleKind.CommonJS
+ : ts.ModuleKind.ESNext,
+ },
false,
scriptKind,
);
@@ -355,6 +564,50 @@ delete Object.prototype.__proto__;
getNewLine() {
return "\n";
},
+ resolveTypeReferenceDirectives(
+ typeDirectiveNames,
+ containingFilePath,
+ redirectedReference,
+ options,
+ containingFileMode,
+ ) {
+ return typeDirectiveNames.map((arg) => {
+ /** @type {ts.FileReference} */
+ const fileReference = typeof arg === "string"
+ ? {
+ pos: -1,
+ end: -1,
+ fileName: arg,
+ }
+ : arg;
+ if (fileReference.fileName.startsWith("npm:")) {
+ /** @type {[string, ts.Extension] | undefined} */
+ const resolved = ops.op_resolve({
+ specifiers: [fileReference.fileName],
+ base: containingFilePath,
+ })?.[0];
+ if (resolved) {
+ isCjsCache.add(resolved);
+ return {
+ primary: true,
+ resolvedFileName: resolved[0],
+ };
+ } else {
+ return undefined;
+ }
+ } else {
+ return ts.resolveTypeReferenceDirective(
+ fileReference.fileName,
+ containingFilePath,
+ options,
+ host,
+ redirectedReference,
+ undefined,
+ containingFileMode ?? fileReference.resolutionMode,
+ ).resolvedTypeReferenceDirective;
+ }
+ });
+ },
resolveModuleNames(specifiers, base) {
debug(`host.resolveModuleNames()`);
debug(` base: ${base}`);
@@ -367,7 +620,12 @@ delete Object.prototype.__proto__;
if (resolved) {
const result = resolved.map((item) => {
if (item) {
+ isCjsCache.add(item);
const [resolvedFileName, extension] = item;
+ if (resolvedFileName.startsWith("node:")) {
+ // probably means the user doesn't have @types/node, so resolve to undefined
+ return undefined;
+ }
return {
resolvedFileName,
extension,
@@ -444,6 +702,23 @@ delete Object.prototype.__proto__;
},
};
+ // override the npm install @types package diagnostics to be deno specific
+ ts.setLocalizedDiagnosticMessages((() => {
+ const nodeMessage = "Cannot find name '{0}'."; // don't offer any suggestions
+ const jqueryMessage =
+ "Cannot find name '{0}'. Did you mean to import jQuery? Try adding `import $ from \"npm:jquery\";`.";
+ return {
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashno_2580":
+ nodeMessage,
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashno_2591":
+ nodeMessage,
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slash_2581":
+ jqueryMessage,
+ "Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slash_2592":
+ jqueryMessage,
+ };
+ })());
+
/** @type {Array<[string, number]>} */
const stats = [];
let statsStart = 0;
@@ -557,7 +832,25 @@ delete Object.prototype.__proto__;
...program.getOptionsDiagnostics(),
...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics(),
- ].filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));
+ ].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;
+ }
+ });
// emit the tsbuildinfo file
// @ts-ignore: emitBuildInfo is not exposed (https://github.com/microsoft/TypeScript/issues/49871)
@@ -922,13 +1215,14 @@ delete Object.prototype.__proto__;
}
hasStarted = true;
cwd = rootUri;
- languageService = ts.createLanguageService(host);
+ languageService = ts.createLanguageService(host, documentRegistry);
setLogDebug(debugFlag, "TSLS");
debug("serverInit()");
}
function serverRestart() {
- languageService = ts.createLanguageService(host);
+ languageService = ts.createLanguageService(host, documentRegistry);
+ isNodeSourceFileCache.clear();
debug("serverRestart()");
}
diff --git a/cli/tsc/compiler.d.ts b/cli/tsc/compiler.d.ts
index b550c938b..62a1bbdd2 100644
--- a/cli/tsc/compiler.d.ts
+++ b/cli/tsc/compiler.d.ts
@@ -12,6 +12,7 @@ declare global {
var normalizePath: (path: string) => string;
interface SourceFile {
version?: string;
+ fileName: string;
}
interface CompilerHost {
@@ -24,6 +25,12 @@ declare global {
}
var performance: Performance;
+
+ namespace deno {
+ function setIsNodeSourceFileCallback(
+ callback: (sourceFile: SourceFile) => boolean,
+ );
+ }
}
namespace ts {
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 5178d81f7..d9e5dcdbd 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -28,6 +28,7 @@ pub use resolution::package_imports_resolve;
pub use resolution::package_resolve;
pub use resolution::NodeModuleKind;
pub use resolution::DEFAULT_CONDITIONS;
+pub use resolution::TYPES_CONDITIONS;
pub trait NodePermissions {
fn check_read(&mut self, path: &Path) -> Result<(), AnyError>;
@@ -38,6 +39,7 @@ pub trait RequireNpmResolver {
&self,
specifier: &str,
referrer: &Path,
+ conditions: &[&str],
) -> Result<PathBuf, AnyError>;
fn resolve_package_folder_from_path(
@@ -304,6 +306,7 @@ fn op_require_resolve_deno_dir(
.resolve_package_folder_from_package(
&request,
&PathBuf::from(parent_filename),
+ DEFAULT_CONDITIONS,
)
.ok()
.map(|p| p.to_string_lossy().to_string())
diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs
index 81daa7ca7..85caac5f4 100644
--- a/ext/node/package_json.rs
+++ b/ext/node/package_json.rs
@@ -50,6 +50,12 @@ impl PackageJson {
path: PathBuf,
) -> Result<PackageJson, AnyError> {
resolver.ensure_read_permission(&path)?;
+ Self::load_skip_read_permission(path)
+ }
+
+ pub fn load_skip_read_permission(
+ path: PathBuf,
+ ) -> Result<PackageJson, AnyError> {
let source = match std::fs::read_to_string(&path) {
Ok(source) => source,
Err(err) if err.kind() == ErrorKind::NotFound => {
diff --git a/ext/node/resolution.rs b/ext/node/resolution.rs
index 1bde99709..de8f6e87b 100644
--- a/ext/node/resolution.rs
+++ b/ext/node/resolution.rs
@@ -19,6 +19,7 @@ use crate::RequireNpmResolver;
pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"];
pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"];
+pub static TYPES_CONDITIONS: &[&str] = &["types"];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NodeModuleKind {
@@ -251,13 +252,17 @@ fn resolve_package_target_string(
};
let package_json_url =
ModuleSpecifier::from_file_path(package_json_path).unwrap();
- return package_resolve(
+ return match package_resolve(
&export_target,
&package_json_url,
referrer_kind,
conditions,
npm_resolver,
- );
+ ) {
+ Ok(Some(path)) => Ok(path),
+ Ok(None) => Err(generic_error("not found")),
+ Err(err) => Err(err),
+ };
}
}
return Err(throw_invalid_package_target(
@@ -593,7 +598,7 @@ pub fn package_resolve(
referrer_kind: NodeModuleKind,
conditions: &[&str],
npm_resolver: &dyn RequireNpmResolver,
-) -> Result<PathBuf, AnyError> {
+) -> Result<Option<PathBuf>, AnyError> {
let (package_name, package_subpath, _is_scoped) =
parse_package_name(specifier, referrer)?;
@@ -611,13 +616,15 @@ pub fn package_resolve(
referrer_kind,
conditions,
npm_resolver,
- );
+ )
+ .map(Some);
}
}
let package_dir_path = npm_resolver.resolve_package_folder_from_package(
&package_name,
&referrer.to_file_path().unwrap(),
+ conditions,
)?;
let package_json_path = package_dir_path.join("package.json");
@@ -645,13 +652,16 @@ pub fn package_resolve(
referrer_kind,
conditions,
npm_resolver,
- );
+ )
+ .map(Some);
}
if package_subpath == "." {
- return legacy_main_resolve(&package_json, referrer_kind);
+ return legacy_main_resolve(&package_json, referrer_kind, conditions);
}
- Ok(package_json.path.parent().unwrap().join(&package_subpath))
+ Ok(Some(
+ package_json.path.parent().unwrap().join(&package_subpath),
+ ))
}
pub fn get_package_scope_config(
@@ -706,41 +716,40 @@ fn file_exists(path: &Path) -> bool {
pub fn legacy_main_resolve(
package_json: &PackageJson,
referrer_kind: NodeModuleKind,
-) -> Result<PathBuf, AnyError> {
- let maybe_main = package_json.main(referrer_kind);
+ conditions: &[&str],
+) -> Result<Option<PathBuf>, AnyError> {
+ let is_types = conditions == TYPES_CONDITIONS;
+ let maybe_main = if is_types {
+ package_json.types.as_ref()
+ } else {
+ package_json.main(referrer_kind)
+ };
let mut guess;
if let Some(main) = maybe_main {
guess = package_json.path.parent().unwrap().join(main).clean();
if file_exists(&guess) {
- return Ok(guess);
+ return Ok(Some(guess));
}
let mut found = false;
- // todo(dsherret): investigate exactly how node handles this
- let endings = match referrer_kind {
- NodeModuleKind::Cjs => vec![
- ".js",
- ".cjs",
- ".json",
- ".node",
- "/index.js",
- "/index.cjs",
- "/index.json",
- "/index.node",
- ],
- NodeModuleKind::Esm => vec![
- ".js",
- ".mjs",
- ".json",
- ".node",
- "/index.js",
- "/index.mjs",
- ".cjs",
- "/index.cjs",
- "/index.json",
- "/index.node",
- ],
+ // todo(dsherret): investigate exactly how node and typescript handles this
+ let endings = if is_types {
+ match referrer_kind {
+ NodeModuleKind::Cjs => {
+ vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"]
+ }
+ NodeModuleKind::Esm => vec![
+ ".d.ts",
+ ".d.mts",
+ "/index.d.ts",
+ "/index.d.mts",
+ ".d.cts",
+ "/index.d.cts",
+ ],
+ }
+ } else {
+ vec![".js", "/index.js"]
};
for ending in endings {
guess = package_json
@@ -757,21 +766,18 @@ pub fn legacy_main_resolve(
if found {
// TODO(bartlomieju): emitLegacyIndexDeprecation()
- return Ok(guess);
+ return Ok(Some(guess));
}
}
- let index_file_names = match referrer_kind {
- NodeModuleKind::Cjs => {
- vec!["index.js", "index.cjs", "index.json", "index.node"]
+ let index_file_names = if is_types {
+ // todo(dsherret): investigate exactly how typescript does this
+ match referrer_kind {
+ NodeModuleKind::Cjs => vec!["index.d.ts", "index.d.cts"],
+ NodeModuleKind::Esm => vec!["index.d.ts", "index.d.mts", "index.d.cts"],
}
- NodeModuleKind::Esm => vec![
- "index.js",
- "index.mjs",
- "index.cjs",
- "index.json",
- "index.node",
- ],
+ } else {
+ vec!["index.js"]
};
for index_file_name in index_file_names {
guess = package_json
@@ -782,11 +788,11 @@ pub fn legacy_main_resolve(
.clean();
if file_exists(&guess) {
// TODO(bartlomieju): emitLegacyIndexDeprecation()
- return Ok(guess);
+ return Ok(Some(guess));
}
}
- Err(generic_error("not found"))
+ Ok(None)
}
#[cfg(test)]
diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml
index 114f52c61..ac3858499 100644
--- a/test_util/Cargo.toml
+++ b/test_util/Cargo.toml
@@ -35,6 +35,7 @@ tar = "0.4.38"
tokio = { version = "1.21", features = ["full"] }
tokio-rustls = "0.23"
tokio-tungstenite = "0.16"
+url = { version = "2.3.1", features = ["serde", "expose_internals"] }
[target.'cfg(unix)'.dependencies]
pty = "0.2.2"
diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs
index b5d509945..5bbccacd8 100644
--- a/test_util/src/lib.rs
+++ b/test_util/src/lib.rs
@@ -49,6 +49,7 @@ use tokio::net::TcpStream;
use tokio_rustls::rustls;
use tokio_rustls::TlsAcceptor;
use tokio_tungstenite::accept_async;
+use url::Url;
pub mod assertions;
pub mod lsp;
@@ -120,10 +121,19 @@ pub fn napi_tests_path() -> PathBuf {
root_path().join("test_napi")
}
+/// Test server registry url.
+pub fn npm_registry_url() -> String {
+ "http://localhost:4545/npm/registry/".to_string()
+}
+
pub fn std_path() -> PathBuf {
root_path().join("test_util").join("std")
}
+pub fn std_file_url() -> String {
+ Url::from_directory_path(std_path()).unwrap().to_string()
+}
+
pub fn target_dir() -> PathBuf {
let current_exe = std::env::current_exe().unwrap();
let target_dir = current_exe.parent().unwrap().parent().unwrap();
diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs
index c61793f98..c0fd8ff1b 100644
--- a/test_util/src/lsp.rs
+++ b/test_util/src/lsp.rs
@@ -1,5 +1,8 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+use crate::npm_registry_url;
+use crate::std_file_url;
+
use super::new_deno_dir;
use super::TempDir;
@@ -230,6 +233,8 @@ impl LspClient {
let mut command = Command::new(deno_exe);
command
.env("DENO_DIR", deno_dir.path())
+ .env("DENO_NODE_COMPAT_URL", std_file_url())
+ .env("DENO_NPM_REGISTRY", npm_registry_url())
.arg("lsp")
.stdin(Stdio::piped())
.stdout(Stdio::piped());