mirror of
https://github.com/openharmony/third_party_rust_codespan.git
synced 2026-07-21 03:55:28 -04:00
Merge pull request #66 from brendanzab/components
Make diagnostics look more like what the LSP expects
This commit is contained in:
+11
-28
@@ -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())
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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,34 +15,19 @@ 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, message: impl Into<String>) -> Label {
|
||||
Label {
|
||||
span,
|
||||
message: String::new(),
|
||||
style,
|
||||
message: message.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.
|
||||
/// Represents a diagnostic message that can provide information like errors and
|
||||
/// warnings to the user.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "memory_usage", derive(heapsize_derive::HeapSizeOf))]
|
||||
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
||||
@@ -62,41 +36,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 describes 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 +80,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
|
||||
}
|
||||
}
|
||||
|
||||
+326
-225
@@ -1,19 +1,31 @@
|
||||
use codespan::{CodeMap, LineIndex, RawIndex};
|
||||
use codespan::{ByteIndex, CodeMap, FileMap, LineIndex, RawIndex};
|
||||
use std::io;
|
||||
use termcolor::{Color, ColorSpec, WriteColor};
|
||||
|
||||
use crate::{Diagnostic, LabelStyle, Severity};
|
||||
use crate::{Diagnostic, Label, Severity};
|
||||
|
||||
/// Configures how a diagnostic is rendered.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// The color to use when rendering bugs. Defaults to `Color::Red`.
|
||||
pub bug_color: Color,
|
||||
/// The color to use when rendering errors. Defaults to `Color::Red`.
|
||||
pub error_color: Color,
|
||||
/// The color to use when rendering warnings. Defaults to `Color::Yellow`.
|
||||
pub warning_color: Color,
|
||||
/// The color to use when rendering notes. Defaults to `Color::Green`.
|
||||
pub note_color: Color,
|
||||
/// The color to use when rendering helps. Defaults to `Color::Cyan`.
|
||||
pub help_color: Color,
|
||||
/// The color to use when rendering secondary labels. Defaults to
|
||||
/// `Color::Blue` (or `Color::Cyan` on windows).
|
||||
pub secondary_color: Color,
|
||||
/// The color to use when rendering gutters. Defaults to `Color::Blue`
|
||||
/// (or `Color::Cyan` on windows).
|
||||
pub gutter_color: Color,
|
||||
/// The character to use when underlining a primary label. Defaults to: `^`.
|
||||
pub primary_mark: char,
|
||||
/// The character to use when underlining a secondary label. Defaults to: `-`.
|
||||
pub secondary_mark: char,
|
||||
}
|
||||
|
||||
@@ -50,22 +62,6 @@ impl Config {
|
||||
Severity::Help => self.help_color,
|
||||
}
|
||||
}
|
||||
|
||||
/// The style used to mark the labelled section of code.
|
||||
pub fn label_color(&self, severity: Severity, label_style: LabelStyle) -> Color {
|
||||
match label_style {
|
||||
LabelStyle::Primary => self.severity_color(severity),
|
||||
LabelStyle::Secondary => self.secondary_color,
|
||||
}
|
||||
}
|
||||
|
||||
/// The character used for the underlined section of code.
|
||||
fn underline_char(&self, label_style: LabelStyle) -> char {
|
||||
match label_style {
|
||||
LabelStyle::Primary => self.primary_mark,
|
||||
LabelStyle::Secondary => self.secondary_mark,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit(
|
||||
@@ -74,221 +70,326 @@ pub fn emit(
|
||||
codemap: &CodeMap<impl AsRef<str>>,
|
||||
diagnostic: &Diagnostic,
|
||||
) -> io::Result<()> {
|
||||
let severity_color = config.severity_color(diagnostic.severity);
|
||||
let gutter_spec = ColorSpec::new().set_fg(Some(config.gutter_color)).clone();
|
||||
let header_message_spec = ColorSpec::new().set_bold(true).set_intense(true).clone();
|
||||
let header_primary_spec = ColorSpec::new()
|
||||
.set_bold(true)
|
||||
.set_intense(true)
|
||||
.set_fg(Some(severity_color))
|
||||
.clone();
|
||||
Header::new(diagnostic).emit(&mut writer, config)?;
|
||||
|
||||
// Diagnostic header
|
||||
//
|
||||
// ```
|
||||
// error[E0001]: Unexpected type in `+` application
|
||||
// ```
|
||||
|
||||
// Write severity name
|
||||
//
|
||||
// ```
|
||||
// error
|
||||
// ```
|
||||
writer.set_color(&header_primary_spec)?;
|
||||
write!(writer, "{}", severity_name(diagnostic.severity))?;
|
||||
if let Some(code) = &diagnostic.code {
|
||||
// Write error code
|
||||
//
|
||||
// ```
|
||||
// [E0001]
|
||||
// ```
|
||||
write!(writer, "[{}]", code)?;
|
||||
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)?,
|
||||
}
|
||||
|
||||
// Write diagnostic message
|
||||
//
|
||||
// ```
|
||||
// : Unexpected type in `+` application
|
||||
// ```
|
||||
writer.set_color(&header_message_spec)?;
|
||||
write!(writer, ": {}", diagnostic.message)?;
|
||||
write!(writer, "\n")?;
|
||||
writer.reset()?;
|
||||
|
||||
for label in &diagnostic.labels {
|
||||
for label in &diagnostic.secondary_labels {
|
||||
match codemap.find_file(label.span.start()) {
|
||||
None => {
|
||||
if !label.message.is_empty() {
|
||||
write!(writer, "- {}", label.message)?;
|
||||
write!(writer, "\n")?;
|
||||
}
|
||||
},
|
||||
Some(file) => {
|
||||
let (start_line, start_column) =
|
||||
file.location(label.span.start()).expect("location_start");
|
||||
let (end_line, _) = file.location(label.span.end()).expect("location_end");
|
||||
let start_line_span = file.line_span(start_line).expect("line_span");
|
||||
let end_line_span = file.line_span(end_line).expect("line_span");
|
||||
|
||||
// Use the length of the last line number as the gutter padding
|
||||
let gutter_padding = format!("{}", end_line.number()).len();
|
||||
|
||||
// File name
|
||||
//
|
||||
// ```
|
||||
// ┌╴ <test>:2:9
|
||||
// ```
|
||||
|
||||
// Write gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} ┌╴ ", "", width = gutter_padding)?;
|
||||
writer.reset()?;
|
||||
|
||||
// Write file name
|
||||
write!(
|
||||
writer,
|
||||
"{file}:{line}:{column}",
|
||||
file = file.name(),
|
||||
line = start_line.number(),
|
||||
column = start_column.number(),
|
||||
)?;
|
||||
write!(writer, "\n")?;
|
||||
|
||||
// Source code snippet
|
||||
//
|
||||
// ```
|
||||
// │
|
||||
// 2 │ (+ test "")
|
||||
// │ ^^ Expected integer but got string
|
||||
// ╵
|
||||
// ```
|
||||
|
||||
// Write line number and gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
|
||||
write!(writer, "\n")?;
|
||||
write!(
|
||||
writer,
|
||||
"{: >width$} │ ",
|
||||
start_line.number(),
|
||||
width = gutter_padding,
|
||||
)?;
|
||||
writer.reset()?;
|
||||
|
||||
// Write source prefix before marked section
|
||||
let source_prefix = file
|
||||
.src_slice(start_line_span.with_end(label.span.start()))
|
||||
.expect("prefix");
|
||||
write!(writer, "{}", source_prefix)?;
|
||||
|
||||
let label_color = config.label_color(diagnostic.severity, label.style);
|
||||
let label_spec = ColorSpec::new().set_fg(Some(label_color)).clone();
|
||||
|
||||
// Write marked section
|
||||
let mark_len = if start_line == end_line {
|
||||
// Single line
|
||||
|
||||
// Write marked source section
|
||||
let marked_source = file.src_slice(label.span).expect("marked_source");
|
||||
writer.set_color(&label_spec)?;
|
||||
write!(writer, "{}", marked_source)?;
|
||||
writer.reset()?;
|
||||
marked_source.len()
|
||||
} else {
|
||||
// Multiple lines
|
||||
|
||||
// Write marked source section
|
||||
let marked_source = file
|
||||
.src_slice(start_line_span.with_start(label.span.start()))
|
||||
.expect("start_of_marked");
|
||||
writer.set_color(&label_spec)?;
|
||||
write!(writer, "{}", marked_source)?;
|
||||
|
||||
for line_index in ((start_line.to_usize() + 1)..end_line.to_usize())
|
||||
.map(|i| LineIndex::from(i as RawIndex))
|
||||
{
|
||||
// Write line number and gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(
|
||||
writer,
|
||||
"{: >width$} │ ",
|
||||
line_index.number(),
|
||||
width = gutter_padding,
|
||||
)?;
|
||||
|
||||
// Write marked source section
|
||||
let line_span = file.line_span(line_index).expect("marked_line_span");
|
||||
let marked_source = file
|
||||
.src_slice(line_span)
|
||||
.expect("marked_source")
|
||||
.trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
|
||||
writer.set_color(&label_spec)?;
|
||||
write!(writer, "{}", marked_source)?;
|
||||
write!(writer, "\n")?;
|
||||
}
|
||||
|
||||
// Write line number and gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(
|
||||
writer,
|
||||
"{: >width$} │ ",
|
||||
end_line.number(),
|
||||
width = gutter_padding,
|
||||
)?;
|
||||
|
||||
// Write marked source section
|
||||
let marked_source = file
|
||||
.src_slice(end_line_span.with_end(label.span.end()))
|
||||
.expect("marked_source");
|
||||
writer.set_color(&label_spec)?;
|
||||
write!(writer, "{}", marked_source)?;
|
||||
writer.reset()?;
|
||||
marked_source.len()
|
||||
};
|
||||
|
||||
// Write source suffix after marked section
|
||||
let source = file
|
||||
.src_slice(end_line_span.with_start(label.span.end()))
|
||||
.expect("suffix")
|
||||
.trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
|
||||
write!(writer, "{}", source)?;
|
||||
write!(writer, "\n")?;
|
||||
|
||||
// Write underline gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
|
||||
writer.reset()?;
|
||||
|
||||
// Write underline and label
|
||||
writer.set_color(&label_spec)?;
|
||||
write!(writer, "{: >width$}", "", width = source_prefix.len())?;
|
||||
for _ in 0..mark_len {
|
||||
write!(writer, "{}", config.underline_char(label.style))?;
|
||||
}
|
||||
if !label.message.is_empty() {
|
||||
write!(writer, " {}", label.message)?;
|
||||
write!(writer, "\n")?;
|
||||
}
|
||||
writer.reset()?;
|
||||
|
||||
// Write final gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} ╵", "", width = gutter_padding)?;
|
||||
write!(writer, "\n")?;
|
||||
writer.reset()?;
|
||||
},
|
||||
None => SimpleMessage::new(&label).emit(&mut writer, config)?,
|
||||
Some(file) => MarkedSource::new_secondary(file, &label).emit(&mut writer, config)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A string that explains this diagnostic severity.
|
||||
fn severity_name(severity: Severity) -> &'static str {
|
||||
match severity {
|
||||
Severity::Bug => "bug",
|
||||
Severity::Error => "error",
|
||||
Severity::Warning => "warning",
|
||||
Severity::Note => "note",
|
||||
Severity::Help => "help",
|
||||
/// Diagnostic header
|
||||
///
|
||||
/// ```text
|
||||
/// error[E0001]: Unexpected type in `+` application
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Header<'a> {
|
||||
severity: Severity,
|
||||
code: Option<&'a str>,
|
||||
message: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Header<'a> {
|
||||
fn new(diagnostic: &'a Diagnostic) -> Header<'a> {
|
||||
Header {
|
||||
severity: diagnostic.severity,
|
||||
code: diagnostic.code.as_ref().map(String::as_str),
|
||||
message: &diagnostic.message,
|
||||
}
|
||||
}
|
||||
|
||||
fn severity_name(&self) -> &'static str {
|
||||
match self.severity {
|
||||
Severity::Bug => "bug",
|
||||
Severity::Error => "error",
|
||||
Severity::Warning => "warning",
|
||||
Severity::Help => "help",
|
||||
Severity::Note => "note",
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(&self, writer: &mut impl WriteColor, config: &Config) -> io::Result<()> {
|
||||
let message_spec = ColorSpec::new().set_bold(true).set_intense(true).clone();
|
||||
let primary_spec = ColorSpec::new()
|
||||
.set_bold(true)
|
||||
.set_intense(true)
|
||||
.set_fg(Some(config.severity_color(self.severity)))
|
||||
.clone();
|
||||
|
||||
// Write severity name
|
||||
//
|
||||
// ```
|
||||
// error
|
||||
// ```
|
||||
writer.set_color(&primary_spec)?;
|
||||
write!(writer, "{}", self.severity_name())?;
|
||||
if let Some(code) = &self.code {
|
||||
// Write error code
|
||||
//
|
||||
// ```
|
||||
// [E0001]
|
||||
// ```
|
||||
write!(writer, "[{}]", code)?;
|
||||
}
|
||||
|
||||
// Write diagnostic message
|
||||
//
|
||||
// ```
|
||||
// : Unexpected type in `+` application
|
||||
// ```
|
||||
writer.set_color(&message_spec)?;
|
||||
write!(writer, ": {}", self.message)?;
|
||||
write!(writer, "\n")?;
|
||||
writer.reset()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple message
|
||||
///
|
||||
/// ```text
|
||||
/// - Expected integer but got string
|
||||
/// ```
|
||||
struct SimpleMessage<'a> {
|
||||
message: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> SimpleMessage<'a> {
|
||||
fn new(label: &'a Label) -> SimpleMessage<'a> {
|
||||
SimpleMessage {
|
||||
message: &label.message,
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(&self, writer: &mut impl WriteColor, _config: &Config) -> io::Result<()> {
|
||||
if !self.message.is_empty() {
|
||||
write!(writer, "- {}", self.message)?;
|
||||
write!(writer, "\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum MarkStyle {
|
||||
Primary(Severity),
|
||||
Secondary,
|
||||
}
|
||||
|
||||
/// A marked section of source code
|
||||
///
|
||||
/// ```text
|
||||
/// ┌╴ <test>:2:9
|
||||
/// │
|
||||
/// 2 │ (+ test "")
|
||||
/// │ ^^ Expected integer but got string
|
||||
/// ╵
|
||||
/// ```
|
||||
struct MarkedSource<'a, S: AsRef<str>> {
|
||||
file: &'a FileMap<S>,
|
||||
label: &'a Label,
|
||||
mark_style: MarkStyle,
|
||||
}
|
||||
|
||||
impl<'a, S: AsRef<str>> MarkedSource<'a, S> {
|
||||
fn new_primary(file: &'a FileMap<S>, diagnostic: &'a Diagnostic) -> MarkedSource<'a, S> {
|
||||
MarkedSource {
|
||||
file,
|
||||
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.mark_style {
|
||||
MarkStyle::Primary(severity) => config.severity_color(severity),
|
||||
MarkStyle::Secondary => config.secondary_color,
|
||||
}
|
||||
}
|
||||
|
||||
fn underline_char(&self, config: &Config) -> char {
|
||||
match self.mark_style {
|
||||
MarkStyle::Primary(_) => config.primary_mark,
|
||||
MarkStyle::Secondary => config.secondary_mark,
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(&self, writer: &mut impl WriteColor, config: &Config) -> io::Result<()> {
|
||||
let gutter_spec = ColorSpec::new().set_fg(Some(config.gutter_color)).clone();
|
||||
let label_spec = ColorSpec::new()
|
||||
.set_fg(Some(self.label_color(config)))
|
||||
.clone();
|
||||
|
||||
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");
|
||||
|
||||
// Use the length of the last line number as the gutter padding
|
||||
let gutter_padding = format!("{}", end_line.number()).len();
|
||||
|
||||
// File name
|
||||
//
|
||||
// ```
|
||||
// ┌╴ <test>:2:9
|
||||
// ```
|
||||
|
||||
// Write gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} ┌╴ ", "", width = gutter_padding)?;
|
||||
writer.reset()?;
|
||||
|
||||
// Write file name
|
||||
write!(
|
||||
writer,
|
||||
"{file}:{line}:{column}",
|
||||
file = self.file.name(),
|
||||
line = start_line.number(),
|
||||
column = start_column.number(),
|
||||
)?;
|
||||
write!(writer, "\n")?;
|
||||
|
||||
// Source code snippet
|
||||
//
|
||||
// ```
|
||||
// │
|
||||
// 2 │ (+ test "")
|
||||
// │ ^^ Expected integer but got string
|
||||
// ╵
|
||||
// ```
|
||||
|
||||
// Write line number and gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
|
||||
write!(writer, "\n")?;
|
||||
write!(
|
||||
writer,
|
||||
"{: >width$} │ ",
|
||||
start_line.number(),
|
||||
width = gutter_padding,
|
||||
)?;
|
||||
writer.reset()?;
|
||||
|
||||
let line_trimmer = |ch: char| ch == '\r' || ch == '\n';
|
||||
|
||||
// Write source prefix before marked section
|
||||
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
|
||||
let mark_len = if start_line == end_line {
|
||||
// Single line
|
||||
|
||||
// Write marked source section
|
||||
let marked_source = self.file.src_slice(self.label.span).expect("marked_source");
|
||||
writer.set_color(&label_spec)?;
|
||||
write!(writer, "{}", marked_source)?;
|
||||
writer.reset()?;
|
||||
marked_source.len()
|
||||
} else {
|
||||
// Multiple lines
|
||||
|
||||
// Write marked source section
|
||||
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)?;
|
||||
|
||||
for line_index in ((start_line.to_usize() + 1)..end_line.to_usize())
|
||||
.map(|i| LineIndex::from(i as RawIndex))
|
||||
{
|
||||
// Write line number and gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(
|
||||
writer,
|
||||
"{: >width$} │ ",
|
||||
line_index.number(),
|
||||
width = gutter_padding,
|
||||
)?;
|
||||
|
||||
// Write marked source section
|
||||
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.trim_end_matches(line_trimmer))?;
|
||||
write!(writer, "\n")?;
|
||||
}
|
||||
|
||||
// Write line number and gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(
|
||||
writer,
|
||||
"{: >width$} │ ",
|
||||
end_line.number(),
|
||||
width = gutter_padding,
|
||||
)?;
|
||||
|
||||
// Write marked source section
|
||||
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()?;
|
||||
marked_source.len()
|
||||
};
|
||||
|
||||
// Write source suffix after marked section
|
||||
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
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
|
||||
writer.reset()?;
|
||||
|
||||
// Write underline and label
|
||||
writer.set_color(&label_spec)?;
|
||||
write!(writer, "{: >width$}", "", width = source_prefix.len())?;
|
||||
for _ in 0..mark_len {
|
||||
write!(writer, "{}", self.underline_char(config))?;
|
||||
}
|
||||
if !self.label.message.is_empty() {
|
||||
write!(writer, " {}", self.label.message)?;
|
||||
write!(writer, "\n")?;
|
||||
}
|
||||
writer.reset()?;
|
||||
|
||||
// Write final gutter
|
||||
writer.set_color(&gutter_spec)?;
|
||||
write!(writer, "{: >width$} ╵", "", width = gutter_padding)?;
|
||||
write!(writer, "\n")?;
|
||||
writer.reset()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user