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