diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 1b6a81b0222a90..6b8e7f08a6fec5 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -579,6 +579,13 @@ pub struct WorkspaceSettings { #[serde(default = "default_document_preload_limit")] pub document_preload_limit: usize, + /// Disables the server-capability for pull diagnostics to force push-based + /// diagnostics. NOTE: This is read and stored from the initialization + /// options. This value may be updated on configuration change events but + /// the new value will be ignored. + #[serde(default)] + pub force_push_based_diagnostics: bool, + #[serde(default)] pub suggest: DenoCompletionSettings, @@ -626,6 +633,7 @@ impl Default for WorkspaceSettings { log_file: false, lint: true, document_preload_limit: default_document_preload_limit(), + force_push_based_diagnostics: false, suggest: Default::default(), testing: Default::default(), tls_certificate: None, @@ -2145,6 +2153,7 @@ mod tests { log_file: false, lint: true, document_preload_limit: 1_000, + force_push_based_diagnostics: false, suggest: DenoCompletionSettings { imports: ImportCompletionSettings { auto_discover: true, diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 4c800f821db5cb..2fd6d31782cff9 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -312,6 +312,7 @@ pub struct Inner { project_version: usize, /// A collection of measurements which instrument that performance of the LSP. performance: Arc, + force_push_based_diagnostics: bool, registered_semantic_tokens_capabilities: bool, pub resolver: Arc, task_queue: LanguageServerTaskQueue, @@ -608,6 +609,7 @@ impl Inner { npm_search_api, performance, registered_semantic_tokens_capabilities: false, + force_push_based_diagnostics: false, resolver: Default::default(), ts_fixable_diagnostics: Default::default(), ts_server, @@ -894,6 +896,10 @@ impl Inner { })); self.registered_semantic_tokens_capabilities = true; } + + fn is_using_push_based_diagnostics(&self) -> bool { + self.force_push_based_diagnostics || !self.config.diagnostic_capable() + } } // lspower::LanguageServer methods. This file's LanguageServer delegates to us. @@ -910,8 +916,6 @@ impl Inner { parent_process_checker::start(parent_pid) } - let capabilities = capabilities::server_capabilities(¶ms.capabilities); - let version = format!( "{} ({}, {})", DENO_VERSION_INFO.deno, @@ -982,15 +986,15 @@ impl Inner { } self.config.set_workspace_folders(workspace_folders); if let Some(options) = params.initialization_options { - self.config.set_workspace_settings( - WorkspaceSettings::from_initialization_options(options), - vec![], - ); + let settings = WorkspaceSettings::from_initialization_options(options); + self.force_push_based_diagnostics = + settings.force_push_based_diagnostics; + self.config.set_workspace_settings(settings, vec![]); } self.config.set_client_capabilities(params.capabilities); } - if !self.config.diagnostic_capable() { + if self.is_using_push_based_diagnostics() { let mut diagnostics_server = DiagnosticsServer::new( self.client.clone(), self.performance.clone(), @@ -1006,6 +1010,13 @@ impl Inner { self.update_tracing(); self.update_debug_flag(); + let mut capabilities = + capabilities::server_capabilities(&self.config.client_capabilities); + + if self.force_push_based_diagnostics { + capabilities.diagnostic_provider = None; + } + if capabilities.semantic_tokens_provider.is_some() { self.registered_semantic_tokens_capabilities = true; } @@ -1728,7 +1739,7 @@ impl Inner { ); self.ts_server.cleanup_semantic_cache(self.snapshot()).await; self.send_diagnostics_update(); - if self.config.diagnostic_capable() + if !self.is_using_push_based_diagnostics() && self.config.diagnostic_refresh_capable() { self.client.refresh_diagnostics(); @@ -4850,7 +4861,7 @@ impl Inner { self.project_changed(vec![], ProjectScopesChange::Config); self.ts_server.cleanup_semantic_cache(self.snapshot()).await; self.send_diagnostics_update(); - if self.config.diagnostic_capable() + if !self.is_using_push_based_diagnostics() && self.config.diagnostic_refresh_capable() { self.client.refresh_diagnostics(); diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index 2d88b4d4028888..5de2e643a686e4 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -313,6 +313,7 @@ pub fn get_repl_workspace_settings() -> WorkspaceSettings { log_file: false, lint: false, document_preload_limit: 0, // don't pre-load any modules as it's expensive and not useful for the repl + force_push_based_diagnostics: false, tls_certificate: None, unsafely_ignore_certificate_errors: None, unstable: Default::default(), diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index f0a6c561c0b626..711013fa5a7256 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -19810,6 +19810,99 @@ fn lsp_push_diagnostics() { ); } +#[test(timeout = 300)] +fn lsp_force_push_based_diagnostics_setting() { + let context = TestContextBuilder::new().use_temp_cwd().build(); + let mut client = context.new_lsp_command().build(); + client.initialize(|builder| { + builder.set_force_push_based_diagnostics(true); + }); + client.did_open(json!({ + "textDocument": { + "uri": "file:///a/file1.ts", + "languageId": "typescript", + "version": 1, + "text": r#" + const foo: string = 1; + foo; + "#, + }, + })); + client.did_open(json!({ + "textDocument": { + "uri": "file:///a/file2.ts", + "languageId": "typescript", + "version": 1, + "text": r#" + import "foo"; + "#, + }, + })); + let diagnostics = client.did_open(json!({ + "textDocument": { + "uri": "file:///a/file3.ts", + "languageId": "typescript", + "version": 1, + "text": r#" + // @ts-ignore + "#, + }, + })); + assert_eq!( + json!(diagnostics.all_messages()), + json!([ + { + "uri": "file:///a/file1.ts", + "diagnostics": [ + { + "range": { + "start": { "line": 1, "character": 14 }, + "end": { "line": 1, "character": 17 }, + }, + "severity": 1, + "code": 2322, + "source": "deno-ts", + "message": "Type 'number' is not assignable to type 'string'.", + }, + ], + "version": 1, + }, + { + "uri": "file:///a/file2.ts", + "diagnostics": [ + { + "range": { + "start": { "line": 1, "character": 15 }, + "end": { "line": 1, "character": 20 }, + }, + "severity": 1, + "code": "import-prefix-missing", + "source": "deno", + "message": "Import \"foo\" not a dependency\n hint: If you want to use the npm package, try running `deno add npm:foo`", + }, + ], + "version": 1, + }, + { + "uri": "file:///a/file3.ts", + "diagnostics": [ + { + "range": { + "start": { "line": 1, "character": 8 }, + "end": { "line": 1, "character": 21 }, + }, + "severity": 2, + "code": "ban-ts-comment", + "source": "deno-lint", + "message": "`@ts-ignore` is not allowed without comment\nAdd an in-line comment explaining the reason for using `@ts-ignore`, like `// @ts-ignore: `", + }, + ], + "version": 1, + }, + ]), + ); +} + #[test(timeout = 300)] fn lsp_isolated_declarations() { let context = TestContextBuilder::new().use_temp_cwd().build(); diff --git a/tests/util/server/src/lsp.rs b/tests/util/server/src/lsp.rs index bf882871dd745c..8746ebabaa3e12 100644 --- a/tests/util/server/src/lsp.rs +++ b/tests/util/server/src/lsp.rs @@ -405,6 +405,12 @@ impl InitializeParamsBuilder { self } + pub fn set_force_push_based_diagnostics(&mut self, value: bool) -> &mut Self { + let options = self.initialization_options_mut(); + options.insert("forcePushBasedDiagnostics".to_string(), value.into()); + self + } + pub fn add_test_server_suggestions(&mut self) -> &mut Self { self.set_suggest_imports_hosts(vec![( "http://localhost:4545/".to_string(), @@ -877,12 +883,21 @@ impl LspClient { Some(workspace) => workspace.configuration == Some(true), _ => false, }; - self.supports_pull_diagnostics = params - .capabilities - .text_document - .as_ref() - .and_then(|t| t.diagnostic.as_ref()) - .is_some(); + let force_push_based_diagnostics = (|| { + params + .initialization_options + .as_ref()? + .get("forcePushBasedDiagnostics")? + .as_bool() + })() + .unwrap_or(false); + self.supports_pull_diagnostics = !force_push_based_diagnostics + && params + .capabilities + .text_document + .as_ref() + .and_then(|t| t.diagnostic.as_ref()) + .is_some(); self.write_request("initialize", params); self.write_notification("initialized", json!({}));