Skip to main content

solar_interface/diagnostics/emitter/
mod.rs

1use 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
16/// Dynamic diagnostic emitter. See [`Emitter`].
17pub type DynEmitter = dyn Emitter + Send;
18
19/// Diagnostic emitter.
20pub trait Emitter: Any {
21    /// Emits a diagnostic.
22    fn emit_diagnostic(&mut self, diagnostic: &mut Diag);
23
24    /// Emits a diagnostic by reference.
25    fn emit_diagnostic_ref(&mut self, diagnostic: &Diag) {
26        let mut diagnostic = diagnostic.clone();
27        self.emit_diagnostic(&mut diagnostic);
28    }
29
30    /// Returns a reference to the source map, if any.
31    #[inline]
32    fn source_map(&self) -> Option<&Arc<SourceMap>> {
33        None
34    }
35
36    /// Returns `true` if we can use colors in the current output stream.
37    #[inline]
38    fn supports_color(&self) -> bool {
39        false
40    }
41
42    /// Formats the substitutions of the primary_span
43    ///
44    /// There are a lot of conditions to this method, but in short:
45    ///
46    /// * If the current `DiagInner` has only one visible `CodeSuggestion`, we format the `help`
47    ///   suggestion depending on the content of the substitutions. In that case, we modify the span
48    ///   and clear the suggestions.
49    ///
50    /// * If the current `DiagInner` has multiple suggestions, we leave `primary_span` and the
51    ///   suggestions untouched.
52    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            // if there are multiple suggestions, print them all in full
59            // to be consistent.
60            && rest.is_empty()
61            // don't display multi-suggestions as labels
62            && let [substitution] = sugg.substitutions.as_slice()
63            // don't display multipart suggestions as labels
64            && let [part] = substitution.parts.as_slice()
65            // don't display long messages as labels
66            && sugg.msg.as_str().split_whitespace().count() < 10
67            // don't display multiline suggestions as labels
68            && !part.snippet.contains('\n')
69            && ![
70                // when this style is set we want the suggestion to be a message, not inline
71                SuggestionStyle::HideCodeAlways,
72                // trivial suggestion for tooling's sake, never shown
73                SuggestionStyle::CompletelyHidden,
74                // subtle suggestion, never shown inline
75                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                // This substitution is only removal OR we explicitly don't want to show the
81                // code inline (`hide_inline`). Therefore, we don't show the substitution.
82                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            // Since we only return the modified primary_span, we disable suggestions.
89            *suggestions.to_mut() = Suggestions::Disabled;
90        } else {
91            // Do nothing.
92        }
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
102/// Diagnostic emitter.
103///
104/// Emits fatal diagnostics by default, with `note` if set.
105pub struct SilentEmitter {
106    fatal_emitter: Option<Box<DynEmitter>>,
107    note: Option<String>,
108}
109
110impl SilentEmitter {
111    /// Creates a new `SilentEmitter`. Emits fatal diagnostics with `fatal_emitter`.
112    pub fn new(fatal_emitter: impl Emitter + Send) -> Self {
113        Self::new_boxed(Some(Box::new(fatal_emitter)))
114    }
115
116    /// Creates a new `SilentEmitter`. Emits fatal diagnostics with `fatal_emitter` if `Some`.
117    pub fn new_boxed(fatal_emitter: Option<Box<DynEmitter>>) -> Self {
118        Self { fatal_emitter, note: None }
119    }
120
121    /// Creates a new `SilentEmitter` that does not emit any diagnostics at all.
122    ///
123    /// Same as `new_boxed(None)`.
124    pub fn new_silent() -> Self {
125        Self::new_boxed(None)
126    }
127
128    /// Sets the note to be emitted for fatal diagnostics.
129    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/// Diagnostic emitter that only stores emitted diagnostics.
153#[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    /// Creates a new `LocalEmitter`.
166    pub fn new() -> Self {
167        Self { diagnostics: Vec::new() }
168    }
169
170    /// Returns a reference to the emitted diagnostics.
171    pub fn diagnostics(&self) -> &[Diag] {
172        &self.diagnostics
173    }
174
175    /// Consumes the emitter and returns the emitted diagnostics.
176    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
194// We replace some characters so the CLI output is always consistent and underlines aligned.
195// Keep the following list in sync with `rustc_span::char_width`.
196const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
197    // In terminals without Unicode support the following will be garbled, but in *all* terminals
198    // the underlying codepoint will be as well. We could gate this replacement behind a "unicode
199    // support" gate.
200    ('\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', "    "), // We do our own tab replacement
210    ('\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}', ""), // Replace ZWJ for consistent terminal output of grapheme clusters.
233    ('\u{202a}', "�"), // The following unicode text flow control characters are inconsistently
234    ('\u{202b}', "�"), // supported across CLIs and can cause confusion due to the bytes on disk
235    ('\u{202c}', "�"), // not corresponding to the visible source code, so we replace them always.
236    ('\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    // Scan the input string for a character in the ordered table above.
257    // If it's present, replace it with its alternative string (it can be more than 1 char!).
258    // Otherwise, retain the input char.
259    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}