wright/
reporting.rs

1//! Reporting for errors, warnings, and everything else.
2//!
3//! The code in this module is heavily inspired by [codespan-reporting] and [ariadne].
4//!
5//! [codespan-reporting]: https://crates.io/crates/codespan-reporting
6//! [ariadne]: https://crates.io/crates/ariadne
7
8use crate::source_tracking::SourceMap;
9use crate::source_tracking::filename::FileName;
10use crate::source_tracking::immutable_string::ImmutableString;
11use crate::source_tracking::{fragment::Fragment, source::SourceId};
12use codespan_reporting::diagnostic::Diagnostic as CRDiagnostic;
13use codespan_reporting::diagnostic::Label;
14use codespan_reporting::files::{Error as CRError, Files};
15use codespan_reporting::term::Config;
16use std::io::Write;
17use std::sync::Mutex;
18use termcolor::{ColorChoice, StandardStream, WriteColor};
19
20#[cfg(doc)]
21use crate::source_tracking::source::Source;
22
23// Publicly re-export codespan's severity type, since it's pretty much exactly what we want/use.
24pub use codespan_reporting::diagnostic::Severity;
25
26/// The style/priority applied to a [Highlight] being rendered.
27pub use codespan_reporting::diagnostic::LabelStyle;
28
29/// The global-static color choice for printing to the standard output.
30static STDOUT_COLOR_CHOICE: Mutex<ColorChoice> = Mutex::new(ColorChoice::Auto);
31
32/// Set the [ColorChoice] to use when printing [Diagnostic]s to the standard output.
33pub fn set_stdout_color(c: ColorChoice) {
34    *STDOUT_COLOR_CHOICE.lock().unwrap() = c;
35}
36
37/// Get the current [ColorChoice] used when printing [Diagnostic]s to the standard output.
38pub fn get_stdout_color() -> ColorChoice {
39    *STDOUT_COLOR_CHOICE.lock().unwrap()
40}
41
42/// Wright's [Diagnostic] type wraps one from [codespan_reporting] to make it compatible with
43/// things like [Fragment] and [SourceId].
44#[derive(Debug)]
45pub struct Diagnostic(pub CRDiagnostic<SourceId>);
46
47impl Diagnostic {
48    /// Construct a new [Diagnostic] with the given [Severity].
49    pub fn new(severity: Severity) -> Self {
50        Diagnostic(CRDiagnostic::new(severity))
51    }
52
53    /// Construct a new [Diagnostic] representing a wright compiler bug.
54    #[inline]
55    pub fn bug() -> Self {
56        Self::new(Severity::Bug)
57    }
58
59    /// Construct a new [Diagnostic] representing an error.
60    #[inline]
61    pub fn error() -> Self {
62        Self::new(Severity::Error)
63    }
64
65    /// Construct a new [Diagnostic] representing a warning.
66    #[inline]
67    pub fn warning() -> Self {
68        Self::new(Severity::Warning)
69    }
70
71    /// Construct a new [Diagnostic] representing a note.
72    #[inline]
73    pub fn note() -> Self {
74        Self::new(Severity::Note)
75    }
76
77    /// Construct a new [Diagnostic] representing a help message to the user.
78    #[inline]
79    pub fn help() -> Self {
80        Self::new(Severity::Help)
81    }
82
83    /// Builder style function to set an error/warning code for this [Diagnostic].
84    pub fn with_code(self, code: impl Into<String>) -> Self {
85        Diagnostic(self.0.with_code(code))
86    }
87
88    /// Builder style function to set a message for this [Diagnostic].
89    pub fn with_message(self, message: impl Into<String>) -> Self {
90        Diagnostic(self.0.with_message(message))
91    }
92
93    /// Add all the notes from the given [Iterator] to this [Diagnostic].
94    pub fn with_notes<I: Into<String>>(mut self, notes: impl IntoIterator<Item = I>) -> Self {
95        self.0.notes.extend(notes.into_iter().map(Into::into));
96        self
97    }
98
99    /// Add all the [Highlight]s from a given [Iterator] to this [Diagnostic].
100    pub fn with_highlights(mut self, highlights: impl IntoIterator<Item = Highlight>) -> Self {
101        self.0.labels.extend(highlights.into_iter().map(Into::into));
102        self
103    }
104
105    /// Write this [Diagnostic] to a given [WriteColor]. This will error if any of the [Highlight]s are not in
106    /// the referenced [SourceMap], or if any were constructed from invalid [Fragment]s.
107    pub fn write(
108        &self,
109        map: &SourceMap,
110        writer: &mut dyn WriteColor,
111        config: &Config,
112    ) -> Result<(), CRError> {
113        codespan_reporting::term::emit(writer, config, map, &self.0)
114    }
115
116    /// Print this [Diagnostic] to the standard output. This locks the standard output until the diagnostic is printed.
117    /// This uses the global [get_stdout_color] function to determine whether or not to use colors while printing.
118    /// This uses the [Config::default] configuration from [codespan_reporting] when printing.
119    pub fn print(&self, map: &SourceMap) -> Result<(), codespan_reporting::files::Error> {
120        let stream = StandardStream::stdout(get_stdout_color());
121        let mut lock = stream.lock();
122        self.write(map, &mut lock, &Config::default())?;
123        lock.flush()?;
124        Ok(())
125    }
126}
127
128/// A highlighted section of a [Source] that's used in a [Diagnostic].
129#[derive(Clone, Debug)]
130pub struct Highlight {
131    /// The style/importance of this [Highlight]. [Highlight]s with [LabelStyle::Primary] are given priority
132    /// when being displayed.
133    pub style: LabelStyle,
134    /// The [Fragment] containing the relevant section of code.
135    pub fragment: Fragment,
136    /// The message attached to this [Highlight]. This can be empty, in which case the [Fragment] will
137    /// just be underlined when displayed.
138    pub message: String,
139}
140
141impl Highlight {
142    /// Construct a new [Highlight] with [LabelStyle::Primary].
143    pub fn primary(fragment: Fragment, message: impl Into<String>) -> Self {
144        Highlight {
145            style: LabelStyle::Primary,
146            fragment,
147            message: message.into(),
148        }
149    }
150
151    /// Construct a new [Highlight] with [LabelStyle::Secondary].
152    pub fn secondary(fragment: Fragment, message: impl Into<String>) -> Self {
153        Highlight {
154            style: LabelStyle::Secondary,
155            fragment,
156            message: message.into(),
157        }
158    }
159
160    /// Builder style function to set the [Highlight::message].
161    pub fn with_message(mut self, message: impl Into<String>) -> Self {
162        self.message = message.into();
163        self
164    }
165}
166
167impl From<Highlight> for Label<SourceId> {
168    fn from(value: Highlight) -> Self {
169        Label {
170            style: value.style,
171            file_id: value.fragment.source.id,
172            range: value.fragment.range,
173            message: value.message,
174        }
175    }
176}
177
178impl<'f> Files<'f> for SourceMap {
179    type FileId = SourceId;
180
181    type Name = FileName;
182
183    type Source = ImmutableString;
184
185    fn name(&'f self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
186        self.get(id)
187            .map(|source| source.name().clone())
188            .ok_or(CRError::FileMissing)
189    }
190
191    fn source(
192        &'f self,
193        id: Self::FileId,
194    ) -> Result<Self::Source, codespan_reporting::files::Error> {
195        self.get(id)
196            .map(|source| source.source().clone())
197            .ok_or(CRError::FileMissing)
198    }
199
200    fn line_index(
201        &'f self,
202        id: Self::FileId,
203        byte_index: usize,
204    ) -> Result<usize, codespan_reporting::files::Error> {
205        Ok(self
206            .get(id)
207            .ok_or(CRError::FileMissing)?
208            .line_index(byte_index))
209    }
210
211    fn line_range(
212        &'f self,
213        id: Self::FileId,
214        line_index: usize,
215    ) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
216        Ok(self
217            .get(id)
218            .ok_or(CRError::FileMissing)?
219            .get_line(line_index)
220            .range)
221    }
222}