solar_interface/diagnostics/emitter/
json.rs1use super::{human::HumanBufferEmitter, io_panic, Emitter};
2use crate::{
3 diagnostics::{Level, MultiSpan, SpanLabel},
4 source_map::{LineInfo, SourceFile},
5 SourceMap, Span,
6};
7use anstream::ColorChoice;
8use serde::Serialize;
9use std::{io, sync::Arc};
10
11pub struct JsonEmitter {
13 writer: Box<dyn io::Write + Send>,
14 pretty: bool,
15 rustc_like: bool,
16
17 human_emitter: HumanBufferEmitter,
18}
19
20impl Emitter for JsonEmitter {
21 fn emit_diagnostic(&mut self, diagnostic: &crate::diagnostics::Diagnostic) {
22 if self.rustc_like {
23 let diagnostic = self.diagnostic(diagnostic);
24 self.emit(&EmitTyped::Diagnostic(diagnostic))
25 } else {
26 let diagnostic = self.solc_diagnostic(diagnostic);
27 self.emit(&diagnostic)
28 }
29 .unwrap_or_else(|e| io_panic(e));
30 }
31
32 fn source_map(&self) -> Option<&Arc<SourceMap>> {
33 Emitter::source_map(&self.human_emitter)
34 }
35}
36
37impl JsonEmitter {
38 pub fn new(writer: Box<dyn io::Write + Send>, source_map: Arc<SourceMap>) -> Self {
40 Self {
41 writer,
42 pretty: false,
43 rustc_like: false,
44 human_emitter: HumanBufferEmitter::new(ColorChoice::Never).source_map(Some(source_map)),
45 }
46 }
47
48 pub fn pretty(mut self, pretty: bool) -> Self {
50 self.pretty = pretty;
51 self
52 }
53
54 pub fn rustc_like(mut self, yes: bool) -> Self {
58 self.rustc_like = yes;
59 self
60 }
61
62 pub fn ui_testing(mut self, yes: bool) -> Self {
64 self.human_emitter = self.human_emitter.ui_testing(yes);
65 self
66 }
67
68 fn source_map(&self) -> &Arc<SourceMap> {
69 Emitter::source_map(self).unwrap()
70 }
71
72 fn diagnostic(&mut self, diagnostic: &crate::diagnostics::Diagnostic) -> Diagnostic {
73 Diagnostic {
74 message: diagnostic.label().into_owned(),
75 code: diagnostic.id().map(|code| DiagnosticCode { code, explanation: None }),
76 level: diagnostic.level.to_str(),
77 spans: self.spans(&diagnostic.span),
78 children: diagnostic.children.iter().map(|sub| self.sub_diagnostic(sub)).collect(),
79 rendered: Some(self.emit_diagnostic_to_buffer(diagnostic)),
80 }
81 }
82
83 fn sub_diagnostic(&self, diagnostic: &crate::diagnostics::SubDiagnostic) -> Diagnostic {
84 Diagnostic {
85 message: diagnostic.label().into_owned(),
86 code: None,
87 level: diagnostic.level.to_str(),
88 spans: self.spans(&diagnostic.span),
89 children: vec![],
90 rendered: None,
91 }
92 }
93
94 fn spans(&self, msp: &MultiSpan) -> Vec<DiagnosticSpan> {
95 msp.span_labels().iter().map(|label| self.span(label)).collect()
96 }
97
98 fn span(&self, label: &SpanLabel) -> DiagnosticSpan {
99 let sm = &**self.source_map();
100 let span = label.span;
101 let start = sm.lookup_char_pos(span.lo());
102 let end = sm.lookup_char_pos(span.hi());
103 DiagnosticSpan {
104 file_name: sm.filename_for_diagnostics(&start.file.name).to_string(),
105 byte_start: start.file.original_relative_byte_pos(span.lo()).0,
106 byte_end: start.file.original_relative_byte_pos(span.hi()).0,
107 line_start: start.line,
108 line_end: end.line,
109 column_start: start.col.0 + 1,
110 column_end: end.col.0 + 1,
111 is_primary: label.is_primary,
112 text: self.span_lines(span),
113 label: label.label.as_ref().map(|msg| msg.as_str().into()),
114 }
115 }
116
117 fn span_lines(&self, span: Span) -> Vec<DiagnosticSpanLine> {
118 let Ok(f) = self.source_map().span_to_lines(span) else { return Vec::new() };
119 let sf = &*f.file;
120 f.lines.iter().map(|line| self.span_line(sf, line)).collect()
121 }
122
123 fn span_line(&self, sf: &SourceFile, line: &LineInfo) -> DiagnosticSpanLine {
124 DiagnosticSpanLine {
125 text: sf.get_line(line.line_index).map_or_else(String::new, |l| l.to_string()),
126 highlight_start: line.start_col.0 + 1,
127 highlight_end: line.end_col.0 + 1,
128 }
129 }
130
131 fn solc_diagnostic(&mut self, diagnostic: &crate::diagnostics::Diagnostic) -> SolcDiagnostic {
132 let primary = diagnostic.span.primary_span();
133 let file = primary
134 .map(|span| {
135 let sm = &**self.source_map();
136 let start = sm.lookup_char_pos(span.lo());
137 sm.filename_for_diagnostics(&start.file.name).to_string()
138 })
139 .unwrap_or_default();
140
141 let severity = to_severity(diagnostic.level);
142
143 SolcDiagnostic {
144 source_location: primary
145 .is_some()
146 .then(|| self.solc_span(&diagnostic.span, &file, None)),
147 secondary_source_locations: diagnostic
148 .children
149 .iter()
150 .map(|sub| self.solc_span(&sub.span, &file, Some(sub.label().into_owned())))
151 .collect(),
152 r#type: match severity {
153 Severity::Error => match diagnostic.level {
154 Level::Bug => "InternalCompilerError",
155 Level::Fatal => "FatalError",
156 Level::Error => "Exception",
157 _ => unreachable!(),
158 },
159 Severity::Warning => "Warning",
160 Severity::Info => "Info",
161 }
162 .into(),
163 component: "general".into(),
164 severity,
165 error_code: diagnostic.id(),
166 message: diagnostic.label().into_owned(),
167 formatted_message: Some(self.emit_diagnostic_to_buffer(diagnostic)),
168 }
169 }
170
171 fn solc_span(&self, span: &MultiSpan, file: &str, message: Option<String>) -> SourceLocation {
172 let sm = &**self.source_map();
173 let sp = span.primary_span();
174 SourceLocation {
175 file: sp
176 .map(|span| {
177 let start = sm.lookup_char_pos(span.lo());
178 sm.filename_for_diagnostics(&start.file.name).to_string()
179 })
180 .unwrap_or_else(|| file.into()),
181 start: sp
182 .map(|span| {
183 let start = sm.lookup_char_pos(span.lo());
184 start.file.original_relative_byte_pos(span.lo()).0
185 })
186 .unwrap_or(0),
187 end: sp
188 .map(|span| {
189 let end = sm.lookup_char_pos(span.hi());
190 end.file.original_relative_byte_pos(span.hi()).0
191 })
192 .unwrap_or(0),
193 message,
194 }
195 }
196
197 fn emit_diagnostic_to_buffer(&mut self, diagnostic: &crate::diagnostics::Diagnostic) -> String {
198 self.human_emitter.emit_diagnostic(diagnostic);
199 std::mem::take(self.human_emitter.buffer_mut())
200 }
201
202 fn emit<T: ?Sized + Serialize>(&mut self, value: &T) -> io::Result<()> {
203 if self.pretty {
204 serde_json::to_writer_pretty(&mut *self.writer, value)
205 } else {
206 serde_json::to_writer(&mut *self.writer, value)
207 }?;
208 self.writer.write_all(b"\n")?;
209 self.writer.flush()
210 }
211}
212
213#[derive(Serialize)]
216#[serde(tag = "$message_type", rename_all = "snake_case")]
217enum EmitTyped {
218 Diagnostic(Diagnostic),
219}
220
221#[derive(Serialize)]
222struct Diagnostic {
223 message: String,
225 code: Option<DiagnosticCode>,
226 level: &'static str,
228 spans: Vec<DiagnosticSpan>,
229 children: Vec<Diagnostic>,
231 rendered: Option<String>,
233}
234
235#[derive(Serialize)]
236struct DiagnosticSpan {
237 file_name: String,
238 byte_start: u32,
239 byte_end: u32,
240 line_start: usize,
242 line_end: usize,
243 column_start: usize,
245 column_end: usize,
246 is_primary: bool,
249 text: Vec<DiagnosticSpanLine>,
251 label: Option<String>,
253 }
257
258#[derive(Serialize)]
259struct DiagnosticSpanLine {
260 text: String,
261
262 highlight_start: usize,
264
265 highlight_end: usize,
266}
267
268#[derive(Serialize)]
269struct DiagnosticCode {
270 code: String,
272 explanation: Option<&'static str>,
274}
275
276#[derive(Serialize)]
279#[serde(rename_all = "camelCase")]
280struct SolcDiagnostic {
281 source_location: Option<SourceLocation>,
282 secondary_source_locations: Vec<SourceLocation>,
283 r#type: String,
284 component: String,
285 severity: Severity,
286 error_code: Option<String>,
287 message: String,
288 formatted_message: Option<String>,
289}
290
291#[derive(Serialize)]
292struct SourceLocation {
293 file: String,
294 start: u32,
295 end: u32,
296 #[serde(default, skip_serializing_if = "Option::is_none")]
298 message: Option<String>,
299}
300
301#[derive(Serialize)]
302#[serde(rename_all = "lowercase")]
303enum Severity {
304 Error,
305 Warning,
306 Info,
307}
308
309fn to_severity(level: Level) -> Severity {
310 match level {
311 Level::Bug | Level::Fatal | Level::Error => Severity::Error,
312 Level::Warning => Severity::Warning,
313 #[rustfmt::skip]
314 Level::Note | Level::OnceNote | Level::FailureNote |
315 Level::Help | Level::OnceHelp |
316 Level::Allow => Severity::Info,
317 }
318}