Skip to content

Commit

Permalink
Allowed to get the diagnostics mappings in the LS.
Browse files Browse the repository at this point in the history
  • Loading branch information
orizi committed Aug 27, 2024
1 parent e28e48a commit 51ac1c7
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 19 deletions.
19 changes: 15 additions & 4 deletions crates/cairo-lang-language-server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf>,
/// 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 {
Expand All @@ -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)
Expand All @@ -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:#?}");
}
Expand Down
56 changes: 45 additions & 11 deletions crates/cairo-lang-language-server/src/lang/diagnostics/lsp.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -14,35 +15,46 @@ pub fn map_cairo_diagnostics_to_lsp<T: DiagnosticEntry>(
db: &T::DbType,
diags: &mut Vec<Diagnostic>,
diagnostics: &Diagnostics<T>,
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) = &note.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 {
message += &format!("\nnote: {}", note.text);
}
}

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,
Expand All @@ -53,9 +65,31 @@ pub fn map_cairo_diagnostics_to_lsp<T: DiagnosticEntry>(
}
}

/// 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<dyn FilesGroup> + ?Sized),
orig: &DiagnosticLocation,
related_info: Option<&mut Vec<DiagnosticRelatedInformation>>,
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<Range> {
let location = location.user_location(db);
fn get_lsp_range(db: &dyn FilesGroup, location: &DiagnosticLocation) -> Option<Range> {
let Some(span) = location.span.position_in_file(db, location.file_id) else {
error!("failed to get range for diagnostic");
return None;
Expand Down
25 changes: 21 additions & 4 deletions crates/cairo-lang-language-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 include_diag_mapping = self.config.read().await.trace_macro_diagnostics;
map_cairo_diagnostics_to_lsp(
(*db).upcast(),
&mut diags,
&new_file_diagnostics.parser,
include_diag_mapping,
);
map_cairo_diagnostics_to_lsp(
(*db).upcast(),
&mut diags,
&new_file_diagnostics.semantic,
include_diag_mapping,
);
map_cairo_diagnostics_to_lsp(
(*db).upcast(),
&mut diags,
&new_file_diagnostics.lowering,
include_diag_mapping,
);

// Drop database snapshot before we wait for the client responding to our notification.
drop(db);
Expand Down Expand Up @@ -648,11 +664,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();
}
}

Expand Down
5 changes: 5 additions & 0 deletions vscode-cairo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions vscode-cairo/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface ConfigProps {
preferScarbLanguageServer: boolean;
scarbPath: string;
corelibPath: string;
traceMacroDiagnostics: boolean;
languageServerExtraEnv: null | Record<string, string | number>;
}

Expand Down

0 comments on commit 51ac1c7

Please sign in to comment.