solar_interface/diagnostics/emitter/
mod.rs1use super::{Diag, Level, MultiSpan, SuggestionStyle};
2use crate::{SourceMap, diagnostics::Suggestions};
3use std::{any::Any, borrow::Cow, sync::Arc};
4
5mod human;
6pub use human::{HumanBufferEmitter, HumanEmitter};
7
8#[cfg(feature = "json")]
9mod json;
10#[cfg(feature = "json")]
11pub use json::{JsonEmitter, Severity, SolcDiagnostic, SourceLocation};
12
13mod mem;
14pub use mem::InMemoryEmitter;
15
16pub type DynEmitter = dyn Emitter + Send;
18
19pub trait Emitter: Any {
21 fn emit_diagnostic(&mut self, diagnostic: &mut Diag);
23
24 fn emit_diagnostic_ref(&mut self, diagnostic: &Diag) {
26 let mut diagnostic = diagnostic.clone();
27 self.emit_diagnostic(&mut diagnostic);
28 }
29
30 #[inline]
32 fn source_map(&self) -> Option<&Arc<SourceMap>> {
33 None
34 }
35
36 #[inline]
38 fn supports_color(&self) -> bool {
39 false
40 }
41
42 fn primary_span_formatted<'a>(
53 &self,
54 primary_span: &mut Cow<'a, MultiSpan>,
55 suggestions: &mut Cow<'a, Suggestions>,
56 ) {
57 if let Some((sugg, rest)) = &suggestions.split_first()
58 && rest.is_empty()
61 && let [substitution] = sugg.substitutions.as_slice()
63 && let [part] = substitution.parts.as_slice()
65 && sugg.msg.as_str().split_whitespace().count() < 10
67 && !part.snippet.contains('\n')
69 && ![
70 SuggestionStyle::HideCodeAlways,
72 SuggestionStyle::CompletelyHidden,
74 SuggestionStyle::ShowAlways,
76 ].contains(&sugg.style)
77 {
78 let snippet = part.snippet.trim();
79 let msg = if snippet.is_empty() || sugg.style.hide_inline() {
80 format!("help: {}", sugg.msg.as_str())
83 } else {
84 format!("help: {}: `{}`", sugg.msg.as_str(), snippet)
85 };
86 primary_span.to_mut().push_span_label(part.span, msg);
87
88 *suggestions.to_mut() = Suggestions::Disabled;
90 } else {
91 }
93 }
94}
95
96impl DynEmitter {
97 pub(crate) fn local_buffer(&self) -> Option<&str> {
98 (self as &dyn Any).downcast_ref::<HumanBufferEmitter>().map(HumanBufferEmitter::buffer)
99 }
100}
101
102pub struct SilentEmitter {
106 fatal_emitter: Option<Box<DynEmitter>>,
107 note: Option<String>,
108}
109
110impl SilentEmitter {
111 pub fn new(fatal_emitter: impl Emitter + Send) -> Self {
113 Self::new_boxed(Some(Box::new(fatal_emitter)))
114 }
115
116 pub fn new_boxed(fatal_emitter: Option<Box<DynEmitter>>) -> Self {
118 Self { fatal_emitter, note: None }
119 }
120
121 pub fn new_silent() -> Self {
125 Self::new_boxed(None)
126 }
127
128 pub fn with_note(mut self, note: Option<String>) -> Self {
130 self.note = note;
131 self
132 }
133}
134
135impl Emitter for SilentEmitter {
136 fn emit_diagnostic(&mut self, diagnostic: &mut Diag) {
137 let Some(fatal_emitter) = self.fatal_emitter.as_deref_mut() else { return };
138 if diagnostic.level != Level::Fatal {
139 return;
140 }
141
142 if let Some(note) = &self.note {
143 let mut diagnostic = diagnostic.clone();
144 diagnostic.note(note.clone());
145 fatal_emitter.emit_diagnostic(&mut diagnostic);
146 } else {
147 fatal_emitter.emit_diagnostic(diagnostic);
148 }
149 }
150}
151
152#[derive(Clone, Debug)]
154pub struct LocalEmitter {
155 diagnostics: Vec<Diag>,
156}
157
158impl Default for LocalEmitter {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164impl LocalEmitter {
165 pub fn new() -> Self {
167 Self { diagnostics: Vec::new() }
168 }
169
170 pub fn diagnostics(&self) -> &[Diag] {
172 &self.diagnostics
173 }
174
175 pub fn into_diagnostics(self) -> Vec<Diag> {
177 self.diagnostics
178 }
179}
180
181impl Emitter for LocalEmitter {
182 fn emit_diagnostic(&mut self, diagnostic: &mut Diag) {
183 self.diagnostics.push(diagnostic.clone());
184 }
185}
186
187#[cfg(feature = "json")]
188#[cold]
189#[inline(never)]
190fn io_panic(error: std::io::Error) -> ! {
191 panic!("failed to emit diagnostic: {error}");
192}
193
194const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
197 ('\0', "␀"),
201 ('\u{0001}', "␁"),
202 ('\u{0002}', "␂"),
203 ('\u{0003}', "␃"),
204 ('\u{0004}', "␄"),
205 ('\u{0005}', "␅"),
206 ('\u{0006}', "␆"),
207 ('\u{0007}', "␇"),
208 ('\u{0008}', "␈"),
209 ('\t', " "), ('\u{000b}', "␋"),
211 ('\u{000c}', "␌"),
212 ('\u{000d}', "␍"),
213 ('\u{000e}', "␎"),
214 ('\u{000f}', "␏"),
215 ('\u{0010}', "␐"),
216 ('\u{0011}', "␑"),
217 ('\u{0012}', "␒"),
218 ('\u{0013}', "␓"),
219 ('\u{0014}', "␔"),
220 ('\u{0015}', "␕"),
221 ('\u{0016}', "␖"),
222 ('\u{0017}', "␗"),
223 ('\u{0018}', "␘"),
224 ('\u{0019}', "␙"),
225 ('\u{001a}', "␚"),
226 ('\u{001b}', "␛"),
227 ('\u{001c}', "␜"),
228 ('\u{001d}', "␝"),
229 ('\u{001e}', "␞"),
230 ('\u{001f}', "␟"),
231 ('\u{007f}', "␡"),
232 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
237 ('\u{202e}', "�"),
238 ('\u{2066}', "�"),
239 ('\u{2067}', "�"),
240 ('\u{2068}', "�"),
241 ('\u{2069}', "�"),
242];
243
244pub(crate) fn normalize_whitespace(s: &str) -> String {
245 const {
246 let mut i = 1;
247 while i < OUTPUT_REPLACEMENTS.len() {
248 assert!(
249 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
250 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
251 and must contain no duplicate entries"
252 );
253 i += 1;
254 }
255 }
256 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
260 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
261 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
262 _ => s.push(c),
263 }
264 s
265 })
266}