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 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 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 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
91pub 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 eprintln!("{}", std::str::from_utf8(buffer.as_slice()).unwrap());
103}
104
105pub 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}