Emit additional diagnostics for hints/help/etc

This commit is contained in:
Jonas Schievink 2020-12-06 01:24:37 +01:00
parent 8d5aa08712
commit 9d96a6d7af

View file

@ -75,8 +75,10 @@ fn diagnostic_related_information(
} }
enum MappedRustChildDiagnostic { enum MappedRustChildDiagnostic {
Related(lsp_types::DiagnosticRelatedInformation), Related {
SuggestedFix(lsp_ext::CodeAction), related: lsp_types::DiagnosticRelatedInformation,
suggested_fix: Option<lsp_ext::CodeAction>,
},
MessageLine(String), MessageLine(String),
} }
@ -103,23 +105,32 @@ fn map_rust_child_diagnostic(
} }
if edit_map.is_empty() { if edit_map.is_empty() {
MappedRustChildDiagnostic::Related(lsp_types::DiagnosticRelatedInformation { MappedRustChildDiagnostic::Related {
location: location(workspace_root, spans[0]), related: lsp_types::DiagnosticRelatedInformation {
message: rd.message.clone(), location: location(workspace_root, spans[0]),
}) message: rd.message.clone(),
},
suggested_fix: None,
}
} else { } else {
MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { MappedRustChildDiagnostic::Related {
title: rd.message.clone(), related: lsp_types::DiagnosticRelatedInformation {
group: None, location: location(workspace_root, spans[0]),
kind: Some(lsp_types::CodeActionKind::QUICKFIX), message: rd.message.clone(),
edit: Some(lsp_ext::SnippetWorkspaceEdit { },
// FIXME: there's no good reason to use edit_map here.... suggested_fix: Some(lsp_ext::CodeAction {
changes: Some(edit_map), title: rd.message.clone(),
document_changes: None, group: None,
kind: Some(lsp_types::CodeActionKind::QUICKFIX),
edit: Some(lsp_ext::SnippetWorkspaceEdit {
// FIXME: there's no good reason to use edit_map here....
changes: Some(edit_map),
document_changes: None,
}),
is_preferred: Some(true),
data: None,
}), }),
is_preferred: Some(true), }
data: None,
})
} }
} }
@ -179,8 +190,12 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
for child in &rd.children { for child in &rd.children {
let child = map_rust_child_diagnostic(workspace_root, &child); let child = map_rust_child_diagnostic(workspace_root, &child);
match child { match child {
MappedRustChildDiagnostic::Related(related) => related_information.push(related), MappedRustChildDiagnostic::Related { related, suggested_fix } => {
MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), related_information.push(related);
if let Some(code_action) = suggested_fix {
fixes.push(code_action);
}
}
MappedRustChildDiagnostic::MessageLine(message_line) => { MappedRustChildDiagnostic::MessageLine(message_line) => {
format_to!(message, "\n{}", message_line); format_to!(message, "\n{}", message_line);
@ -219,7 +234,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
primary_spans primary_spans
.iter() .iter()
.map(|primary_span| { .flat_map(|primary_span| {
let location = location(workspace_root, &primary_span); let location = location(workspace_root, &primary_span);
let mut message = message.clone(); let mut message = message.clone();
@ -229,72 +244,100 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
} }
} }
// Each primary diagnostic span may result in multiple LSP diagnostics.
let mut diagnostics = Vec::new();
let mut related_macro_info = None;
// If error occurs from macro expansion, add related info pointing to // If error occurs from macro expansion, add related info pointing to
// where the error originated // where the error originated
// Also, we would generate an additional diagnostic, so that exact place of macro // Also, we would generate an additional diagnostic, so that exact place of macro
// will be highlighted in the error origin place. // will be highlighted in the error origin place.
let additional_diagnostic = if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { let in_macro_location = location_naive(workspace_root, &primary_span);
let in_macro_location = location_naive(workspace_root, &primary_span);
// Add related information for the main disagnostic. // Add related information for the main disagnostic.
related_information.push(lsp_types::DiagnosticRelatedInformation { related_macro_info = Some(lsp_types::DiagnosticRelatedInformation {
location: in_macro_location.clone(), location: in_macro_location.clone(),
message: "Error originated from macro here".to_string(), message: "Error originated from macro here".to_string(),
}); });
// For the additional in-macro diagnostic we add the inverse message pointing to the error location in code. // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
let information_for_additional_diagnostic = let information_for_additional_diagnostic =
vec![lsp_types::DiagnosticRelatedInformation { vec![lsp_types::DiagnosticRelatedInformation {
location: location.clone(), location: location.clone(),
message: "Exact error occured here".to_string(), message: "Exact error occured here".to_string(),
}]; }];
let diagnostic = lsp_types::Diagnostic { let diagnostic = lsp_types::Diagnostic {
range: in_macro_location.range, range: in_macro_location.range,
severity, severity,
code: code.clone().map(lsp_types::NumberOrString::String),
code_description: code_description.clone(),
source: Some(source.clone()),
message: message.clone(),
related_information: Some(information_for_additional_diagnostic),
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
data: None,
};
diagnostics.push(MappedRustDiagnostic {
url: in_macro_location.uri,
diagnostic,
fixes: fixes.clone(),
});
}
// Emit the primary diagnostic.
diagnostics.push(MappedRustDiagnostic {
url: location.uri.clone(),
diagnostic: lsp_types::Diagnostic {
range: location.range,
severity,
code: code.clone().map(lsp_types::NumberOrString::String),
code_description: code_description.clone(),
source: Some(source.clone()),
message,
related_information: if related_information.is_empty() {
None
} else {
let mut related = related_information.clone();
related.extend(related_macro_info);
Some(related)
},
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
data: None,
},
fixes: fixes.clone(),
});
// Emit hint-level diagnostics for all `related_information` entries such as "help"s.
// This is useful because they will show up in the user's editor, unlike
// `related_information`, which just produces hard-to-read links, at least in VS Code.
let back_ref = lsp_types::DiagnosticRelatedInformation {
location,
message: "original diagnostic".to_string(),
};
for info in &related_information {
diagnostics.push(MappedRustDiagnostic {
url: info.location.uri.clone(),
fixes: fixes.clone(), // share fixes to make them easier to apply
diagnostic: lsp_types::Diagnostic {
range: info.location.range,
severity: Some(lsp_types::DiagnosticSeverity::Hint),
code: code.clone().map(lsp_types::NumberOrString::String), code: code.clone().map(lsp_types::NumberOrString::String),
code_description: code_description.clone(), code_description: code_description.clone(),
source: Some(source.clone()), source: Some(source.clone()),
message: message.clone(), message: info.message.clone(),
related_information: Some(information_for_additional_diagnostic), related_information: Some(vec![back_ref.clone()]),
tags: if tags.is_empty() { None } else { Some(tags.clone()) }, tags: None, // don't apply modifiers again
data: None, data: None,
}; },
});
Some(MappedRustDiagnostic {
url: in_macro_location.uri,
diagnostic,
fixes: fixes.clone(),
})
} else {
None
};
let diagnostic = lsp_types::Diagnostic {
range: location.range,
severity,
code: code.clone().map(lsp_types::NumberOrString::String),
code_description: code_description.clone(),
source: Some(source.clone()),
message,
related_information: if related_information.is_empty() {
None
} else {
Some(related_information.clone())
},
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
data: None,
};
let main_diagnostic =
MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() };
match additional_diagnostic {
None => vec![main_diagnostic],
Some(additional_diagnostic) => vec![main_diagnostic, additional_diagnostic],
} }
diagnostics
}) })
.flatten()
.collect() .collect()
} }