[ty] Add new diagnosticMode: off (#22073)

This commit is contained in:
Micha Reiser
2025-12-22 16:46:02 +01:00
committed by GitHub
parent 29d7f22c1f
commit ec034fc359
7 changed files with 164 additions and 28 deletions

View File

@@ -354,7 +354,7 @@ pub(crate) fn server_capabilities(
// capabilities dynamically based on the `ty.diagnosticMode` setting.
None
} else {
// Otherwise, we always advertise support for workspace diagnostics.
// Otherwise, we always advertise support for workspace and pull diagnostics.
Some(DiagnosticServerCapabilities::Options(
server_diagnostic_options(true),
))

View File

@@ -21,7 +21,7 @@ use crate::document::{FileRangeExt, ToRangeExt};
use crate::session::DocumentHandle;
use crate::session::client::Client;
use crate::system::{AnySystemPath, file_to_url};
use crate::{DIAGNOSTIC_NAME, Db};
use crate::{DIAGNOSTIC_NAME, Db, DiagnosticMode};
use crate::{PositionEncoding, Session};
pub(super) struct Diagnostics {
@@ -134,7 +134,7 @@ pub(super) fn clear_diagnostics_if_needed(
return;
}
clear_diagnostics(document.url(), client);
clear_diagnostics(document.url(), session, client);
}
/// Clears the diagnostics for the document identified by `uri`.
@@ -142,7 +142,11 @@ pub(super) fn clear_diagnostics_if_needed(
/// This is done by notifying the client with an empty list of diagnostics for the document.
/// For notebook cells, this clears diagnostics for the specific cell.
/// For other document types, this clears diagnostics for the main document.
pub(super) fn clear_diagnostics(uri: &lsp_types::Url, client: &Client) {
pub(super) fn clear_diagnostics(uri: &lsp_types::Url, session: &Session, client: &Client) {
if session.global_settings().diagnostic_mode().is_off() {
return;
}
client.send_notification::<PublishDiagnostics>(PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics: vec![],
@@ -174,6 +178,10 @@ pub(super) fn publish_diagnostics_if_needed(
/// Publishes the diagnostics for the given document snapshot using the [publish diagnostics
/// notification].
pub(super) fn publish_diagnostics(document: &DocumentHandle, session: &Session, client: &Client) {
if session.global_settings().diagnostic_mode().is_off() {
return;
}
let db = session.project_db(document.notebook_or_file_path());
let Some(diagnostics) = compute_diagnostics(db, document, session.position_encoding()) else {
@@ -215,8 +223,11 @@ pub(crate) fn publish_settings_diagnostics(
// Note we DO NOT respect the fact that clients support pulls because these are
// files they *specifically* won't pull diagnostics from us for, because we don't
// claim to be an LSP for them.
if session.global_settings().diagnostic_mode().is_workspace() {
return;
match session.global_settings().diagnostic_mode() {
DiagnosticMode::Workspace | DiagnosticMode::Off => {
return;
}
DiagnosticMode::OpenFilesOnly => {}
}
let session_encoding = session.position_encoding();

View File

@@ -33,6 +33,12 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler {
_client: &Client,
params: DocumentDiagnosticParams,
) -> Result<DocumentDiagnosticReportResult> {
if snapshot.global_settings().diagnostic_mode().is_off() {
return Ok(DocumentDiagnosticReportResult::Report(
DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport::default()),
));
}
let diagnostics = compute_diagnostics(db, snapshot.document(), snapshot.encoding());
let Some(diagnostics) = diagnostics else {

View File

@@ -634,24 +634,35 @@ impl Session {
let diagnostic_mode = self.global_settings.diagnostic_mode;
tracing::debug!(
"Registering diagnostic capability with {diagnostic_mode:?} diagnostic mode"
);
registrations.push(Registration {
id: DIAGNOSTIC_REGISTRATION_ID.into(),
method: DocumentDiagnosticRequest::METHOD.into(),
register_options: Some(
serde_json::to_value(DiagnosticServerCapabilities::RegistrationOptions(
DiagnosticRegistrationOptions {
diagnostic_options: server_diagnostic_options(
diagnostic_mode.is_workspace(),
),
..Default::default()
},
))
.unwrap(),
),
});
match diagnostic_mode {
DiagnosticMode::Off => {
tracing::debug!(
"Skipping registration of diagnostic capability because diagnostics are turned off"
);
}
DiagnosticMode::OpenFilesOnly | DiagnosticMode::Workspace => {
tracing::debug!(
"Registering diagnostic capability with {diagnostic_mode:?} diagnostic mode"
);
registrations.push(Registration {
id: DIAGNOSTIC_REGISTRATION_ID.into(),
method: DocumentDiagnosticRequest::METHOD.into(),
register_options: Some(
serde_json::to_value(
DiagnosticServerCapabilities::RegistrationOptions(
DiagnosticRegistrationOptions {
diagnostic_options: server_diagnostic_options(
diagnostic_mode.is_workspace(),
),
..Default::default()
},
),
)
.unwrap(),
),
});
}
}
}
if let Some(register_options) = self.file_watcher_registration_options() {
@@ -1024,7 +1035,6 @@ impl DocumentSnapshot {
}
/// Returns the client settings for all workspaces.
#[expect(unused)]
pub(crate) fn global_settings(&self) -> &GlobalSettings {
&self.global_settings
}

View File

@@ -334,9 +334,13 @@ impl CompletionOptions {
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum DiagnosticMode {
/// Disable all diagnostics so that ty can be used as an LSP only
Off,
/// Check only currently open files.
#[default]
OpenFilesOnly,
/// Check all files in the workspace.
Workspace,
}
@@ -351,6 +355,10 @@ impl DiagnosticMode {
pub(crate) const fn is_open_files_only(self) -> bool {
matches!(self, DiagnosticMode::OpenFilesOnly)
}
pub(crate) const fn is_off(self) -> bool {
matches!(self, DiagnosticMode::Off)
}
}
impl Combine for DiagnosticMode {
@@ -363,8 +371,8 @@ impl Combine for DiagnosticMode {
// So, this is a workaround to ensure that if the diagnostic mode is set to `workspace` in
// either an initialization options or one of the workspace options, it is always set to
// `workspace` in the global options.
if other.is_workspace() {
*self = DiagnosticMode::Workspace;
if other != DiagnosticMode::default() {
*self = other;
}
}
}

View File

@@ -6,6 +6,7 @@ use lsp_types::{
notification::{DidOpenTextDocument, PublishDiagnostics},
};
use ruff_db::system::SystemPath;
use ty_server::ClientOptions;
use crate::TestServerBuilder;
@@ -33,6 +34,37 @@ def foo() -> str:
Ok(())
}
#[test]
fn on_did_open_diagnostics_off() -> Result<()> {
let workspace_root = SystemPath::new("src");
let foo = SystemPath::new("src/foo.py");
let foo_content = "\
def foo() -> str:
return 42
";
let mut server = TestServerBuilder::new()?
.with_workspace(
workspace_root,
Some(ClientOptions::default().with_diagnostic_mode(ty_server::DiagnosticMode::Off)),
)?
.with_file(foo, foo_content)?
.enable_pull_diagnostics(false)
.build()
.wait_until_workspaces_are_initialized();
server.open_text_document(foo, foo_content, 1);
let diagnostics =
server.try_await_notification::<PublishDiagnostics>(Some(Duration::from_millis(100)));
assert!(
diagnostics.is_err(),
"Server should not send a publish diagnostics notification when diagnostics are off"
);
Ok(())
}
#[test]
fn on_did_change() -> Result<()> {
let workspace_root = SystemPath::new("src");
@@ -69,6 +101,46 @@ def foo() -> str:
Ok(())
}
#[test]
fn on_did_change_diagnostics_off() -> Result<()> {
let workspace_root = SystemPath::new("src");
let foo = SystemPath::new("src/foo.py");
let foo_content = "\
def foo() -> str:
return 42
";
let mut server = TestServerBuilder::new()?
.with_workspace(
workspace_root,
Some(ClientOptions::default().with_diagnostic_mode(ty_server::DiagnosticMode::Off)),
)?
.with_file(foo, foo_content)?
.enable_pull_diagnostics(false)
.build()
.wait_until_workspaces_are_initialized();
server.open_text_document(foo, foo_content, 1);
let changes = vec![lsp_types::TextDocumentContentChangeEvent {
range: None,
range_length: None,
text: "def foo() -> int: return 42".to_string(),
}];
server.change_text_document(foo, changes, 2);
let diagnostics =
server.try_await_notification::<PublishDiagnostics>(Some(Duration::from_millis(100)));
assert!(
diagnostics.is_err(),
"Server should not send a publish diagnostics notification when diagnostics are off"
);
Ok(())
}
#[test]
fn message_without_related_information_support() -> Result<()> {
let workspace_root = SystemPath::new("src");

View File

@@ -1,7 +1,7 @@
use std::time::Duration;
use anyhow::Result;
use insta::assert_debug_snapshot;
use insta::{assert_compact_json_snapshot, assert_debug_snapshot};
use lsp_server::RequestId;
use lsp_types::request::WorkspaceDiagnosticRequest;
use lsp_types::{
@@ -39,6 +39,35 @@ def foo() -> str:
Ok(())
}
#[test]
fn on_did_open_diagnostics_off() -> Result<()> {
let _filter = filter_result_id();
let workspace_root = SystemPath::new("src");
let foo = SystemPath::new("src/foo.py");
let foo_content = "\
def foo() -> str:
return 42
";
let mut server = TestServerBuilder::new()?
.with_workspace(
workspace_root,
Some(ClientOptions::default().with_diagnostic_mode(DiagnosticMode::Off)),
)?
.with_file(foo, foo_content)?
.enable_pull_diagnostics(true)
.build()
.wait_until_workspaces_are_initialized();
server.open_text_document(foo, foo_content, 1);
let diagnostics = server.document_diagnostic_request(foo, None);
assert_compact_json_snapshot!(diagnostics, @r#"{"kind": "full", "items": []}"#);
Ok(())
}
#[test]
fn document_diagnostic_caching_unchanged() -> Result<()> {
let _filter = filter_result_id();