1use std::{fmt, mem::transmute};
2
3use miette::{GraphicalReportHandler, Severity, SourceOffset, SourceSpan};
4use swc_common::{
5 errors::{Diagnostic, DiagnosticId, Level, SubDiagnostic},
6 BytePos, FileName, SourceMap, Span,
7};
8
9pub struct PrettyDiagnostic<'a> {
10 source_code: PrettySourceCode<'a>,
11 d: &'a Diagnostic,
12
13 children: Vec<PrettySubDiagnostic<'a>>,
14}
15
16impl<'a> PrettyDiagnostic<'a> {
17 pub fn new(d: &'a Diagnostic, cm: &'a SourceMap, skip_filename: bool) -> Self {
18 let source_code = PrettySourceCode { cm, skip_filename };
19
20 let children = d
21 .children
22 .iter()
23 .filter(|d| !matches!(d.level, Level::Help))
24 .map(|d| PrettySubDiagnostic { source_code, d })
25 .collect();
26 Self {
27 source_code,
28 d,
29 children,
30 }
31 }
32}
33
34impl miette::Diagnostic for PrettyDiagnostic<'_> {
35 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
36 self.d
37 .code
38 .as_ref()
39 .map(|v| match v {
40 DiagnosticId::Error(v) => v,
41 DiagnosticId::Lint(v) => v,
42 })
43 .map(|code| Box::new(code) as Box<dyn fmt::Display>)
44 }
45
46 fn severity(&self) -> Option<Severity> {
47 level_to_severity(self.d.level)
48 }
49
50 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
51 self.d
52 .children
53 .iter()
54 .filter(|s| s.level == Level::Help)
55 .map(|s| Box::new(&s.message[0].0) as Box<_>)
56 .next()
57 }
58
59 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
60 if let Some(span) = self.d.span.primary_span() {
61 if span.lo.is_dummy() || span.hi.is_dummy() {
62 return None;
63 }
64 } else {
65 return None;
66 }
67
68 Some(&self.source_code as &dyn miette::SourceCode)
69 }
70
71 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
72 let iter = self.d.span.span_labels().into_iter().map(|span_label| {
73 miette::LabeledSpan::new_with_span(span_label.label, convert_span(span_label.span))
74 });
75
76 Some(Box::new(iter))
77 }
78
79 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
80 if self.children.is_empty() {
81 None
82 } else {
83 Some(Box::new(
84 self.children.iter().map(|d| d as &dyn miette::Diagnostic),
85 ))
86 }
87 }
88}
89
90impl<'a> PrettyDiagnostic<'a> {
91 pub fn to_pretty_string(&self, handler: &'a GraphicalReportHandler) -> String {
92 let mut wr = String::new();
93 handler.render_report(&mut wr, self).unwrap();
94 wr
95 }
96}
97
98impl std::error::Error for PrettyDiagnostic<'_> {}
99
100impl fmt::Debug for PrettyDiagnostic<'_> {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 fmt::Debug::fmt(&self.d, f)
104 }
105}
106
107impl fmt::Display for PrettyDiagnostic<'_> {
108 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109 self.d.message[0].0.fmt(f)
110 }
111}
112
113#[derive(Clone, Copy)]
114pub struct PrettySourceCode<'a> {
115 cm: &'a SourceMap,
116 skip_filename: bool,
117}
118
119impl miette::SourceCode for PrettySourceCode<'_> {
120 fn read_span<'a>(
121 &'a self,
122 span: &SourceSpan,
123 context_lines_before: usize,
124 context_lines_after: usize,
125 ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
126 let lo = span.offset();
127 let hi = lo + span.len();
128
129 let mut span = Span::new(BytePos(lo as _), BytePos(hi as _));
130
131 span = self
132 .cm
133 .with_span_to_prev_source(span, |src| {
134 let len = src
135 .rsplit('\n')
136 .take(context_lines_before + 1)
137 .map(|s| s.len() + 1)
138 .sum::<usize>();
139
140 span.lo.0 -= (len as u32) - 1;
141 span
142 })
143 .unwrap_or(span);
144
145 span = self
146 .cm
147 .with_span_to_next_source(span, |src| {
148 let len = src
149 .split('\n')
150 .take(context_lines_after + 1)
151 .map(|s| s.len() + 1)
152 .sum::<usize>();
153
154 span.hi.0 += (len as u32) - 1;
155 span
156 })
157 .unwrap_or(span);
158
159 span = self
160 .cm
161 .with_snippet_of_span(span, |src| {
162 if src.lines().next().is_some() {
163 return span;
164 }
165 let lo = src.len() - src.trim_start().len();
166 let hi = src.len() - src.trim_end().len();
167
168 span.lo.0 += lo as u32;
169 span.hi.0 -= hi as u32;
170
171 span
172 })
173 .unwrap_or(span);
174
175 let mut src = self
176 .cm
177 .with_snippet_of_span(span, |s| unsafe { transmute::<&str, &str>(s) })
178 .unwrap_or(" ");
179
180 if span.lo == span.hi {
181 src = " ";
182 }
183
184 let loc = self.cm.lookup_char_pos(span.lo());
185 let line_count = loc.file.analyze().lines.len();
186
187 let name = if self.skip_filename {
188 None
189 } else {
190 match &*loc.file.name {
191 FileName::Real(ref path) => Some(path.to_string_lossy().into_owned()),
192 FileName::Custom(ref name) => Some(name.clone()),
193 FileName::Anon => None,
194 _ => Some(loc.file.name.to_string()),
195 }
196 };
197
198 Ok(Box::new(SpanContentsImpl {
199 _cm: self.cm,
200 data: src,
201 span: convert_span(span),
202 line: loc.line.saturating_sub(1),
203 column: loc.col_display,
204 line_count,
205 name,
206 }))
207 }
208}
209
210pub fn to_pretty_source_code(cm: &SourceMap, skip_filename: bool) -> PrettySourceCode<'_> {
211 PrettySourceCode { cm, skip_filename }
212}
213struct PrettySubDiagnostic<'a> {
214 source_code: PrettySourceCode<'a>,
215 d: &'a SubDiagnostic,
216}
217
218impl std::error::Error for PrettySubDiagnostic<'_> {}
219
220impl fmt::Debug for PrettySubDiagnostic<'_> {
222 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223 fmt::Debug::fmt(&self.d, f)
224 }
225}
226
227impl fmt::Display for PrettySubDiagnostic<'_> {
228 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229 fmt::Display::fmt(&self.d.message[0].0, f)
230 }
231}
232
233impl miette::Diagnostic for PrettySubDiagnostic<'_> {
234 fn severity(&self) -> Option<Severity> {
235 level_to_severity(self.d.level)
236 }
237
238 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
239 Some(&self.source_code)
240 }
241
242 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
243 let iter = self.d.span.span_labels().into_iter().map(|span_label| {
244 miette::LabeledSpan::new_with_span(span_label.label, convert_span(span_label.span))
245 });
246
247 Some(Box::new(iter))
248 }
249}
250
251struct SpanContentsImpl<'a> {
252 _cm: &'a SourceMap,
254
255 data: &'a str,
257 span: SourceSpan,
259 line: usize,
261 column: usize,
263 line_count: usize,
265 name: Option<String>,
267}
268
269impl<'a> miette::SpanContents<'a> for SpanContentsImpl<'a> {
270 fn data(&self) -> &'a [u8] {
271 self.data.as_bytes()
272 }
273
274 fn span(&self) -> &SourceSpan {
275 &self.span
276 }
277
278 fn line(&self) -> usize {
279 self.line
280 }
281
282 fn column(&self) -> usize {
283 self.column
284 }
285
286 fn line_count(&self) -> usize {
287 self.line_count
288 }
289
290 fn name(&self) -> Option<&str> {
291 self.name.as_deref()
292 }
293}
294
295fn level_to_severity(level: Level) -> Option<Severity> {
296 match level {
297 Level::FailureNote | Level::Bug | Level::Fatal | Level::PhaseFatal | Level::Error => {
298 Some(Severity::Error)
299 }
300 Level::Warning => Some(Severity::Warning),
301 Level::Note | Level::Help => Some(Severity::Advice),
302 Level::Cancelled => None,
303 }
304}
305
306pub fn convert_span(span: Span) -> SourceSpan {
307 let len = span.hi - span.lo;
308 let start = SourceOffset::from(span.lo.0 as usize);
309 SourceSpan::new(start, len.0 as usize)
310}
311
312pub trait ToPrettyDiagnostic {
313 fn to_pretty_diagnostic<'a>(
314 &'a self,
315 cm: &'a SourceMap,
316 skip_filename: bool,
317 ) -> PrettyDiagnostic<'a>;
318
319 fn to_pretty_string<'a>(
320 &self,
321 cm: &'a SourceMap,
322 skip_filename: bool,
323 handler: &'a GraphicalReportHandler,
324 ) -> String;
325}
326
327impl ToPrettyDiagnostic for Diagnostic {
329 fn to_pretty_diagnostic<'a>(
331 &'a self,
332 cm: &'a SourceMap,
333 skip_filename: bool,
334 ) -> PrettyDiagnostic<'a> {
335 PrettyDiagnostic::new(self, cm, skip_filename)
336 }
337
338 fn to_pretty_string<'a>(
358 &self,
359 cm: &'a SourceMap,
360 skip_filename: bool,
361 handler: &'a GraphicalReportHandler,
362 ) -> String {
363 let pretty_diagnostic = PrettyDiagnostic::new(self, cm, skip_filename);
364 pretty_diagnostic.to_pretty_string(handler)
365 }
366}