Make diagnostics look closer to what the LSP expects

This commit is contained in:
Brendan Zabarauskas
2019-05-30 21:11:26 +10:00
parent 1c14e952be
commit f87fcaa925
5 changed files with 115 additions and 161 deletions
+11 -28
View File
@@ -180,8 +180,6 @@ pub fn make_lsp_diagnostic<F>(
where
F: FnMut(&str) -> Result<Url, ()>,
{
use codespan_reporting::LabelStyle;
let find_file = |index| {
code_map
.find_file(index)
@@ -189,37 +187,22 @@ where
};
// We need a position for the primary error so take the span from the first primary label
let (primary_file_map, primary_label_range) = {
let first_primary_label = diagnostic
.labels
.iter()
.find(|label| label.style == LabelStyle::Primary);
match first_primary_label {
Some(label) => {
let file_map = find_file(label.span.start())?;
(Some(file_map), byte_span_to_range(&file_map, label.span)?)
},
None => (None, UNKNOWN_RANGE),
}
};
let primary_file_map = find_file(diagnostic.primary_label.span.start())?;
let primary_label_range = byte_span_to_range(&primary_file_map, diagnostic.primary_label.span)?;
let related_information = diagnostic
.labels
.secondary_labels
.into_iter()
.map(|label| {
let (file_map, range) = match primary_file_map {
// If the label's span does not point anywhere, assume it comes from the same file
// as the primary label
Some(file_map) if label.span.start() == ByteIndex::none() => {
(file_map, UNKNOWN_RANGE)
},
Some(_) | None => {
let file_map = find_file(label.span.start())?;
let range = byte_span_to_range(file_map, label.span)?;
// If the label's span does not point anywhere, assume it comes from the same file
// as the primary label
let (file_map, range) = if label.span.start() == ByteIndex::none() {
(primary_file_map, UNKNOWN_RANGE)
} else {
let file_map = find_file(label.span.start())?;
let range = byte_span_to_range(file_map, label.span)?;
(file_map, range)
},
(file_map, range)
};
let uri = codespan_name_to_file(file_map.name())
+15 -17
View File
@@ -2,7 +2,7 @@ use structopt::StructOpt;
use codespan::{CodeMap, Span};
use codespan_reporting::termcolor::StandardStream;
use codespan_reporting::{emit, ColorArg, Diagnostic, Label, Severity};
use codespan_reporting::{emit, ColorArg, Diagnostic, Label};
#[derive(Debug, StructOpt)]
#[structopt(name = "emit")]
@@ -31,25 +31,23 @@ fn main() {
let file_map = code_map.add_filemap("test".into(), source.to_string());
let str_start = file_map.byte_index(3.into(), 6.into()).unwrap();
let error = Diagnostic::new(Severity::Error, "Unexpected type in `+` application")
.with_label(
Label::new_primary(Span::from_offset(str_start, 2.into()))
.with_message("Expected integer but got string"),
)
.with_label(
Label::new_secondary(Span::from_offset(str_start, 2.into()))
.with_message("Expected integer but got string"),
)
.with_code("E0001");
let error = Diagnostic::new_error(
"Unexpected type in `+` application",
Label::new(
Span::from_offset(str_start, 2.into()),
"Expected integer but got string",
),
)
.with_code("E0001")
.with_secondary_labels(vec![Label::new(
Span::from_offset(str_start, 2.into()),
"Expected integer but got string",
)]);
let line_start = file_map.byte_index(2.into(), 3.into()).unwrap();
let warning = Diagnostic::new(
Severity::Warning,
let warning = Diagnostic::new_warning(
"`+` function has no effect unless its result is used",
)
.with_label(
Label::new_primary(Span::from_offset(line_start, 27.into()))
.with_message("Value discarded"),
Label::new(Span::from_offset(line_start, 27.into()), "Value discarded"),
);
let diagnostics = [error, warning];
+23 -53
View File
@@ -6,18 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::Severity;
/// A style for the label
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "memory_usage", derive(heapsize_derive::HeapSizeOf))]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub enum LabelStyle {
/// The main focus of the diagnostic
Primary,
/// Supporting labels that may help to isolate the cause of the diagnostic
Secondary,
}
/// A label describing an underlined region of code associated with a diagnostic
/// A label describing an underlined region of code associated with a diagnostic.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "memory_usage", derive(heapsize_derive::HeapSizeOf))]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
@@ -26,31 +15,15 @@ pub struct Label {
pub span: ByteSpan,
/// A message to provide some additional information for the underlined code.
pub message: String,
/// The style to use for the label.
pub style: LabelStyle,
}
impl Label {
pub fn new(span: ByteSpan, style: LabelStyle) -> Label {
pub fn new(span: ByteSpan, mesaage: impl Into<String>) -> Label {
Label {
span,
message: String::new(),
style,
message: mesaage.into(),
}
}
pub fn new_primary(span: ByteSpan) -> Label {
Label::new(span, LabelStyle::Primary)
}
pub fn new_secondary(span: ByteSpan) -> Label {
Label::new(span, LabelStyle::Secondary)
}
pub fn with_message(mut self, message: impl Into<String>) -> Label {
self.message = message.into();
self
}
}
/// Represents a diagnostic message and associated child messages.
@@ -62,41 +35,43 @@ pub struct Diagnostic {
pub severity: Severity,
/// An optional code that identifies this diagnostic.
pub code: Option<String>,
/// The main message associated with this diagnostic
/// The main message associated with this diagnostic.
pub message: String,
/// The labelled spans marking the regions of code that cause this
/// diagnostic to be raised
pub labels: Vec<Label>,
/// A label that marks the primary cause of this diagnostic.
pub primary_label: Label,
/// Secondary labels that provide additional context for the diagnostic.
pub secondary_labels: Vec<Label>,
}
impl Diagnostic {
pub fn new(severity: Severity, message: impl Into<String>) -> Diagnostic {
pub fn new(severity: Severity, message: impl Into<String>, primary_label: Label) -> Diagnostic {
Diagnostic {
severity,
code: None,
message: message.into(),
labels: Vec::new(),
primary_label,
secondary_labels: Vec::new(),
}
}
pub fn new_bug(message: impl Into<String>) -> Diagnostic {
Diagnostic::new(Severity::Bug, message)
pub fn new_bug(message: impl Into<String>, primary_label: Label) -> Diagnostic {
Diagnostic::new(Severity::Bug, message, primary_label)
}
pub fn new_error(message: impl Into<String>) -> Diagnostic {
Diagnostic::new(Severity::Error, message)
pub fn new_error(message: impl Into<String>, primary_label: Label) -> Diagnostic {
Diagnostic::new(Severity::Error, message, primary_label)
}
pub fn new_warning(message: impl Into<String>) -> Diagnostic {
Diagnostic::new(Severity::Warning, message)
pub fn new_warning(message: impl Into<String>, primary_label: Label) -> Diagnostic {
Diagnostic::new(Severity::Warning, message, primary_label)
}
pub fn new_note(message: impl Into<String>) -> Diagnostic {
Diagnostic::new(Severity::Note, message)
pub fn new_note(message: impl Into<String>, primary_label: Label) -> Diagnostic {
Diagnostic::new(Severity::Note, message, primary_label)
}
pub fn new_help(message: impl Into<String>) -> Diagnostic {
Diagnostic::new(Severity::Help, message)
pub fn new_help(message: impl Into<String>, primary_label: Label) -> Diagnostic {
Diagnostic::new(Severity::Help, message, primary_label)
}
pub fn with_code(mut self, code: impl Into<String>) -> Diagnostic {
@@ -104,13 +79,8 @@ impl Diagnostic {
self
}
pub fn with_label(mut self, label: Label) -> Diagnostic {
self.labels.push(label);
self
}
pub fn with_labels(mut self, labels: impl IntoIterator<Item = Label>) -> Diagnostic {
self.labels.extend(labels);
pub fn with_secondary_labels(mut self, labels: impl IntoIterator<Item = Label>) -> Diagnostic {
self.secondary_labels.extend(labels);
self
}
}
+65 -62
View File
@@ -1,8 +1,8 @@
use codespan::{ByteSpan, CodeMap, FileMap, LineIndex, RawIndex};
use codespan::{ByteIndex, CodeMap, FileMap, LineIndex, RawIndex};
use std::io;
use termcolor::{Color, ColorSpec, WriteColor};
use crate::{Diagnostic, Label, LabelStyle, Severity};
use crate::{Diagnostic, Label, Severity};
#[derive(Clone, Debug)]
pub struct Config {
@@ -60,12 +60,15 @@ pub fn emit(
) -> io::Result<()> {
Header::new(diagnostic).emit(&mut writer, config)?;
for label in &diagnostic.labels {
match codemap.find_file(diagnostic.primary_label.span.start()) {
None => SimpleMessage::new(&diagnostic.primary_label).emit(&mut writer, config)?,
Some(file) => MarkedSource::new_primary(file, &diagnostic).emit(&mut writer, config)?,
}
for label in &diagnostic.secondary_labels {
match codemap.find_file(label.span.start()) {
None => SimpleMessage::new(&label.message).emit(&mut writer, config)?,
Some(file) => {
MarkedSource::new(file, &diagnostic, &label).emit(&mut writer, config)?
},
None => SimpleMessage::new(&label).emit(&mut writer, config)?,
Some(file) => MarkedSource::new_secondary(file, &label).emit(&mut writer, config)?,
}
}
@@ -151,8 +154,10 @@ struct SimpleMessage<'a> {
}
impl<'a> SimpleMessage<'a> {
fn new(message: &'a str) -> SimpleMessage<'a> {
SimpleMessage { message }
fn new(label: &'a Label) -> SimpleMessage<'a> {
SimpleMessage {
message: &label.message,
}
}
fn emit(&self, writer: &mut impl WriteColor, _config: &Config) -> io::Result<()> {
@@ -165,6 +170,11 @@ impl<'a> SimpleMessage<'a> {
}
}
enum MarkStyle {
Primary(Severity),
Secondary,
}
/// A marked section of source code
///
/// ```text
@@ -176,39 +186,46 @@ impl<'a> SimpleMessage<'a> {
/// ```
struct MarkedSource<'a, S: AsRef<str>> {
file: &'a FileMap<S>,
span: ByteSpan,
message: &'a str,
severity: Option<Severity>,
label: &'a Label,
mark_style: MarkStyle,
}
impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
fn new(
file: &'a FileMap<S>,
diagnostic: &Diagnostic,
label: &'a Label,
) -> MarkedSource<'a, S> {
fn new_primary(file: &'a FileMap<S>, diagnostic: &'a Diagnostic) -> MarkedSource<'a, S> {
MarkedSource {
file,
span: label.span,
message: &label.message,
severity: match label.style {
LabelStyle::Primary => Some(diagnostic.severity),
LabelStyle::Secondary => None,
},
label: &diagnostic.primary_label,
mark_style: MarkStyle::Primary(diagnostic.severity),
}
}
fn new_secondary(file: &'a FileMap<S>, label: &'a Label) -> MarkedSource<'a, S> {
MarkedSource {
file,
label,
mark_style: MarkStyle::Secondary,
}
}
fn start(&self) -> ByteIndex {
self.label.span.start()
}
fn end(&self) -> ByteIndex {
self.label.span.end()
}
fn label_color(&self, config: &Config) -> Color {
match self.severity {
None => config.secondary_color,
Some(severity) => config.severity_color(severity),
match self.mark_style {
MarkStyle::Primary(severity) => config.severity_color(severity),
MarkStyle::Secondary => config.secondary_color,
}
}
fn underline_char(&self, config: &Config) -> char {
match self.severity {
Some(_) => config.primary_mark,
None => config.secondary_mark,
match self.mark_style {
MarkStyle::Primary(_) => config.primary_mark,
MarkStyle::Secondary => config.secondary_mark,
}
}
@@ -217,11 +234,8 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
let label_color = self.label_color(config);
let label_spec = ColorSpec::new().set_fg(Some(label_color)).clone();
let (start_line, start_column) = self
.file
.location(self.span.start())
.expect("location_start");
let (end_line, _) = self.file.location(self.span.end()).expect("location_end");
let (start_line, start_column) = self.file.location(self.start()).expect("location_start");
let (end_line, _) = self.file.location(self.end()).expect("location_end");
let start_line_span = self.file.line_span(start_line).expect("line_span");
let end_line_span = self.file.line_span(end_line).expect("line_span");
@@ -270,11 +284,11 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
)?;
writer.reset()?;
let line_trimmer = |ch: char| ch == '\r' || ch == '\n';
// Write source prefix before marked section
let source_prefix = self
.file
.src_slice(start_line_span.with_end(self.span.start()))
.expect("prefix");
let prefix_span = start_line_span.with_end(self.start());
let source_prefix = self.file.src_slice(prefix_span).expect("prefix");
write!(writer, "{}", source_prefix)?;
// Write marked section
@@ -282,7 +296,7 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
// Single line
// Write marked source section
let marked_source = self.file.src_slice(self.span).expect("marked_source");
let marked_source = self.file.src_slice(self.label.span).expect("marked_source");
writer.set_color(&label_spec)?;
write!(writer, "{}", marked_source)?;
writer.reset()?;
@@ -291,10 +305,8 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
// Multiple lines
// Write marked source section
let marked_source = self
.file
.src_slice(start_line_span.with_start(self.span.start()))
.expect("start_of_marked");
let marked_span = start_line_span.with_start(self.start());
let marked_source = self.file.src_slice(marked_span).expect("start_of_marked");
writer.set_color(&label_spec)?;
write!(writer, "{}", marked_source)?;
@@ -311,14 +323,10 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
)?;
// Write marked source section
let line_span = self.file.line_span(line_index).expect("marked_line_span");
let marked_source = self
.file
.src_slice(line_span)
.expect("marked_source")
.trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
let mark_span = self.file.line_span(line_index).expect("marked_line_span");
let marked_source = self.file.src_slice(mark_span).expect("marked_source");
writer.set_color(&label_spec)?;
write!(writer, "{}", marked_source)?;
write!(writer, "{}", marked_source.trim_end_matches(line_trimmer))?;
write!(writer, "\n")?;
}
@@ -332,10 +340,8 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
)?;
// Write marked source section
let marked_source = self
.file
.src_slice(end_line_span.with_end(self.span.end()))
.expect("marked_source");
let mark_span = end_line_span.with_end(self.end());
let marked_source = self.file.src_slice(mark_span).expect("marked_source");
writer.set_color(&label_spec)?;
write!(writer, "{}", marked_source)?;
writer.reset()?;
@@ -343,12 +349,9 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
};
// Write source suffix after marked section
let source = self
.file
.src_slice(end_line_span.with_start(self.span.end()))
.expect("suffix")
.trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
write!(writer, "{}", source)?;
let suffix_span = end_line_span.with_start(self.end());
let source_suffix = self.file.src_slice(suffix_span).expect("suffix");
write!(writer, "{}", source_suffix.trim_end_matches(line_trimmer))?;
write!(writer, "\n")?;
// Write underline gutter
@@ -362,8 +365,8 @@ impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
for _ in 0..mark_len {
write!(writer, "{}", self.underline_char(config))?;
}
if !self.message.is_empty() {
write!(writer, " {}", self.message)?;
if !self.label.message.is_empty() {
write!(writer, " {}", self.label.message)?;
write!(writer, "\n")?;
}
writer.reset()?;
+1 -1
View File
@@ -9,7 +9,7 @@ mod emitter;
pub use termcolor;
pub use self::diagnostic::{Diagnostic, Label, LabelStyle};
pub use self::diagnostic::{Diagnostic, Label};
pub use self::emitter::{emit, Config};
/// A severity level for diagnostic messages