From 659db48af59bfeca0eda18e6cf7315e9e2800822 Mon Sep 17 00:00:00 2001 From: Ori Ziv Date: Sun, 25 Aug 2024 14:04:22 +0300 Subject: [PATCH] Allowed to get the diagnostics mappings in the LS. --- .../cairo-lang-language-server/src/config.rs | 19 +++++-- .../src/lang/diagnostics/lsp.rs | 56 +++++++++++++++---- crates/cairo-lang-language-server/src/lib.rs | 25 +++++++-- vscode-cairo/package.json | 5 ++ vscode-cairo/src/config.ts | 1 + 5 files changed, 87 insertions(+), 19 deletions(-) diff --git a/crates/cairo-lang-language-server/src/config.rs b/crates/cairo-lang-language-server/src/config.rs index decd08f978e..e22cec3fdd0 100644 --- a/crates/cairo-lang-language-server/src/config.rs +++ b/crates/cairo-lang-language-server/src/config.rs @@ -28,6 +28,12 @@ pub struct Config { /// /// The property is set by the user under the `cairo1.corelibPath` key in client configuration. pub unmanaged_core_path: Option, + /// Whether to include the trace of the generation location of diagnostic location mapped by + /// macros. + /// + /// The property is set by the user under the `cairo1.traceMacroDiagnostics` key in client + /// configuration. + pub trace_macro_diagnostics: bool, } impl Config { @@ -42,10 +48,13 @@ impl Config { return; } - let items = vec![ConfigurationItem { - scope_uri: None, - section: Some("cairo1.corelibPath".to_owned()), - }]; + let items = vec![ + ConfigurationItem { scope_uri: None, section: Some("cairo1.corelibPath".to_owned()) }, + ConfigurationItem { + scope_uri: None, + section: Some("cairo1.traceMacroDiagnostics".to_owned()), + }, + ]; let expected_len = items.len(); if let Ok(response) = client .configuration(items) @@ -71,6 +80,8 @@ impl Config { .and_then(Value::as_str) .filter(|s| !s.is_empty()) .map(Into::into); + self.trace_macro_diagnostics = + response.pop_front().as_ref().and_then(Value::as_bool).unwrap_or_default(); debug!("reloaded configuration: {self:#?}"); } diff --git a/crates/cairo-lang-language-server/src/lang/diagnostics/lsp.rs b/crates/cairo-lang-language-server/src/lang/diagnostics/lsp.rs index 0381ab20bff..0799a0e1438 100644 --- a/crates/cairo-lang-language-server/src/lang/diagnostics/lsp.rs +++ b/crates/cairo-lang-language-server/src/lang/diagnostics/lsp.rs @@ -1,5 +1,6 @@ use cairo_lang_diagnostics::{DiagnosticEntry, DiagnosticLocation, Diagnostics, Severity}; use cairo_lang_filesystem::db::FilesGroup; +use cairo_lang_filesystem::ids::FileId; use cairo_lang_utils::Upcast; use tower_lsp::lsp_types::{ Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, Location, NumberOrString, Range, @@ -14,17 +15,27 @@ pub fn map_cairo_diagnostics_to_lsp( db: &T::DbType, diags: &mut Vec, diagnostics: &Diagnostics, + trace_macro_diagnostics: bool, ) { - for diagnostic in diagnostics.get_diagnostics_without_duplicates(db) { + for diagnostic in if trace_macro_diagnostics { + diagnostics.get_all() + } else { + diagnostics.get_diagnostics_without_duplicates(db) + } { let mut message = diagnostic.format(db); let mut related_information = vec![]; for note in diagnostic.notes(db) { if let Some(location) = ¬e.location { - let Some(range) = get_range(db.upcast(), location) else { + let Some((range, file_id)) = get_mapped_range_and_add_mapping_note( + db, + location, + trace_macro_diagnostics.then_some(&mut related_information), + "Next note mapped from here.", + ) else { continue; }; related_information.push(DiagnosticRelatedInformation { - location: Location { uri: db.url_for_file(location.file_id), range }, + location: Location { uri: db.url_for_file(file_id), range }, message: note.text.clone(), }); } else { @@ -32,17 +43,18 @@ pub fn map_cairo_diagnostics_to_lsp( } } - let Some(range) = get_range(db.upcast(), &diagnostic.location(db)) else { + let Some((range, _)) = get_mapped_range_and_add_mapping_note( + db, + &diagnostic.location(db), + trace_macro_diagnostics.then_some(&mut related_information), + "Diagnostic mapped from here.", + ) else { continue; }; diags.push(Diagnostic { range, message, - related_information: if related_information.is_empty() { - None - } else { - Some(related_information) - }, + related_information: (!related_information.is_empty()).then_some(related_information), severity: Some(match diagnostic.severity() { Severity::Error => DiagnosticSeverity::ERROR, Severity::Warning => DiagnosticSeverity::WARNING, @@ -53,9 +65,31 @@ pub fn map_cairo_diagnostics_to_lsp( } } +/// Returns the mapped range of a location, optionally adds a note about the mapping of the +/// location. +fn get_mapped_range_and_add_mapping_note( + db: &(impl Upcast + ?Sized), + orig: &DiagnosticLocation, + related_info: Option<&mut Vec>, + message: &str, +) -> Option<(Range, FileId)> { + let mapped = orig.user_location(db.upcast()); + let mapped_range = get_lsp_range(db.upcast(), &mapped)?; + if let Some(related_info) = related_info { + if *orig != mapped { + if let Some(range) = get_lsp_range(db.upcast(), orig) { + related_info.push(DiagnosticRelatedInformation { + location: Location { uri: db.url_for_file(orig.file_id), range }, + message: message.to_string(), + }); + } + } + } + Some((mapped_range, mapped.file_id)) +} + /// Converts an internal diagnostic location to an LSP range. -fn get_range(db: &dyn FilesGroup, location: &DiagnosticLocation) -> Option { - let location = location.user_location(db); +fn get_lsp_range(db: &dyn FilesGroup, location: &DiagnosticLocation) -> Option { let Some(span) = location.span.position_in_file(db, location.file_id) else { error!("failed to get range for diagnostic"); return None; diff --git a/crates/cairo-lang-language-server/src/lib.rs b/crates/cairo-lang-language-server/src/lib.rs index 68f29aece21..9bc69f2180c 100644 --- a/crates/cairo-lang-language-server/src/lib.rs +++ b/crates/cairo-lang-language-server/src/lib.rs @@ -452,9 +452,25 @@ impl Backend { drop(state); let mut diags = Vec::new(); - map_cairo_diagnostics_to_lsp((*db).upcast(), &mut diags, &new_file_diagnostics.parser); - map_cairo_diagnostics_to_lsp((*db).upcast(), &mut diags, &new_file_diagnostics.semantic); - map_cairo_diagnostics_to_lsp((*db).upcast(), &mut diags, &new_file_diagnostics.lowering); + let trace_macro_diagnostics = self.config.read().await.trace_macro_diagnostics; + map_cairo_diagnostics_to_lsp( + (*db).upcast(), + &mut diags, + &new_file_diagnostics.parser, + trace_macro_diagnostics, + ); + map_cairo_diagnostics_to_lsp( + (*db).upcast(), + &mut diags, + &new_file_diagnostics.semantic, + trace_macro_diagnostics, + ); + map_cairo_diagnostics_to_lsp( + (*db).upcast(), + &mut diags, + &new_file_diagnostics.lowering, + trace_macro_diagnostics, + ); // Drop database snapshot before we wait for the client responding to our notification. drop(db); @@ -654,11 +670,12 @@ impl Backend { /// Reload the [`Config`] and all its dependencies. async fn reload_config(&self) { - let mut config = self.config.write().await; { + let mut config = self.config.write().await; let client_capabilities = self.client_capabilities.read().await; config.reload(&self.client, &client_capabilities).await; } + self.refresh_diagnostics().await.ok(); } } diff --git a/vscode-cairo/package.json b/vscode-cairo/package.json index 3c3df174328..935896de3ef 100644 --- a/vscode-cairo/package.json +++ b/vscode-cairo/package.json @@ -117,6 +117,11 @@ "description": "Absolute path to the Cairo core library, used in non-Scarb projects.", "scope": "window" }, + "cairo1.traceMacroDiagnostics": { + "type": "boolean", + "description": "Attach additional information to diagnostics coming from macros, providing diagnostic source in macro generated code.", + "scope": "window" + }, "cairo1.languageServerExtraEnv": { "type": [ "null", diff --git a/vscode-cairo/src/config.ts b/vscode-cairo/src/config.ts index a0c32628714..1a644e1fa5c 100644 --- a/vscode-cairo/src/config.ts +++ b/vscode-cairo/src/config.ts @@ -8,6 +8,7 @@ interface ConfigProps { preferScarbLanguageServer: boolean; scarbPath: string; corelibPath: string; + traceMacroDiagnostics: boolean; languageServerExtraEnv: null | Record; }