From 11901384b407f8a13ea6d20a410c833545f92572 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 9 Dec 2025 13:18:30 +0100 Subject: [PATCH] [ty] Use concise message for LSP clients not supporting related diagnostic information (#21850) --- crates/ty_server/src/capabilities.rs | 23 +++- .../ty_server/src/server/api/diagnostics.rs | 90 +++++++++---- .../src/server/api/requests/diagnostic.rs | 4 +- .../api/requests/workspace_diagnostic.rs | 13 +- crates/ty_server/tests/e2e/main.rs | 10 ++ crates/ty_server/tests/e2e/notebook.rs | 4 +- .../tests/e2e/publish_diagnostics.rs | 51 +++++++ .../e2e__code_actions__code_action.snap | 2 - ...action_attribute_access_on_unimported.snap | 6 +- ...n_existing_import_undefined_decorator.snap | 6 +- ...ode_action_invalid_string_annotations.snap | 3 +- ..._possible_missing_submodule_attribute.snap | 3 +- ...ions__code_action_undefined_decorator.snap | 6 +- ...code_action_undefined_reference_multi.snap | 6 +- ...e2e__notebook__diagnostic_end_of_file.snap | 83 ++---------- ...sage_with_related_information_support.snap | 99 ++++++++++++++ ...e_without_related_information_support.snap | 70 ++++++++++ ...e2e__publish_diagnostics__on_did_open.snap | 31 +---- .../e2e__pull_diagnostics__on_did_open.snap | 31 +---- ...s__workspace_diagnostic_after_changes.snap | 93 +------------ ...s__workspace_diagnostic_initial_state.snap | 124 +----------------- ...agnostic_long_polling_change_response.snap | 31 +---- ...ace_diagnostic_streaming_with_caching.snap | 12 +- ...suspend_change_suspend_first_response.snap | 31 +---- 24 files changed, 361 insertions(+), 471 deletions(-) create mode 100644 crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_with_related_information_support.snap create mode 100644 crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_without_related_information_support.snap diff --git a/crates/ty_server/src/capabilities.rs b/crates/ty_server/src/capabilities.rs index 23daa43dee..60b5298280 100644 --- a/crates/ty_server/src/capabilities.rs +++ b/crates/ty_server/src/capabilities.rs @@ -36,6 +36,7 @@ bitflags::bitflags! { const WORKSPACE_CONFIGURATION = 1 << 15; const RENAME_DYNAMIC_REGISTRATION = 1 << 16; const COMPLETION_ITEM_LABEL_DETAILS_SUPPORT = 1 << 17; + const DIAGNOSTIC_RELATED_INFORMATION = 1 << 18; } } @@ -163,6 +164,11 @@ impl ResolvedClientCapabilities { self.contains(Self::DIAGNOSTIC_DYNAMIC_REGISTRATION) } + /// Returns `true` if the client has related information support for diagnostics. + pub(crate) const fn supports_diagnostic_related_information(self) -> bool { + self.contains(Self::DIAGNOSTIC_RELATED_INFORMATION) + } + /// Returns `true` if the client supports dynamic registration for rename capabilities. pub(crate) const fn supports_rename_dynamic_registration(self) -> bool { self.contains(Self::RENAME_DYNAMIC_REGISTRATION) @@ -211,15 +217,22 @@ impl ResolvedClientCapabilities { } } - if text_document.is_some_and(|text_document| text_document.diagnostic.is_some()) { + if let Some(diagnostic) = + text_document.and_then(|text_document| text_document.diagnostic.as_ref()) + { flags |= Self::PULL_DIAGNOSTICS; + + if diagnostic.dynamic_registration == Some(true) { + flags |= Self::DIAGNOSTIC_DYNAMIC_REGISTRATION; + } } - if text_document - .and_then(|text_document| text_document.diagnostic.as_ref()?.dynamic_registration) - .unwrap_or_default() + if let Some(publish_diagnostics) = + text_document.and_then(|text_document| text_document.publish_diagnostics.as_ref()) { - flags |= Self::DIAGNOSTIC_DYNAMIC_REGISTRATION; + if publish_diagnostics.related_information == Some(true) { + flags |= Self::DIAGNOSTIC_RELATED_INFORMATION; + } } if text_document diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index 3344738cbf..60420b4651 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -16,6 +16,7 @@ use ruff_db::system::SystemPathBuf; use serde::{Deserialize, Serialize}; use ty_project::{Db as _, ProjectDatabase}; +use crate::capabilities::ResolvedClientCapabilities; use crate::document::{FileRangeExt, ToRangeExt}; use crate::session::DocumentHandle; use crate::session::client::Client; @@ -56,7 +57,11 @@ impl Diagnostics { Self::result_id_from_hash(&self.items) } - pub(super) fn to_lsp_diagnostics(&self, db: &ProjectDatabase) -> LspDiagnostics { + pub(super) fn to_lsp_diagnostics( + &self, + db: &ProjectDatabase, + client_capabilities: ResolvedClientCapabilities, + ) -> LspDiagnostics { if let Some(notebook_document) = db.notebook_document(self.file_or_notebook) { let mut cell_diagnostics: FxHashMap> = FxHashMap::default(); @@ -67,7 +72,8 @@ impl Diagnostics { } for diagnostic in &self.items { - let (url, lsp_diagnostic) = to_lsp_diagnostic(db, diagnostic, self.encoding); + let (url, lsp_diagnostic) = + to_lsp_diagnostic(db, diagnostic, self.encoding, client_capabilities); let Some(url) = url else { tracing::warn!("Unable to find notebook cell"); @@ -85,7 +91,9 @@ impl Diagnostics { LspDiagnostics::TextDocument( self.items .iter() - .map(|diagnostic| to_lsp_diagnostic(db, diagnostic, self.encoding).1) + .map(|diagnostic| { + to_lsp_diagnostic(db, diagnostic, self.encoding, client_capabilities).1 + }) .collect(), ) } @@ -181,7 +189,7 @@ pub(super) fn publish_diagnostics(document: &DocumentHandle, session: &Session, }); }; - match diagnostics.to_lsp_diagnostics(db) { + match diagnostics.to_lsp_diagnostics(db, session.client_capabilities()) { LspDiagnostics::TextDocument(diagnostics) => { publish_diagnostics_notification(document.url().clone(), diagnostics); } @@ -212,6 +220,7 @@ pub(crate) fn publish_settings_diagnostics( } let session_encoding = session.position_encoding(); + let client_capabilities = session.client_capabilities(); let state = session.project_state_mut(&AnySystemPath::System(path)); let db = &state.db; let project = db.project(); @@ -253,7 +262,9 @@ pub(crate) fn publish_settings_diagnostics( // Convert diagnostics to LSP format let lsp_diagnostics = file_diagnostics .into_iter() - .map(|diagnostic| to_lsp_diagnostic(db, &diagnostic, session_encoding).1) + .map(|diagnostic| { + to_lsp_diagnostic(db, &diagnostic, session_encoding, client_capabilities).1 + }) .collect::>(); client.send_notification::(PublishDiagnosticsParams { @@ -292,7 +303,11 @@ pub(super) fn to_lsp_diagnostic( db: &dyn Db, diagnostic: &ruff_db::diagnostic::Diagnostic, encoding: PositionEncoding, + client_capabilities: ResolvedClientCapabilities, ) -> (Option, Diagnostic) { + let supports_related_information = + client_capabilities.supports_diagnostic_related_information(); + let location = diagnostic.primary_span().and_then(|span| { let file = span.expect_ty_file(); span.range()? @@ -330,31 +345,35 @@ pub(super) fn to_lsp_diagnostic( Some(CodeDescription { href }) }); - let mut related_information = Vec::new(); + let related_information = + if supports_related_information { + let mut related_information = Vec::new(); + related_information.extend(diagnostic.secondary_annotations().filter_map( + |annotation| annotation_to_related_information(db, annotation, encoding), + )); - related_information.extend( - diagnostic - .secondary_annotations() - .filter_map(|annotation| annotation_to_related_information(db, annotation, encoding)), - ); + for sub_diagnostic in diagnostic.sub_diagnostics() { + related_information.extend(sub_diagnostic_to_related_information( + db, + sub_diagnostic, + encoding, + )); - for sub_diagnostic in diagnostic.sub_diagnostics() { - related_information.extend(sub_diagnostic_to_related_information( - db, - sub_diagnostic, - encoding, - )); + related_information.extend( + sub_diagnostic + .annotations() + .iter() + .filter(|annotation| !annotation.is_primary()) + .filter_map(|annotation| { + annotation_to_related_information(db, annotation, encoding) + }), + ); + } - related_information.extend( - sub_diagnostic - .annotations() - .iter() - .filter(|annotation| !annotation.is_primary()) - .filter_map(|annotation| { - annotation_to_related_information(db, annotation, encoding) - }), - ); - } + Some(related_information) + } else { + None + }; let data = DiagnosticData::try_from_diagnostic(db, diagnostic, encoding); @@ -367,8 +386,21 @@ pub(super) fn to_lsp_diagnostic( code: Some(NumberOrString::String(diagnostic.id().to_string())), code_description, source: Some(DIAGNOSTIC_NAME.into()), - message: diagnostic.concise_message().to_string(), - related_information: Some(related_information), + message: if supports_related_information { + // Show both the primary and annotation messages if available, + // because we don't create a related information for the primary message. + if let Some(annotation_message) = diagnostic + .primary_annotation() + .and_then(|annotation| annotation.get_message()) + { + format!("{}: {annotation_message}", diagnostic.primary_message()) + } else { + diagnostic.primary_message().to_string() + } + } else { + diagnostic.concise_message().to_string() + }, + related_information, data: serde_json::to_value(data).ok(), }, ) diff --git a/crates/ty_server/src/server/api/requests/diagnostic.rs b/crates/ty_server/src/server/api/requests/diagnostic.rs index ad1b8a4dc0..97f0633c8a 100644 --- a/crates/ty_server/src/server/api/requests/diagnostic.rs +++ b/crates/ty_server/src/server/api/requests/diagnostic.rs @@ -59,7 +59,9 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler { result_id: new_id, // SAFETY: Pull diagnostic requests are only called for text documents, not for // notebook documents. - items: diagnostics.to_lsp_diagnostics(db).expect_text_document(), + items: diagnostics + .to_lsp_diagnostics(db, snapshot.resolved_client_capabilities()) + .expect_text_document(), }, }) } diff --git a/crates/ty_server/src/server/api/requests/workspace_diagnostic.rs b/crates/ty_server/src/server/api/requests/workspace_diagnostic.rs index a9f33880e5..4935735ced 100644 --- a/crates/ty_server/src/server/api/requests/workspace_diagnostic.rs +++ b/crates/ty_server/src/server/api/requests/workspace_diagnostic.rs @@ -19,6 +19,7 @@ use serde::{Deserialize, Serialize}; use ty_project::{ProgressReporter, ProjectDatabase}; use crate::PositionEncoding; +use crate::capabilities::ResolvedClientCapabilities; use crate::document::DocumentKey; use crate::server::api::diagnostics::{Diagnostics, to_lsp_diagnostic}; use crate::server::api::traits::{ @@ -318,6 +319,7 @@ struct ResponseWriter<'a> { mode: ReportingMode, index: &'a Index, position_encoding: PositionEncoding, + client_capabilities: ResolvedClientCapabilities, // It's important that we use `AnySystemPath` over `Url` here because // `file_to_url` isn't guaranteed to return the exact same URL as the one provided // by the client. @@ -357,6 +359,7 @@ impl<'a> ResponseWriter<'a> { mode, index, position_encoding, + client_capabilities: snapshot.resolved_client_capabilities(), previous_result_ids, } } @@ -406,7 +409,15 @@ impl<'a> ResponseWriter<'a> { new_id => { let lsp_diagnostics = diagnostics .iter() - .map(|diagnostic| to_lsp_diagnostic(db, diagnostic, self.position_encoding).1) + .map(|diagnostic| { + to_lsp_diagnostic( + db, + diagnostic, + self.position_encoding, + self.client_capabilities, + ) + .1 + }) .collect::>(); WorkspaceDocumentDiagnosticReport::Full(WorkspaceFullDocumentDiagnosticReport { diff --git a/crates/ty_server/tests/e2e/main.rs b/crates/ty_server/tests/e2e/main.rs index 7e380562fa..94557172d0 100644 --- a/crates/ty_server/tests/e2e/main.rs +++ b/crates/ty_server/tests/e2e/main.rs @@ -1103,6 +1103,16 @@ impl TestServerBuilder { self } + pub(crate) fn enable_diagnostic_related_information(mut self, enabled: bool) -> Self { + self.client_capabilities + .text_document + .get_or_insert_default() + .publish_diagnostics + .get_or_insert_default() + .related_information = Some(enabled); + self + } + /// Set custom client capabilities (overrides any previously set capabilities) #[expect(dead_code)] pub(crate) fn with_client_capabilities(mut self, capabilities: ClientCapabilities) -> Self { diff --git a/crates/ty_server/tests/e2e/notebook.rs b/crates/ty_server/tests/e2e/notebook.rs index b8cb10643b..7c7847dfcb 100644 --- a/crates/ty_server/tests/e2e/notebook.rs +++ b/crates/ty_server/tests/e2e/notebook.rs @@ -10,6 +10,7 @@ static FILTERS: &[(&str, &str)] = &[(r#""sortText": "[0-9 ]+""#, r#""sortText": #[test] fn publish_diagnostics_open() -> anyhow::Result<()> { let mut server = TestServerBuilder::new()? + .enable_diagnostic_related_information(true) .build() .wait_until_workspaces_are_initialized(); @@ -219,8 +220,7 @@ fn swap_cells() -> anyhow::Result<()> { "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `a` used when not defined", - "relatedInformation": [] + "message": "Name `a` used when not defined" } ], "vscode-notebook-cell://src/test.ipynb#1": [], diff --git a/crates/ty_server/tests/e2e/publish_diagnostics.rs b/crates/ty_server/tests/e2e/publish_diagnostics.rs index b7f1eaf2d9..64580bc88c 100644 --- a/crates/ty_server/tests/e2e/publish_diagnostics.rs +++ b/crates/ty_server/tests/e2e/publish_diagnostics.rs @@ -30,6 +30,57 @@ def foo() -> str: Ok(()) } +#[test] +fn message_without_related_information_support() -> Result<()> { + let workspace_root = SystemPath::new("src"); + let foo = SystemPath::new("src/foo.py"); + let foo_content = r#" +from typing import assert_type + +assert_type("test", list[str]) +"#; + + let mut server = TestServerBuilder::new()? + .with_workspace(workspace_root, None)? + .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.await_notification::(); + + insta::assert_debug_snapshot!(diagnostics); + + Ok(()) +} + +#[test] +fn message_with_related_information_support() -> Result<()> { + let workspace_root = SystemPath::new("src"); + let foo = SystemPath::new("src/foo.py"); + let foo_content = r#" +from typing import assert_type + +assert_type("test", list[str]) +"#; + + let mut server = TestServerBuilder::new()? + .with_workspace(workspace_root, None)? + .with_file(foo, foo_content)? + .enable_diagnostic_related_information(true) + .enable_pull_diagnostics(false) + .build() + .wait_until_workspaces_are_initialized(); + + server.open_text_document(foo, foo_content, 1); + let diagnostics = server.await_notification::(); + + insta::assert_debug_snapshot!(diagnostics); + + Ok(()) +} + #[test] fn on_did_change_watched_files() -> Result<()> { let workspace_root = SystemPath::new("src"); diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action.snap index ae0da5c3ad..af59d0f038 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action.snap @@ -25,7 +25,6 @@ expression: code_actions }, "source": "ty", "message": "Unused `ty: ignore` directive", - "relatedInformation": [], "tags": [ 1 ] @@ -74,7 +73,6 @@ expression: code_actions }, "source": "ty", "message": "Unused `ty: ignore` directive", - "relatedInformation": [], "tags": [ 1 ] diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_attribute_access_on_unimported.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_attribute_access_on_unimported.snap index 3026696d8e..78da8d45f7 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_attribute_access_on_unimported.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_attribute_access_on_unimported.snap @@ -24,8 +24,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `typing` used when not defined", - "relatedInformation": [] + "message": "Name `typing` used when not defined" } ], "edit": { @@ -70,8 +69,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `typing` used when not defined", - "relatedInformation": [] + "message": "Name `typing` used when not defined" } ], "edit": { diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_existing_import_undefined_decorator.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_existing_import_undefined_decorator.snap index fd022ed8b2..f5e5a4f565 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_existing_import_undefined_decorator.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_existing_import_undefined_decorator.snap @@ -24,8 +24,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `deprecated` used when not defined", - "relatedInformation": [] + "message": "Name `deprecated` used when not defined" } ], "edit": { @@ -70,8 +69,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `deprecated` used when not defined", - "relatedInformation": [] + "message": "Name `deprecated` used when not defined" } ], "edit": { diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_invalid_string_annotations.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_invalid_string_annotations.snap index 07ae5cb675..6e5e2f3edc 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_invalid_string_annotations.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_invalid_string_annotations.snap @@ -24,8 +24,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `foobar` used when not defined", - "relatedInformation": [] + "message": "Name `foobar` used when not defined" } ], "edit": { diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_possible_missing_submodule_attribute.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_possible_missing_submodule_attribute.snap index fe723d2fcc..5ab3b0dc23 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_possible_missing_submodule_attribute.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_possible_missing_submodule_attribute.snap @@ -24,8 +24,7 @@ expression: code_actions "href": "https://ty.dev/rules#possibly-missing-attribute" }, "source": "ty", - "message": "Submodule `parser` may not be available as an attribute on module `html`", - "relatedInformation": [] + "message": "Submodule `parser` may not be available as an attribute on module `html`" } ], "edit": { diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap index 44c5c5cd22..6c42ea73da 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_decorator.snap @@ -24,8 +24,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `deprecated` used when not defined", - "relatedInformation": [] + "message": "Name `deprecated` used when not defined" } ], "edit": { @@ -70,8 +69,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `deprecated` used when not defined", - "relatedInformation": [] + "message": "Name `deprecated` used when not defined" } ], "edit": { diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap index a57ac11745..c1ed495ca8 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__code_actions__code_action_undefined_reference_multi.snap @@ -24,8 +24,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `Literal` used when not defined", - "relatedInformation": [] + "message": "Name `Literal` used when not defined" } ], "edit": { @@ -70,8 +69,7 @@ expression: code_actions "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `Literal` used when not defined", - "relatedInformation": [] + "message": "Name `Literal` used when not defined" } ], "edit": { diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__diagnostic_end_of_file.snap b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__diagnostic_end_of_file.snap index 3d11a0804c..24b529f89a 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__notebook__diagnostic_end_of_file.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__notebook__diagnostic_end_of_file.snap @@ -22,8 +22,7 @@ expression: diagnostics "href": "https://ty.dev/rules#invalid-return-type" }, "source": "ty", - "message": "Function can implicitly return `None`, which is not assignable to return type `str`", - "relatedInformation": [] + "message": "Function can implicitly return `None`, which is not assignable to return type `str`" } ], "vscode-notebook-cell://test.ipynb#2": [ @@ -41,8 +40,7 @@ expression: diagnostics "severity": 1, "code": "invalid-syntax", "source": "ty", - "message": "Expected `,`, found name", - "relatedInformation": [] + "message": "Expected `,`, found name" }, { "range": { @@ -61,8 +59,7 @@ expression: diagnostics "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `word` used when not defined", - "relatedInformation": [] + "message": "Name `word` used when not defined" }, { "range": { @@ -78,8 +75,7 @@ expression: diagnostics "severity": 1, "code": "invalid-syntax", "source": "ty", - "message": "Expected `,`, found string", - "relatedInformation": [] + "message": "Expected `,`, found string" }, { "range": { @@ -98,41 +94,7 @@ expression: diagnostics "href": "https://ty.dev/rules#invalid-argument-type" }, "source": "ty", - "message": "Argument to function `with_style` is incorrect: Expected `Style`, found `Literal[/", /"]`", - "relatedInformation": [ - { - "location": { - "uri": "vscode-notebook-cell://test.ipynb#1", - "range": { - "start": { - "line": 0, - "character": 4 - }, - "end": { - "line": 0, - "character": 14 - } - } - }, - "message": "Function defined here" - }, - { - "location": { - "uri": "vscode-notebook-cell://test.ipynb#1", - "range": { - "start": { - "line": 0, - "character": 32 - }, - "end": { - "line": 0, - "character": 44 - } - } - }, - "message": "Parameter declared here" - } - ] + "message": "Argument to function `with_style` is incorrect: Expected `Style`, found `Literal[/", /"]`" }, { "range": { @@ -148,8 +110,7 @@ expression: diagnostics "severity": 1, "code": "invalid-syntax", "source": "ty", - "message": "Expected `,`, found name", - "relatedInformation": [] + "message": "Expected `,`, found name" }, { "range": { @@ -168,8 +129,7 @@ expression: diagnostics "href": "https://ty.dev/rules#unresolved-reference" }, "source": "ty", - "message": "Name `underline` used when not defined", - "relatedInformation": [] + "message": "Name `underline` used when not defined" }, { "range": { @@ -188,25 +148,7 @@ expression: diagnostics "href": "https://ty.dev/rules#too-many-positional-arguments" }, "source": "ty", - "message": "Too many positional arguments to function `with_style`: expected 3, got 6", - "relatedInformation": [ - { - "location": { - "uri": "vscode-notebook-cell://test.ipynb#1", - "range": { - "start": { - "line": 0, - "character": 4 - }, - "end": { - "line": 0, - "character": 52 - } - } - }, - "message": "Function signature here" - } - ] + "message": "Too many positional arguments to function `with_style`: expected 3, got 6" }, { "range": { @@ -222,8 +164,7 @@ expression: diagnostics "severity": 1, "code": "invalid-syntax", "source": "ty", - "message": "missing closing quote in string literal", - "relatedInformation": [] + "message": "missing closing quote in string literal" }, { "range": { @@ -239,8 +180,7 @@ expression: diagnostics "severity": 1, "code": "invalid-syntax", "source": "ty", - "message": "Expected `,`, found name", - "relatedInformation": [] + "message": "Expected `,`, found name" }, { "range": { @@ -256,8 +196,7 @@ expression: diagnostics "severity": 1, "code": "invalid-syntax", "source": "ty", - "message": "unexpected EOF while parsing", - "relatedInformation": [] + "message": "unexpected EOF while parsing" } ] } diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_with_related_information_support.snap b/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_with_related_information_support.snap new file mode 100644 index 0000000000..7c18c8f93d --- /dev/null +++ b/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_with_related_information_support.snap @@ -0,0 +1,99 @@ +--- +source: crates/ty_server/tests/e2e/publish_diagnostics.rs +expression: diagnostics +--- +PublishDiagnosticsParams { + uri: Url { + scheme: "file", + cannot_be_a_base: false, + username: "", + password: None, + host: None, + port: None, + path: "/src/foo.py", + query: None, + fragment: None, + }, + diagnostics: [ + Diagnostic { + range: Range { + start: Position { + line: 3, + character: 0, + }, + end: Position { + line: 3, + character: 30, + }, + }, + severity: Some( + Error, + ), + code: Some( + String( + "type-assertion-failure", + ), + ), + code_description: Some( + CodeDescription { + href: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "ty.dev", + ), + ), + port: None, + path: "/rules", + query: None, + fragment: Some( + "type-assertion-failure", + ), + }, + }, + ), + source: Some( + "ty", + ), + message: "Argument does not have asserted type `list[str]`", + related_information: Some( + [ + DiagnosticRelatedInformation { + location: Location { + uri: Url { + scheme: "file", + cannot_be_a_base: false, + username: "", + password: None, + host: None, + port: None, + path: "/src/foo.py", + query: None, + fragment: None, + }, + range: Range { + start: Position { + line: 3, + character: 12, + }, + end: Position { + line: 3, + character: 18, + }, + }, + }, + message: "Inferred type is `Literal[/"test/"]`", + }, + ], + ), + tags: None, + data: None, + }, + ], + version: Some( + 1, + ), +} diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_without_related_information_support.snap b/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_without_related_information_support.snap new file mode 100644 index 0000000000..d2056ebf20 --- /dev/null +++ b/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__message_without_related_information_support.snap @@ -0,0 +1,70 @@ +--- +source: crates/ty_server/tests/e2e/publish_diagnostics.rs +expression: diagnostics +--- +PublishDiagnosticsParams { + uri: Url { + scheme: "file", + cannot_be_a_base: false, + username: "", + password: None, + host: None, + port: None, + path: "/src/foo.py", + query: None, + fragment: None, + }, + diagnostics: [ + Diagnostic { + range: Range { + start: Position { + line: 3, + character: 0, + }, + end: Position { + line: 3, + character: 30, + }, + }, + severity: Some( + Error, + ), + code: Some( + String( + "type-assertion-failure", + ), + ), + code_description: Some( + CodeDescription { + href: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "ty.dev", + ), + ), + port: None, + path: "/rules", + query: None, + fragment: Some( + "type-assertion-failure", + ), + }, + }, + ), + source: Some( + "ty", + ), + message: "Type `list[str]` does not match asserted type `Literal[/"test/"]`", + related_information: None, + tags: None, + data: None, + }, + ], + version: Some( + 1, + ), +} diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__on_did_open.snap b/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__on_did_open.snap index be48dde9dc..63f94e2ae7 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__on_did_open.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__publish_diagnostics__on_did_open.snap @@ -59,36 +59,7 @@ PublishDiagnosticsParams { "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/foo.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__on_did_open.snap b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__on_did_open.snap index 12ade6fda2..86cf96fef5 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__on_did_open.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__on_did_open.snap @@ -55,36 +55,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/foo.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_after_changes.snap b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_after_changes.snap index 38f03c13e2..02b3f7f831 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_after_changes.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_after_changes.snap @@ -70,36 +70,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `int`, found `Literal[/"hello/"]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/changed_error.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `int` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, @@ -194,36 +165,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/modified_same_error.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 3, - character: 13, - }, - end: Position { - line: 3, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, @@ -296,36 +238,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/new_error.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_initial_state.snap b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_initial_state.snap index 2b3bc9b579..ddd3944928 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_initial_state.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_initial_state.snap @@ -68,36 +68,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/changed_error.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, @@ -168,36 +139,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/fixed_error.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, @@ -268,36 +210,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/modified_same_error.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, @@ -370,36 +283,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/unchanged.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 13, - }, - end: Position { - line: 0, - character: 16, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_long_polling_change_response.snap b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_long_polling_change_response.snap index e2f12e261c..e1e5223e0d 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_long_polling_change_response.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_long_polling_change_response.snap @@ -70,36 +70,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/test.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 15, - }, - end: Position { - line: 0, - character: 18, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, }, diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_streaming_with_caching.snap b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_streaming_with_caching.snap index 90fb910f5e..d6832c5425 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_streaming_with_caching.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_streaming_with_caching.snap @@ -68,9 +68,7 @@ expression: all_items "ty", ), message: "Name `true` used when not defined", - related_information: Some( - [], - ), + related_information: None, tags: None, data: None, }, @@ -143,9 +141,7 @@ expression: all_items "ty", ), message: "Name `true` used when not defined", - related_information: Some( - [], - ), + related_information: None, tags: None, data: None, }, @@ -218,9 +214,7 @@ expression: all_items "ty", ), message: "Name `true` used when not defined", - related_information: Some( - [], - ), + related_information: None, tags: None, data: None, }, diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_suspend_change_suspend_first_response.snap b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_suspend_change_suspend_first_response.snap index 6f38e6bc19..2052485560 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_suspend_change_suspend_first_response.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__pull_diagnostics__workspace_diagnostic_suspend_change_suspend_first_response.snap @@ -70,36 +70,7 @@ Report( "ty", ), message: "Return type does not match returned value: expected `str`, found `Literal[42]`", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: Url { - scheme: "file", - cannot_be_a_base: false, - username: "", - password: None, - host: None, - port: None, - path: "/src/test.py", - query: None, - fragment: None, - }, - range: Range { - start: Position { - line: 0, - character: 15, - }, - end: Position { - line: 0, - character: 18, - }, - }, - }, - message: "Expected `str` because of return type", - }, - ], - ), + related_information: None, tags: None, data: None, },