use super::{Diag, Level, MultiSpan, SuggestionStyle};
use crate::{SourceMap, diagnostics::Suggestions};
use std::{any::Any, borrow::Cow, sync::Arc};
mod human;
pub use human::{HumanBufferEmitter, HumanEmitter};
#[cfg(feature = "json")]
mod json;
#[cfg(feature = "json")]
pub use json::{JsonEmitter, Severity, SolcDiagnostic, SourceLocation};
mod mem;
pub use mem::InMemoryEmitter;
pub type DynEmitter = dyn Emitter + Send;
pub trait Emitter: Any {
fn emit_diagnostic(&mut self, diagnostic: &mut Diag);
fn emit_diagnostic_ref(&mut self, diagnostic: &Diag) {
let mut diagnostic = diagnostic.clone();
self.emit_diagnostic(&mut diagnostic);
}
#[inline]
fn source_map(&self) -> Option<&Arc<SourceMap>> {
None
}
#[inline]
fn supports_color(&self) -> bool {
false
}
fn primary_span_formatted<'a>(
&self,
primary_span: &mut Cow<'a, MultiSpan>,
suggestions: &mut Cow<'a, Suggestions>,
) {
if let Some((sugg, rest)) = &suggestions.split_first()
&& rest.is_empty()
&& let [substitution] = sugg.substitutions.as_slice()
&& let [part] = substitution.parts.as_slice()
&& sugg.msg.as_str().split_whitespace().count() < 10
&& !part.snippet.contains('\n')
&& ![
SuggestionStyle::HideCodeAlways,
SuggestionStyle::CompletelyHidden,
SuggestionStyle::ShowAlways,
].contains(&sugg.style)
{
let snippet = part.snippet.trim();
let msg = if snippet.is_empty() || sugg.style.hide_inline() {
format!("help: {}", sugg.msg.as_str())
} else {
format!("help: {}: `{}`", sugg.msg.as_str(), snippet)
};
primary_span.to_mut().push_span_label(part.span, msg);
*suggestions.to_mut() = Suggestions::Disabled;
} else {
}
}
}
impl DynEmitter {
pub(crate) fn local_buffer(&self) -> Option<&str> {
(self as &dyn Any).downcast_ref::<HumanBufferEmitter>().map(HumanBufferEmitter::buffer)
}
}
pub struct SilentEmitter {
fatal_emitter: Option<Box<DynEmitter>>,
note: Option<String>,
}
impl SilentEmitter {
pub fn new(fatal_emitter: impl Emitter + Send) -> Self {
Self::new_boxed(Some(Box::new(fatal_emitter)))
}
pub fn new_boxed(fatal_emitter: Option<Box<DynEmitter>>) -> Self {
Self { fatal_emitter, note: None }
}
pub fn new_silent() -> Self {
Self::new_boxed(None)
}
pub fn with_note(mut self, note: Option<String>) -> Self {
self.note = note;
self
}
}
impl Emitter for SilentEmitter {
fn emit_diagnostic(&mut self, diagnostic: &mut Diag) {
let Some(fatal_emitter) = self.fatal_emitter.as_deref_mut() else { return };
if diagnostic.level != Level::Fatal {
return;
}
if let Some(note) = &self.note {
let mut diagnostic = diagnostic.clone();
diagnostic.note(note.clone());
fatal_emitter.emit_diagnostic(&mut diagnostic);
} else {
fatal_emitter.emit_diagnostic(diagnostic);
}
}
}
#[derive(Clone, Debug)]
pub struct LocalEmitter {
diagnostics: Vec<Diag>,
}
impl Default for LocalEmitter {
fn default() -> Self {
Self::new()
}
}
impl LocalEmitter {
pub fn new() -> Self {
Self { diagnostics: Vec::new() }
}
pub fn diagnostics(&self) -> &[Diag] {
&self.diagnostics
}
pub fn into_diagnostics(self) -> Vec<Diag> {
self.diagnostics
}
}
impl Emitter for LocalEmitter {
fn emit_diagnostic(&mut self, diagnostic: &mut Diag) {
self.diagnostics.push(diagnostic.clone());
}
}
#[cfg(feature = "json")]
#[cold]
#[inline(never)]
fn io_panic(error: std::io::Error) -> ! {
panic!("failed to emit diagnostic: {error}");
}
const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
('\0', "␀"),
('\u{0001}', "␁"),
('\u{0002}', "␂"),
('\u{0003}', "␃"),
('\u{0004}', "␄"),
('\u{0005}', "␅"),
('\u{0006}', "␆"),
('\u{0007}', "␇"),
('\u{0008}', "␈"),
('\t', " "), ('\u{000b}', "␋"),
('\u{000c}', "␌"),
('\u{000d}', "␍"),
('\u{000e}', "␎"),
('\u{000f}', "␏"),
('\u{0010}', "␐"),
('\u{0011}', "␑"),
('\u{0012}', "␒"),
('\u{0013}', "␓"),
('\u{0014}', "␔"),
('\u{0015}', "␕"),
('\u{0016}', "␖"),
('\u{0017}', "␗"),
('\u{0018}', "␘"),
('\u{0019}', "␙"),
('\u{001a}', "␚"),
('\u{001b}', "␛"),
('\u{001c}', "␜"),
('\u{001d}', "␝"),
('\u{001e}', "␞"),
('\u{001f}', "␟"),
('\u{007f}', "␡"),
('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
('\u{202e}', "�"),
('\u{2066}', "�"),
('\u{2067}', "�"),
('\u{2068}', "�"),
('\u{2069}', "�"),
];
pub(crate) fn normalize_whitespace(s: &str) -> String {
const {
let mut i = 1;
while i < OUTPUT_REPLACEMENTS.len() {
assert!(
OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
"The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
and must contain no duplicate entries"
);
i += 1;
}
}
s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
_ => s.push(c),
}
s
})
}