use crate::diagnostics::{Diagnostic, DiagnosticLevel};
use crate::slice_file::{SliceFile, Span};
use crate::slice_options::{DiagnosticFormat, SliceOptions};
use serde::ser::SerializeStruct;
use serde::Serializer;
use std::io::{Result, Write};
use std::path::Path;
#[derive(Debug)]
pub struct DiagnosticEmitter<'a, T: Write> {
output: &'a mut T,
diagnostic_format: DiagnosticFormat,
disable_color: bool,
files: &'a [SliceFile],
}
impl<'a, T: Write> DiagnosticEmitter<'a, T> {
pub fn new(output: &'a mut T, slice_options: &SliceOptions, files: &'a [SliceFile]) -> Self {
DiagnosticEmitter {
output,
diagnostic_format: slice_options.diagnostic_format,
disable_color: slice_options.disable_color,
files,
}
}
pub fn emit_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) -> Result<()> {
if self.disable_color {
console::set_colors_enabled(false);
console::set_colors_enabled_stderr(false);
}
match self.diagnostic_format {
DiagnosticFormat::Human => self.emit_diagnostics_in_human(diagnostics),
DiagnosticFormat::Json => self.emit_diagnostics_in_json(diagnostics),
}
}
fn emit_diagnostics_in_human(&mut self, diagnostics: Vec<Diagnostic>) -> Result<()> {
for diagnostic in diagnostics {
let code = diagnostic.code();
let prefix = match diagnostic.level() {
DiagnosticLevel::Error => console::style(format!("error [{code}]")).red().bold(),
DiagnosticLevel::Warning => console::style(format!("warning [{code}]")).yellow().bold(),
DiagnosticLevel::Allowed => continue,
};
writeln!(self.output, "{prefix}: {}", console::style(diagnostic.message()).bold())?;
if let Some(span) = diagnostic.span() {
self.emit_snippet(span)?;
}
for note in diagnostic.notes() {
writeln!(
self.output,
"{}: {}",
console::style("note").blue().bold(),
console::style(¬e.message).bold(),
)?;
if let Some(span) = ¬e.span {
self.emit_snippet(span)?;
}
}
}
Ok(())
}
fn emit_diagnostics_in_json(&mut self, diagnostics: Vec<Diagnostic>) -> Result<()> {
for diagnostic in diagnostics {
let severity = match diagnostic.level() {
DiagnosticLevel::Error => "error",
DiagnosticLevel::Warning => "warning",
DiagnosticLevel::Allowed => continue,
};
let mut serializer = serde_json::Serializer::new(&mut *self.output);
let mut state = serializer.serialize_struct("Diagnostic", 5)?;
state.serialize_field("message", &diagnostic.message())?;
state.serialize_field("severity", severity)?;
state.serialize_field("span", &diagnostic.span())?;
state.serialize_field("notes", diagnostic.notes())?;
state.serialize_field("error_code", diagnostic.code())?;
state.end()?;
writeln!(self.output)?; }
Ok(())
}
fn emit_snippet(&mut self, span: &Span) -> Result<()> {
writeln!(
self.output,
" {} {}:{}:{}",
console::style("-->").blue().bold(),
Path::new(&span.file).display(),
span.start.row,
span.start.col,
)?;
let file = self.files.iter().find(|f| f.relative_path == span.file).unwrap();
writeln!(self.output, "{}", file.get_snippet(span.start, span.end))?;
Ok(())
}
}
pub fn emit_totals(total_warnings: usize, total_errors: usize) -> Result<()> {
let stdout = &mut console::Term::stdout();
if total_warnings > 0 {
let warnings = console::style("Warnings").yellow().bold();
writeln!(stdout, "{warnings}: Compilation generated {total_warnings} warning(s)")?;
}
if total_errors > 0 {
let failed = console::style("Failed").red().bold();
writeln!(stdout, "{failed}: Compilation failed with {total_errors} error(s)")?;
}
Ok(())
}