summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/client.rs152
-rw-r--r--cli/lsp/code_lens.rs2
-rw-r--r--cli/lsp/completions.rs24
-rw-r--r--cli/lsp/config.rs466
-rw-r--r--cli/lsp/diagnostics.rs71
-rw-r--r--cli/lsp/language_server.rs251
-rw-r--r--cli/lsp/tsc.rs7
-rw-r--r--cli/tests/integration/lsp_tests.rs211
-rw-r--r--test_util/src/lsp.rs144
9 files changed, 559 insertions, 769 deletions
diff --git a/cli/lsp/client.rs b/cli/lsp/client.rs
index 210aa1da3..84953c4d4 100644
--- a/cli/lsp/client.rs
+++ b/cli/lsp/client.rs
@@ -6,14 +6,13 @@ use async_trait::async_trait;
use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
-use deno_core::serde_json;
+use deno_core::serde_json::json;
use deno_core::unsync::spawn;
use tower_lsp::lsp_types as lsp;
use tower_lsp::lsp_types::ConfigurationItem;
use crate::lsp::repl::get_repl_workspace_settings;
-use super::config::SpecifierSettings;
use super::config::WorkspaceSettings;
use super::config::SETTINGS_SECTION;
use super::lsp_custom;
@@ -125,46 +124,11 @@ impl OutsideLockClient {
self.0.register_capability(registrations).await
}
- pub async fn specifier_configurations(
- &self,
- specifiers: Vec<LspClientUrl>,
- ) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
- self
- .0
- .specifier_configurations(
- specifiers.into_iter().map(|s| s.into_url()).collect(),
- )
- .await
- }
-
- pub async fn specifier_configuration(
- &self,
- specifier: &LspClientUrl,
- ) -> Result<SpecifierSettings, AnyError> {
- let values = self
- .0
- .specifier_configurations(vec![specifier.as_url().clone()])
- .await?;
- if let Some(value) = values.into_iter().next() {
- value.map_err(|err| {
- anyhow!(
- "Error converting specifier settings ({}): {}",
- specifier,
- err
- )
- })
- } else {
- bail!(
- "Expected the client to return a configuration item for specifier: {}",
- specifier
- );
- }
- }
-
pub async fn workspace_configuration(
&self,
- ) -> Result<WorkspaceSettings, AnyError> {
- self.0.workspace_configuration().await
+ scopes: Vec<Option<lsp::Url>>,
+ ) -> Result<Vec<WorkspaceSettings>, AnyError> {
+ self.0.workspace_configuration(scopes).await
}
pub async fn publish_diagnostics(
@@ -201,13 +165,10 @@ trait ClientTrait: Send + Sync {
&self,
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
);
- async fn specifier_configurations(
- &self,
- uris: Vec<lsp::Url>,
- ) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>;
async fn workspace_configuration(
&self,
- ) -> Result<WorkspaceSettings, AnyError>;
+ scopes: Vec<Option<lsp::Url>>,
+ ) -> Result<Vec<WorkspaceSettings>, AnyError>;
async fn show_message(&self, message_type: lsp::MessageType, text: String);
async fn register_capability(
&self,
@@ -288,67 +249,50 @@ impl ClientTrait for TowerClient {
.await
}
- async fn specifier_configurations(
+ async fn workspace_configuration(
&self,
- uris: Vec<lsp::Url>,
- ) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
+ scopes: Vec<Option<lsp::Url>>,
+ ) -> Result<Vec<WorkspaceSettings>, AnyError> {
let config_response = self
.0
.configuration(
- uris
- .into_iter()
- .map(|uri| ConfigurationItem {
- scope_uri: Some(uri),
- section: Some(SETTINGS_SECTION.to_string()),
+ scopes
+ .iter()
+ .flat_map(|scope_uri| {
+ vec![
+ ConfigurationItem {
+ scope_uri: scope_uri.clone(),
+ section: Some(SETTINGS_SECTION.to_string()),
+ },
+ ConfigurationItem {
+ scope_uri: scope_uri.clone(),
+ section: Some("javascript".to_string()),
+ },
+ ConfigurationItem {
+ scope_uri: scope_uri.clone(),
+ section: Some("typescript".to_string()),
+ },
+ ]
})
.collect(),
)
- .await?;
-
- Ok(
- config_response
- .into_iter()
- .map(|value| {
- serde_json::from_value::<SpecifierSettings>(value).map_err(|err| {
- anyhow!("Error converting specifier settings: {}", err)
- })
- })
- .collect(),
- )
- }
-
- async fn workspace_configuration(
- &self,
- ) -> Result<WorkspaceSettings, AnyError> {
- let config_response = self
- .0
- .configuration(vec![
- ConfigurationItem {
- scope_uri: None,
- section: Some(SETTINGS_SECTION.to_string()),
- },
- ConfigurationItem {
- scope_uri: None,
- section: Some("javascript".to_string()),
- },
- ConfigurationItem {
- scope_uri: None,
- section: Some("typescript".to_string()),
- },
- ])
.await;
match config_response {
Ok(configs) => {
let mut configs = configs.into_iter();
- let deno = serde_json::to_value(configs.next()).unwrap();
- let javascript = serde_json::to_value(configs.next()).unwrap();
- let typescript = serde_json::to_value(configs.next()).unwrap();
- Ok(WorkspaceSettings::from_raw_settings(
- deno, javascript, typescript,
- ))
+ let mut result = Vec::with_capacity(scopes.len());
+ for _ in 0..scopes.len() {
+ let deno = json!(configs.next());
+ let javascript = json!(configs.next());
+ let typescript = json!(configs.next());
+ result.push(WorkspaceSettings::from_raw_settings(
+ deno, javascript, typescript,
+ ));
+ }
+ Ok(result)
}
Err(err) => {
- bail!("Error getting workspace configuration: {}", err)
+ bail!("Error getting workspace configurations: {}", err)
}
}
}
@@ -406,27 +350,11 @@ impl ClientTrait for ReplClient {
) {
}
- async fn specifier_configurations(
- &self,
- uris: Vec<lsp::Url>,
- ) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
- // all specifiers are enabled for the REPL
- let settings = uris
- .into_iter()
- .map(|_| {
- Ok(SpecifierSettings {
- enable: Some(true),
- ..Default::default()
- })
- })
- .collect();
- Ok(settings)
- }
-
async fn workspace_configuration(
&self,
- ) -> Result<WorkspaceSettings, AnyError> {
- Ok(get_repl_workspace_settings())
+ scopes: Vec<Option<lsp::Url>>,
+ ) -> Result<Vec<WorkspaceSettings>, AnyError> {
+ Ok(vec![get_repl_workspace_settings(); scopes.len()])
}
async fn show_message(
diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs
index cab7af1a6..d0885294f 100644
--- a/cli/lsp/code_lens.rs
+++ b/cli/lsp/code_lens.rs
@@ -408,7 +408,7 @@ fn collect_test(
config: &Config,
) -> Result<Vec<lsp::CodeLens>, AnyError> {
if config.specifier_enabled_for_test(specifier)
- && config.specifier_code_lens_test(specifier)
+ && config.enabled_code_lens_test_for_specifier(specifier)
{
if let Some(parsed_source) = parsed_source {
let mut collector =
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 08f8dbf81..2b528b010 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -2,6 +2,7 @@
use super::client::Client;
use super::config::ConfigSnapshot;
+use super::config::WorkspaceSettings;
use super::documents::file_like_to_file_specifier;
use super::documents::Documents;
use super::documents::DocumentsFilter;
@@ -52,12 +53,12 @@ pub struct CompletionItemData {
/// a notification to the client.
async fn check_auto_config_registry(
url_str: &str,
- config: &ConfigSnapshot,
+ workspace_settings: &WorkspaceSettings,
client: &Client,
module_registries: &ModuleRegistry,
) {
// check to see if auto discovery is enabled
- if config.settings.workspace.suggest.imports.auto_discover {
+ if workspace_settings.suggest.imports.auto_discover {
if let Ok(specifier) = resolve_url(url_str) {
let scheme = specifier.scheme();
let path = &specifier[Position::BeforePath..];
@@ -67,11 +68,14 @@ async fn check_auto_config_registry(
{
// check to see if this origin is already explicitly set
let in_config =
- config.settings.workspace.suggest.imports.hosts.iter().any(
- |(h, _)| {
+ workspace_settings
+ .suggest
+ .imports
+ .hosts
+ .iter()
+ .any(|(h, _)| {
resolve_url(h).map(|u| u.origin()) == Ok(specifier.origin())
- },
- );
+ });
// if it isn't in the configuration, we will check to see if it supports
// suggestions and send a notification to the client.
if !in_config {
@@ -176,7 +180,13 @@ pub async fn get_import_completions(
}))
} else if !text.is_empty() {
// completion of modules from a module registry or cache
- check_auto_config_registry(&text, config, client, module_registries).await;
+ check_auto_config_registry(
+ &text,
+ config.workspace_settings_for_specifier(specifier),
+ client,
+ module_registries,
+ )
+ .await;
let offset = if position.character > range.start.character {
(position.character - range.start.character) as usize
} else {
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 6a005e83b..ebcedb024 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -73,21 +73,6 @@ impl Default for CodeLensSettings {
}
}
-#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct CodeLensSpecifierSettings {
- /// Flag for providing test code lens on `Deno.test` statements. There is
- /// also the `test_args` setting, but this is not used by the server.
- #[serde(default = "is_true")]
- pub test: bool,
-}
-
-impl Default for CodeLensSpecifierSettings {
- fn default() -> Self {
- Self { test: true }
- }
-}
-
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DenoCompletionSettings {
@@ -277,25 +262,6 @@ impl Default for ImportCompletionSettings {
}
}
-/// Deno language server specific settings that can be applied uniquely to a
-/// specifier.
-#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct SpecifierSettings {
- /// A flag that indicates if Deno is enabled for this specifier or not.
- pub enable: Option<bool>,
- /// A list of paths, using the workspace folder as a base that should be Deno
- /// disabled.
- #[serde(default)]
- pub disable_paths: Vec<String>,
- /// A list of paths, using the workspace folder as a base that should be Deno
- /// enabled.
- pub enable_paths: Option<Vec<String>>,
- /// Code lens specific settings for the resource.
- #[serde(default)]
- pub code_lens: CodeLensSpecifierSettings,
-}
-
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TestingSettings {
@@ -712,56 +678,6 @@ impl WorkspaceSettings {
.unwrap_or_default();
Self::from_raw_settings(deno, javascript, typescript)
}
-
- /// Determine if any code lenses are enabled at all. This allows short
- /// circuiting when there are no code lenses enabled.
- pub fn enabled_code_lens(&self) -> bool {
- self.code_lens.implementations || self.code_lens.references
- }
-
- // TODO(nayeemrmn): Factor in out-of-band media type here.
- pub fn language_settings_for_specifier(
- &self,
- specifier: &ModuleSpecifier,
- ) -> Option<&LanguageWorkspaceSettings> {
- if specifier.scheme() == "deno-notebook-cell" {
- return Some(&self.typescript);
- }
- match MediaType::from_specifier(specifier) {
- MediaType::JavaScript
- | MediaType::Jsx
- | MediaType::Mjs
- | MediaType::Cjs => Some(&self.javascript),
- MediaType::TypeScript
- | MediaType::Mts
- | MediaType::Cts
- | MediaType::Dts
- | MediaType::Dmts
- | MediaType::Dcts
- | MediaType::Tsx => Some(&self.typescript),
- MediaType::Json
- | MediaType::Wasm
- | MediaType::TsBuildInfo
- | MediaType::SourceMap
- | MediaType::Unknown => None,
- }
- }
-
- /// Determine if any inlay hints are enabled. This allows short circuiting
- /// when there are no inlay hints enabled.
- pub fn enabled_inlay_hints(&self, specifier: &ModuleSpecifier) -> bool {
- let Some(settings) = self.language_settings_for_specifier(specifier) else {
- return false;
- };
- !matches!(
- settings.inlay_hints.parameter_names.enabled,
- InlayHintsParamNamesEnabled::None
- ) || settings.inlay_hints.parameter_types.enabled
- || settings.inlay_hints.variable_types.enabled
- || settings.inlay_hints.property_declaration_types.enabled
- || settings.inlay_hints.function_like_return_types.enabled
- || settings.inlay_hints.enum_member_values.enabled
- }
}
#[derive(Debug, Clone, Default)]
@@ -773,6 +689,13 @@ pub struct ConfigSnapshot {
}
impl ConfigSnapshot {
+ pub fn workspace_settings_for_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> &WorkspaceSettings {
+ self.settings.get_for_specifier(specifier).0
+ }
+
/// Determine if the provided specifier is enabled or not.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
specifier_enabled(
@@ -803,8 +726,59 @@ impl ConfigSnapshot {
#[derive(Debug, Default, Clone)]
pub struct Settings {
- pub specifiers: BTreeMap<ModuleSpecifier, SpecifierSettings>,
- pub workspace: WorkspaceSettings,
+ pub unscoped: WorkspaceSettings,
+ pub by_workspace_folder: Option<BTreeMap<ModuleSpecifier, WorkspaceSettings>>,
+}
+
+impl Settings {
+ pub fn get_unscoped(&self) -> &WorkspaceSettings {
+ &self.unscoped
+ }
+
+ pub fn get_for_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> (&WorkspaceSettings, Option<&ModuleSpecifier>) {
+ let Ok(path) = specifier_to_file_path(specifier) else {
+ return (&self.unscoped, None);
+ };
+ if let Some(by_workspace_folder) = &self.by_workspace_folder {
+ for (folder_uri, settings) in by_workspace_folder.iter().rev() {
+ let Ok(folder_path) = specifier_to_file_path(folder_uri) else {
+ continue;
+ };
+ if path.starts_with(folder_path) {
+ return (settings, Some(folder_uri));
+ }
+ }
+ }
+ (&self.unscoped, None)
+ }
+
+ pub fn set_unscoped(&mut self, mut settings: WorkspaceSettings) {
+ // See https://github.com/denoland/vscode_deno/issues/908.
+ if settings.enable_paths == Some(vec![]) {
+ settings.enable_paths = None;
+ }
+ self.unscoped = settings;
+ }
+
+ pub fn set_for_workspace_folders(
+ &mut self,
+ mut by_workspace_folder: Option<
+ BTreeMap<ModuleSpecifier, WorkspaceSettings>,
+ >,
+ ) {
+ if let Some(by_workspace_folder) = &mut by_workspace_folder {
+ for settings in by_workspace_folder.values_mut() {
+ // See https://github.com/denoland/vscode_deno/issues/908.
+ if settings.enable_paths == Some(vec![]) {
+ settings.enable_paths = None;
+ }
+ }
+ }
+ self.by_workspace_folder = by_workspace_folder;
+ }
}
#[derive(Debug)]
@@ -860,6 +834,93 @@ impl Config {
config
}
+ pub fn set_workspace_settings(
+ &mut self,
+ unscoped: WorkspaceSettings,
+ by_workspace_folder: Option<BTreeMap<ModuleSpecifier, WorkspaceSettings>>,
+ ) {
+ self.settings.set_unscoped(unscoped);
+ self.settings.set_for_workspace_folders(by_workspace_folder);
+ }
+
+ pub fn workspace_settings(&self) -> &WorkspaceSettings {
+ self.settings.get_unscoped()
+ }
+
+ pub fn workspace_settings_for_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> &WorkspaceSettings {
+ self.settings.get_for_specifier(specifier).0
+ }
+
+ pub fn language_settings_for_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<&LanguageWorkspaceSettings> {
+ let workspace_settings = self.workspace_settings_for_specifier(specifier);
+ if specifier.scheme() == "deno-notebook-cell" {
+ return Some(&workspace_settings.typescript);
+ }
+ match MediaType::from_specifier(specifier) {
+ MediaType::JavaScript
+ | MediaType::Jsx
+ | MediaType::Mjs
+ | MediaType::Cjs => Some(&workspace_settings.javascript),
+ MediaType::TypeScript
+ | MediaType::Mts
+ | MediaType::Cts
+ | MediaType::Dts
+ | MediaType::Dmts
+ | MediaType::Dcts
+ | MediaType::Tsx => Some(&workspace_settings.typescript),
+ MediaType::Json
+ | MediaType::Wasm
+ | MediaType::TsBuildInfo
+ | MediaType::SourceMap
+ | MediaType::Unknown => None,
+ }
+ }
+
+ /// Determine if any inlay hints are enabled. This allows short circuiting
+ /// when there are no inlay hints enabled.
+ pub fn enabled_inlay_hints_for_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> bool {
+ let Some(settings) = self.language_settings_for_specifier(specifier) else {
+ return false;
+ };
+ !matches!(
+ settings.inlay_hints.parameter_names.enabled,
+ InlayHintsParamNamesEnabled::None
+ ) || settings.inlay_hints.parameter_types.enabled
+ || settings.inlay_hints.variable_types.enabled
+ || settings.inlay_hints.property_declaration_types.enabled
+ || settings.inlay_hints.function_like_return_types.enabled
+ || settings.inlay_hints.enum_member_values.enabled
+ }
+
+ /// Determine if any code lenses are enabled at all. This allows short
+ /// circuiting when there are no code lenses enabled.
+ pub fn enabled_code_lens_for_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> bool {
+ let settings = self.workspace_settings_for_specifier(specifier);
+ settings.code_lens.implementations
+ || settings.code_lens.references
+ || settings.code_lens.test
+ }
+
+ pub fn enabled_code_lens_test_for_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> bool {
+ let settings = self.workspace_settings_for_specifier(specifier);
+ settings.code_lens.test
+ }
+
pub fn root_uri(&self) -> Option<&Url> {
self.workspace_folders.get(0).map(|p| &p.0)
}
@@ -949,20 +1010,6 @@ impl Config {
});
}
- pub fn workspace_settings(&self) -> &WorkspaceSettings {
- &self.settings.workspace
- }
-
- /// Set the workspace settings directly, which occurs during initialization
- /// and when the client does not support workspace configuration requests
- pub fn set_workspace_settings(&mut self, settings: WorkspaceSettings) {
- self.settings.workspace = settings;
- // See https://github.com/denoland/vscode_deno/issues/908.
- if self.settings.workspace.enable_paths == Some(vec![]) {
- self.settings.workspace.enable_paths = None;
- }
- }
-
pub fn snapshot(&self) -> Arc<ConfigSnapshot> {
Arc::new(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(),
@@ -972,10 +1019,6 @@ impl Config {
})
}
- pub fn has_specifier_settings(&self, specifier: &ModuleSpecifier) -> bool {
- self.settings.specifiers.contains_key(specifier)
- }
-
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
specifier_enabled(
specifier,
@@ -1009,11 +1052,8 @@ impl Config {
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
continue;
};
- let specifier_settings = self.settings.specifiers.get(workspace_uri);
- let enable_paths = specifier_settings
- .and_then(|s| s.enable_paths.as_ref())
- .or(self.settings.workspace.enable_paths.as_ref());
- if let Some(enable_paths) = enable_paths {
+ let settings = self.workspace_settings_for_specifier(workspace_uri);
+ if let Some(enable_paths) = &settings.enable_paths {
for path in enable_paths {
paths.push(workspace_path.join(path));
}
@@ -1035,25 +1075,14 @@ impl Config {
}
}
}
- let root_enable = self
- .settings
- .workspace
- .enable
- .unwrap_or(self.has_config_file());
for (workspace_uri, _) in &self.workspace_folders {
let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else {
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
continue;
};
- let specifier_settings = self.settings.specifiers.get(workspace_uri);
- let enable = specifier_settings
- .and_then(|s| s.enable)
- .unwrap_or(root_enable);
- if enable {
- let disable_paths = specifier_settings
- .map(|s| &s.disable_paths)
- .unwrap_or(&self.settings.workspace.disable_paths);
- for path in disable_paths {
+ let settings = self.workspace_settings_for_specifier(workspace_uri);
+ if settings.enable.unwrap_or_else(|| self.has_config_file()) {
+ for path in &settings.disable_paths {
paths.push(workspace_path.join(path));
}
} else {
@@ -1065,16 +1094,6 @@ impl Config {
paths
}
- pub fn specifier_code_lens_test(&self, specifier: &ModuleSpecifier) -> bool {
- let value = self
- .settings
- .specifiers
- .get(specifier)
- .map(|settings| settings.code_lens.test)
- .unwrap_or_else(|| self.settings.workspace.code_lens.test);
- value
- }
-
pub fn update_capabilities(
&mut self,
capabilities: &lsp::ClientCapabilities,
@@ -1127,37 +1146,13 @@ impl Config {
};
}
}
-
- pub fn get_specifiers(&self) -> Vec<ModuleSpecifier> {
- self.settings.specifiers.keys().cloned().collect()
- }
-
- pub fn set_specifier_settings(
- &mut self,
- specifier: ModuleSpecifier,
- mut settings: SpecifierSettings,
- ) -> bool {
- // See https://github.com/denoland/vscode_deno/issues/908.
- if settings.enable_paths == Some(vec![]) {
- settings.enable_paths = None;
- }
-
- if let Some(existing) = self.settings.specifiers.get(&specifier) {
- if *existing == settings {
- return false;
- }
- }
-
- self.settings.specifiers.insert(specifier, settings);
- true
- }
}
fn specifier_enabled(
specifier: &Url,
config_file: Option<&ConfigFile>,
settings: &Settings,
- workspace_folders: &Vec<(Url, lsp::WorkspaceFolder)>,
+ workspace_folders: &[(Url, lsp::WorkspaceFolder)],
) -> bool {
if let Some(cf) = config_file {
if let Some(files) = cf.to_files_config().ok().flatten() {
@@ -1166,55 +1161,42 @@ fn specifier_enabled(
}
}
}
-
- let root_enable = settings.workspace.enable.unwrap_or(config_file.is_some());
- if let Some(settings) = settings.specifiers.get(specifier) {
- // TODO(nayeemrmn): We don't know from where to resolve path lists in this
- // case. If they're detected, instead defer to workspace scopes.
- if settings.enable_paths.is_none() && settings.disable_paths.is_empty() {
- return settings.enable.unwrap_or(root_enable);
- }
- }
let Ok(path) = specifier_to_file_path(specifier) else {
// Non-file URLs are not disabled by these settings.
return true;
};
- for (workspace_uri, _) in workspace_folders {
- let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else {
- lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
- continue;
- };
- if path.starts_with(&workspace_path) {
- let specifier_settings = settings.specifiers.get(workspace_uri);
- let disable_paths = specifier_settings
- .map(|s| &s.disable_paths)
- .unwrap_or(&settings.workspace.disable_paths);
- let resolved_disable_paths = disable_paths
+ let (settings, mut folder_uri) = settings.get_for_specifier(specifier);
+ folder_uri = folder_uri.or_else(|| workspace_folders.get(0).map(|f| &f.0));
+ let mut disable_paths = vec![];
+ let mut enable_paths = None;
+ if let Some(folder_uri) = folder_uri {
+ if let Ok(folder_path) = specifier_to_file_path(folder_uri) {
+ disable_paths = settings
+ .disable_paths
.iter()
- .map(|p| workspace_path.join(p))
+ .map(|p| folder_path.join(p))
.collect::<Vec<_>>();
- let enable_paths = specifier_settings
- .and_then(|s| s.enable_paths.as_ref())
- .or(settings.workspace.enable_paths.as_ref());
- if let Some(enable_paths) = enable_paths {
- for enable_path in enable_paths {
- let enable_path = workspace_path.join(enable_path);
- if path.starts_with(&enable_path)
- && !resolved_disable_paths.iter().any(|p| path.starts_with(p))
- {
- return true;
- }
- }
- return false;
- } else {
- return specifier_settings
- .and_then(|s| s.enable)
- .unwrap_or(root_enable)
- && !resolved_disable_paths.iter().any(|p| path.starts_with(p));
+ enable_paths = settings.enable_paths.as_ref().map(|enable_paths| {
+ enable_paths
+ .iter()
+ .map(|p| folder_path.join(p))
+ .collect::<Vec<_>>()
+ });
+ }
+ }
+ if let Some(enable_paths) = &enable_paths {
+ for enable_path in enable_paths {
+ if path.starts_with(enable_path)
+ && !disable_paths.iter().any(|p| path.starts_with(p))
+ {
+ return true;
}
}
+ false
+ } else {
+ settings.enable.unwrap_or_else(|| config_file.is_some())
+ && !disable_paths.iter().any(|p| path.starts_with(p))
}
- root_enable
}
fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> {
@@ -1285,6 +1267,7 @@ mod tests {
"enable": true
}))
.unwrap(),
+ None,
);
assert!(config.specifier_enabled(&specifier));
}
@@ -1300,6 +1283,7 @@ mod tests {
"enable": true
}))
.unwrap(),
+ None,
);
let config_snapshot = config.snapshot();
assert!(config_snapshot.specifier_enabled(&specifier));
@@ -1315,7 +1299,7 @@ mod tests {
assert!(!config.specifier_enabled(&specifier_b));
let workspace_settings =
serde_json::from_str(r#"{ "enablePaths": ["worker"] }"#).unwrap();
- config.set_workspace_settings(workspace_settings);
+ config.set_workspace_settings(workspace_settings, None);
assert!(config.specifier_enabled(&specifier_a));
assert!(!config.specifier_enabled(&specifier_b));
let config_snapshot = config.snapshot();
@@ -1327,10 +1311,10 @@ mod tests {
fn test_config_specifier_disabled_path() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone());
- config.settings.workspace.enable = Some(true);
- config.settings.workspace.enable_paths =
+ config.settings.unscoped.enable = Some(true);
+ config.settings.unscoped.enable_paths =
Some(vec!["mod1.ts".to_string(), "mod2.ts".to_string()]);
- config.settings.workspace.disable_paths = vec!["mod2.ts".to_string()];
+ config.settings.unscoped.disable_paths = vec!["mod2.ts".to_string()];
assert!(config.specifier_enabled(&root_uri.join("mod1.ts").unwrap()));
assert!(!config.specifier_enabled(&root_uri.join("mod2.ts").unwrap()));
@@ -1340,7 +1324,8 @@ mod tests {
#[test]
fn test_set_workspace_settings_defaults() {
let mut config = Config::new();
- config.set_workspace_settings(serde_json::from_value(json!({})).unwrap());
+ config
+ .set_workspace_settings(serde_json::from_value(json!({})).unwrap(), None);
assert_eq!(
config.workspace_settings().clone(),
WorkspaceSettings {
@@ -1472,6 +1457,7 @@ mod tests {
let mut config = Config::new();
config.set_workspace_settings(
serde_json::from_value(json!({ "cache": "" })).unwrap(),
+ None,
);
assert_eq!(
config.workspace_settings().clone(),
@@ -1484,6 +1470,7 @@ mod tests {
let mut config = Config::new();
config.set_workspace_settings(
serde_json::from_value(json!({ "import_map": "" })).unwrap(),
+ None,
);
assert_eq!(
config.workspace_settings().clone(),
@@ -1496,6 +1483,7 @@ mod tests {
let mut config = Config::new();
config.set_workspace_settings(
serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(),
+ None,
);
assert_eq!(
config.workspace_settings().clone(),
@@ -1508,6 +1496,7 @@ mod tests {
let mut config = Config::new();
config.set_workspace_settings(
serde_json::from_value(json!({ "config": "" })).unwrap(),
+ None,
);
assert_eq!(
config.workspace_settings().clone(),
@@ -1541,30 +1530,39 @@ mod tests {
},
),
];
- config.set_specifier_settings(
- Url::parse("file:///root1/").unwrap(),
- SpecifierSettings {
- enable_paths: Some(vec![
- "sub_dir".to_string(),
- "sub_dir/other".to_string(),
- "test.ts".to_string(),
- ]),
- ..Default::default()
- },
- );
- config.set_specifier_settings(
- Url::parse("file:///root2/").unwrap(),
- SpecifierSettings {
- enable_paths: Some(vec!["other.ts".to_string()]),
- ..Default::default()
- },
- );
- config.set_specifier_settings(
- Url::parse("file:///root3/").unwrap(),
- SpecifierSettings {
- enable: Some(true),
- ..Default::default()
- },
+ config.set_workspace_settings(
+ Default::default(),
+ Some(
+ vec![
+ (
+ Url::parse("file:///root1/").unwrap(),
+ WorkspaceSettings {
+ enable_paths: Some(vec![
+ "sub_dir".to_string(),
+ "sub_dir/other".to_string(),
+ "test.ts".to_string(),
+ ]),
+ ..Default::default()
+ },
+ ),
+ (
+ Url::parse("file:///root2/").unwrap(),
+ WorkspaceSettings {
+ enable_paths: Some(vec!["other.ts".to_string()]),
+ ..Default::default()
+ },
+ ),
+ (
+ Url::parse("file:///root3/").unwrap(),
+ WorkspaceSettings {
+ enable: Some(true),
+ ..Default::default()
+ },
+ ),
+ ]
+ .into_iter()
+ .collect(),
+ ),
);
assert_eq!(
@@ -1583,7 +1581,7 @@ mod tests {
fn config_enable_via_config_file_detection() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone());
- config.settings.workspace.enable = None;
+ config.settings.unscoped.enable = None;
assert!(!config.specifier_enabled(&root_uri));
config.set_config_file(
@@ -1597,7 +1595,7 @@ mod tests {
fn config_specifier_enabled_matches_by_path_component() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone());
- config.settings.workspace.enable_paths = Some(vec!["mo".to_string()]);
+ config.settings.unscoped.enable_paths = Some(vec!["mo".to_string()]);
assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap()));
}
@@ -1605,11 +1603,11 @@ mod tests {
fn config_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone());
- config.settings.workspace.enable = Some(true);
+ config.settings.unscoped.enable = Some(true);
- config.settings.workspace.enable_paths =
+ config.settings.unscoped.enable_paths =
Some(vec!["mod1.ts".to_string(), "mod2.ts".to_string()]);
- config.settings.workspace.disable_paths = vec!["mod2.ts".to_string()];
+ config.settings.unscoped.disable_paths = vec!["mod2.ts".to_string()];
assert!(
config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
);
@@ -1619,7 +1617,7 @@ mod tests {
assert!(
!config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap())
);
- config.settings.workspace.enable_paths = None;
+ config.settings.unscoped.enable_paths = None;
config.set_config_file(
ConfigFile::new(
@@ -1688,7 +1686,7 @@ mod tests {
fn config_snapshot_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone());
- config.settings.workspace.enable = Some(true);
+ config.settings.unscoped.enable = Some(true);
config.set_config_file(
ConfigFile::new(
&json!({
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 37427d878..8f9696845 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -788,37 +788,37 @@ fn generate_lint_diagnostics(
let documents = snapshot
.documents
.documents(DocumentsFilter::OpenDiagnosable);
- let workspace_settings = config.settings.workspace.clone();
let lint_rules = get_configured_rules(lint_options.rules.clone());
let mut diagnostics_vec = Vec::new();
- if workspace_settings.lint {
- for document in documents {
- // exit early if cancelled
- if token.is_cancelled() {
- break;
- }
-
- // ignore any npm package files
- if let Some(npm) = &snapshot.npm {
- if npm.node_resolver.in_npm_package(document.specifier()) {
- continue;
- }
+ for document in documents {
+ let settings =
+ config.workspace_settings_for_specifier(document.specifier());
+ if !settings.lint {
+ continue;
+ }
+ // exit early if cancelled
+ if token.is_cancelled() {
+ break;
+ }
+ // ignore any npm package files
+ if let Some(npm) = &snapshot.npm {
+ if npm.node_resolver.in_npm_package(document.specifier()) {
+ continue;
}
-
- let version = document.maybe_lsp_version();
- diagnostics_vec.push(DiagnosticRecord {
- specifier: document.specifier().clone(),
- versioned: VersionedDiagnostics {
- version,
- diagnostics: generate_document_lint_diagnostics(
- config,
- lint_options,
- lint_rules.clone(),
- &document,
- ),
- },
- });
}
+ let version = document.maybe_lsp_version();
+ diagnostics_vec.push(DiagnosticRecord {
+ specifier: document.specifier().clone(),
+ versioned: VersionedDiagnostics {
+ version,
+ diagnostics: generate_document_lint_diagnostics(
+ config,
+ lint_options,
+ lint_rules.clone(),
+ &document,
+ ),
+ },
+ });
}
diagnostics_vec
}
@@ -1442,7 +1442,6 @@ mod tests {
use crate::cache::RealDenoCacheEnv;
use crate::lsp::config::ConfigSnapshot;
use crate::lsp::config::Settings;
- use crate::lsp::config::SpecifierSettings;
use crate::lsp::config::WorkspaceSettings;
use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId;
@@ -1497,7 +1496,7 @@ mod tests {
let root_uri = resolve_url("file:///").unwrap();
ConfigSnapshot {
settings: Settings {
- workspace: WorkspaceSettings {
+ unscoped: WorkspaceSettings {
enable: Some(true),
lint: true,
..Default::default()
@@ -1529,7 +1528,6 @@ mod tests {
#[tokio::test]
async fn test_enabled_then_disabled_specifier() {
let temp_dir = TempDir::new();
- let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
let (snapshot, cache_location) = setup(
&temp_dir,
&[(
@@ -1578,15 +1576,10 @@ let c: number = "a";
// now test disabled specifier
{
let mut disabled_config = mock_config();
- disabled_config.settings.specifiers.insert(
- specifier.clone(),
- SpecifierSettings {
- enable: Some(false),
- disable_paths: vec![],
- enable_paths: None,
- code_lens: Default::default(),
- },
- );
+ disabled_config.settings.unscoped = WorkspaceSettings {
+ enable: Some(false),
+ ..Default::default()
+ };
let diagnostics = generate_lint_diagnostics(
&snapshot,
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index e6e49c654..8afcc7ffc 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -23,6 +23,7 @@ use import_map::ImportMap;
use indexmap::IndexSet;
use log::error;
use serde_json::from_value;
+use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
@@ -80,7 +81,6 @@ use super::tsc::AssetsSnapshot;
use super::tsc::GetCompletionDetailsArgs;
use super::tsc::TsServer;
use super::urls;
-use super::urls::LspClientUrl;
use crate::args::get_root_cert_store;
use crate::args::package_json;
use crate::args::resolve_import_map_from_specifier;
@@ -403,68 +403,42 @@ impl LanguageServer {
}
}
- pub async fn refresh_specifiers_from_client(&self) -> bool {
- let (client, specifiers) = {
+ pub async fn refresh_configuration(&self) {
+ let (client, folders, capable) = {
let ls = self.0.read().await;
- let specifiers = if ls.config.client_capabilities.workspace_configuration
- {
- let root_capacity = std::cmp::max(ls.config.workspace_folders.len(), 1);
- let config_specifiers = ls.config.get_specifiers();
- let mut specifiers =
- HashMap::with_capacity(root_capacity + config_specifiers.len());
- for (specifier, folder) in &ls.config.workspace_folders {
- specifiers
- .insert(specifier.clone(), LspClientUrl::new(folder.uri.clone()));
- }
- specifiers.extend(
- ls.config
- .get_specifiers()
- .iter()
- .map(|s| (s.clone(), ls.url_map.normalize_specifier(s).unwrap())),
- );
-
- Some(specifiers.into_iter().collect::<Vec<_>>())
- } else {
- None
- };
-
- (ls.client.clone(), specifiers)
+ (
+ ls.client.clone(),
+ ls.config.workspace_folders.clone(),
+ ls.config.client_capabilities.workspace_configuration,
+ )
};
-
- let mut touched = false;
- if let Some(specifiers) = specifiers {
- let configs_result = client
+ if capable {
+ let mut scopes = Vec::with_capacity(folders.len() + 1);
+ scopes.push(None);
+ for (_, folder) in &folders {
+ scopes.push(Some(folder.uri.clone()));
+ }
+ let configs = client
.when_outside_lsp_lock()
- .specifier_configurations(
- specifiers
- .iter()
- .map(|(_, client_uri)| client_uri.clone())
- .collect(),
- )
+ .workspace_configuration(scopes)
.await;
-
- let mut ls = self.0.write().await;
- if let Ok(configs) = configs_result {
- for (value, internal_uri) in
- configs.into_iter().zip(specifiers.into_iter().map(|s| s.0))
- {
- match value {
- Ok(specifier_settings) => {
- if ls
- .config
- .set_specifier_settings(internal_uri, specifier_settings)
- {
- touched = true;
- }
- }
- Err(err) => {
- error!("{}", err);
- }
- }
+ if let Ok(configs) = configs {
+ if configs.len() != folders.len() + 1 {
+ lsp_warn!("Incorrect number of configurations received.");
+ return;
}
+ let mut configs = configs.into_iter();
+ let unscoped = configs.next().unwrap();
+ let mut by_workspace_folder = BTreeMap::new();
+ for (folder_uri, _) in &folders {
+ by_workspace_folder
+ .insert(folder_uri.clone(), configs.next().unwrap());
+ }
+ let mut ls = self.0.write().await;
+ ls.config
+ .set_workspace_settings(unscoped, Some(by_workspace_folder));
}
}
- touched
}
}
@@ -1182,6 +1156,7 @@ impl Inner {
if let Some(options) = params.initialization_options {
self.config.set_workspace_settings(
WorkspaceSettings::from_initialization_options(options),
+ None,
);
}
if let Some(folders) = params.workspace_folders {
@@ -1391,27 +1366,22 @@ impl Inner {
async fn did_change_configuration(
&mut self,
- client_workspace_config: Option<WorkspaceSettings>,
params: DidChangeConfigurationParams,
) {
- let maybe_config =
- if self.config.client_capabilities.workspace_configuration {
- client_workspace_config
- } else {
- params.settings.as_object().map(|settings| {
- let deno =
- serde_json::to_value(settings.get(SETTINGS_SECTION)).unwrap();
- let javascript =
- serde_json::to_value(settings.get("javascript")).unwrap();
- let typescript =
- serde_json::to_value(settings.get("typescript")).unwrap();
- WorkspaceSettings::from_raw_settings(deno, javascript, typescript)
- })
- };
-
- if let Some(settings) = maybe_config {
- self.config.set_workspace_settings(settings);
- }
+ if !self.config.client_capabilities.workspace_configuration {
+ let config = params.settings.as_object().map(|settings| {
+ let deno =
+ serde_json::to_value(settings.get(SETTINGS_SECTION)).unwrap();
+ let javascript =
+ serde_json::to_value(settings.get("javascript")).unwrap();
+ let typescript =
+ serde_json::to_value(settings.get("typescript")).unwrap();
+ WorkspaceSettings::from_raw_settings(deno, javascript, typescript)
+ });
+ if let Some(settings) = config {
+ self.config.set_workspace_settings(settings, None);
+ }
+ };
self.update_debug_flag();
if let Err(err) = self.update_cache().await {
@@ -2148,8 +2118,7 @@ impl Inner {
.normalize_url(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
- || !(self.config.workspace_settings().enabled_code_lens()
- || self.config.specifier_code_lens_test(&specifier))
+ || !self.config.enabled_code_lens_for_specifier(&specifier)
{
return Ok(None);
}
@@ -2385,10 +2354,8 @@ impl Inner {
&params.text_document_position.text_document.uri,
LspUrlKind::File,
);
- let language_settings = self
- .config
- .workspace_settings()
- .language_settings_for_specifier(&specifier);
+ let language_settings =
+ self.config.language_settings_for_specifier(&specifier);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
|| !language_settings.map(|s| s.suggest.enabled).unwrap_or(true)
@@ -2457,7 +2424,6 @@ impl Inner {
line_index,
&self
.config
- .workspace_settings()
.language_settings_for_specifier(&specifier)
.cloned()
.unwrap_or_default()
@@ -2992,7 +2958,6 @@ impl Inner {
);
let options = self
.config
- .workspace_settings()
.language_settings_for_specifier(&old_specifier)
.map(|s| s.update_imports_on_file_move.clone())
.unwrap_or_default();
@@ -3188,7 +3153,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
}
}
- self.refresh_specifiers_from_client().await;
+ self.refresh_configuration().await;
{
let mut ls = self.0.write().await;
@@ -3212,58 +3177,17 @@ impl tower_lsp::LanguageServer for LanguageServer {
return;
}
- let (client, client_uri, specifier, should_get_specifier_settings) = {
- let mut inner = self.0.write().await;
- let client = inner.client.clone();
- let client_uri = LspClientUrl::new(params.text_document.uri.clone());
- let specifier = inner
- .url_map
- .normalize_url(client_uri.as_url(), LspUrlKind::File);
- let document = inner.did_open(&specifier, params).await;
- let should_get_specifier_settings =
- !inner.config.has_specifier_settings(&specifier)
- && inner.config.client_capabilities.workspace_configuration;
- if document.is_diagnosable() {
- inner.refresh_npm_specifiers().await;
- let specifiers = inner.documents.dependents(&specifier);
- inner.diagnostics_server.invalidate(&specifiers);
- // don't send diagnostics yet if we don't have the specifier settings
- if !should_get_specifier_settings {
- inner.send_diagnostics_update();
- inner.send_testing_update();
- }
- }
- (client, client_uri, specifier, should_get_specifier_settings)
- };
-
- // retrieve the specifier settings outside the lock if
- // they haven't been asked for yet
- if should_get_specifier_settings {
- let response = client
- .when_outside_lsp_lock()
- .specifier_configuration(&client_uri)
- .await;
- let mut ls = self.0.write().await;
- match response {
- Ok(specifier_settings) => {
- ls.config
- .set_specifier_settings(specifier.clone(), specifier_settings);
- }
- Err(err) => {
- error!("{}", err);
- }
- }
-
- if ls
- .documents
- .get(&specifier)
- .map(|d| d.is_diagnosable())
- .unwrap_or(false)
- {
- ls.refresh_documents_config().await;
- ls.send_diagnostics_update();
- ls.send_testing_update();
- }
+ let mut inner = self.0.write().await;
+ let specifier = inner
+ .url_map
+ .normalize_url(&params.text_document.uri, LspUrlKind::File);
+ let document = inner.did_open(&specifier, params).await;
+ if document.is_diagnosable() {
+ inner.refresh_npm_specifiers().await;
+ let specifiers = inner.documents.dependents(&specifier);
+ inner.diagnostics_server.invalidate(&specifiers);
+ inner.send_diagnostics_update();
+ inner.send_testing_update();
}
}
@@ -3277,7 +3201,10 @@ impl tower_lsp::LanguageServer for LanguageServer {
let mut inner = self.0.write().await;
let specifier = inner.url_map.normalize_url(uri, LspUrlKind::File);
inner.documents.save(&specifier);
- if !inner.config.workspace_settings().cache_on_save
+ if !inner
+ .config
+ .workspace_settings_for_specifier(&specifier)
+ .cache_on_save
|| !inner.config.specifier_enabled(&specifier)
|| !inner.diagnostics_state.has_no_cache_diagnostics(&specifier)
{
@@ -3310,47 +3237,17 @@ impl tower_lsp::LanguageServer for LanguageServer {
&self,
params: DidChangeConfigurationParams,
) {
- let (mark, has_workspace_capability, client) = {
+ let mark = {
let inner = self.0.read().await;
- (
- inner
- .performance
- .mark("did_change_configuration", Some(&params)),
- inner.config.client_capabilities.workspace_configuration,
- inner.client.clone(),
- )
+ inner
+ .performance
+ .mark("did_change_configuration", Some(&params))
};
- self.refresh_specifiers_from_client().await;
-
- // Get the configuration from the client outside of the lock
- // in order to prevent potential deadlocking scenarios where
- // the server holds a lock and calls into the client, which
- // calls into the server which deadlocks acquiring the lock.
- // There is a gap here between when the configuration is
- // received and acquiring the lock, but most likely there
- // won't be any racing here.
- let client_workspace_config = if has_workspace_capability {
- let config_response = client
- .when_outside_lsp_lock()
- .workspace_configuration()
- .await;
- match config_response {
- Ok(settings) => Some(settings),
- Err(err) => {
- error!("{}", err);
- None
- }
- }
- } else {
- None
- };
+ self.refresh_configuration().await;
- // now update the inner state
let mut inner = self.0.write().await;
- inner
- .did_change_configuration(client_workspace_config, params)
- .await;
+ inner.did_change_configuration(params).await;
inner.performance.measure(mark);
}
@@ -3374,7 +3271,8 @@ impl tower_lsp::LanguageServer for LanguageServer {
(ls.performance.clone(), mark)
};
- if self.refresh_specifiers_from_client().await {
+ self.refresh_configuration().await;
+ {
let mut ls = self.0.write().await;
ls.refresh_documents_config().await;
ls.diagnostics_server.invalidate_all();
@@ -3681,10 +3579,7 @@ impl Inner {
.normalize_url(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
- || !self
- .config
- .workspace_settings()
- .enabled_inlay_hints(&specifier)
+ || !self.config.enabled_inlay_hints_for_specifier(&specifier)
{
return Ok(None);
}
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 7f650348b..94770603c 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -4215,9 +4215,8 @@ impl UserPreferences {
use_label_details_in_completion_entries: Some(true),
..Default::default()
};
- let Some(language_settings) = config
- .workspace_settings()
- .language_settings_for_specifier(specifier)
+ let Some(language_settings) =
+ config.language_settings_for_specifier(specifier)
else {
return base_preferences;
};
@@ -5312,7 +5311,7 @@ mod tests {
.variable_types
.suppress_when_type_matches_name = true;
let mut config = config::Config::new();
- config.set_workspace_settings(settings);
+ config.set_workspace_settings(settings, None);
let user_preferences = UserPreferences::from_config_for_specifier(
&config,
&Default::default(),
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index 85e55ee81..42cd11b3d 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -1024,23 +1024,23 @@ fn lsp_import_attributes() {
client.initialize(|builder| {
builder.set_import_map("data:application/json;utf8,{\"imports\": { \"example\": \"https://deno.land/x/example/mod.ts\" }}");
});
-
- client.did_open_with_config(
- json!({
- "textDocument": {
- "uri": "file:///a/test.json",
- "languageId": "json",
- "version": 1,
- "text": "{\"a\":1}"
- }
- }),
- &json!({ "deno": {
+ client.change_configuration(json!({
+ "deno": {
"enable": true,
"codeLens": {
- "test": true
- }
- } }),
- );
+ "test": true,
+ },
+ },
+ }));
+
+ client.did_open(json!({
+ "textDocument": {
+ "uri": "file:///a/test.json",
+ "languageId": "json",
+ "version": 1,
+ "text": "{\"a\":1}",
+ },
+ }));
let diagnostics = client.did_open(json!({
"textDocument": {
@@ -1380,17 +1380,15 @@ fn lsp_hover_disabled() {
client.initialize(|builder| {
builder.set_deno_enable(false);
});
- client.did_open_with_config(
- json!({
- "textDocument": {
- "uri": "file:///a/file.ts",
- "languageId": "typescript",
- "version": 1,
- "text": "console.log(Date.now());\n"
- }
- }),
- &json!({ "deno": { "enable": false } }),
- );
+ client.change_configuration(json!({ "deno": { "enable": false } }));
+ client.did_open(json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": "console.log(Date.now());\n",
+ },
+ }));
let res = client.write_request(
"textDocument/hover",
@@ -3794,24 +3792,22 @@ fn lsp_code_lens_test_disabled() {
"test": false
})));
});
- client
- .did_open_with_config(
- json!({
- "textDocument": {
- "uri": "file:///a/file.ts",
- "languageId": "typescript",
- "version": 1,
- "text": "const { test } = Deno;\nconst { test: test2 } = Deno;\nconst test3 = Deno.test;\n\nDeno.test(\"test a\", () => {});\nDeno.test({\n name: \"test b\",\n fn() {},\n});\ntest({\n name: \"test c\",\n fn() {},\n});\ntest(\"test d\", () => {});\ntest2({\n name: \"test e\",\n fn() {},\n});\ntest2(\"test f\", () => {});\ntest3({\n name: \"test g\",\n fn() {},\n});\ntest3(\"test h\", () => {});\n"
- }
- }),
- // disable test code lens
- &json!({ "deno": {
- "enable": true,
- "codeLens": {
- "test": false
- }
- } }),
- );
+ client.change_configuration(json!({
+ "deno": {
+ "enable": true,
+ "codeLens": {
+ "test": false,
+ },
+ },
+ }));
+ client.did_open(json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": "const { test } = Deno;\nconst { test: test2 } = Deno;\nconst test3 = Deno.test;\n\nDeno.test(\"test a\", () => {});\nDeno.test({\n name: \"test b\",\n fn() {},\n});\ntest({\n name: \"test c\",\n fn() {},\n});\ntest(\"test d\", () => {});\ntest2({\n name: \"test e\",\n fn() {},\n});\ntest2(\"test f\", () => {});\ntest3({\n name: \"test g\",\n fn() {},\n});\ntest3(\"test h\", () => {});\n"
+ },
+ }));
let res = client.write_request(
"textDocument/codeLens",
json!({
@@ -3820,7 +3816,7 @@ fn lsp_code_lens_test_disabled() {
}
}),
);
- assert_eq!(res, json!([]));
+ assert_eq!(res, json!(null));
client.shutdown();
}
@@ -4898,22 +4894,12 @@ fn lsp_cache_on_save() {
);
let mut client = context.new_lsp_command().build();
client.initialize_default();
- client.write_notification(
- "workspace/didChangeConfiguration",
- json!({
- "settings": {}
- }),
- );
- let settings = json!({
+ client.change_configuration(json!({
"deno": {
"enable": true,
"cacheOnSave": true,
},
- });
- // one for the workspace
- client.handle_configuration_request(&settings);
- // one for the specifier
- client.handle_configuration_request(&settings);
+ }));
let diagnostics = client.did_open(json!({
"textDocument": {
@@ -5592,23 +5578,16 @@ fn lsp_quote_style_from_workspace_settings() {
);
let mut client = context.new_lsp_command().build();
client.initialize_default();
- client.write_notification(
- "workspace/didChangeConfiguration",
- json!({
- "settings": {}
- }),
- );
- let settings = json!({
+ client.change_configuration(json!({
+ "deno": {
+ "enable": true,
+ },
"typescript": {
"preferences": {
"quoteStyle": "single",
},
},
- });
- // one for the workspace
- client.handle_configuration_request(&settings);
- // one for the specifier
- client.handle_configuration_request(&settings);
+ }));
let code_action_params = json!({
"textDocument": {
@@ -5792,7 +5771,7 @@ fn lsp_code_actions_deadlock() {
let large_file_text =
fs::read_to_string(testdata_path().join("lsp").join("large_file.txt"))
.unwrap();
- client.did_open_raw(json!({
+ client.did_open(json!({
"textDocument": {
"uri": "file:///a/file.ts",
"languageId": "javascript",
@@ -5800,7 +5779,6 @@ fn lsp_code_actions_deadlock() {
"text": large_file_text,
}
}));
- client.handle_configuration_request(&json!({ "deno": { "enable": true } }));
client.write_request(
"textDocument/semanticTokens/full",
json!({
@@ -5809,7 +5787,6 @@ fn lsp_code_actions_deadlock() {
}
}),
);
- client.read_diagnostics();
client.write_notification(
"textDocument/didChange",
json!({
@@ -8901,17 +8878,11 @@ fn lsp_configuration_did_change() {
"text": "import * as a from \"http://localhost:4545/x/a@\""
}
}));
- client.write_notification(
- "workspace/didChangeConfiguration",
- json!({
- "settings": {}
- }),
- );
- let settings = json!({ "deno": {
+ client.change_configuration(json!({ "deno": {
"enable": true,
"codeLens": {
"implementations": true,
- "references": true
+ "references": true,
},
"importMap": null,
"lint": true,
@@ -8922,16 +8893,12 @@ fn lsp_configuration_did_change() {
"paths": true,
"imports": {
"hosts": {
- "http://localhost:4545/": true
- }
- }
+ "http://localhost:4545/": true,
+ },
+ },
},
- "unstable": false
- } });
- // one for the workspace
- client.handle_configuration_request(&settings);
- // one for the specifier
- client.handle_configuration_request(&settings);
+ "unstable": false,
+ } }));
let list = client.get_completion_list(
"file:///a/file.ts",
@@ -8997,13 +8964,7 @@ fn lsp_completions_complete_function_calls() {
"text": "[]."
}
}));
- client.write_notification(
- "workspace/didChangeConfiguration",
- json!({
- "settings": {}
- }),
- );
- let settings = json!({
+ client.change_configuration(json!({
"deno": {
"enable": true,
},
@@ -9012,11 +8973,7 @@ fn lsp_completions_complete_function_calls() {
"completeFunctionCalls": true,
},
},
- });
- // one for the workspace
- client.handle_configuration_request(&settings);
- // one for the specifier
- client.handle_configuration_request(&settings);
+ }));
let list = client.get_completion_list(
"file:///a/file.ts",
@@ -10099,22 +10056,12 @@ fn lsp_node_modules_dir() {
"{ \"nodeModulesDir\": true, \"lock\": false }\n",
);
let refresh_config = |client: &mut LspClient| {
- client.write_notification(
- "workspace/didChangeConfiguration",
- json!({
- "settings": {
- "enable": true,
- "config": "./deno.json",
- }
- }),
- );
-
- let settings = json!({ "deno": {
+ client.change_configuration(json!({ "deno": {
"enable": true,
"config": "./deno.json",
"codeLens": {
"implementations": true,
- "references": true
+ "references": true,
},
"importMap": null,
"lint": false,
@@ -10123,14 +10070,10 @@ fn lsp_node_modules_dir() {
"completeFunctionCalls": false,
"names": true,
"paths": true,
- "imports": {}
- },
- "unstable": false
- } });
- // one for the workspace
- client.handle_configuration_request(&settings);
- // one for the specifier
- client.handle_configuration_request(&settings);
+ "imports": {},
+ },
+ "unstable": false,
+ } }));
};
refresh_config(&mut client);
@@ -10228,22 +10171,12 @@ fn lsp_vendor_dir() {
"{ \"vendor\": true, \"lock\": false }\n",
);
let refresh_config = |client: &mut LspClient| {
- client.write_notification(
- "workspace/didChangeConfiguration",
- json!({
- "settings": {
- "enable": true,
- "config": "./deno.json",
- }
- }),
- );
-
- let settings = json!({ "deno": {
+ client.change_configuration(json!({ "deno": {
"enable": true,
"config": "./deno.json",
"codeLens": {
"implementations": true,
- "references": true
+ "references": true,
},
"importMap": null,
"lint": false,
@@ -10252,14 +10185,10 @@ fn lsp_vendor_dir() {
"completeFunctionCalls": false,
"names": true,
"paths": true,
- "imports": {}
- },
- "unstable": false
- } });
- // one for the workspace
- client.handle_configuration_request(&settings);
- // one for the specifier
- client.handle_configuration_request(&settings);
+ "imports": {},
+ },
+ "unstable": false,
+ } }));
};
refresh_config(&mut client);
diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs
index 4e87503f2..352f5d365 100644
--- a/test_util/src/lsp.rs
+++ b/test_util/src/lsp.rs
@@ -210,7 +210,22 @@ pub struct InitializeParamsBuilder {
impl InitializeParamsBuilder {
#[allow(clippy::new_without_default)]
- pub fn new() -> Self {
+ pub fn new(config: Value) -> Self {
+ let mut config_as_options = json!({});
+ if let Some(object) = config.as_object() {
+ if let Some(deno) = object.get("deno") {
+ if let Some(deno) = deno.as_object() {
+ config_as_options = json!(deno.clone());
+ }
+ }
+ let config_as_options = config_as_options.as_object_mut().unwrap();
+ if let Some(typescript) = object.get("typescript") {
+ config_as_options.insert("typescript".to_string(), typescript.clone());
+ }
+ if let Some(javascript) = object.get("javascript") {
+ config_as_options.insert("javascript".to_string(), javascript.clone());
+ }
+ }
Self {
params: InitializeParams {
process_id: None,
@@ -219,38 +234,7 @@ impl InitializeParamsBuilder {
version: Some("1.0.0".to_string()),
}),
root_uri: None,
- initialization_options: Some(json!({
- "enableBuiltinCommands": true,
- "enable": true,
- "cache": null,
- "certificateStores": null,
- "codeLens": {
- "implementations": true,
- "references": true,
- "test": true
- },
- "config": null,
- "importMap": null,
- "lint": true,
- "suggest": {
- "autoImports": true,
- "completeFunctionCalls": false,
- "names": true,
- "paths": true,
- "imports": {
- "hosts": {}
- }
- },
- "testing": {
- "args": [
- "--allow-all"
- ],
- "enable": true
- },
- "tlsCertificate": null,
- "unsafelyIgnoreCertificateErrors": null,
- "unstable": false
- })),
+ initialization_options: Some(config_as_options),
capabilities: ClientCapabilities {
text_document: Some(TextDocumentClientCapabilities {
code_action: Some(CodeActionClientCapabilities {
@@ -686,21 +670,70 @@ impl LspClient {
) {
self.initialize_with_config(
do_build,
- json!({"deno":{
- "enable": true
- }}),
+ json!({ "deno": {
+ "enableBuiltinCommands": true,
+ "enable": true,
+ "cache": null,
+ "certificateStores": null,
+ "codeLens": {
+ "implementations": true,
+ "references": true,
+ "test": true,
+ },
+ "config": null,
+ "importMap": null,
+ "lint": true,
+ "suggest": {
+ "autoImports": true,
+ "completeFunctionCalls": false,
+ "names": true,
+ "paths": true,
+ "imports": {
+ "hosts": {},
+ },
+ },
+ "testing": {
+ "args": [
+ "--allow-all"
+ ],
+ "enable": true,
+ },
+ "tlsCertificate": null,
+ "unsafelyIgnoreCertificateErrors": null,
+ "unstable": false,
+ } }),
)
}
pub fn initialize_with_config(
&mut self,
do_build: impl Fn(&mut InitializeParamsBuilder),
- config: Value,
+ mut config: Value,
) {
- let mut builder = InitializeParamsBuilder::new();
+ let mut builder = InitializeParamsBuilder::new(config.clone());
builder.set_root_uri(self.context.temp_dir().uri());
do_build(&mut builder);
let params: InitializeParams = builder.build();
+ // `config` must be updated to account for the builder changes.
+ // TODO(nayeemrmn): Remove config-related methods from builder.
+ if let Some(options) = &params.initialization_options {
+ if let Some(options) = options.as_object() {
+ if let Some(config) = config.as_object_mut() {
+ let mut deno = options.clone();
+ let typescript = options.get("typescript");
+ let javascript = options.get("javascript");
+ deno.remove("typescript");
+ deno.remove("javascript");
+ config.insert("deno".to_string(), json!(deno));
+ if let Some(typescript) = typescript {
+ config.insert("typescript".to_string(), typescript.clone());
+ }
+ if let Some(javascript) = javascript {
+ config.insert("javascript".to_string(), javascript.clone());
+ }
+ }
+ }
+ }
self.supports_workspace_configuration = match &params.capabilities.workspace
{
Some(workspace) => workspace.configuration == Some(true),
@@ -710,23 +743,12 @@ impl LspClient {
self.write_notification("initialized", json!({}));
self.config = config;
if self.supports_workspace_configuration {
- self.handle_configuration_request(&self.config.clone());
+ self.handle_configuration_request();
}
}
pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics {
- self.did_open_with_config(params, &self.config.clone())
- }
-
- pub fn did_open_with_config(
- &mut self,
- params: Value,
- config: &Value,
- ) -> CollectedDiagnostics {
self.did_open_raw(params);
- if self.supports_workspace_configuration {
- self.handle_configuration_request(config);
- }
self.read_diagnostics()
}
@@ -734,17 +756,33 @@ impl LspClient {
self.write_notification("textDocument/didOpen", params);
}
- pub fn handle_configuration_request(&mut self, settings: &Value) {
+ pub fn change_configuration(&mut self, config: Value) {
+ self.config = config;
+ if self.supports_workspace_configuration {
+ self.write_notification(
+ "workspace/didChangeConfiguration",
+ json!({ "settings": {} }),
+ );
+ self.handle_configuration_request();
+ } else {
+ self.write_notification(
+ "workspace/didChangeConfiguration",
+ json!({ "settings": &self.config }),
+ );
+ }
+ }
+
+ pub fn handle_configuration_request(&mut self) {
let (id, method, args) = self.read_request::<Value>();
assert_eq!(method, "workspace/configuration");
let params = args.as_ref().unwrap().as_object().unwrap();
let items = params.get("items").unwrap().as_array().unwrap();
- let settings_object = settings.as_object().unwrap();
+ let config_object = self.config.as_object().unwrap();
let mut result = vec![];
for item in items {
let item = item.as_object().unwrap();
let section = item.get("section").unwrap().as_str().unwrap();
- result.push(settings_object.get(section).cloned().unwrap_or_default());
+ result.push(config_object.get(section).cloned().unwrap_or_default());
}
self.write_response(id, result);
}