summaryrefslogtreecommitdiff
path: root/cli/lsp
diff options
context:
space:
mode:
authorhaturau <135221985+haturatu@users.noreply.github.com>2024-11-20 01:20:47 +0900
committerGitHub <noreply@github.com>2024-11-20 01:20:47 +0900
commit85719a67e59c7aa45bead26e4942d7df8b1b42d4 (patch)
treeface0aecaac53e93ce2f23b53c48859bcf1a36ec /cli/lsp
parent67697bc2e4a62a9670699fd18ad0dd8efc5bd955 (diff)
parent186b52731c6bb326c4d32905c5e732d082e83465 (diff)
Merge branch 'denoland:main' into main
Diffstat (limited to 'cli/lsp')
-rw-r--r--cli/lsp/analysis.rs237
-rw-r--r--cli/lsp/capabilities.rs4
-rw-r--r--cli/lsp/client.rs35
-rw-r--r--cli/lsp/code_lens.rs4
-rw-r--r--cli/lsp/completions.rs22
-rw-r--r--cli/lsp/config.rs79
-rw-r--r--cli/lsp/diagnostics.rs7
-rw-r--r--cli/lsp/documents.rs304
-rw-r--r--cli/lsp/language_server.rs121
-rw-r--r--cli/lsp/lsp_custom.rs30
-rw-r--r--cli/lsp/npm.rs6
-rw-r--r--cli/lsp/parent_process_checker.rs2
-rw-r--r--cli/lsp/registries.rs1
-rw-r--r--cli/lsp/repl.rs2
-rw-r--r--cli/lsp/resolver.rs712
-rw-r--r--cli/lsp/testing/collectors.rs2
-rw-r--r--cli/lsp/tsc.rs261
17 files changed, 1338 insertions, 491 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 8c2e8bb1d..044b1573b 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -10,15 +10,17 @@ use super::tsc;
use super::urls::url_to_uri;
use crate::args::jsr_url;
+use crate::lsp::logging::lsp_warn;
use crate::lsp::search::PackageSearchApi;
use crate::tools::lint::CliLinter;
+use crate::util::path::relative_specifier;
use deno_config::workspace::MappedResolution;
+use deno_graph::source::ResolutionMode;
use deno_lint::diagnostic::LintDiagnosticRange;
use deno_ast::SourceRange;
use deno_ast::SourceRangedForSpanned;
use deno_ast::SourceTextInfo;
-use deno_core::anyhow::anyhow;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::serde::Deserialize;
@@ -37,9 +39,10 @@ use deno_semver::package::PackageReq;
use deno_semver::package::PackageReqReference;
use deno_semver::Version;
use import_map::ImportMap;
-use node_resolver::NpmResolver;
+use node_resolver::NodeModuleKind;
use once_cell::sync::Lazy;
use regex::Regex;
+use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
@@ -229,6 +232,7 @@ pub struct TsResponseImportMapper<'a> {
documents: &'a Documents,
maybe_import_map: Option<&'a ImportMap>,
resolver: &'a LspResolver,
+ tsc_specifier_map: &'a tsc::TscSpecifierMap,
file_referrer: ModuleSpecifier,
}
@@ -237,12 +241,14 @@ impl<'a> TsResponseImportMapper<'a> {
documents: &'a Documents,
maybe_import_map: Option<&'a ImportMap>,
resolver: &'a LspResolver,
+ tsc_specifier_map: &'a tsc::TscSpecifierMap,
file_referrer: &ModuleSpecifier,
) -> Self {
Self {
documents,
maybe_import_map,
resolver,
+ tsc_specifier_map,
file_referrer: file_referrer.clone(),
}
}
@@ -336,7 +342,11 @@ impl<'a> TsResponseImportMapper<'a> {
.resolver
.maybe_managed_npm_resolver(Some(&self.file_referrer))
{
- if npm_resolver.in_npm_package(specifier) {
+ let in_npm_pkg = self
+ .resolver
+ .in_npm_pkg_checker(Some(&self.file_referrer))
+ .in_npm_package(specifier);
+ if in_npm_pkg {
if let Ok(Some(pkg_id)) =
npm_resolver.resolve_pkg_id_from_specifier(specifier)
{
@@ -383,6 +393,11 @@ impl<'a> TsResponseImportMapper<'a> {
}
}
}
+ } else if let Some(dep_name) = self
+ .resolver
+ .file_url_to_package_json_dep(specifier, Some(&self.file_referrer))
+ {
+ return Some(dep_name);
}
// check if the import map has this specifier
@@ -452,20 +467,39 @@ impl<'a> TsResponseImportMapper<'a> {
&self,
specifier: &str,
referrer: &ModuleSpecifier,
+ referrer_kind: NodeModuleKind,
) -> Option<String> {
- if let Ok(specifier) = referrer.join(specifier) {
- if let Some(specifier) = self.check_specifier(&specifier, referrer) {
- return Some(specifier);
- }
- }
- let specifier = specifier.strip_suffix(".js").unwrap_or(specifier);
- for ext in SUPPORTED_EXTENSIONS {
- let specifier_with_ext = format!("{specifier}{ext}");
- if self
- .documents
- .contains_import(&specifier_with_ext, referrer)
+ let specifier_stem = specifier.strip_suffix(".js").unwrap_or(specifier);
+ let specifiers = std::iter::once(Cow::Borrowed(specifier)).chain(
+ SUPPORTED_EXTENSIONS
+ .iter()
+ .map(|ext| Cow::Owned(format!("{specifier_stem}{ext}"))),
+ );
+ for specifier in specifiers {
+ if let Some(specifier) = self
+ .resolver
+ .as_cli_resolver(Some(&self.file_referrer))
+ .resolve(
+ &specifier,
+ &deno_graph::Range {
+ specifier: referrer.clone(),
+ start: deno_graph::Position::zeroed(),
+ end: deno_graph::Position::zeroed(),
+ },
+ referrer_kind,
+ ResolutionMode::Types,
+ )
+ .ok()
+ .and_then(|s| self.tsc_specifier_map.normalize(s.as_str()).ok())
+ .filter(|s| self.documents.exists(s, Some(&self.file_referrer)))
{
- return Some(specifier_with_ext);
+ if let Some(specifier) = self
+ .check_specifier(&specifier, referrer)
+ .or_else(|| relative_specifier(referrer, &specifier))
+ .filter(|s| !s.contains("/node_modules/"))
+ {
+ return Some(specifier);
+ }
}
}
None
@@ -475,10 +509,11 @@ impl<'a> TsResponseImportMapper<'a> {
&self,
specifier_text: &str,
referrer: &ModuleSpecifier,
+ referrer_kind: NodeModuleKind,
) -> bool {
self
.resolver
- .as_graph_resolver(Some(&self.file_referrer))
+ .as_cli_resolver(Some(&self.file_referrer))
.resolve(
specifier_text,
&deno_graph::Range {
@@ -486,6 +521,7 @@ impl<'a> TsResponseImportMapper<'a> {
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
},
+ referrer_kind,
deno_graph::source::ResolutionMode::Types,
)
.is_ok()
@@ -554,9 +590,11 @@ fn try_reverse_map_package_json_exports(
/// like an import and rewrite the import specifier to include the extension
pub fn fix_ts_import_changes(
referrer: &ModuleSpecifier,
+ referrer_kind: NodeModuleKind,
changes: &[tsc::FileTextChanges],
- import_mapper: &TsResponseImportMapper,
+ language_server: &language_server::Inner,
) -> Result<Vec<tsc::FileTextChanges>, AnyError> {
+ let import_mapper = language_server.get_ts_response_import_mapper(referrer);
let mut r = Vec::new();
for change in changes {
let mut text_changes = Vec::new();
@@ -569,8 +607,8 @@ pub fn fix_ts_import_changes(
if let Some(captures) = IMPORT_SPECIFIER_RE.captures(line) {
let specifier =
captures.iter().skip(1).find_map(|s| s).unwrap().as_str();
- if let Some(new_specifier) =
- import_mapper.check_unresolved_specifier(specifier, referrer)
+ if let Some(new_specifier) = import_mapper
+ .check_unresolved_specifier(specifier, referrer, referrer_kind)
{
line.replace(specifier, &new_specifier)
} else {
@@ -598,68 +636,64 @@ pub fn fix_ts_import_changes(
/// Fix tsc import code actions so that the module specifier is correct for
/// resolution by Deno (includes the extension).
-fn fix_ts_import_action(
+fn fix_ts_import_action<'a>(
referrer: &ModuleSpecifier,
- action: &tsc::CodeFixAction,
- import_mapper: &TsResponseImportMapper,
-) -> Result<Option<tsc::CodeFixAction>, AnyError> {
- if matches!(
+ referrer_kind: NodeModuleKind,
+ action: &'a tsc::CodeFixAction,
+ language_server: &language_server::Inner,
+) -> Option<Cow<'a, tsc::CodeFixAction>> {
+ if !matches!(
action.fix_name.as_str(),
"import" | "fixMissingFunctionDeclaration"
) {
- let change = action
+ return Some(Cow::Borrowed(action));
+ }
+ let specifier = (|| {
+ let text_change = action.changes.first()?.text_changes.first()?;
+ let captures = IMPORT_SPECIFIER_RE.captures(&text_change.new_text)?;
+ Some(captures.get(1)?.as_str())
+ })();
+ let Some(specifier) = specifier else {
+ return Some(Cow::Borrowed(action));
+ };
+ let import_mapper = language_server.get_ts_response_import_mapper(referrer);
+ if let Some(new_specifier) =
+ import_mapper.check_unresolved_specifier(specifier, referrer, referrer_kind)
+ {
+ let description = action.description.replace(specifier, &new_specifier);
+ let changes = action
.changes
- .first()
- .ok_or_else(|| anyhow!("Unexpected action changes."))?;
- let text_change = change
- .text_changes
- .first()
- .ok_or_else(|| anyhow!("Missing text change."))?;
- if let Some(captures) = IMPORT_SPECIFIER_RE.captures(&text_change.new_text)
- {
- let specifier = captures
- .get(1)
- .ok_or_else(|| anyhow!("Missing capture."))?
- .as_str();
- if let Some(new_specifier) =
- import_mapper.check_unresolved_specifier(specifier, referrer)
- {
- let description = action.description.replace(specifier, &new_specifier);
- let changes = action
- .changes
+ .iter()
+ .map(|c| {
+ let text_changes = c
+ .text_changes
.iter()
- .map(|c| {
- let text_changes = c
- .text_changes
- .iter()
- .map(|tc| tsc::TextChange {
- span: tc.span.clone(),
- new_text: tc.new_text.replace(specifier, &new_specifier),
- })
- .collect();
- tsc::FileTextChanges {
- file_name: c.file_name.clone(),
- text_changes,
- is_new_file: c.is_new_file,
- }
+ .map(|tc| tsc::TextChange {
+ span: tc.span.clone(),
+ new_text: tc.new_text.replace(specifier, &new_specifier),
})
.collect();
-
- return Ok(Some(tsc::CodeFixAction {
- description,
- changes,
- commands: None,
- fix_name: action.fix_name.clone(),
- fix_id: None,
- fix_all_description: None,
- }));
- } else if !import_mapper.is_valid_import(specifier, referrer) {
- return Ok(None);
- }
- }
+ tsc::FileTextChanges {
+ file_name: c.file_name.clone(),
+ text_changes,
+ is_new_file: c.is_new_file,
+ }
+ })
+ .collect();
+
+ Some(Cow::Owned(tsc::CodeFixAction {
+ description,
+ changes,
+ commands: None,
+ fix_name: action.fix_name.clone(),
+ fix_id: None,
+ fix_all_description: None,
+ }))
+ } else if !import_mapper.is_valid_import(specifier, referrer, referrer_kind) {
+ None
+ } else {
+ Some(Cow::Borrowed(action))
}
-
- Ok(Some(action.clone()))
}
/// Determines if two TypeScript diagnostic codes are effectively equivalent.
@@ -720,8 +754,14 @@ pub fn ts_changes_to_edit(
) -> Result<Option<lsp::WorkspaceEdit>, AnyError> {
let mut text_document_edits = Vec::new();
for change in changes {
- let text_document_edit = change.to_text_document_edit(language_server)?;
- text_document_edits.push(text_document_edit);
+ let edit = match change.to_text_document_edit(language_server) {
+ Ok(e) => e,
+ Err(err) => {
+ lsp_warn!("Couldn't covert text document edit: {:#}", err);
+ continue;
+ }
+ };
+ text_document_edits.push(edit);
}
Ok(Some(lsp::WorkspaceEdit {
changes: None,
@@ -730,7 +770,7 @@ pub fn ts_changes_to_edit(
}))
}
-#[derive(Debug, Deserialize)]
+#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionData {
pub specifier: ModuleSpecifier,
@@ -983,6 +1023,7 @@ impl CodeActionCollection {
pub fn add_ts_fix_action(
&mut self,
specifier: &ModuleSpecifier,
+ specifier_kind: NodeModuleKind,
action: &tsc::CodeFixAction,
diagnostic: &lsp::Diagnostic,
language_server: &language_server::Inner,
@@ -1000,11 +1041,8 @@ impl CodeActionCollection {
"The action returned from TypeScript is unsupported.",
));
}
- let Some(action) = fix_ts_import_action(
- specifier,
- action,
- &language_server.get_ts_response_import_mapper(specifier),
- )?
+ let Some(action) =
+ fix_ts_import_action(specifier, specifier_kind, action, language_server)
else {
return Ok(());
};
@@ -1027,7 +1065,7 @@ impl CodeActionCollection {
});
self
.actions
- .push(CodeActionKind::Tsc(code_action, action.clone()));
+ .push(CodeActionKind::Tsc(code_action, action.as_ref().clone()));
if let Some(fix_id) = &action.fix_id {
if let Some(CodeActionKind::Tsc(existing_fix_all, existing_action)) =
@@ -1054,10 +1092,12 @@ impl CodeActionCollection {
specifier: &ModuleSpecifier,
diagnostic: &lsp::Diagnostic,
) {
- let data = Some(json!({
- "specifier": specifier,
- "fixId": action.fix_id,
- }));
+ let data = action.fix_id.as_ref().map(|fix_id| {
+ json!(CodeActionData {
+ specifier: specifier.clone(),
+ fix_id: fix_id.clone(),
+ })
+ });
let title = if let Some(description) = &action.fix_all_description {
description.clone()
} else {
@@ -1206,14 +1246,11 @@ impl CodeActionCollection {
}),
);
- match parsed_source.program_ref() {
- deno_ast::swc::ast::Program::Module(module) => module
- .body
- .iter()
- .find(|i| i.range().contains(&specifier_range))
- .map(|i| text_info.line_and_column_index(i.range().start)),
- deno_ast::swc::ast::Program::Script(_) => None,
- }
+ parsed_source
+ .program_ref()
+ .body()
+ .find(|i| i.range().contains(&specifier_range))
+ .map(|i| text_info.line_and_column_index(i.range().start))
}
async fn deno_types_for_npm_action(
@@ -1247,6 +1284,9 @@ impl CodeActionCollection {
import_start_from_specifier(document, i)
})?;
let referrer = document.specifier();
+ let referrer_kind = language_server
+ .is_cjs_resolver
+ .get_doc_module_kind(document);
let file_referrer = document.file_referrer();
let config_data = language_server
.config
@@ -1269,10 +1309,11 @@ impl CodeActionCollection {
if !config_data.byonm {
return None;
}
- if !language_server
- .resolver
- .is_bare_package_json_dep(&dep_key, referrer)
- {
+ if !language_server.resolver.is_bare_package_json_dep(
+ &dep_key,
+ referrer,
+ referrer_kind,
+ ) {
return None;
}
NpmPackageReqReference::from_str(&format!("npm:{}", &dep_key)).ok()?
@@ -1291,7 +1332,7 @@ impl CodeActionCollection {
}
if language_server
.resolver
- .npm_to_file_url(&npm_ref, document.specifier(), file_referrer)
+ .npm_to_file_url(&npm_ref, referrer, referrer_kind, file_referrer)
.is_some()
{
// The package import has types.
diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs
index e93d3b7c2..5cdb1224d 100644
--- a/cli/lsp/capabilities.rs
+++ b/cli/lsp/capabilities.rs
@@ -147,11 +147,11 @@ pub fn server_capabilities(
moniker_provider: None,
experimental: Some(json!({
"denoConfigTasks": true,
- "testingApi":true,
+ "testingApi": true,
+ "didRefreshDenoConfigurationTreeNotifications": true,
})),
inlay_hint_provider: Some(OneOf::Left(true)),
position_encoding: None,
- // TODO(nayeemrmn): Support pull-based diagnostics.
diagnostic_provider: None,
inline_value_provider: None,
inline_completion_provider: None,
diff --git a/cli/lsp/client.rs b/cli/lsp/client.rs
index b3f0d64fa..65865d5b3 100644
--- a/cli/lsp/client.rs
+++ b/cli/lsp/client.rs
@@ -92,6 +92,19 @@ impl Client {
});
}
+ pub fn send_did_refresh_deno_configuration_tree_notification(
+ &self,
+ params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams,
+ ) {
+ // do on a task in case the caller currently is in the lsp lock
+ let client = self.0.clone();
+ spawn(async move {
+ client
+ .send_did_refresh_deno_configuration_tree_notification(params)
+ .await;
+ });
+ }
+
pub fn send_did_change_deno_configuration_notification(
&self,
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
@@ -169,6 +182,10 @@ trait ClientTrait: Send + Sync {
params: lsp_custom::DiagnosticBatchNotificationParams,
);
async fn send_test_notification(&self, params: TestingNotification);
+ async fn send_did_refresh_deno_configuration_tree_notification(
+ &self,
+ params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams,
+ );
async fn send_did_change_deno_configuration_notification(
&self,
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
@@ -249,6 +266,18 @@ impl ClientTrait for TowerClient {
}
}
+ async fn send_did_refresh_deno_configuration_tree_notification(
+ &self,
+ params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams,
+ ) {
+ self
+ .0
+ .send_notification::<lsp_custom::DidRefreshDenoConfigurationTreeNotification>(
+ params,
+ )
+ .await
+ }
+
async fn send_did_change_deno_configuration_notification(
&self,
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
@@ -366,6 +395,12 @@ impl ClientTrait for ReplClient {
async fn send_test_notification(&self, _params: TestingNotification) {}
+ async fn send_did_refresh_deno_configuration_tree_notification(
+ &self,
+ _params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams,
+ ) {
+ }
+
async fn send_did_change_deno_configuration_notification(
&self,
_params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs
index e117888fb..a57ca3ac9 100644
--- a/cli/lsp/code_lens.rs
+++ b/cli/lsp/code_lens.rs
@@ -421,7 +421,7 @@ pub fn collect_test(
) -> Result<Vec<lsp::CodeLens>, AnyError> {
let mut collector =
DenoTestCollector::new(specifier.clone(), parsed_source.clone());
- parsed_source.module().visit_with(&mut collector);
+ parsed_source.program().visit_with(&mut collector);
Ok(collector.take())
}
@@ -581,7 +581,7 @@ mod tests {
.unwrap();
let mut collector =
DenoTestCollector::new(specifier, parsed_module.clone());
- parsed_module.module().visit_with(&mut collector);
+ parsed_module.program().visit_with(&mut collector);
assert_eq!(
collector.take(),
vec![
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 1590743b2..3ee8ae93e 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -9,6 +9,7 @@ use super::jsr::CliJsrSearchApi;
use super::lsp_custom;
use super::npm::CliNpmSearchApi;
use super::registries::ModuleRegistry;
+use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver;
use super::search::PackageSearchApi;
use super::tsc;
@@ -35,6 +36,7 @@ use deno_semver::package::PackageNv;
use import_map::ImportMap;
use indexmap::IndexSet;
use lsp_types::CompletionList;
+use node_resolver::NodeModuleKind;
use once_cell::sync::Lazy;
use regex::Regex;
use tower_lsp::lsp_types as lsp;
@@ -159,15 +161,17 @@ pub async fn get_import_completions(
jsr_search_api: &CliJsrSearchApi,
npm_search_api: &CliNpmSearchApi,
documents: &Documents,
+ is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver,
maybe_import_map: Option<&ImportMap>,
) -> Option<lsp::CompletionResponse> {
let document = documents.get(specifier)?;
+ let specifier_kind = is_cjs_resolver.get_doc_module_kind(&document);
let file_referrer = document.file_referrer();
let (text, _, range) = document.get_maybe_dependency(position)?;
let range = to_narrow_lsp_range(document.text_info(), &range);
let resolved = resolver
- .as_graph_resolver(file_referrer)
+ .as_cli_resolver(file_referrer)
.resolve(
&text,
&Range {
@@ -175,6 +179,7 @@ pub async fn get_import_completions(
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
},
+ specifier_kind,
ResolutionMode::Execution,
)
.ok();
@@ -201,7 +206,7 @@ pub async fn get_import_completions(
// completions for import map specifiers
Some(lsp::CompletionResponse::List(completion_list))
} else if let Some(completion_list) =
- get_local_completions(specifier, &text, &range, resolver)
+ get_local_completions(specifier, specifier_kind, &text, &range, resolver)
{
// completions for local relative modules
Some(lsp::CompletionResponse::List(completion_list))
@@ -355,24 +360,26 @@ fn get_import_map_completions(
/// Return local completions that are relative to the base specifier.
fn get_local_completions(
- base: &ModuleSpecifier,
+ referrer: &ModuleSpecifier,
+ referrer_kind: NodeModuleKind,
text: &str,
range: &lsp::Range,
resolver: &LspResolver,
) -> Option<CompletionList> {
- if base.scheme() != "file" {
+ if referrer.scheme() != "file" {
return None;
}
let parent = &text[..text.char_indices().rfind(|(_, c)| *c == '/')?.0 + 1];
let resolved_parent = resolver
- .as_graph_resolver(Some(base))
+ .as_cli_resolver(Some(referrer))
.resolve(
parent,
&Range {
- specifier: base.clone(),
+ specifier: referrer.clone(),
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
},
+ referrer_kind,
ResolutionMode::Execution,
)
.ok()?;
@@ -385,7 +392,7 @@ fn get_local_completions(
let de = de.ok()?;
let label = de.path().file_name()?.to_string_lossy().to_string();
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
- if entry_specifier == *base {
+ if entry_specifier == *referrer {
return None;
}
let full_text = format!("{parent}{label}");
@@ -905,6 +912,7 @@ mod tests {
ModuleSpecifier::from_file_path(file_c).expect("could not create");
let actual = get_local_completions(
&specifier,
+ NodeModuleKind::Esm,
"./",
&lsp::Range {
start: lsp::Position {
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 07fdd3c65..ea77e36bc 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -4,6 +4,7 @@ use deno_ast::MediaType;
use deno_config::deno_json::DenoJsonCache;
use deno_config::deno_json::FmtConfig;
use deno_config::deno_json::FmtOptionsConfig;
+use deno_config::deno_json::JsxImportSourceConfig;
use deno_config::deno_json::LintConfig;
use deno_config::deno_json::NodeModulesDirMode;
use deno_config::deno_json::TestConfig;
@@ -41,6 +42,7 @@ use deno_runtime::deno_node::PackageJson;
use indexmap::IndexSet;
use lsp_types::ClientCapabilities;
use std::collections::BTreeMap;
+use std::collections::BTreeSet;
use std::collections::HashMap;
use std::ops::Deref;
use std::ops::DerefMut;
@@ -50,6 +52,8 @@ use std::sync::Arc;
use tower_lsp::lsp_types as lsp;
use super::logging::lsp_log;
+use super::lsp_custom;
+use super::urls::url_to_uri;
use crate::args::discover_npmrc_from_workspace;
use crate::args::has_flag_env_var;
use crate::args::CliLockfile;
@@ -437,6 +441,8 @@ pub struct LanguagePreferences {
pub use_aliases_for_renames: bool,
#[serde(default)]
pub quote_style: QuoteStyle,
+ #[serde(default)]
+ pub prefer_type_only_auto_imports: bool,
}
impl Default for LanguagePreferences {
@@ -447,6 +453,7 @@ impl Default for LanguagePreferences {
auto_import_file_exclude_patterns: vec![],
use_aliases_for_renames: true,
quote_style: Default::default(),
+ prefer_type_only_auto_imports: false,
}
}
}
@@ -979,7 +986,7 @@ impl Config {
| MediaType::Tsx => Some(&workspace_settings.typescript),
MediaType::Json
| MediaType::Wasm
- | MediaType::TsBuildInfo
+ | MediaType::Css
| MediaType::SourceMap
| MediaType::Unknown => None,
}
@@ -1185,6 +1192,7 @@ pub struct ConfigData {
pub resolver: Arc<WorkspaceResolver>,
pub sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
pub import_map_from_settings: Option<ModuleSpecifier>,
+ pub unstable: BTreeSet<String>,
watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
}
@@ -1582,9 +1590,16 @@ impl ConfigData {
.join("\n")
);
}
+ let unstable = member_dir
+ .workspace
+ .unstable_features()
+ .iter()
+ .chain(settings.unstable.as_deref())
+ .cloned()
+ .collect::<BTreeSet<_>>();
let unstable_sloppy_imports = std::env::var("DENO_UNSTABLE_SLOPPY_IMPORTS")
.is_ok()
- || member_dir.workspace.has_unstable("sloppy-imports");
+ || unstable.contains("sloppy-imports");
let sloppy_imports_resolver = unstable_sloppy_imports.then(|| {
Arc::new(CliSloppyImportsResolver::new(
SloppyImportsCachedFs::new_without_stat_cache(Arc::new(
@@ -1625,6 +1640,7 @@ impl ConfigData {
lockfile,
npmrc,
import_map_from_settings,
+ unstable,
watched_files,
}
}
@@ -1639,6 +1655,17 @@ impl ConfigData {
self.member_dir.maybe_pkg_json()
}
+ pub fn maybe_jsx_import_source_config(
+ &self,
+ ) -> Option<JsxImportSourceConfig> {
+ self
+ .member_dir
+ .workspace
+ .to_maybe_jsx_import_source_config()
+ .ok()
+ .flatten()
+ }
+
pub fn scope_contains_specifier(&self, specifier: &ModuleSpecifier) -> bool {
specifier.as_str().starts_with(self.scope.as_str())
|| self
@@ -1716,14 +1743,14 @@ impl ConfigTree {
.unwrap_or_else(|| Arc::new(FmtConfig::new_with_base(PathBuf::from("/"))))
}
- /// Returns (scope_uri, type).
+ /// Returns (scope_url, type).
pub fn watched_file_type(
&self,
specifier: &ModuleSpecifier,
) -> Option<(&ModuleSpecifier, ConfigWatchedFileType)> {
- for (scope_uri, data) in self.scopes.iter() {
+ for (scope_url, data) in self.scopes.iter() {
if let Some(typ) = data.watched_files.get(specifier) {
- return Some((scope_uri, *typ));
+ return Some((scope_url, *typ));
}
}
None
@@ -1747,6 +1774,46 @@ impl ConfigTree {
.any(|data| data.watched_files.contains_key(specifier))
}
+ pub fn to_did_refresh_params(
+ &self,
+ ) -> lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams {
+ let data = self
+ .scopes
+ .values()
+ .filter_map(|data| {
+ let workspace_root_scope_uri =
+ Some(data.member_dir.workspace.root_dir())
+ .filter(|s| *s != data.member_dir.dir_url())
+ .and_then(|s| url_to_uri(s).ok());
+ Some(lsp_custom::DenoConfigurationData {
+ scope_uri: url_to_uri(&data.scope).ok()?,
+ deno_json: data.maybe_deno_json().and_then(|c| {
+ if workspace_root_scope_uri.is_some()
+ && Some(&c.specifier)
+ == data
+ .member_dir
+ .workspace
+ .root_deno_json()
+ .map(|c| &c.specifier)
+ {
+ return None;
+ }
+ Some(lsp::TextDocumentIdentifier {
+ uri: url_to_uri(&c.specifier).ok()?,
+ })
+ }),
+ package_json: data.maybe_pkg_json().and_then(|p| {
+ Some(lsp::TextDocumentIdentifier {
+ uri: url_to_uri(&p.specifier()).ok()?,
+ })
+ }),
+ workspace_root_scope_uri,
+ })
+ })
+ .collect();
+ lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams { data }
+ }
+
pub async fn refresh(
&mut self,
settings: &Settings,
@@ -2209,6 +2276,7 @@ mod tests {
auto_import_file_exclude_patterns: vec![],
use_aliases_for_renames: true,
quote_style: QuoteStyle::Auto,
+ prefer_type_only_auto_imports: false,
},
suggest: CompletionSettings {
complete_function_calls: false,
@@ -2254,6 +2322,7 @@ mod tests {
auto_import_file_exclude_patterns: vec![],
use_aliases_for_renames: true,
quote_style: QuoteStyle::Auto,
+ prefer_type_only_auto_imports: false,
},
suggest: CompletionSettings {
complete_function_calls: false,
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index caabd3f04..e4fb82e58 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -1499,7 +1499,11 @@ fn diagnose_dependency(
.data_for_specifier(referrer_doc.file_referrer().unwrap_or(referrer))
.and_then(|d| d.resolver.maybe_import_map());
if let Some(import_map) = import_map {
- if let Resolution::Ok(resolved) = &dependency.maybe_code {
+ let resolved = dependency
+ .maybe_code
+ .ok()
+ .or_else(|| dependency.maybe_type.ok());
+ if let Some(resolved) = resolved {
if let Some(to) = import_map.lookup(&resolved.specifier, referrer) {
if dependency_key != to {
diagnostics.push(
@@ -1703,6 +1707,7 @@ mod tests {
documents: Arc::new(documents),
assets: Default::default(),
config: Arc::new(config),
+ is_cjs_resolver: Default::default(),
resolver,
},
)
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index 7d1ca6810..b01544ddf 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -3,7 +3,10 @@
use super::cache::calculate_fs_version;
use super::cache::LspCache;
use super::config::Config;
+use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver;
+use super::resolver::ScopeDepInfo;
+use super::resolver::SingleReferrerGraphResolver;
use super::testing::TestCollector;
use super::testing::TestModule;
use super::text::LineIndex;
@@ -33,9 +36,9 @@ use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
use indexmap::IndexMap;
use indexmap::IndexSet;
+use node_resolver::NodeModuleKind;
use std::borrow::Cow;
use std::collections::BTreeMap;
-use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs;
@@ -272,7 +275,7 @@ fn get_maybe_test_module_fut(
parsed_source.specifier().clone(),
parsed_source.text_info_lazy().clone(),
);
- parsed_source.module().visit_with(&mut collector);
+ parsed_source.program().visit_with(&mut collector);
Arc::new(collector.take())
})
.map(Result::ok)
@@ -293,6 +296,8 @@ pub struct Document {
/// Contains the last-known-good set of dependencies from parsing the module.
config: Arc<Config>,
dependencies: Arc<IndexMap<String, deno_graph::Dependency>>,
+ /// If this is maybe a CJS script and maybe not an ES module.
+ is_script: Option<bool>,
// TODO(nayeemrmn): This is unused, use it for scope attribution for remote
// modules.
file_referrer: Option<ModuleSpecifier>,
@@ -323,6 +328,7 @@ impl Document {
maybe_lsp_version: Option<i32>,
maybe_language_id: Option<LanguageId>,
maybe_headers: Option<HashMap<String, String>>,
+ is_cjs_resolver: &LspIsCjsResolver,
resolver: Arc<LspResolver>,
config: Arc<Config>,
cache: &Arc<LspCache>,
@@ -332,12 +338,8 @@ impl Document {
.filter(|s| cache.is_valid_file_referrer(s))
.cloned()
.or(file_referrer);
- let media_type = resolve_media_type(
- &specifier,
- maybe_headers.as_ref(),
- maybe_language_id,
- &resolver,
- );
+ let media_type =
+ resolve_media_type(&specifier, maybe_headers.as_ref(), maybe_language_id);
let (maybe_parsed_source, maybe_module) =
if media_type_is_diagnosable(media_type) {
parse_and_analyze_module(
@@ -346,6 +348,7 @@ impl Document {
maybe_headers.as_ref(),
media_type,
file_referrer.as_ref(),
+ is_cjs_resolver,
&resolver,
)
} else {
@@ -371,6 +374,7 @@ impl Document {
file_referrer.as_ref(),
),
file_referrer,
+ is_script: maybe_module.as_ref().map(|m| m.is_script),
maybe_types_dependency,
line_index,
maybe_language_id,
@@ -392,6 +396,7 @@ impl Document {
fn with_new_config(
&self,
+ is_cjs_resolver: &LspIsCjsResolver,
resolver: Arc<LspResolver>,
config: Arc<Config>,
) -> Arc<Self> {
@@ -399,11 +404,11 @@ impl Document {
&self.specifier,
self.maybe_headers.as_ref(),
self.maybe_language_id,
- &resolver,
);
let dependencies;
let maybe_types_dependency;
let maybe_parsed_source;
+ let is_script;
let maybe_test_module_fut;
if media_type != self.media_type {
let parsed_source_result =
@@ -413,6 +418,7 @@ impl Document {
&parsed_source_result,
self.maybe_headers.as_ref(),
self.file_referrer.as_ref(),
+ is_cjs_resolver,
&resolver,
)
.ok();
@@ -420,6 +426,7 @@ impl Document {
.as_ref()
.map(|m| Arc::new(m.dependencies.clone()))
.unwrap_or_default();
+ is_script = maybe_module.as_ref().map(|m| m.is_script);
maybe_types_dependency = maybe_module
.as_ref()
.and_then(|m| Some(Arc::new(m.maybe_types_dependency.clone()?)));
@@ -427,10 +434,19 @@ impl Document {
maybe_test_module_fut =
get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &config);
} else {
- let graph_resolver =
- resolver.as_graph_resolver(self.file_referrer.as_ref());
+ let cli_resolver = resolver.as_cli_resolver(self.file_referrer.as_ref());
let npm_resolver =
resolver.create_graph_npm_resolver(self.file_referrer.as_ref());
+ let config_data = resolver.as_config_data(self.file_referrer.as_ref());
+ let jsx_import_source_config =
+ config_data.and_then(|d| d.maybe_jsx_import_source_config());
+ let resolver = SingleReferrerGraphResolver {
+ valid_referrer: &self.specifier,
+ referrer_kind: is_cjs_resolver
+ .get_lsp_referrer_kind(&self.specifier, self.is_script),
+ cli_resolver,
+ jsx_import_source_config: jsx_import_source_config.as_ref(),
+ };
dependencies = Arc::new(
self
.dependencies
@@ -441,7 +457,7 @@ impl Document {
d.with_new_resolver(
s,
&CliJsrUrlProvider,
- Some(graph_resolver),
+ Some(&resolver),
Some(&npm_resolver),
),
)
@@ -451,10 +467,11 @@ impl Document {
maybe_types_dependency = self.maybe_types_dependency.as_ref().map(|d| {
Arc::new(d.with_new_resolver(
&CliJsrUrlProvider,
- Some(graph_resolver),
+ Some(&resolver),
Some(&npm_resolver),
))
});
+ is_script = self.is_script;
maybe_parsed_source = self.maybe_parsed_source().cloned();
maybe_test_module_fut = self
.maybe_test_module_fut
@@ -466,6 +483,7 @@ impl Document {
// updated properties
dependencies,
file_referrer: self.file_referrer.clone(),
+ is_script,
maybe_types_dependency,
maybe_navigation_tree: Mutex::new(None),
// maintain - this should all be copies/clones
@@ -490,6 +508,7 @@ impl Document {
fn with_change(
&self,
+ is_cjs_resolver: &LspIsCjsResolver,
version: i32,
changes: Vec<lsp::TextDocumentContentChangeEvent>,
) -> Result<Arc<Self>, AnyError> {
@@ -523,6 +542,7 @@ impl Document {
self.maybe_headers.as_ref(),
media_type,
self.file_referrer.as_ref(),
+ is_cjs_resolver,
self.resolver.as_ref(),
)
} else {
@@ -546,6 +566,7 @@ impl Document {
get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &self.config);
Ok(Arc::new(Self {
config: self.config.clone(),
+ is_script: maybe_module.as_ref().map(|m| m.is_script),
specifier: self.specifier.clone(),
file_referrer: self.file_referrer.clone(),
maybe_fs_version: self.maybe_fs_version.clone(),
@@ -580,6 +601,7 @@ impl Document {
),
maybe_language_id: self.maybe_language_id,
dependencies: self.dependencies.clone(),
+ is_script: self.is_script,
maybe_types_dependency: self.maybe_types_dependency.clone(),
text: self.text.clone(),
text_info_cell: once_cell::sync::OnceCell::new(),
@@ -607,6 +629,7 @@ impl Document {
),
maybe_language_id: self.maybe_language_id,
dependencies: self.dependencies.clone(),
+ is_script: self.is_script,
maybe_types_dependency: self.maybe_types_dependency.clone(),
text: self.text.clone(),
text_info_cell: once_cell::sync::OnceCell::new(),
@@ -655,6 +678,13 @@ impl Document {
})
}
+ /// If this is maybe a CJS script and maybe not an ES module.
+ ///
+ /// Use `LspIsCjsResolver` to determine for sure.
+ pub fn is_script(&self) -> Option<bool> {
+ self.is_script
+ }
+
pub fn line_index(&self) -> Arc<LineIndex> {
self.line_index.clone()
}
@@ -764,14 +794,7 @@ fn resolve_media_type(
specifier: &ModuleSpecifier,
maybe_headers: Option<&HashMap<String, String>>,
maybe_language_id: Option<LanguageId>,
- resolver: &LspResolver,
) -> MediaType {
- if resolver.in_node_modules(specifier) {
- if let Some(media_type) = resolver.node_media_type(specifier) {
- return media_type;
- }
- }
-
if let Some(language_id) = maybe_language_id {
return MediaType::from_specifier_and_content_type(
specifier,
@@ -809,6 +832,7 @@ impl FileSystemDocuments {
pub fn get(
&self,
specifier: &ModuleSpecifier,
+ is_cjs_resolver: &LspIsCjsResolver,
resolver: &Arc<LspResolver>,
config: &Arc<Config>,
cache: &Arc<LspCache>,
@@ -832,7 +856,14 @@ impl FileSystemDocuments {
};
if dirty {
// attempt to update the file on the file system
- self.refresh_document(specifier, resolver, config, cache, file_referrer)
+ self.refresh_document(
+ specifier,
+ is_cjs_resolver,
+ resolver,
+ config,
+ cache,
+ file_referrer,
+ )
} else {
old_doc
}
@@ -843,6 +874,7 @@ impl FileSystemDocuments {
fn refresh_document(
&self,
specifier: &ModuleSpecifier,
+ is_cjs_resolver: &LspIsCjsResolver,
resolver: &Arc<LspResolver>,
config: &Arc<Config>,
cache: &Arc<LspCache>,
@@ -859,6 +891,7 @@ impl FileSystemDocuments {
None,
None,
None,
+ is_cjs_resolver,
resolver.clone(),
config.clone(),
cache,
@@ -875,6 +908,7 @@ impl FileSystemDocuments {
None,
None,
None,
+ is_cjs_resolver,
resolver.clone(),
config.clone(),
cache,
@@ -902,6 +936,7 @@ impl FileSystemDocuments {
None,
None,
maybe_headers,
+ is_cjs_resolver,
resolver.clone(),
config.clone(),
cache,
@@ -942,6 +977,11 @@ pub struct Documents {
/// The DENO_DIR that the documents looks for non-file based modules.
cache: Arc<LspCache>,
config: Arc<Config>,
+ /// Resolver for detecting if a document is CJS or ESM.
+ is_cjs_resolver: Arc<LspIsCjsResolver>,
+ /// A resolver that takes into account currently loaded import map and JSX
+ /// settings.
+ resolver: Arc<LspResolver>,
/// A flag that indicates that stated data is potentially invalid and needs to
/// be recalculated before being considered valid.
dirty: bool,
@@ -949,15 +989,7 @@ pub struct Documents {
open_docs: HashMap<ModuleSpecifier, Arc<Document>>,
/// Documents stored on the file system.
file_system_docs: Arc<FileSystemDocuments>,
- /// A resolver that takes into account currently loaded import map and JSX
- /// settings.
- resolver: Arc<LspResolver>,
- /// The npm package requirements found in npm specifiers.
- npm_reqs_by_scope:
- Arc<BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>>,
- /// Config scopes that contain a node: specifier such that a @types/node
- /// package should be injected.
- scopes_with_node_specifier: Arc<HashSet<Option<ModuleSpecifier>>>,
+ dep_info_by_scope: Arc<BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>>,
}
impl Documents {
@@ -982,6 +1014,7 @@ impl Documents {
// the cache for remote modules here in order to get the
// x-typescript-types?
None,
+ &self.is_cjs_resolver,
self.resolver.clone(),
self.config.clone(),
&self.cache,
@@ -1016,7 +1049,7 @@ impl Documents {
))
})?;
self.dirty = true;
- let doc = doc.with_change(version, changes)?;
+ let doc = doc.with_change(&self.is_cjs_resolver, version, changes)?;
self.open_docs.insert(doc.specifier().clone(), doc.clone());
Ok(doc)
}
@@ -1071,34 +1104,6 @@ impl Documents {
self.cache.is_valid_file_referrer(specifier)
}
- /// Return `true` if the provided specifier can be resolved to a document,
- /// otherwise `false`.
- pub fn contains_import(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- ) -> bool {
- let file_referrer = self.get_file_referrer(referrer);
- let maybe_specifier = self
- .resolver
- .as_graph_resolver(file_referrer.as_deref())
- .resolve(
- specifier,
- &deno_graph::Range {
- specifier: referrer.clone(),
- start: deno_graph::Position::zeroed(),
- end: deno_graph::Position::zeroed(),
- },
- ResolutionMode::Types,
- )
- .ok();
- if let Some(import_specifier) = maybe_specifier {
- self.exists(&import_specifier, file_referrer.as_deref())
- } else {
- false
- }
- }
-
pub fn resolve_document_specifier(
&self,
specifier: &ModuleSpecifier,
@@ -1147,17 +1152,20 @@ impl Documents {
false
}
- pub fn npm_reqs_by_scope(
+ pub fn dep_info_by_scope(
&mut self,
- ) -> Arc<BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>> {
- self.calculate_npm_reqs_if_dirty();
- self.npm_reqs_by_scope.clone()
+ ) -> Arc<BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>> {
+ self.calculate_dep_info_if_dirty();
+ self.dep_info_by_scope.clone()
}
- pub fn scopes_with_node_specifier(
- &self,
- ) -> &Arc<HashSet<Option<ModuleSpecifier>>> {
- &self.scopes_with_node_specifier
+ pub fn scopes_with_node_specifier(&self) -> HashSet<Option<ModuleSpecifier>> {
+ self
+ .dep_info_by_scope
+ .iter()
+ .filter(|(_, i)| i.has_node_specifier)
+ .map(|(s, _)| s.clone())
+ .collect::<HashSet<_>>()
}
/// Return a document for the specifier.
@@ -1173,6 +1181,7 @@ impl Documents {
if let Some(old_doc) = old_doc {
self.file_system_docs.get(
specifier,
+ &self.is_cjs_resolver,
&self.resolver,
&self.config,
&self.cache,
@@ -1197,6 +1206,7 @@ impl Documents {
} else {
self.file_system_docs.get(
&specifier,
+ &self.is_cjs_resolver,
&self.resolver,
&self.config,
&self.cache,
@@ -1255,12 +1265,15 @@ impl Documents {
referrer: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Vec<Option<(ModuleSpecifier, MediaType)>> {
- let document = self.get(referrer);
- let file_referrer = document
+ let referrer_doc = self.get(referrer);
+ let file_referrer = referrer_doc
.as_ref()
.and_then(|d| d.file_referrer())
.or(file_referrer);
- let dependencies = document.as_ref().map(|d| d.dependencies());
+ let dependencies = referrer_doc.as_ref().map(|d| d.dependencies());
+ let referrer_kind = self
+ .is_cjs_resolver
+ .get_maybe_doc_module_kind(referrer, referrer_doc.as_deref());
let mut results = Vec::new();
for raw_specifier in raw_specifiers {
if raw_specifier.starts_with("asset:") {
@@ -1277,31 +1290,35 @@ impl Documents {
results.push(self.resolve_dependency(
specifier,
referrer,
+ referrer_kind,
file_referrer,
));
} else if let Some(specifier) = dep.maybe_code.maybe_specifier() {
results.push(self.resolve_dependency(
specifier,
referrer,
+ referrer_kind,
file_referrer,
));
} else {
results.push(None);
}
} else if let Ok(specifier) =
- self.resolver.as_graph_resolver(file_referrer).resolve(
+ self.resolver.as_cli_resolver(file_referrer).resolve(
raw_specifier,
&deno_graph::Range {
specifier: referrer.clone(),
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
},
+ referrer_kind,
ResolutionMode::Types,
)
{
results.push(self.resolve_dependency(
&specifier,
referrer,
+ referrer_kind,
file_referrer,
));
} else {
@@ -1320,7 +1337,11 @@ impl Documents {
) {
self.config = Arc::new(config.clone());
self.cache = Arc::new(cache.clone());
+ self.is_cjs_resolver = Arc::new(LspIsCjsResolver::new(cache));
self.resolver = resolver.clone();
+
+ node_resolver::PackageJsonThreadLocalCache::clear();
+
{
let fs_docs = &self.file_system_docs;
// Clean up non-existent documents.
@@ -1340,14 +1361,21 @@ impl Documents {
if !config.specifier_enabled(doc.specifier()) {
continue;
}
- *doc = doc.with_new_config(self.resolver.clone(), self.config.clone());
+ *doc = doc.with_new_config(
+ &self.is_cjs_resolver,
+ self.resolver.clone(),
+ self.config.clone(),
+ );
}
for mut doc in self.file_system_docs.docs.iter_mut() {
if !config.specifier_enabled(doc.specifier()) {
continue;
}
- *doc.value_mut() =
- doc.with_new_config(self.resolver.clone(), self.config.clone());
+ *doc.value_mut() = doc.with_new_config(
+ &self.is_cjs_resolver,
+ self.resolver.clone(),
+ self.config.clone(),
+ );
}
self.open_docs = open_docs;
let mut preload_count = 0;
@@ -1364,6 +1392,7 @@ impl Documents {
{
fs_docs.refresh_document(
specifier,
+ &self.is_cjs_resolver,
&self.resolver,
&self.config,
&self.cache,
@@ -1379,34 +1408,46 @@ impl Documents {
/// Iterate through the documents, building a map where the key is a unique
/// document and the value is a set of specifiers that depend on that
/// document.
- fn calculate_npm_reqs_if_dirty(&mut self) {
- let mut npm_reqs_by_scope: BTreeMap<_, BTreeSet<_>> = Default::default();
- let mut scopes_with_specifier = HashSet::new();
+ fn calculate_dep_info_if_dirty(&mut self) {
+ let mut dep_info_by_scope: BTreeMap<_, ScopeDepInfo> = Default::default();
let is_fs_docs_dirty = self.file_system_docs.set_dirty(false);
if !is_fs_docs_dirty && !self.dirty {
return;
}
let mut visit_doc = |doc: &Arc<Document>| {
let scope = doc.scope();
- let reqs = npm_reqs_by_scope.entry(scope.cloned()).or_default();
+ let dep_info = dep_info_by_scope.entry(scope.cloned()).or_default();
for dependency in doc.dependencies().values() {
- if let Some(dep) = dependency.get_code() {
+ let code_specifier = dependency.get_code();
+ let type_specifier = dependency.get_type();
+ if let Some(dep) = code_specifier {
if dep.scheme() == "node" {
- scopes_with_specifier.insert(scope.cloned());
+ dep_info.has_node_specifier = true;
}
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
- reqs.insert(reference.into_inner().req);
+ dep_info.npm_reqs.insert(reference.into_inner().req);
}
}
- if let Some(dep) = dependency.get_type() {
+ if let Some(dep) = type_specifier {
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
- reqs.insert(reference.into_inner().req);
+ dep_info.npm_reqs.insert(reference.into_inner().req);
+ }
+ }
+ if dependency.maybe_deno_types_specifier.is_some() {
+ if let (Some(code_specifier), Some(type_specifier)) =
+ (code_specifier, type_specifier)
+ {
+ if MediaType::from_specifier(type_specifier).is_declaration() {
+ dep_info
+ .deno_types_to_code_resolutions
+ .insert(type_specifier.clone(), code_specifier.clone());
+ }
}
}
}
if let Some(dep) = doc.maybe_types_dependency().maybe_specifier() {
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
- reqs.insert(reference.into_inner().req);
+ dep_info.npm_reqs.insert(reference.into_inner().req);
}
}
};
@@ -1417,14 +1458,49 @@ impl Documents {
visit_doc(doc);
}
- // fill the reqs from the lockfile
for (scope, config_data) in self.config.tree.data_by_scope().as_ref() {
+ let dep_info = dep_info_by_scope.entry(Some(scope.clone())).or_default();
+ (|| {
+ let config_file = config_data.maybe_deno_json()?;
+ let jsx_config =
+ config_file.to_maybe_jsx_import_source_config().ok()??;
+ let type_specifier = jsx_config.default_types_specifier.as_ref()?;
+ let code_specifier = jsx_config.default_specifier.as_ref()?;
+ let cli_resolver = self.resolver.as_cli_resolver(Some(scope));
+ let range = deno_graph::Range {
+ specifier: jsx_config.base_url.clone(),
+ start: deno_graph::Position::zeroed(),
+ end: deno_graph::Position::zeroed(),
+ };
+ let type_specifier = cli_resolver
+ .resolve(
+ type_specifier,
+ &range,
+ // todo(dsherret): this is wrong because it doesn't consider CJS referrers
+ deno_package_json::NodeModuleKind::Esm,
+ ResolutionMode::Types,
+ )
+ .ok()?;
+ let code_specifier = cli_resolver
+ .resolve(
+ code_specifier,
+ &range,
+ // todo(dsherret): this is wrong because it doesn't consider CJS referrers
+ deno_package_json::NodeModuleKind::Esm,
+ ResolutionMode::Execution,
+ )
+ .ok()?;
+ dep_info
+ .deno_types_to_code_resolutions
+ .insert(type_specifier, code_specifier);
+ Some(())
+ })();
+ // fill the reqs from the lockfile
if let Some(lockfile) = config_data.lockfile.as_ref() {
- let reqs = npm_reqs_by_scope.entry(Some(scope.clone())).or_default();
let lockfile = lockfile.lock();
for dep_req in lockfile.content.packages.specifiers.keys() {
if dep_req.kind == deno_semver::package::PackageKind::Npm {
- reqs.insert(dep_req.req.clone());
+ dep_info.npm_reqs.insert(dep_req.req.clone());
}
}
}
@@ -1433,15 +1509,22 @@ impl Documents {
// Ensure a @types/node package exists when any module uses a node: specifier.
// Unlike on the command line, here we just add @types/node to the npm package
// requirements since this won't end up in the lockfile.
- for scope in &scopes_with_specifier {
- let reqs = npm_reqs_by_scope.entry(scope.clone()).or_default();
- if !reqs.iter().any(|r| r.name == "@types/node") {
- reqs.insert(PackageReq::from_str("@types/node").unwrap());
+ for dep_info in dep_info_by_scope.values_mut() {
+ if dep_info.has_node_specifier
+ && !dep_info.npm_reqs.iter().any(|r| r.name == "@types/node")
+ {
+ dep_info
+ .npm_reqs
+ .insert(PackageReq::from_str("@types/node").unwrap());
}
}
- self.npm_reqs_by_scope = Arc::new(npm_reqs_by_scope);
- self.scopes_with_node_specifier = Arc::new(scopes_with_specifier);
+ self.dep_info_by_scope = Arc::new(
+ dep_info_by_scope
+ .into_iter()
+ .map(|(s, i)| (s, Arc::new(i)))
+ .collect(),
+ );
self.dirty = false;
}
@@ -1449,6 +1532,7 @@ impl Documents {
&self,
specifier: &ModuleSpecifier,
referrer: &ModuleSpecifier,
+ referrer_kind: NodeModuleKind,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<(ModuleSpecifier, MediaType)> {
if let Some(module_name) = specifier.as_str().strip_prefix("node:") {
@@ -1462,10 +1546,12 @@ impl Documents {
let mut specifier = specifier.clone();
let mut media_type = None;
if let Ok(npm_ref) = NpmPackageReqReference::from_specifier(&specifier) {
- let (s, mt) =
- self
- .resolver
- .npm_to_file_url(&npm_ref, referrer, file_referrer)?;
+ let (s, mt) = self.resolver.npm_to_file_url(
+ &npm_ref,
+ referrer,
+ referrer_kind,
+ file_referrer,
+ )?;
specifier = s;
media_type = Some(mt);
}
@@ -1475,7 +1561,8 @@ impl Documents {
return Some((specifier, media_type));
};
if let Some(types) = doc.maybe_types_dependency().maybe_specifier() {
- self.resolve_dependency(types, &specifier, file_referrer)
+ let specifier_kind = self.is_cjs_resolver.get_doc_module_kind(&doc);
+ self.resolve_dependency(types, &specifier, specifier_kind, file_referrer)
} else {
Some((doc.specifier().clone(), doc.media_type()))
}
@@ -1543,6 +1630,7 @@ fn parse_and_analyze_module(
maybe_headers: Option<&HashMap<String, String>>,
media_type: MediaType,
file_referrer: Option<&ModuleSpecifier>,
+ is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver,
) -> (Option<ParsedSourceResult>, Option<ModuleResult>) {
let parsed_source_result = parse_source(specifier.clone(), text, media_type);
@@ -1551,6 +1639,7 @@ fn parse_and_analyze_module(
&parsed_source_result,
maybe_headers,
file_referrer,
+ is_cjs_resolver,
resolver,
);
(Some(parsed_source_result), Some(module_result))
@@ -1561,7 +1650,7 @@ fn parse_source(
text: Arc<str>,
media_type: MediaType,
) -> ParsedSourceResult {
- deno_ast::parse_module(deno_ast::ParseParams {
+ deno_ast::parse_program(deno_ast::ParseParams {
specifier,
text,
media_type,
@@ -1576,11 +1665,26 @@ fn analyze_module(
parsed_source_result: &ParsedSourceResult,
maybe_headers: Option<&HashMap<String, String>>,
file_referrer: Option<&ModuleSpecifier>,
+ is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver,
) -> ModuleResult {
match parsed_source_result {
Ok(parsed_source) => {
let npm_resolver = resolver.create_graph_npm_resolver(file_referrer);
+ let cli_resolver = resolver.as_cli_resolver(file_referrer);
+ let config_data = resolver.as_config_data(file_referrer);
+ let valid_referrer = specifier.clone();
+ let jsx_import_source_config =
+ config_data.and_then(|d| d.maybe_jsx_import_source_config());
+ let resolver = SingleReferrerGraphResolver {
+ valid_referrer: &valid_referrer,
+ referrer_kind: is_cjs_resolver.get_lsp_referrer_kind(
+ &specifier,
+ Some(parsed_source.compute_is_script()),
+ ),
+ cli_resolver,
+ jsx_import_source_config: jsx_import_source_config.as_ref(),
+ };
Ok(deno_graph::parse_module_from_ast(
deno_graph::ParseModuleFromAstOptions {
graph_kind: deno_graph::GraphKind::TypesOnly,
@@ -1591,7 +1695,7 @@ fn analyze_module(
// dynamic imports like import(`./dir/${something}`) in the LSP
file_system: &deno_graph::source::NullFileSystem,
jsr_url_provider: &CliJsrUrlProvider,
- maybe_resolver: Some(resolver.as_graph_resolver(file_referrer)),
+ maybe_resolver: Some(&resolver),
maybe_npm_resolver: Some(&npm_resolver),
},
))
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 8269dc851..2ce26c1f2 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -22,6 +22,7 @@ use deno_semver::jsr::JsrPackageReqReference;
use indexmap::Equivalent;
use indexmap::IndexSet;
use log::error;
+use node_resolver::NodeModuleKind;
use serde::Deserialize;
use serde_json::from_value;
use std::collections::BTreeMap;
@@ -77,6 +78,7 @@ use super::parent_process_checker;
use super::performance::Performance;
use super::refactor;
use super::registries::ModuleRegistry;
+use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver;
use super::testing;
use super::text;
@@ -144,6 +146,7 @@ pub struct StateSnapshot {
pub project_version: usize,
pub assets: AssetsSnapshot,
pub config: Arc<Config>,
+ pub is_cjs_resolver: Arc<LspIsCjsResolver>,
pub documents: Arc<Documents>,
pub resolver: Arc<LspResolver>,
}
@@ -203,6 +206,7 @@ pub struct Inner {
pub documents: Documents,
http_client_provider: Arc<HttpClientProvider>,
initial_cwd: PathBuf,
+ pub is_cjs_resolver: Arc<LspIsCjsResolver>,
jsr_search_api: CliJsrSearchApi,
/// Handles module registries, which allow discovery of modules
module_registry: ModuleRegistry,
@@ -480,6 +484,7 @@ impl Inner {
let initial_cwd = std::env::current_dir().unwrap_or_else(|_| {
panic!("Could not resolve current working directory")
});
+ let is_cjs_resolver = Arc::new(LspIsCjsResolver::new(&cache));
Self {
assets,
@@ -491,6 +496,7 @@ impl Inner {
documents,
http_client_provider,
initial_cwd: initial_cwd.clone(),
+ is_cjs_resolver,
jsr_search_api,
project_version: 0,
task_queue: Default::default(),
@@ -601,6 +607,7 @@ impl Inner {
project_version: self.project_version,
assets: self.assets.snapshot(),
config: Arc::new(self.config.clone()),
+ is_cjs_resolver: self.is_cjs_resolver.clone(),
documents: Arc::new(self.documents.clone()),
resolver: self.resolver.snapshot(),
})
@@ -622,6 +629,7 @@ impl Inner {
}
});
self.cache = LspCache::new(global_cache_url);
+ self.is_cjs_resolver = Arc::new(LspIsCjsResolver::new(&self.cache));
let deno_dir = self.cache.deno_dir();
let workspace_settings = self.config.workspace_settings();
let maybe_root_path = self
@@ -863,7 +871,10 @@ impl Inner {
// We ignore these directories by default because there is a
// high likelihood they aren't relevant. Someone can opt-into
// them by specifying one of them as an enabled path.
- if matches!(dir_name.as_str(), "vendor" | "node_modules" | ".git") {
+ if matches!(
+ dir_name.as_str(),
+ "vendor" | "coverage" | "node_modules" | ".git"
+ ) {
continue;
}
// ignore cargo target directories for anyone using Deno with Rust
@@ -904,7 +915,7 @@ impl Inner {
| MediaType::Tsx => {}
MediaType::Wasm
| MediaType::SourceMap
- | MediaType::TsBuildInfo
+ | MediaType::Css
| MediaType::Unknown => {
if path.extension().and_then(|s| s.to_str()) != Some("jsonc") {
continue;
@@ -963,6 +974,11 @@ impl Inner {
.tree
.refresh(&self.config.settings, &self.workspace_files, &file_fetcher)
.await;
+ self
+ .client
+ .send_did_refresh_deno_configuration_tree_notification(
+ self.config.tree.to_did_refresh_params(),
+ );
for config_file in self.config.tree.config_files() {
(|| {
let compiler_options = config_file.to_compiler_options().ok()?.options;
@@ -974,7 +990,7 @@ impl Inner {
spawn(async move {
let specifier = {
let inner = ls.inner.read().await;
- let resolver = inner.resolver.as_graph_resolver(Some(&referrer));
+ let resolver = inner.resolver.as_cli_resolver(Some(&referrer));
let Ok(specifier) = resolver.resolve(
&specifier,
&deno_graph::Range {
@@ -982,6 +998,7 @@ impl Inner {
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
},
+ NodeModuleKind::Esm,
deno_graph::source::ResolutionMode::Types,
) else {
return;
@@ -1019,7 +1036,7 @@ impl Inner {
// refresh the npm specifiers because it might have discovered
// a @types/node package and now's a good time to do that anyway
- self.refresh_npm_specifiers().await;
+ self.refresh_dep_info().await;
self.project_changed([], true);
}
@@ -1065,7 +1082,7 @@ impl Inner {
);
if document.is_diagnosable() {
self.project_changed([(document.specifier(), ChangeKind::Opened)], false);
- self.refresh_npm_specifiers().await;
+ self.refresh_dep_info().await;
self.diagnostics_server.invalidate(&[specifier]);
self.send_diagnostics_update();
self.send_testing_update();
@@ -1086,8 +1103,8 @@ impl Inner {
Ok(document) => {
if document.is_diagnosable() {
let old_scopes_with_node_specifier =
- self.documents.scopes_with_node_specifier().clone();
- self.refresh_npm_specifiers().await;
+ self.documents.scopes_with_node_specifier();
+ self.refresh_dep_info().await;
let mut config_changed = false;
if !self
.documents
@@ -1138,13 +1155,15 @@ impl Inner {
}));
}
- async fn refresh_npm_specifiers(&mut self) {
- let package_reqs = self.documents.npm_reqs_by_scope();
+ async fn refresh_dep_info(&mut self) {
+ let dep_info_by_scope = self.documents.dep_info_by_scope();
let resolver = self.resolver.clone();
// spawn due to the lsp's `Send` requirement
- spawn(async move { resolver.set_npm_reqs(&package_reqs).await })
- .await
- .ok();
+ spawn(
+ async move { resolver.set_dep_info_by_scope(&dep_info_by_scope).await },
+ )
+ .await
+ .ok();
}
async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
@@ -1163,7 +1182,7 @@ impl Inner {
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
self.diagnostics_state.clear(&specifier);
if self.is_diagnosable(&specifier) {
- self.refresh_npm_specifiers().await;
+ self.refresh_dep_info().await;
self.diagnostics_server.invalidate(&[specifier.clone()]);
self.send_diagnostics_update();
self.send_testing_update();
@@ -1377,16 +1396,14 @@ impl Inner {
.fmt_config_for_specifier(&specifier)
.options
.clone();
- fmt_options.use_tabs = Some(!params.options.insert_spaces);
- fmt_options.indent_width = Some(params.options.tab_size as u8);
- let maybe_workspace = self
- .config
- .tree
- .data_for_specifier(&specifier)
- .map(|d| &d.member_dir.workspace);
+ let config_data = self.config.tree.data_for_specifier(&specifier);
+ if !config_data.is_some_and(|d| d.maybe_deno_json().is_some()) {
+ fmt_options.use_tabs = Some(!params.options.insert_spaces);
+ fmt_options.indent_width = Some(params.options.tab_size as u8);
+ }
let unstable_options = UnstableFmtOptions {
- component: maybe_workspace
- .map(|w| w.has_unstable("fmt-component"))
+ component: config_data
+ .map(|d| d.unstable.contains("fmt-component"))
.unwrap_or(false),
};
let document = document.clone();
@@ -1618,6 +1635,10 @@ impl Inner {
let file_diagnostics = self
.diagnostics_server
.get_ts_diagnostics(&specifier, asset_or_doc.document_lsp_version());
+ let specifier_kind = asset_or_doc
+ .document()
+ .map(|d| self.is_cjs_resolver.get_doc_module_kind(d))
+ .unwrap_or(NodeModuleKind::Esm);
let mut includes_no_cache = false;
for diagnostic in &fixable_diagnostics {
match diagnostic.source.as_deref() {
@@ -1656,7 +1677,13 @@ impl Inner {
.await;
for action in actions {
code_actions
- .add_ts_fix_action(&specifier, &action, diagnostic, self)
+ .add_ts_fix_action(
+ &specifier,
+ specifier_kind,
+ &action,
+ diagnostic,
+ self,
+ )
.map_err(|err| {
error!("Unable to convert fix: {:#}", err);
LspError::internal_error()
@@ -1802,10 +1829,9 @@ impl Inner {
error!("Unable to decode code action data: {:#}", err);
LspError::invalid_params("The CodeAction's data is invalid.")
})?;
- let scope = self
- .get_asset_or_document(&code_action_data.specifier)
- .ok()
- .and_then(|d| d.scope().cloned());
+ let maybe_asset_or_doc =
+ self.get_asset_or_document(&code_action_data.specifier).ok();
+ let scope = maybe_asset_or_doc.as_ref().and_then(|d| d.scope().cloned());
let combined_code_actions = self
.ts_server
.get_combined_code_fix(
@@ -1832,8 +1858,13 @@ impl Inner {
let changes = if code_action_data.fix_id == "fixMissingImport" {
fix_ts_import_changes(
&code_action_data.specifier,
+ maybe_asset_or_doc
+ .as_ref()
+ .and_then(|d| d.document())
+ .map(|d| self.is_cjs_resolver.get_doc_module_kind(d))
+ .unwrap_or(NodeModuleKind::Esm),
&combined_code_actions.changes,
- &self.get_ts_response_import_mapper(&code_action_data.specifier),
+ self,
)
.map_err(|err| {
error!("Unable to remap changes: {:#}", err);
@@ -1885,8 +1916,12 @@ impl Inner {
if kind_suffix == ".rewrite.function.returnType" {
refactor_edit_info.edits = fix_ts_import_changes(
&action_data.specifier,
+ asset_or_doc
+ .document()
+ .map(|d| self.is_cjs_resolver.get_doc_module_kind(d))
+ .unwrap_or(NodeModuleKind::Esm),
&refactor_edit_info.edits,
- &self.get_ts_response_import_mapper(&action_data.specifier),
+ self,
)
.map_err(|err| {
error!("Unable to remap changes: {:#}", err);
@@ -1917,7 +1952,8 @@ impl Inner {
// todo(dsherret): this should probably just take the resolver itself
// as the import map is an implementation detail
.and_then(|d| d.resolver.maybe_import_map()),
- self.resolver.as_ref(),
+ &self.resolver,
+ &self.ts_server.specifier_map,
file_referrer,
)
}
@@ -2233,6 +2269,7 @@ impl Inner {
&self.jsr_search_api,
&self.npm_search_api,
&self.documents,
+ &self.is_cjs_resolver,
self.resolver.as_ref(),
self
.config
@@ -2280,7 +2317,11 @@ impl Inner {
.into(),
scope.cloned(),
)
- .await;
+ .await
+ .unwrap_or_else(|err| {
+ error!("Unable to get completion info from TypeScript: {:#}", err);
+ None
+ });
if let Some(completions) = maybe_completion_info {
response = Some(
@@ -3563,15 +3604,16 @@ impl Inner {
if byonm {
roots.retain(|s| s.scheme() != "npm");
- } else if let Some(npm_reqs) = self
+ } else if let Some(dep_info) = self
.documents
- .npm_reqs_by_scope()
+ .dep_info_by_scope()
.get(&config_data.map(|d| d.scope.as_ref().clone()))
{
// always include the npm packages since resolution of one npm package
// might affect the resolution of other npm packages
roots.extend(
- npm_reqs
+ dep_info
+ .npm_reqs
.iter()
.map(|req| ModuleSpecifier::parse(&format!("npm:{}", req)).unwrap()),
);
@@ -3592,9 +3634,8 @@ impl Inner {
deno_json_cache: None,
pkg_json_cache: None,
workspace_cache: None,
- config_parse_options: deno_config::deno_json::ConfigParseOptions {
- include_task_comments: false,
- },
+ config_parse_options:
+ deno_config::deno_json::ConfigParseOptions::default(),
additional_config_file_names: &[],
discover_pkg_json: !has_flag_env_var("DENO_NO_PACKAGE_JSON"),
maybe_vendor_override: if force_global_cache {
@@ -3649,7 +3690,7 @@ impl Inner {
async fn post_cache(&mut self) {
self.resolver.did_cache();
- self.refresh_npm_specifiers().await;
+ self.refresh_dep_info().await;
self.diagnostics_server.invalidate_all();
self.project_changed([], true);
self.ts_server.cleanup_semantic_cache(self.snapshot()).await;
@@ -3807,7 +3848,7 @@ impl Inner {
let maybe_inlay_hints = maybe_inlay_hints.map(|hints| {
hints
.iter()
- .map(|hint| hint.to_lsp(line_index.clone()))
+ .map(|hint| hint.to_lsp(line_index.clone(), self))
.collect()
});
self.performance.measure(mark);
@@ -3943,7 +3984,9 @@ mod tests {
fn test_walk_workspace() {
let temp_dir = TempDir::new();
temp_dir.create_dir_all("root1/vendor/");
+ temp_dir.create_dir_all("root1/coverage/");
temp_dir.write("root1/vendor/mod.ts", ""); // no, vendor
+ temp_dir.write("root1/coverage/mod.ts", ""); // no, coverage
temp_dir.create_dir_all("root1/node_modules/");
temp_dir.write("root1/node_modules/mod.ts", ""); // no, node_modules
diff --git a/cli/lsp/lsp_custom.rs b/cli/lsp/lsp_custom.rs
index 5f485db7a..b570b6d0e 100644
--- a/cli/lsp/lsp_custom.rs
+++ b/cli/lsp/lsp_custom.rs
@@ -46,6 +46,30 @@ pub struct DiagnosticBatchNotificationParams {
pub messages_len: usize,
}
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DenoConfigurationData {
+ pub scope_uri: lsp::Uri,
+ pub workspace_root_scope_uri: Option<lsp::Uri>,
+ pub deno_json: Option<lsp::TextDocumentIdentifier>,
+ pub package_json: Option<lsp::TextDocumentIdentifier>,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DidRefreshDenoConfigurationTreeNotificationParams {
+ pub data: Vec<DenoConfigurationData>,
+}
+
+pub enum DidRefreshDenoConfigurationTreeNotification {}
+
+impl lsp::notification::Notification
+ for DidRefreshDenoConfigurationTreeNotification
+{
+ type Params = DidRefreshDenoConfigurationTreeNotificationParams;
+ const METHOD: &'static str = "deno/didRefreshDenoConfigurationTree";
+}
+
#[derive(Debug, Eq, Hash, PartialEq, Copy, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum DenoConfigurationChangeType {
@@ -88,13 +112,15 @@ pub struct DidChangeDenoConfigurationNotificationParams {
pub changes: Vec<DenoConfigurationChangeEvent>,
}
+// TODO(nayeemrmn): This is being replaced by
+// `DidRefreshDenoConfigurationTreeNotification` for Deno > v2.0.0. Remove it
+// soon.
pub enum DidChangeDenoConfigurationNotification {}
impl lsp::notification::Notification
for DidChangeDenoConfigurationNotification
{
type Params = DidChangeDenoConfigurationNotificationParams;
-
const METHOD: &'static str = "deno/didChangeDenoConfiguration";
}
@@ -102,7 +128,6 @@ pub enum DidUpgradeCheckNotification {}
impl lsp::notification::Notification for DidUpgradeCheckNotification {
type Params = DidUpgradeCheckNotificationParams;
-
const METHOD: &'static str = "deno/didUpgradeCheck";
}
@@ -125,6 +150,5 @@ pub enum DiagnosticBatchNotification {}
impl lsp::notification::Notification for DiagnosticBatchNotification {
type Params = DiagnosticBatchNotificationParams;
-
const METHOD: &'static str = "deno/internalTestDiagnosticBatch";
}
diff --git a/cli/lsp/npm.rs b/cli/lsp/npm.rs
index 8bdeb7e7d..2decfc342 100644
--- a/cli/lsp/npm.rs
+++ b/cli/lsp/npm.rs
@@ -4,6 +4,7 @@ use dashmap::DashMap;
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
use deno_core::serde_json;
+use deno_npm::npm_rc::NpmRc;
use deno_semver::package::PackageNv;
use deno_semver::Version;
use serde::Deserialize;
@@ -25,7 +26,10 @@ pub struct CliNpmSearchApi {
impl CliNpmSearchApi {
pub fn new(file_fetcher: Arc<FileFetcher>) -> Self {
- let resolver = NpmFetchResolver::new(file_fetcher.clone());
+ let resolver = NpmFetchResolver::new(
+ file_fetcher.clone(),
+ Arc::new(NpmRc::default().as_resolved(npm_registry_url()).unwrap()),
+ );
Self {
file_fetcher,
resolver,
diff --git a/cli/lsp/parent_process_checker.rs b/cli/lsp/parent_process_checker.rs
index e5b2b2f23..b8a42cd1a 100644
--- a/cli/lsp/parent_process_checker.rs
+++ b/cli/lsp/parent_process_checker.rs
@@ -11,7 +11,7 @@ pub fn start(parent_process_id: u32) {
std::thread::sleep(Duration::from_secs(10));
if !is_process_active(parent_process_id) {
- std::process::exit(1);
+ deno_runtime::exit(1);
}
});
}
diff --git a/cli/lsp/registries.rs b/cli/lsp/registries.rs
index 5f7ce0082..ade353e68 100644
--- a/cli/lsp/registries.rs
+++ b/cli/lsp/registries.rs
@@ -482,6 +482,7 @@ impl ModuleRegistry {
.fetch_with_options(FetchOptions {
specifier: &specifier,
permissions: FetchPermissionsOptionRef::AllowAll,
+ maybe_auth: None,
maybe_accept: Some("application/vnd.deno.reg.v2+json, application/vnd.deno.reg.v1+json;q=0.9, application/json;q=0.8"),
maybe_cache_setting: None,
})
diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs
index fa5809045..b4aaa8cd0 100644
--- a/cli/lsp/repl.rs
+++ b/cli/lsp/repl.rs
@@ -263,7 +263,7 @@ impl ReplLanguageServer {
}
fn get_document_uri(&self) -> Uri {
- uri_parse_unencoded(self.cwd_uri.join("$deno$repl.ts").unwrap().as_str())
+ uri_parse_unencoded(self.cwd_uri.join("$deno$repl.mts").unwrap().as_str())
.unwrap()
}
}
diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs
index c89273147..4ede79922 100644
--- a/cli/lsp/resolver.rs
+++ b/cli/lsp/resolver.rs
@@ -2,27 +2,36 @@
use dashmap::DashMap;
use deno_ast::MediaType;
+use deno_cache_dir::npm::NpmCacheDir;
use deno_cache_dir::HttpCache;
+use deno_config::deno_json::JsxImportSourceConfig;
use deno_config::workspace::PackageJsonDepResolution;
use deno_config::workspace::WorkspaceResolver;
+use deno_core::parking_lot::Mutex;
use deno_core::url::Url;
-use deno_graph::source::Resolver;
+use deno_graph::source::ResolutionMode;
use deno_graph::GraphImport;
use deno_graph::ModuleSpecifier;
+use deno_graph::Range;
use deno_npm::NpmSystemInfo;
+use deno_path_util::url_from_directory_path;
use deno_path_util::url_to_file_path;
+use deno_resolver::npm::NpmReqResolverOptions;
+use deno_resolver::DenoResolverOptions;
+use deno_resolver::NodeAndNpmReqResolver;
use deno_runtime::deno_fs;
use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_node::PackageJson;
+use deno_runtime::deno_node::PackageJsonResolver;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
use indexmap::IndexMap;
use node_resolver::errors::ClosestPkgJsonError;
-use node_resolver::NodeResolution;
+use node_resolver::InNpmPackageChecker;
+use node_resolver::NodeModuleKind;
use node_resolver::NodeResolutionMode;
-use node_resolver::NpmResolver;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
@@ -31,11 +40,14 @@ use std::collections::HashSet;
use std::sync::Arc;
use super::cache::LspCache;
+use super::documents::Document;
use super::jsr::JsrCacheResolver;
use crate::args::create_default_npmrc;
use crate::args::CacheSetting;
use crate::args::CliLockfile;
use crate::args::NpmInstallDepsProvider;
+use crate::cache::DenoCacheEnvFsAdapter;
+use crate::factory::Deferred;
use crate::graph_util::CliJsrUrlProvider;
use crate::http_util::HttpClientProvider;
use crate::lsp::config::Config;
@@ -43,40 +55,56 @@ use crate::lsp::config::ConfigData;
use crate::lsp::logging::lsp_warn;
use crate::npm::create_cli_npm_resolver_for_lsp;
use crate::npm::CliByonmNpmResolverCreateOptions;
+use crate::npm::CliManagedInNpmPkgCheckerCreateOptions;
+use crate::npm::CliManagedNpmResolverCreateOptions;
use crate::npm::CliNpmResolver;
use crate::npm::CliNpmResolverCreateOptions;
-use crate::npm::CliNpmResolverManagedCreateOptions;
use crate::npm::CliNpmResolverManagedSnapshotOption;
+use crate::npm::CreateInNpmPkgCheckerOptions;
use crate::npm::ManagedCliNpmResolver;
-use crate::resolver::CjsResolutionStore;
+use crate::resolver::CliDenoResolver;
use crate::resolver::CliDenoResolverFs;
-use crate::resolver::CliGraphResolver;
-use crate::resolver::CliGraphResolverOptions;
-use crate::resolver::CliNodeResolver;
+use crate::resolver::CliNpmReqResolver;
+use crate::resolver::CliResolver;
+use crate::resolver::CliResolverOptions;
+use crate::resolver::IsCjsResolver;
use crate::resolver::WorkerCliNpmGraphResolver;
+use crate::tsc::into_specifier_and_media_type;
+use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
#[derive(Debug, Clone)]
struct LspScopeResolver {
- graph_resolver: Arc<CliGraphResolver>,
+ resolver: Arc<CliResolver>,
+ in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
jsr_resolver: Option<Arc<JsrCacheResolver>>,
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
- node_resolver: Option<Arc<CliNodeResolver>>,
+ node_resolver: Option<Arc<NodeResolver>>,
+ npm_pkg_req_resolver: Option<Arc<CliNpmReqResolver>>,
+ pkg_json_resolver: Arc<PackageJsonResolver>,
redirect_resolver: Option<Arc<RedirectResolver>>,
graph_imports: Arc<IndexMap<ModuleSpecifier, GraphImport>>,
+ dep_info: Arc<Mutex<Arc<ScopeDepInfo>>>,
+ package_json_deps_by_resolution: Arc<IndexMap<ModuleSpecifier, String>>,
config_data: Option<Arc<ConfigData>>,
}
impl Default for LspScopeResolver {
fn default() -> Self {
+ let factory = ResolverFactory::new(None);
Self {
- graph_resolver: create_graph_resolver(None, None, None),
+ resolver: factory.cli_resolver().clone(),
+ in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(),
jsr_resolver: None,
npm_resolver: None,
node_resolver: None,
+ npm_pkg_req_resolver: None,
+ pkg_json_resolver: factory.pkg_json_resolver().clone(),
redirect_resolver: None,
graph_imports: Default::default(),
+ dep_info: Default::default(),
+ package_json_deps_by_resolution: Default::default(),
config_data: None,
}
}
@@ -88,22 +116,16 @@ impl LspScopeResolver {
cache: &LspCache,
http_client_provider: Option<&Arc<HttpClientProvider>>,
) -> Self {
- let mut npm_resolver = None;
- let mut node_resolver = None;
- if let Some(http_client) = http_client_provider {
- npm_resolver = create_npm_resolver(
- config_data.map(|d| d.as_ref()),
- cache,
- http_client,
- )
- .await;
- node_resolver = create_node_resolver(npm_resolver.as_ref());
+ let mut factory = ResolverFactory::new(config_data);
+ if let Some(http_client_provider) = http_client_provider {
+ factory.init_npm_resolver(http_client_provider, cache).await;
}
- let graph_resolver = create_graph_resolver(
- config_data.map(|d| d.as_ref()),
- npm_resolver.as_ref(),
- node_resolver.as_ref(),
- );
+ let in_npm_pkg_checker = factory.in_npm_pkg_checker().clone();
+ let npm_resolver = factory.npm_resolver().cloned();
+ let node_resolver = factory.node_resolver().cloned();
+ let npm_pkg_req_resolver = factory.npm_pkg_req_resolver().cloned();
+ let cli_resolver = factory.cli_resolver().clone();
+ let pkg_json_resolver = factory.pkg_json_resolver().clone();
let jsr_resolver = Some(Arc::new(JsrCacheResolver::new(
cache.for_specifier(config_data.map(|d| d.scope.as_ref())),
config_data.map(|d| d.as_ref()),
@@ -112,7 +134,9 @@ impl LspScopeResolver {
cache.for_specifier(config_data.map(|d| d.scope.as_ref())),
config_data.and_then(|d| d.lockfile.clone()),
)));
- let npm_graph_resolver = graph_resolver.create_graph_npm_resolver();
+ let npm_graph_resolver = cli_resolver.create_graph_npm_resolver();
+ let maybe_jsx_import_source_config =
+ config_data.and_then(|d| d.maybe_jsx_import_source_config());
let graph_imports = config_data
.and_then(|d| d.member_dir.workspace.to_compiler_option_types().ok())
.map(|imports| {
@@ -120,11 +144,18 @@ impl LspScopeResolver {
imports
.into_iter()
.map(|(referrer, imports)| {
+ let resolver = SingleReferrerGraphResolver {
+ valid_referrer: &referrer,
+ referrer_kind: NodeModuleKind::Esm,
+ cli_resolver: &cli_resolver,
+ jsx_import_source_config: maybe_jsx_import_source_config
+ .as_ref(),
+ };
let graph_import = GraphImport::new(
&referrer,
imports,
&CliJsrUrlProvider,
- Some(graph_resolver.as_ref()),
+ Some(&resolver),
Some(&npm_graph_resolver),
);
(referrer, graph_import)
@@ -133,33 +164,81 @@ impl LspScopeResolver {
)
})
.unwrap_or_default();
+ let package_json_deps_by_resolution = (|| {
+ let npm_pkg_req_resolver = npm_pkg_req_resolver.as_ref()?;
+ let package_json = config_data?.maybe_pkg_json()?;
+ let referrer = package_json.specifier();
+ let dependencies = package_json.dependencies.as_ref()?;
+ let result = dependencies
+ .iter()
+ .flat_map(|(name, _)| {
+ let req_ref =
+ NpmPackageReqReference::from_str(&format!("npm:{name}")).ok()?;
+ let specifier = into_specifier_and_media_type(Some(
+ npm_pkg_req_resolver
+ .resolve_req_reference(
+ &req_ref,
+ &referrer,
+ // todo(dsherret): this is wrong because it doesn't consider CJS referrers
+ NodeModuleKind::Esm,
+ NodeResolutionMode::Types,
+ )
+ .or_else(|_| {
+ npm_pkg_req_resolver.resolve_req_reference(
+ &req_ref,
+ &referrer,
+ // todo(dsherret): this is wrong because it doesn't consider CJS referrers
+ NodeModuleKind::Esm,
+ NodeResolutionMode::Execution,
+ )
+ })
+ .ok()?,
+ ))
+ .0;
+ Some((specifier, name.clone()))
+ })
+ .collect();
+ Some(result)
+ })();
+ let package_json_deps_by_resolution =
+ Arc::new(package_json_deps_by_resolution.unwrap_or_default());
Self {
- graph_resolver,
+ resolver: cli_resolver,
+ in_npm_pkg_checker,
jsr_resolver,
+ npm_pkg_req_resolver,
npm_resolver,
node_resolver,
+ pkg_json_resolver,
redirect_resolver,
graph_imports,
+ dep_info: Default::default(),
+ package_json_deps_by_resolution,
config_data: config_data.cloned(),
}
}
fn snapshot(&self) -> Arc<Self> {
+ let mut factory = ResolverFactory::new(self.config_data.as_ref());
let npm_resolver =
self.npm_resolver.as_ref().map(|r| r.clone_snapshotted());
- let node_resolver = create_node_resolver(npm_resolver.as_ref());
- let graph_resolver = create_graph_resolver(
- self.config_data.as_deref(),
- npm_resolver.as_ref(),
- node_resolver.as_ref(),
- );
+ if let Some(npm_resolver) = &npm_resolver {
+ factory.set_npm_resolver(npm_resolver.clone());
+ }
Arc::new(Self {
- graph_resolver,
+ resolver: factory.cli_resolver().clone(),
+ in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(),
jsr_resolver: self.jsr_resolver.clone(),
- npm_resolver,
- node_resolver,
+ npm_pkg_req_resolver: factory.npm_pkg_req_resolver().cloned(),
+ npm_resolver: factory.npm_resolver().cloned(),
+ node_resolver: factory.node_resolver().cloned(),
redirect_resolver: self.redirect_resolver.clone(),
+ pkg_json_resolver: factory.pkg_json_resolver().clone(),
graph_imports: self.graph_imports.clone(),
+ dep_info: self.dep_info.clone(),
+ package_json_deps_by_resolution: self
+ .package_json_deps_by_resolution
+ .clone(),
config_data: self.config_data.clone(),
})
}
@@ -223,19 +302,24 @@ impl LspResolver {
}
}
- pub async fn set_npm_reqs(
+ pub async fn set_dep_info_by_scope(
&self,
- reqs: &BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>,
+ dep_info_by_scope: &Arc<
+ BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>,
+ >,
) {
for (scope, resolver) in [(None, &self.unscoped)]
.into_iter()
.chain(self.by_scope.iter().map(|(s, r)| (Some(s), r)))
{
+ let dep_info = dep_info_by_scope.get(&scope.cloned());
+ if let Some(dep_info) = dep_info {
+ *resolver.dep_info.lock() = dep_info.clone();
+ }
if let Some(npm_resolver) = resolver.npm_resolver.as_ref() {
if let Some(npm_resolver) = npm_resolver.as_managed() {
- let reqs = reqs
- .get(&scope.cloned())
- .map(|reqs| reqs.iter().cloned().collect::<Vec<_>>())
+ let reqs = dep_info
+ .map(|i| i.npm_reqs.iter().cloned().collect::<Vec<_>>())
.unwrap_or_default();
if let Err(err) = npm_resolver.set_package_reqs(&reqs).await {
lsp_warn!("Could not set npm package requirements: {:#}", err);
@@ -245,12 +329,12 @@ impl LspResolver {
}
}
- pub fn as_graph_resolver(
+ pub fn as_cli_resolver(
&self,
file_referrer: Option<&ModuleSpecifier>,
- ) -> &dyn Resolver {
+ ) -> &CliResolver {
let resolver = self.get_scope_resolver(file_referrer);
- resolver.graph_resolver.as_ref()
+ resolver.resolver.as_ref()
}
pub fn create_graph_npm_resolver(
@@ -258,7 +342,23 @@ impl LspResolver {
file_referrer: Option<&ModuleSpecifier>,
) -> WorkerCliNpmGraphResolver {
let resolver = self.get_scope_resolver(file_referrer);
- resolver.graph_resolver.create_graph_npm_resolver()
+ resolver.resolver.create_graph_npm_resolver()
+ }
+
+ pub fn as_config_data(
+ &self,
+ file_referrer: Option<&ModuleSpecifier>,
+ ) -> Option<&Arc<ConfigData>> {
+ let resolver = self.get_scope_resolver(file_referrer);
+ resolver.config_data.as_ref()
+ }
+
+ pub fn in_npm_pkg_checker(
+ &self,
+ file_referrer: Option<&ModuleSpecifier>,
+ ) -> &Arc<dyn InNpmPackageChecker> {
+ let resolver = self.get_scope_resolver(file_referrer);
+ &resolver.in_npm_pkg_checker
}
pub fn maybe_managed_npm_resolver(
@@ -324,17 +424,48 @@ impl LspResolver {
&self,
req_ref: &NpmPackageReqReference,
referrer: &ModuleSpecifier,
+ referrer_kind: NodeModuleKind,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<(ModuleSpecifier, MediaType)> {
let resolver = self.get_scope_resolver(file_referrer);
- let node_resolver = resolver.node_resolver.as_ref()?;
- Some(NodeResolution::into_specifier_and_media_type(Some(
- node_resolver
- .resolve_req_reference(req_ref, referrer, NodeResolutionMode::Types)
+ let npm_pkg_req_resolver = resolver.npm_pkg_req_resolver.as_ref()?;
+ Some(into_specifier_and_media_type(Some(
+ npm_pkg_req_resolver
+ .resolve_req_reference(
+ req_ref,
+ referrer,
+ referrer_kind,
+ NodeResolutionMode::Types,
+ )
.ok()?,
)))
}
+ pub fn file_url_to_package_json_dep(
+ &self,
+ specifier: &ModuleSpecifier,
+ file_referrer: Option<&ModuleSpecifier>,
+ ) -> Option<String> {
+ let resolver = self.get_scope_resolver(file_referrer);
+ resolver
+ .package_json_deps_by_resolution
+ .get(specifier)
+ .cloned()
+ }
+
+ pub fn deno_types_to_code_resolution(
+ &self,
+ specifier: &ModuleSpecifier,
+ file_referrer: Option<&ModuleSpecifier>,
+ ) -> Option<ModuleSpecifier> {
+ let resolver = self.get_scope_resolver(file_referrer);
+ let dep_info = resolver.dep_info.lock().clone();
+ dep_info
+ .deno_types_to_code_resolutions
+ .get(specifier)
+ .cloned()
+ }
+
pub fn in_node_modules(&self, specifier: &ModuleSpecifier) -> bool {
fn has_node_modules_dir(specifier: &ModuleSpecifier) -> bool {
// consider any /node_modules/ directory as being in the node_modules
@@ -346,14 +477,10 @@ impl LspResolver {
.contains("/node_modules/")
}
- let global_npm_resolver = self
- .get_scope_resolver(Some(specifier))
- .npm_resolver
- .as_ref()
- .and_then(|npm_resolver| npm_resolver.as_managed())
- .filter(|r| r.root_node_modules_path().is_none());
- if let Some(npm_resolver) = &global_npm_resolver {
- if npm_resolver.in_npm_package(specifier) {
+ if let Some(node_resolver) =
+ &self.get_scope_resolver(Some(specifier)).node_resolver
+ {
+ if node_resolver.in_npm_package(specifier) {
return true;
}
}
@@ -361,31 +488,22 @@ impl LspResolver {
has_node_modules_dir(specifier)
}
- pub fn node_media_type(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<MediaType> {
- let resolver = self.get_scope_resolver(Some(specifier));
- let node_resolver = resolver.node_resolver.as_ref()?;
- let resolution = node_resolver
- .url_to_node_resolution(specifier.clone())
- .ok()?;
- Some(NodeResolution::into_specifier_and_media_type(Some(resolution)).1)
- }
-
pub fn is_bare_package_json_dep(
&self,
specifier_text: &str,
referrer: &ModuleSpecifier,
+ referrer_kind: NodeModuleKind,
) -> bool {
let resolver = self.get_scope_resolver(Some(referrer));
- let Some(node_resolver) = resolver.node_resolver.as_ref() else {
+ let Some(npm_pkg_req_resolver) = resolver.npm_pkg_req_resolver.as_ref()
+ else {
return false;
};
- node_resolver
+ npm_pkg_req_resolver
.resolve_if_for_npm_pkg(
specifier_text,
referrer,
+ referrer_kind,
NodeResolutionMode::Types,
)
.ok()
@@ -398,10 +516,9 @@ impl LspResolver {
referrer: &ModuleSpecifier,
) -> Result<Option<Arc<PackageJson>>, ClosestPkgJsonError> {
let resolver = self.get_scope_resolver(Some(referrer));
- let Some(node_resolver) = resolver.node_resolver.as_ref() else {
- return Ok(None);
- };
- node_resolver.get_closest_package_json(referrer)
+ resolver
+ .pkg_json_resolver
+ .get_closest_package_json(referrer)
}
pub fn resolve_redirects(
@@ -453,113 +570,213 @@ impl LspResolver {
}
}
-async fn create_npm_resolver(
- config_data: Option<&ConfigData>,
- cache: &LspCache,
- http_client_provider: &Arc<HttpClientProvider>,
-) -> Option<Arc<dyn CliNpmResolver>> {
- let enable_byonm = config_data.map(|d| d.byonm).unwrap_or(false);
- let options = if enable_byonm {
- CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions {
- fs: CliDenoResolverFs(Arc::new(deno_fs::RealFs)),
- root_node_modules_dir: config_data.and_then(|config_data| {
- config_data.node_modules_dir.clone().or_else(|| {
- url_to_file_path(&config_data.scope)
- .ok()
- .map(|p| p.join("node_modules/"))
- })
- }),
- })
- } else {
- CliNpmResolverCreateOptions::Managed(CliNpmResolverManagedCreateOptions {
- http_client_provider: http_client_provider.clone(),
- snapshot: match config_data.and_then(|d| d.lockfile.as_ref()) {
- Some(lockfile) => {
- CliNpmResolverManagedSnapshotOption::ResolveFromLockfile(
- lockfile.clone(),
- )
- }
- None => CliNpmResolverManagedSnapshotOption::Specified(None),
- },
- // Don't provide the lockfile. We don't want these resolvers
- // updating it. Only the cache request should update the lockfile.
- maybe_lockfile: None,
- fs: Arc::new(deno_fs::RealFs),
- npm_global_cache_dir: cache.deno_dir().npm_folder_path(),
- // 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.
- cache_setting: CacheSetting::Only,
- text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly),
- maybe_node_modules_path: config_data
- .and_then(|d| d.node_modules_dir.clone()),
- // only used for top level install, so we can ignore this
- npm_install_deps_provider: Arc::new(NpmInstallDepsProvider::empty()),
- npmrc: config_data
- .and_then(|d| d.npmrc.clone())
- .unwrap_or_else(create_default_npmrc),
- npm_system_info: NpmSystemInfo::default(),
- lifecycle_scripts: Default::default(),
- })
- };
- Some(create_cli_npm_resolver_for_lsp(options).await)
+#[derive(Debug, Default, Clone)]
+pub struct ScopeDepInfo {
+ pub deno_types_to_code_resolutions: HashMap<ModuleSpecifier, ModuleSpecifier>,
+ pub npm_reqs: BTreeSet<PackageReq>,
+ pub has_node_specifier: bool,
}
-fn create_node_resolver(
- npm_resolver: Option<&Arc<dyn CliNpmResolver>>,
-) -> Option<Arc<CliNodeResolver>> {
- use once_cell::sync::Lazy;
-
- // it's not ideal to share this across all scopes and to
- // never clear it, but it's fine for the time being
- static CJS_RESOLUTIONS: Lazy<Arc<CjsResolutionStore>> =
- Lazy::new(Default::default);
-
- let npm_resolver = npm_resolver?;
- let fs = Arc::new(deno_fs::RealFs);
- let node_resolver_inner = Arc::new(NodeResolver::new(
- deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
- npm_resolver.clone().into_npm_resolver(),
- ));
- Some(Arc::new(CliNodeResolver::new(
- CJS_RESOLUTIONS.clone(),
- fs,
- node_resolver_inner,
- npm_resolver.clone(),
- )))
+#[derive(Default)]
+struct ResolverFactoryServices {
+ cli_resolver: Deferred<Arc<CliResolver>>,
+ in_npm_pkg_checker: Deferred<Arc<dyn InNpmPackageChecker>>,
+ node_resolver: Deferred<Option<Arc<NodeResolver>>>,
+ npm_pkg_req_resolver: Deferred<Option<Arc<CliNpmReqResolver>>>,
+ npm_resolver: Option<Arc<dyn CliNpmResolver>>,
}
-fn create_graph_resolver(
- config_data: Option<&ConfigData>,
- npm_resolver: Option<&Arc<dyn CliNpmResolver>>,
- node_resolver: Option<&Arc<CliNodeResolver>>,
-) -> Arc<CliGraphResolver> {
- let workspace = config_data.map(|d| &d.member_dir.workspace);
- Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
- node_resolver: node_resolver.cloned(),
- npm_resolver: npm_resolver.cloned(),
- workspace_resolver: config_data.map(|d| d.resolver.clone()).unwrap_or_else(
- || {
- Arc::new(WorkspaceResolver::new_raw(
- // this is fine because this is only used before initialization
- Arc::new(ModuleSpecifier::parse("file:///").unwrap()),
- None,
- Vec::new(),
- Vec::new(),
- PackageJsonDepResolution::Disabled,
- ))
- },
- ),
- maybe_jsx_import_source_config: workspace.and_then(|workspace| {
- workspace.to_maybe_jsx_import_source_config().ok().flatten()
- }),
- maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()),
- bare_node_builtins_enabled: workspace
- .is_some_and(|workspace| workspace.has_unstable("bare-node-builtins")),
- sloppy_imports_resolver: config_data
- .and_then(|d| d.sloppy_imports_resolver.clone()),
- }))
+struct ResolverFactory<'a> {
+ config_data: Option<&'a Arc<ConfigData>>,
+ fs: Arc<dyn deno_fs::FileSystem>,
+ pkg_json_resolver: Arc<PackageJsonResolver>,
+ services: ResolverFactoryServices,
+}
+
+impl<'a> ResolverFactory<'a> {
+ pub fn new(config_data: Option<&'a Arc<ConfigData>>) -> Self {
+ let fs = Arc::new(deno_fs::RealFs);
+ let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
+ deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
+ ));
+ Self {
+ config_data,
+ fs,
+ pkg_json_resolver,
+ services: Default::default(),
+ }
+ }
+
+ async fn init_npm_resolver(
+ &mut self,
+ http_client_provider: &Arc<HttpClientProvider>,
+ cache: &LspCache,
+ ) {
+ let enable_byonm = self.config_data.map(|d| d.byonm).unwrap_or(false);
+ let options = if enable_byonm {
+ CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions {
+ fs: CliDenoResolverFs(Arc::new(deno_fs::RealFs)),
+ pkg_json_resolver: self.pkg_json_resolver.clone(),
+ root_node_modules_dir: self.config_data.and_then(|config_data| {
+ config_data.node_modules_dir.clone().or_else(|| {
+ url_to_file_path(&config_data.scope)
+ .ok()
+ .map(|p| p.join("node_modules/"))
+ })
+ }),
+ })
+ } else {
+ let npmrc = self
+ .config_data
+ .and_then(|d| d.npmrc.clone())
+ .unwrap_or_else(create_default_npmrc);
+ let npm_cache_dir = Arc::new(NpmCacheDir::new(
+ &DenoCacheEnvFsAdapter(self.fs.as_ref()),
+ cache.deno_dir().npm_folder_path(),
+ npmrc.get_all_known_registries_urls(),
+ ));
+ CliNpmResolverCreateOptions::Managed(CliManagedNpmResolverCreateOptions {
+ http_client_provider: http_client_provider.clone(),
+ snapshot: match self.config_data.and_then(|d| d.lockfile.as_ref()) {
+ Some(lockfile) => {
+ CliNpmResolverManagedSnapshotOption::ResolveFromLockfile(
+ lockfile.clone(),
+ )
+ }
+ None => CliNpmResolverManagedSnapshotOption::Specified(None),
+ },
+ // Don't provide the lockfile. We don't want these resolvers
+ // updating it. Only the cache request should update the lockfile.
+ maybe_lockfile: None,
+ fs: Arc::new(deno_fs::RealFs),
+ npm_cache_dir,
+ // 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.
+ cache_setting: CacheSetting::Only,
+ text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly),
+ maybe_node_modules_path: self
+ .config_data
+ .and_then(|d| d.node_modules_dir.clone()),
+ // only used for top level install, so we can ignore this
+ npm_install_deps_provider: Arc::new(NpmInstallDepsProvider::empty()),
+ npmrc,
+ npm_system_info: NpmSystemInfo::default(),
+ lifecycle_scripts: Default::default(),
+ })
+ };
+ self.set_npm_resolver(create_cli_npm_resolver_for_lsp(options).await);
+ }
+
+ pub fn set_npm_resolver(&mut self, npm_resolver: Arc<dyn CliNpmResolver>) {
+ self.services.npm_resolver = Some(npm_resolver);
+ }
+
+ pub fn npm_resolver(&self) -> Option<&Arc<dyn CliNpmResolver>> {
+ self.services.npm_resolver.as_ref()
+ }
+
+ pub fn cli_resolver(&self) -> &Arc<CliResolver> {
+ self.services.cli_resolver.get_or_init(|| {
+ let npm_req_resolver = self.npm_pkg_req_resolver().cloned();
+ let deno_resolver = Arc::new(CliDenoResolver::new(DenoResolverOptions {
+ in_npm_pkg_checker: self.in_npm_pkg_checker().clone(),
+ node_and_req_resolver: match (self.node_resolver(), npm_req_resolver) {
+ (Some(node_resolver), Some(npm_req_resolver)) => {
+ Some(NodeAndNpmReqResolver {
+ node_resolver: node_resolver.clone(),
+ npm_req_resolver,
+ })
+ }
+ _ => None,
+ },
+ sloppy_imports_resolver: self
+ .config_data
+ .and_then(|d| d.sloppy_imports_resolver.clone()),
+ workspace_resolver: self
+ .config_data
+ .map(|d| d.resolver.clone())
+ .unwrap_or_else(|| {
+ Arc::new(WorkspaceResolver::new_raw(
+ // this is fine because this is only used before initialization
+ Arc::new(ModuleSpecifier::parse("file:///").unwrap()),
+ None,
+ Vec::new(),
+ Vec::new(),
+ PackageJsonDepResolution::Disabled,
+ ))
+ }),
+ is_byonm: self.config_data.map(|d| d.byonm).unwrap_or(false),
+ maybe_vendor_dir: self.config_data.and_then(|d| d.vendor_dir.as_ref()),
+ }));
+ Arc::new(CliResolver::new(CliResolverOptions {
+ deno_resolver,
+ npm_resolver: self.npm_resolver().cloned(),
+ bare_node_builtins_enabled: self
+ .config_data
+ .is_some_and(|d| d.unstable.contains("bare-node-builtins")),
+ }))
+ })
+ }
+
+ pub fn pkg_json_resolver(&self) -> &Arc<PackageJsonResolver> {
+ &self.pkg_json_resolver
+ }
+
+ pub fn in_npm_pkg_checker(&self) -> &Arc<dyn InNpmPackageChecker> {
+ self.services.in_npm_pkg_checker.get_or_init(|| {
+ crate::npm::create_in_npm_pkg_checker(
+ match self.services.npm_resolver.as_ref().map(|r| r.as_inner()) {
+ Some(crate::npm::InnerCliNpmResolverRef::Byonm(_)) | None => {
+ CreateInNpmPkgCheckerOptions::Byonm
+ }
+ Some(crate::npm::InnerCliNpmResolverRef::Managed(m)) => {
+ CreateInNpmPkgCheckerOptions::Managed(
+ CliManagedInNpmPkgCheckerCreateOptions {
+ root_cache_dir_url: m.global_cache_root_url(),
+ maybe_node_modules_path: m.maybe_node_modules_path(),
+ },
+ )
+ }
+ },
+ )
+ })
+ }
+
+ pub fn node_resolver(&self) -> Option<&Arc<NodeResolver>> {
+ self
+ .services
+ .node_resolver
+ .get_or_init(|| {
+ let npm_resolver = self.services.npm_resolver.as_ref()?;
+ Some(Arc::new(NodeResolver::new(
+ deno_runtime::deno_node::DenoFsNodeResolverEnv::new(self.fs.clone()),
+ self.in_npm_pkg_checker().clone(),
+ npm_resolver.clone().into_npm_pkg_folder_resolver(),
+ self.pkg_json_resolver.clone(),
+ )))
+ })
+ .as_ref()
+ }
+
+ pub fn npm_pkg_req_resolver(&self) -> Option<&Arc<CliNpmReqResolver>> {
+ self
+ .services
+ .npm_pkg_req_resolver
+ .get_or_init(|| {
+ let node_resolver = self.node_resolver()?;
+ let npm_resolver = self.npm_resolver()?;
+ Some(Arc::new(CliNpmReqResolver::new(NpmReqResolverOptions {
+ byonm_resolver: (npm_resolver.clone()).into_maybe_byonm(),
+ fs: CliDenoResolverFs(self.fs.clone()),
+ in_npm_pkg_checker: self.in_npm_pkg_checker().clone(),
+ node_resolver: node_resolver.clone(),
+ npm_req_resolver: npm_resolver.clone().into_npm_req_resolver(),
+ })))
+ })
+ .as_ref()
+ }
}
#[derive(Debug, Eq, PartialEq)]
@@ -586,6 +803,141 @@ impl std::fmt::Debug for RedirectResolver {
}
}
+#[derive(Debug)]
+pub struct LspIsCjsResolver {
+ inner: IsCjsResolver,
+}
+
+impl Default for LspIsCjsResolver {
+ fn default() -> Self {
+ LspIsCjsResolver::new(&Default::default())
+ }
+}
+
+impl LspIsCjsResolver {
+ pub fn new(cache: &LspCache) -> Self {
+ #[derive(Debug)]
+ struct LspInNpmPackageChecker {
+ global_cache_dir: ModuleSpecifier,
+ }
+
+ impl LspInNpmPackageChecker {
+ pub fn new(cache: &LspCache) -> Self {
+ let npm_folder_path = cache.deno_dir().npm_folder_path();
+ Self {
+ global_cache_dir: url_from_directory_path(
+ &canonicalize_path_maybe_not_exists(&npm_folder_path)
+ .unwrap_or(npm_folder_path),
+ )
+ .unwrap_or_else(|_| {
+ ModuleSpecifier::parse("file:///invalid/").unwrap()
+ }),
+ }
+ }
+ }
+
+ impl InNpmPackageChecker for LspInNpmPackageChecker {
+ fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool {
+ if specifier.scheme() != "file" {
+ return false;
+ }
+ if specifier
+ .as_str()
+ .starts_with(self.global_cache_dir.as_str())
+ {
+ return true;
+ }
+ specifier.as_str().contains("/node_modules/")
+ }
+ }
+
+ let fs = Arc::new(deno_fs::RealFs);
+ let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
+ deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
+ ));
+
+ LspIsCjsResolver {
+ inner: IsCjsResolver::new(
+ Arc::new(LspInNpmPackageChecker::new(cache)),
+ pkg_json_resolver,
+ crate::resolver::IsCjsResolverOptions {
+ detect_cjs: true,
+ is_node_main: false,
+ },
+ ),
+ }
+ }
+
+ pub fn get_maybe_doc_module_kind(
+ &self,
+ specifier: &ModuleSpecifier,
+ maybe_document: Option<&Document>,
+ ) -> NodeModuleKind {
+ self.get_lsp_referrer_kind(
+ specifier,
+ maybe_document.and_then(|d| d.is_script()),
+ )
+ }
+
+ pub fn get_doc_module_kind(&self, document: &Document) -> NodeModuleKind {
+ self.get_lsp_referrer_kind(document.specifier(), document.is_script())
+ }
+
+ pub fn get_lsp_referrer_kind(
+ &self,
+ specifier: &ModuleSpecifier,
+ is_script: Option<bool>,
+ ) -> NodeModuleKind {
+ self.inner.get_lsp_referrer_kind(specifier, is_script)
+ }
+}
+
+#[derive(Debug)]
+pub struct SingleReferrerGraphResolver<'a> {
+ pub valid_referrer: &'a ModuleSpecifier,
+ pub referrer_kind: NodeModuleKind,
+ pub cli_resolver: &'a CliResolver,
+ pub jsx_import_source_config: Option<&'a JsxImportSourceConfig>,
+}
+
+impl<'a> deno_graph::source::Resolver for SingleReferrerGraphResolver<'a> {
+ fn default_jsx_import_source(&self) -> Option<String> {
+ self
+ .jsx_import_source_config
+ .and_then(|c| c.default_specifier.clone())
+ }
+
+ fn default_jsx_import_source_types(&self) -> Option<String> {
+ self
+ .jsx_import_source_config
+ .and_then(|c| c.default_types_specifier.clone())
+ }
+
+ fn jsx_import_source_module(&self) -> &str {
+ self
+ .jsx_import_source_config
+ .map(|c| c.module.as_str())
+ .unwrap_or(deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE)
+ }
+
+ fn resolve(
+ &self,
+ specifier_text: &str,
+ referrer_range: &Range,
+ mode: ResolutionMode,
+ ) -> Result<ModuleSpecifier, deno_graph::source::ResolveError> {
+ // this resolver assumes it will only be used with a single referrer
+ // with the provided referrer kind
+ debug_assert_eq!(referrer_range.specifier, *self.valid_referrer);
+ self.cli_resolver.resolve(
+ specifier_text,
+ referrer_range,
+ self.referrer_kind,
+ mode,
+ )
+ }
+}
+
impl RedirectResolver {
fn new(
cache: Arc<dyn HttpCache>,
diff --git a/cli/lsp/testing/collectors.rs b/cli/lsp/testing/collectors.rs
index 2f2ddb877..2dd7ec0d9 100644
--- a/cli/lsp/testing/collectors.rs
+++ b/cli/lsp/testing/collectors.rs
@@ -650,7 +650,7 @@ pub mod tests {
.unwrap();
let text_info = parsed_module.text_info_lazy().clone();
let mut collector = TestCollector::new(specifier, text_info);
- parsed_module.module().visit_with(&mut collector);
+ parsed_module.program().visit_with(&mut collector);
collector.take()
}
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index c8b5c47f8..fc7ff5794 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -34,6 +34,7 @@ use crate::util::path::relative_specifier;
use crate::util::path::to_percent_decoded_str;
use crate::util::result::InfallibleResultExt;
use crate::util::v8::convert;
+use crate::worker::create_isolate_create_params;
use deno_core::convert::Smi;
use deno_core::convert::ToV8;
use deno_core::error::StdAnyError;
@@ -69,6 +70,7 @@ use indexmap::IndexMap;
use indexmap::IndexSet;
use lazy_regex::lazy_regex;
use log::error;
+use node_resolver::NodeModuleKind;
use once_cell::sync::Lazy;
use regex::Captures;
use regex::Regex;
@@ -236,7 +238,7 @@ pub struct TsServer {
performance: Arc<Performance>,
sender: mpsc::UnboundedSender<Request>,
receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>,
- specifier_map: Arc<TscSpecifierMap>,
+ pub specifier_map: Arc<TscSpecifierMap>,
inspector_server: Mutex<Option<Arc<InspectorServer>>>,
pending_change: Mutex<Option<PendingChange>>,
}
@@ -882,20 +884,22 @@ impl TsServer {
options: GetCompletionsAtPositionOptions,
format_code_settings: FormatCodeSettings,
scope: Option<ModuleSpecifier>,
- ) -> Option<CompletionInfo> {
+ ) -> Result<Option<CompletionInfo>, AnyError> {
let req = TscRequest::GetCompletionsAtPosition(Box::new((
self.specifier_map.denormalize(&specifier),
position,
options,
format_code_settings,
)));
- match self.request(snapshot, req, scope).await {
- Ok(maybe_info) => maybe_info,
- Err(err) => {
- log::error!("Unable to get completion info from TypeScript: {:#}", err);
- None
- }
- }
+ self
+ .request::<Option<CompletionInfo>>(snapshot, req, scope)
+ .await
+ .map(|mut info| {
+ if let Some(info) = &mut info {
+ info.normalize(&self.specifier_map);
+ }
+ info
+ })
}
pub async fn get_completion_details(
@@ -2183,6 +2187,50 @@ impl NavigateToItem {
}
#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct InlayHintDisplayPart {
+ pub text: String,
+ pub span: Option<TextSpan>,
+ pub file: Option<String>,
+}
+
+impl InlayHintDisplayPart {
+ pub fn to_lsp(
+ &self,
+ language_server: &language_server::Inner,
+ ) -> lsp::InlayHintLabelPart {
+ let location = self.file.as_ref().map(|f| {
+ let specifier =
+ resolve_url(f).unwrap_or_else(|_| INVALID_SPECIFIER.clone());
+ let file_referrer =
+ language_server.documents.get_file_referrer(&specifier);
+ let uri = language_server
+ .url_map
+ .specifier_to_uri(&specifier, file_referrer.as_deref())
+ .unwrap_or_else(|_| INVALID_URI.clone());
+ let range = self
+ .span
+ .as_ref()
+ .and_then(|s| {
+ let asset_or_doc =
+ language_server.get_asset_or_document(&specifier).ok()?;
+ Some(s.to_range(asset_or_doc.line_index()))
+ })
+ .unwrap_or_else(|| {
+ lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0))
+ });
+ lsp::Location { uri, range }
+ });
+ lsp::InlayHintLabelPart {
+ value: self.text.clone(),
+ tooltip: None,
+ location,
+ command: None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Deserialize)]
pub enum InlayHintKind {
Type,
Parameter,
@@ -2203,6 +2251,7 @@ impl InlayHintKind {
#[serde(rename_all = "camelCase")]
pub struct InlayHint {
pub text: String,
+ pub display_parts: Option<Vec<InlayHintDisplayPart>>,
pub position: u32,
pub kind: InlayHintKind,
pub whitespace_before: Option<bool>,
@@ -2210,10 +2259,23 @@ pub struct InlayHint {
}
impl InlayHint {
- pub fn to_lsp(&self, line_index: Arc<LineIndex>) -> lsp::InlayHint {
+ pub fn to_lsp(
+ &self,
+ line_index: Arc<LineIndex>,
+ language_server: &language_server::Inner,
+ ) -> lsp::InlayHint {
lsp::InlayHint {
position: line_index.position_tsc(self.position.into()),
- label: lsp::InlayHintLabel::String(self.text.clone()),
+ label: if let Some(display_parts) = &self.display_parts {
+ lsp::InlayHintLabel::LabelParts(
+ display_parts
+ .iter()
+ .map(|p| p.to_lsp(language_server))
+ .collect(),
+ )
+ } else {
+ lsp::InlayHintLabel::String(self.text.clone())
+ },
kind: self.kind.to_lsp(),
padding_left: self.whitespace_before,
padding_right: self.whitespace_after,
@@ -3355,9 +3417,18 @@ fn parse_code_actions(
additional_text_edits.extend(change.text_changes.iter().map(|tc| {
let mut text_edit = tc.as_text_edit(asset_or_doc.line_index());
if let Some(specifier_rewrite) = &data.specifier_rewrite {
- text_edit.new_text = text_edit
- .new_text
- .replace(&specifier_rewrite.0, &specifier_rewrite.1);
+ text_edit.new_text = text_edit.new_text.replace(
+ &specifier_rewrite.old_specifier,
+ &specifier_rewrite.new_specifier,
+ );
+ if let Some(deno_types_specifier) =
+ &specifier_rewrite.new_deno_types_specifier
+ {
+ text_edit.new_text = format!(
+ "// @deno-types=\"{}\"\n{}",
+ deno_types_specifier, &text_edit.new_text
+ );
+ }
}
text_edit
}));
@@ -3516,17 +3587,23 @@ impl CompletionEntryDetails {
let mut text_edit = original_item.text_edit.clone();
if let Some(specifier_rewrite) = &data.specifier_rewrite {
if let Some(text_edit) = &mut text_edit {
- match text_edit {
- lsp::CompletionTextEdit::Edit(text_edit) => {
- text_edit.new_text = text_edit
- .new_text
- .replace(&specifier_rewrite.0, &specifier_rewrite.1);
- }
+ let new_text = match text_edit {
+ lsp::CompletionTextEdit::Edit(text_edit) => &mut text_edit.new_text,
lsp::CompletionTextEdit::InsertAndReplace(insert_replace_edit) => {
- insert_replace_edit.new_text = insert_replace_edit
- .new_text
- .replace(&specifier_rewrite.0, &specifier_rewrite.1);
+ &mut insert_replace_edit.new_text
}
+ };
+ *new_text = new_text.replace(
+ &specifier_rewrite.old_specifier,
+ &specifier_rewrite.new_specifier,
+ );
+ if let Some(deno_types_specifier) =
+ &specifier_rewrite.new_deno_types_specifier
+ {
+ *new_text = format!(
+ "// @deno-types=\"{}\"\n{}",
+ deno_types_specifier, new_text
+ );
}
}
}
@@ -3584,6 +3661,12 @@ pub struct CompletionInfo {
}
impl CompletionInfo {
+ fn normalize(&mut self, specifier_map: &TscSpecifierMap) {
+ for entry in &mut self.entries {
+ entry.normalize(specifier_map);
+ }
+ }
+
pub fn as_completion_response(
&self,
line_index: Arc<LineIndex>,
@@ -3625,6 +3708,13 @@ impl CompletionInfo {
}
}
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct CompletionSpecifierRewrite {
+ old_specifier: String,
+ new_specifier: String,
+ new_deno_types_specifier: Option<String>,
+}
+
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionItemData {
@@ -3637,7 +3727,7 @@ pub struct CompletionItemData {
/// be rewritten by replacing the first string with the second. Intended for
/// auto-import specifiers to be reverse-import-mapped.
#[serde(skip_serializing_if = "Option::is_none")]
- pub specifier_rewrite: Option<(String, String)>,
+ pub specifier_rewrite: Option<CompletionSpecifierRewrite>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
pub use_code_snippet: bool,
@@ -3645,11 +3735,17 @@ pub struct CompletionItemData {
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
-struct CompletionEntryDataImport {
+struct CompletionEntryDataAutoImport {
module_specifier: String,
file_name: String,
}
+#[derive(Debug)]
+pub struct CompletionNormalizedAutoImportData {
+ raw: CompletionEntryDataAutoImport,
+ normalized: ModuleSpecifier,
+}
+
#[derive(Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionEntry {
@@ -3682,9 +3778,28 @@ pub struct CompletionEntry {
is_import_statement_completion: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>,
+ /// This is not from tsc, we add it for convenience during normalization.
+ /// Represents `self.data.file_name`, but normalized.
+ #[serde(skip)]
+ auto_import_data: Option<CompletionNormalizedAutoImportData>,
}
impl CompletionEntry {
+ fn normalize(&mut self, specifier_map: &TscSpecifierMap) {
+ let Some(data) = &self.data else {
+ return;
+ };
+ let Ok(raw) =
+ serde_json::from_value::<CompletionEntryDataAutoImport>(data.clone())
+ else {
+ return;
+ };
+ if let Ok(normalized) = specifier_map.normalize(&raw.file_name) {
+ self.auto_import_data =
+ Some(CompletionNormalizedAutoImportData { raw, normalized });
+ }
+ }
+
fn get_commit_characters(
&self,
info: &CompletionInfo,
@@ -3833,25 +3948,44 @@ impl CompletionEntry {
if let Some(source) = &self.source {
let mut display_source = source.clone();
- if let Some(data) = &self.data {
- if let Ok(import_data) =
- serde_json::from_value::<CompletionEntryDataImport>(data.clone())
+ if let Some(import_data) = &self.auto_import_data {
+ let import_mapper =
+ language_server.get_ts_response_import_mapper(specifier);
+ if let Some(mut new_specifier) = import_mapper
+ .check_specifier(&import_data.normalized, specifier)
+ .or_else(|| relative_specifier(specifier, &import_data.normalized))
{
- if let Ok(import_specifier) = resolve_url(&import_data.file_name) {
- if let Some(new_module_specifier) = language_server
- .get_ts_response_import_mapper(specifier)
- .check_specifier(&import_specifier, specifier)
- .or_else(|| relative_specifier(specifier, &import_specifier))
- {
- display_source.clone_from(&new_module_specifier);
- if new_module_specifier != import_data.module_specifier {
- specifier_rewrite =
- Some((import_data.module_specifier, new_module_specifier));
- }
- } else if source.starts_with(jsr_url().as_str()) {
- return None;
- }
+ if new_specifier.contains("/node_modules/") {
+ return None;
+ }
+ let mut new_deno_types_specifier = None;
+ if let Some(code_specifier) = language_server
+ .resolver
+ .deno_types_to_code_resolution(
+ &import_data.normalized,
+ Some(specifier),
+ )
+ .and_then(|s| {
+ import_mapper
+ .check_specifier(&s, specifier)
+ .or_else(|| relative_specifier(specifier, &s))
+ })
+ {
+ new_deno_types_specifier =
+ Some(std::mem::replace(&mut new_specifier, code_specifier));
+ }
+ display_source.clone_from(&new_specifier);
+ if new_specifier != import_data.raw.module_specifier
+ || new_deno_types_specifier.is_some()
+ {
+ specifier_rewrite = Some(CompletionSpecifierRewrite {
+ old_specifier: import_data.raw.module_specifier.clone(),
+ new_specifier,
+ new_deno_types_specifier,
+ });
}
+ } else if source.starts_with(jsr_url().as_str()) {
+ return None;
}
}
// We want relative or bare (import-mapped or otherwise) specifiers to
@@ -3939,7 +4073,7 @@ pub struct OutliningSpan {
kind: OutliningSpanKind,
}
-const FOLD_END_PAIR_CHARACTERS: &[u8] = &[b'}', b']', b')', b'`'];
+const FOLD_END_PAIR_CHARACTERS: &[u8] = b"}])`";
impl OutliningSpan {
pub fn to_folding_range(
@@ -4154,6 +4288,11 @@ impl TscSpecifierMap {
return specifier.to_string();
}
let mut specifier = original.to_string();
+ if !specifier.contains("/node_modules/@types/node/") {
+ // The ts server doesn't give completions from files in
+ // `node_modules/.deno/`. We work around it like this.
+ specifier = specifier.replace("/node_modules/", "/$node_modules/");
+ }
let media_type = MediaType::from_specifier(original);
// If the URL-inferred media type doesn't correspond to tsc's path-inferred
// media type, force it to be the same by appending an extension.
@@ -4271,7 +4410,7 @@ fn op_is_cancelled(state: &mut OpState) -> bool {
fn op_is_node_file(state: &mut OpState, #[string] path: String) -> bool {
let state = state.borrow::<State>();
let mark = state.performance.mark("tsc.op.op_is_node_file");
- let r = match ModuleSpecifier::parse(&path) {
+ let r = match state.specifier_map.normalize(path) {
Ok(specifier) => state.state_snapshot.resolver.in_node_modules(&specifier),
Err(_) => false,
};
@@ -4308,13 +4447,16 @@ fn op_load<'s>(
data: doc.text(),
script_kind: crate::tsc::as_ts_script_kind(doc.media_type()),
version: state.script_version(&specifier),
- is_cjs: matches!(
- doc.media_type(),
- MediaType::Cjs | MediaType::Cts | MediaType::Dcts
- ),
+ is_cjs: doc
+ .document()
+ .map(|d| state.state_snapshot.is_cjs_resolver.get_doc_module_kind(d))
+ .unwrap_or(NodeModuleKind::Esm)
+ == NodeModuleKind::Cjs,
})
};
+ lsp_warn!("op_load {} {}", &specifier, maybe_load_response.is_some());
+
let serialized = serde_v8::to_v8(scope, maybe_load_response)?;
state.performance.measure(mark);
@@ -4540,7 +4682,10 @@ fn op_script_names(state: &mut OpState) -> ScriptNames {
for doc in &docs {
let specifier = doc.specifier();
let is_open = doc.is_open();
- if is_open || specifier.scheme() == "file" {
+ if is_open
+ || (specifier.scheme() == "file"
+ && !state.state_snapshot.resolver.in_node_modules(specifier))
+ {
let script_names = doc
.scope()
.and_then(|s| result.by_scope.get_mut(s))
@@ -4551,6 +4696,10 @@ fn op_script_names(state: &mut OpState) -> ScriptNames {
let (types, _) = documents.resolve_dependency(
types,
specifier,
+ state
+ .state_snapshot
+ .is_cjs_resolver
+ .get_doc_module_kind(doc),
doc.file_referrer(),
)?;
let types_doc = documents.get_or_load(&types, doc.file_referrer())?;
@@ -4654,6 +4803,7 @@ fn run_tsc_thread(
specifier_map,
request_rx,
)],
+ create_params: create_isolate_create_params(),
startup_snapshot: Some(tsc::compiler_snapshot()),
inspector: has_inspector_server,
..Default::default()
@@ -4892,6 +5042,10 @@ pub struct UserPreferences {
pub allow_rename_of_import_path: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_import_file_exclude_patterns: Option<Vec<String>>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub interactive_inlay_hints: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub prefer_type_only_auto_imports: Option<bool>,
}
impl UserPreferences {
@@ -4909,6 +5063,7 @@ impl UserPreferences {
include_completions_with_snippet_text: Some(
config.snippet_support_capable(),
),
+ interactive_inlay_hints: Some(true),
provide_refactor_not_applicable_reason: Some(true),
quote_preference: Some(fmt_config.into()),
use_label_details_in_completion_entries: Some(true),
@@ -5013,6 +5168,9 @@ impl UserPreferences {
} else {
Some(language_settings.preferences.quote_style)
},
+ prefer_type_only_auto_imports: Some(
+ language_settings.preferences.prefer_type_only_auto_imports,
+ ),
..base_preferences
}
}
@@ -5425,6 +5583,7 @@ mod tests {
documents: Arc::new(documents),
assets: Default::default(),
config: Arc::new(config),
+ is_cjs_resolver: Default::default(),
resolver,
});
let performance = Arc::new(Performance::default());
@@ -5958,6 +6117,7 @@ mod tests {
Some(temp_dir.url()),
)
.await
+ .unwrap()
.unwrap();
assert_eq!(info.entries.len(), 22);
let details = ts_server
@@ -6117,6 +6277,7 @@ mod tests {
Some(temp_dir.url()),
)
.await
+ .unwrap()
.unwrap();
let entry = info
.entries
@@ -6154,7 +6315,7 @@ mod tests {
let change = changes.text_changes.first().unwrap();
assert_eq!(
change.new_text,
- "import type { someLongVariable } from './b.ts'\n"
+ "import { someLongVariable } from './b.ts'\n"
);
}