1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 16:19:12 -05:00

feat(unstable/npm): initial type checking of npm specifiers (#16332)

This commit is contained in:
David Sherret 2022-10-21 11:20:18 -04:00 committed by GitHub
parent 0e1a71fec6
commit bcfe279fba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 2135 additions and 280 deletions

1
Cargo.lock generated
View file

@ -4752,6 +4752,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tokio-tungstenite",
"url",
"winapi 0.3.9",
]

View file

@ -201,6 +201,11 @@ fn create_compiler_snapshot(
false
}
#[op]
fn op_is_node_file() -> bool {
false
}
#[op]
fn op_script_version(
_state: &mut OpState,
@ -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(),
])

18
cli/dts/README.md vendored
View file

@ -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/
```

View file

@ -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,
);
}
}

View file

@ -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,

View file

@ -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

View file

@ -2678,6 +2678,20 @@ fn op_is_cancelled(state: &mut OpState) -> bool {
state.token.is_cancelled()
}
#[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,
@ -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(),

View file

@ -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,

View file

@ -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");

View file

@ -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 {

View file

@ -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;

View file

@ -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 mut snapshot = self.snapshot.read().clone();
let mut pending_dependencies = VecDeque::new();
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 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) => {

View file

@ -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;

View file

@ -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
}

View file

@ -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,

View file

@ -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 {
@ -76,6 +90,24 @@ impl NpmPackageResolver {
unstable: bool,
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(|| {
@ -83,24 +115,29 @@ impl NpmPackageResolver {
.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(

View file

@ -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));

View file

@ -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();

View file

@ -3346,6 +3346,37 @@ fn lsp_code_actions_deno_cache() {
session.shutdown_and_exit();
}
#[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");
@ -4046,6 +4077,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();

View file

@ -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()),
]
}

View file

@ -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.

View file

@ -0,0 +1,2 @@
$;
process;

View file

@ -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"
]
}
}

View file

@ -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"
]
]
}
}
]

View file

@ -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
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -0,0 +1,3 @@
import * as test from "npm:@denotest/check-error";
console.log(test.Asdf); // should error

View file

@ -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.

View file

@ -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

View file

@ -1,2 +0,0 @@
import * as globals from "npm:@denotest/globals";
console.log(globals.global === globals.globalThis);

View file

@ -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
[]

View file

@ -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

View file

@ -1,3 +1,4 @@
// @ts-check
import cjsDefault, {
MyClass as MyCjsClass,
} from "npm:@denotest/cjs-default-export";

View file

@ -0,0 +1,6 @@
// intentional type checking errors
export class Class1 extends Class2 {
}
export class Class2 extends Class1 {
}

View file

@ -0,0 +1,6 @@
module.exports = {
Class1: class {
},
Class2: class {
},
};

View file

@ -0,0 +1,5 @@
{
"name": "@denotest/check-error",
"version": "1.0.0",
"types": "./index.d.ts"
}

View file

@ -0,0 +1,6 @@
export default function (): number;
export declare function named(): number;
declare class MyClass {
static someStaticMethod(): string;
}
export { MyClass };

View file

@ -1,4 +1,5 @@
{
"name": "@denotest/cjs-default-export",
"version": "1.0.0"
"version": "1.0.0",
"types": "./index.d.ts"
}

View file

@ -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"
}

View file

@ -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
>;

View file

@ -1,2 +1,3 @@
exports.globalThis = globalThis;
exports.global = global;
exports.process = process;

View file

@ -1,4 +1,5 @@
{
"name": "@denotest/globals",
"version": "1.0.0"
"version": "1.0.0",
"types": "index.d.ts"
}

View file

@ -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;
}
}

View file

@ -0,0 +1,3 @@
export class Test {
prop = 5;
}

View file

@ -0,0 +1,5 @@
{
"name": "@denotest/types-ambient",
"version": "1.0.0",
"types": "./index.d.ts"
}

Binary file not shown.

View file

@ -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"
}

View file

@ -0,0 +1,5 @@
{
"imports": {
"types-ambient": "npm:@denotest/types-ambient"
}
}

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,4 @@
import * as mod from "npm:@denotest/types-ambient";
console.log(mod.Test);
console.log(mod.Test2); // should error

View file

@ -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
}

View file

@ -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]

View file

@ -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;
}

View file

@ -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()");
}

View file

@ -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 {

View file

@ -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())

View file

@ -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 => {

View file

@ -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)]

View file

@ -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"

View file

@ -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();

View file

@ -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());