fe_common/
diagnostics.rs

1use crate::db::SourceDb;
2use crate::files::{SourceFileId, Utf8PathBuf};
3use crate::Span;
4pub use codespan_reporting::diagnostic as cs;
5use codespan_reporting::files::Error as CsError;
6use codespan_reporting::term;
7pub use cs::Severity;
8use std::ops::Range;
9use std::rc::Rc;
10use term::termcolor::{BufferWriter, ColorChoice};
11
12#[derive(Debug, PartialEq, Eq, Hash, Clone)]
13pub struct Diagnostic {
14    pub severity: Severity,
15    pub message: String,
16    pub labels: Vec<Label>,
17    pub notes: Vec<String>,
18}
19impl Diagnostic {
20    pub fn into_cs(self) -> cs::Diagnostic<SourceFileId> {
21        cs::Diagnostic {
22            severity: self.severity,
23            code: None,
24            message: self.message,
25            labels: self.labels.into_iter().map(Label::into_cs_label).collect(),
26            notes: self.notes,
27        }
28    }
29    pub fn error(message: String) -> Self {
30        Self {
31            severity: Severity::Error,
32            message,
33            labels: vec![],
34            notes: vec![],
35        }
36    }
37}
38
39#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
40pub enum LabelStyle {
41    Primary,
42    Secondary,
43}
44impl From<LabelStyle> for cs::LabelStyle {
45    fn from(other: LabelStyle) -> cs::LabelStyle {
46        match other {
47            LabelStyle::Primary => cs::LabelStyle::Primary,
48            LabelStyle::Secondary => cs::LabelStyle::Secondary,
49        }
50    }
51}
52
53#[derive(Debug, PartialEq, Eq, Hash, Clone)]
54pub struct Label {
55    pub style: LabelStyle,
56    pub span: Span,
57    pub message: String,
58}
59impl Label {
60    /// Create a primary label with the given message. This will underline the
61    /// given span with carets (`^^^^`).
62    pub fn primary<S: Into<String>>(span: Span, message: S) -> Self {
63        Label {
64            style: LabelStyle::Primary,
65            span,
66            message: message.into(),
67        }
68    }
69
70    /// Create a secondary label with the given message. This will underline the
71    /// given span with hyphens (`----`).
72    pub fn secondary<S: Into<String>>(span: Span, message: S) -> Self {
73        Label {
74            style: LabelStyle::Secondary,
75            span,
76            message: message.into(),
77        }
78    }
79
80    /// Convert into a [`codespan_reporting::Diagnostic::Label`]
81    pub fn into_cs_label(self) -> cs::Label<SourceFileId> {
82        cs::Label {
83            style: self.style.into(),
84            file_id: self.span.file_id,
85            range: self.span.into(),
86            message: self.message,
87        }
88    }
89}
90
91/// Print the given diagnostics to stderr.
92pub fn print_diagnostics(db: &dyn SourceDb, diagnostics: &[Diagnostic]) {
93    let writer = BufferWriter::stderr(ColorChoice::Auto);
94    let mut buffer = writer.buffer();
95    let config = term::Config::default();
96    let files = SourceDbWrapper(db);
97
98    for diag in diagnostics {
99        term::emit(&mut buffer, &config, &files, &diag.clone().into_cs()).unwrap();
100    }
101    // If we use `writer` here, the output won't be captured by rust's test system.
102    eprintln!("{}", std::str::from_utf8(buffer.as_slice()).unwrap());
103}
104
105/// Format the given diagnostics as a string.
106pub fn diagnostics_string(db: &dyn SourceDb, diagnostics: &[Diagnostic]) -> String {
107    let writer = BufferWriter::stderr(ColorChoice::Never);
108    let mut buffer = writer.buffer();
109    let config = term::Config::default();
110    let files = SourceDbWrapper(db);
111
112    for diag in diagnostics {
113        term::emit(&mut buffer, &config, &files, &diag.clone().into_cs())
114            .expect("failed to emit diagnostic");
115    }
116    std::str::from_utf8(buffer.as_slice()).unwrap().to_string()
117}
118
119struct SourceDbWrapper<'a>(pub &'a dyn SourceDb);
120
121impl<'a> codespan_reporting::files::Files<'_> for SourceDbWrapper<'a> {
122    type FileId = SourceFileId;
123    type Name = Rc<Utf8PathBuf>;
124    type Source = Rc<str>;
125
126    fn name(&self, file: SourceFileId) -> Result<Self::Name, CsError> {
127        Ok(file.path(self.0))
128    }
129
130    fn source(&self, file: SourceFileId) -> Result<Self::Source, CsError> {
131        Ok(file.content(self.0))
132    }
133
134    fn line_index(&self, file: SourceFileId, byte_index: usize) -> Result<usize, CsError> {
135        Ok(file.line_index(self.0, byte_index))
136    }
137
138    fn line_range(&self, file: SourceFileId, line_index: usize) -> Result<Range<usize>, CsError> {
139        file.line_range(self.0, line_index)
140            .ok_or(CsError::LineTooLarge {
141                given: line_index,
142                max: self.0.file_line_starts(file).len() - 1,
143            })
144    }
145}