1use std::{fmt, path::Path};
2
3use helptext::text;
4use pomsky::diagnose::{DiagnosticCode, DiagnosticKind};
5use serde::{Deserialize, Serialize};
6
7use crate::format::Logger;
8
9mod serde_code;
10
11#[derive(Debug, PartialEq, Serialize, Deserialize)]
12pub struct CompilationResult {
13 pub version: Version,
15 pub success: bool,
19 pub path: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub output: Option<String>,
24 pub diagnostics: Vec<Diagnostic>,
26 pub timings: Timings,
28}
29
30#[derive(Debug, PartialEq, Serialize, Deserialize)]
31pub enum Version {
32 #[serde(rename = "1")]
33 V1,
34}
35
36impl CompilationResult {
37 #[allow(clippy::too_many_arguments)]
38 pub(crate) fn success(
39 path: Option<&Path>,
40 output: String,
41 time_all_micros: u128,
42 time_test_micros: u128,
43 diagnostics: impl IntoIterator<Item = pomsky::diagnose::Diagnostic>,
44 source_code: &str,
45 warnings: &crate::args::DiagnosticSet,
46 json: bool,
47 ) -> Self {
48 Self {
49 path: path
50 .map(|p| p.canonicalize().as_deref().unwrap_or(p).to_string_lossy().to_string()),
51 version: Version::V1,
52 success: true,
53 output: Some(output),
54 diagnostics: Self::convert_diagnostics(diagnostics, source_code, warnings, json),
55 timings: Timings::from_micros(time_all_micros, time_test_micros),
56 }
57 }
58
59 pub(crate) fn error(
60 path: Option<&Path>,
61 time_all_micros: u128,
62 time_test_micros: u128,
63 diagnostics: impl IntoIterator<Item = pomsky::diagnose::Diagnostic>,
64 source_code: &str,
65 warnings: &crate::args::DiagnosticSet,
66 json: bool,
67 ) -> Self {
68 Self {
69 path: path
70 .map(|p| p.canonicalize().as_deref().unwrap_or(p).to_string_lossy().to_string()),
71 version: Version::V1,
72 success: false,
73 output: None,
74 diagnostics: Self::convert_diagnostics(diagnostics, source_code, warnings, json),
75 timings: Timings::from_micros(time_all_micros, time_test_micros),
76 }
77 }
78
79 fn convert_diagnostics(
80 diagnostics: impl IntoIterator<Item = pomsky::diagnose::Diagnostic>,
81 source_code: &str,
82 warnings: &crate::args::DiagnosticSet,
83 json: bool,
84 ) -> Vec<Diagnostic> {
85 let source_code = Some(source_code);
86 diagnostics
87 .into_iter()
88 .filter_map(|d| match d.severity {
89 pomsky::diagnose::Severity::Warning if !warnings.is_enabled(d.kind) => None,
90 _ => Some(Diagnostic::from(d, source_code, json)),
91 })
92 .collect()
93 }
94
95 pub(crate) fn output(
96 self,
97 logger: &Logger,
98 json: bool,
99 new_line: bool,
100 in_test_suite: bool,
101 source_code: &str,
102 ) {
103 let success = self.success;
104 if json {
105 match serde_json::to_string(&self) {
106 Ok(string) => println!("{string}"),
107 Err(e) => eprintln!("{e}"),
108 }
109 } else {
110 if in_test_suite {
111 logger.basic().fmtln(if success { text![G!"ok"] } else { text![R!"failed"] });
112 }
113 self.output_human_readable(logger, new_line, in_test_suite, Some(source_code));
114 }
115 if !success && !in_test_suite {
116 std::process::exit(1);
117 }
118 }
119
120 fn output_human_readable(
121 mut self,
122 logger: &Logger,
123 new_line: bool,
124 in_test_suite: bool,
125 source_code: Option<&str>,
126 ) {
127 if self.output.is_none() {
128 self.diagnostics.retain(|d| d.severity == Severity::Error);
129 }
130 let initial_len = self.diagnostics.len();
131
132 if self.diagnostics.len() > 8 {
133 self.diagnostics.drain(8..);
134 }
135
136 for diag in &self.diagnostics {
137 diag.print_human_readable(logger, source_code);
138 }
139
140 if !self.diagnostics.is_empty() && initial_len > self.diagnostics.len() {
141 let omitted = initial_len - self.diagnostics.len();
142 logger.note().println(format_args!("{omitted} diagnostic(s) were omitted"));
143 }
144
145 if let Some(compiled) = &self.output {
146 if in_test_suite {
147 } else if new_line {
149 println!("{compiled}");
150 } else {
151 use std::io::Write;
152
153 print!("{compiled}");
154 std::io::stdout().flush().unwrap();
155 }
156 }
157 }
158}
159
160#[derive(Debug, PartialEq, Serialize, Deserialize)]
161pub struct Diagnostic {
162 pub severity: Severity,
164 pub kind: Kind,
169 #[serde(with = "serde_code", skip_serializing_if = "Option::is_none")]
171 pub code: Option<DiagnosticCode>,
172 pub spans: Vec<Span>,
176 pub description: String,
178 pub help: Vec<String>,
182 pub fixes: Vec<QuickFix>,
186 pub visual: String,
188}
189
190#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy)]
191#[serde(rename_all = "lowercase")]
192pub enum Severity {
193 Error,
194 Warning,
195}
196
197impl From<pomsky::diagnose::Severity> for Severity {
198 fn from(value: pomsky::diagnose::Severity) -> Self {
199 match value {
200 pomsky::diagnose::Severity::Error => Severity::Error,
201 pomsky::diagnose::Severity::Warning => Severity::Warning,
202 }
203 }
204}
205
206#[derive(Debug, PartialEq, Serialize, Deserialize)]
207#[serde(rename_all = "lowercase")]
208pub enum Kind {
209 Syntax,
210 Resolve,
211 Compat,
212 Unsupported,
213 Deprecated,
214 Limits,
215 Invalid,
216 Test,
217 Other,
218}
219
220impl Kind {
221 pub fn as_str(&self) -> &'static str {
222 match self {
223 Kind::Syntax => "syntax",
224 Kind::Resolve => "resolve",
225 Kind::Compat => "compat",
226 Kind::Unsupported => "unsupported",
227 Kind::Deprecated => "deprecated",
228 Kind::Limits => "limits",
229 Kind::Invalid => "invalid",
230 Kind::Test => "test",
231 Kind::Other => "other",
232 }
233 }
234}
235
236impl From<DiagnosticKind> for Kind {
237 fn from(value: DiagnosticKind) -> Self {
238 match value {
239 DiagnosticKind::Syntax => Kind::Syntax,
240 DiagnosticKind::Resolve => Kind::Resolve,
241 DiagnosticKind::Compat => Kind::Compat,
242 DiagnosticKind::Unsupported => Kind::Unsupported,
243 DiagnosticKind::Deprecated => Kind::Deprecated,
244 DiagnosticKind::Limits => Kind::Limits,
245 DiagnosticKind::Invalid => Kind::Invalid,
246 DiagnosticKind::Test => Kind::Test,
247 DiagnosticKind::Other => Kind::Other,
248 _ => panic!("unknown diagnostic kind"),
249 }
250 }
251}
252
253#[derive(Debug, PartialEq, Serialize, Deserialize)]
254pub struct Timings {
255 pub all: u128,
257 pub tests: u128,
258}
259
260impl Timings {
261 pub fn from_micros(all: u128, tests: u128) -> Self {
262 Timings { all, tests }
263 }
264}
265
266#[derive(Debug, PartialEq, Serialize, Deserialize)]
267pub struct Span {
268 pub start: usize,
272 pub end: usize,
277 #[serde(skip_serializing_if = "Option::is_none")]
281 pub label: Option<String>,
282}
283
284impl From<std::ops::Range<usize>> for Span {
285 fn from(value: std::ops::Range<usize>) -> Self {
286 Span { start: value.start, end: value.end, label: None }
287 }
288}
289
290#[derive(Debug, PartialEq, Serialize, Deserialize)]
291pub struct QuickFix {
292 pub description: String,
294 pub replacements: Vec<Replacement>,
299}
300
301#[derive(Debug, PartialEq, Serialize, Deserialize)]
302pub struct Replacement {
303 pub start: usize,
307 pub end: usize,
312 pub insert: String,
314}
315
316impl Diagnostic {
317 pub(crate) fn from(
318 value: pomsky::diagnose::Diagnostic,
319 source_code: Option<&str>,
320 json: bool,
321 ) -> Self {
322 let kind = value.kind.to_string();
323 let severity: &str = value.severity.into();
324
325 let visual = if json {
326 let display = value.display_ascii(source_code);
327 let visual = match value.code {
328 Some(code) => format!("{severity} {code}{kind}:{display}"),
329 None => format!("{severity}{kind}:{display}"),
330 };
331 drop(display);
332 visual
333 } else {
334 String::new()
336 };
337
338 Diagnostic {
339 severity: value.severity.into(),
340 kind: value.kind.into(),
341 code: value.code,
342 spans: value.span.range().into_iter().map(From::from).collect(),
343 description: value.msg,
344 help: value.help.into_iter().collect(),
345 fixes: vec![],
346 visual,
347 }
348 }
349
350 fn print_human_readable(&self, logger: &Logger, source_code: Option<&str>) {
351 let kind = self.kind.as_str();
352 let display = self.miette_display(source_code);
353
354 match self.code {
355 Some(code) => {
356 logger.diagnostic_with_code(self.severity, &code.to_string(), kind).print(display);
357 }
358 None => logger.diagnostic(self.severity, kind).print(display),
359 }
360 }
361
362 fn miette_display<'a>(&'a self, source_code: Option<&'a str>) -> impl std::fmt::Display + 'a {
363 use miette::ReportHandler;
364 use std::fmt;
365
366 #[derive(Debug)]
367 struct MietteDiagnostic<'a> {
368 diagnostic: &'a Diagnostic,
369 source_code: Option<&'a str>,
370 }
371
372 impl fmt::Display for MietteDiagnostic<'_> {
373 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374 self.diagnostic.description.fmt(f)
375 }
376 }
377
378 impl std::error::Error for MietteDiagnostic<'_> {}
379
380 impl miette::Diagnostic for MietteDiagnostic<'_> {
381 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
382 if self.diagnostic.help.is_empty() {
383 None
384 } else {
385 let help = self.diagnostic.help.join("\n");
386 Some(Box::new(help))
387 }
388 }
389
390 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
391 self.source_code.as_ref().map(|s| s as &dyn miette::SourceCode)
392 }
393
394 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
395 if let Some(Span { start, end, label }) = self.diagnostic.spans.first() {
396 let label = label.as_deref().unwrap_or(match self.diagnostic.severity {
397 Severity::Error => "error occurred here",
398 Severity::Warning => "warning originated here",
399 });
400 Some(Box::new(std::iter::once(miette::LabeledSpan::new(
401 Some(label.into()),
402 *start,
403 end - start,
404 ))))
405 } else {
406 None
407 }
408 }
409
410 fn severity(&self) -> Option<miette::Severity> {
411 Some(match self.diagnostic.severity {
412 Severity::Error => miette::Severity::Error,
413 Severity::Warning => miette::Severity::Warning,
414 })
415 }
416 }
417
418 struct Handler<'a>(MietteDiagnostic<'a>);
419
420 impl fmt::Display for Handler<'_> {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 miette::MietteHandler::default().debug(&self.0, f)
423 }
424 }
425
426 Handler(MietteDiagnostic { diagnostic: self, source_code })
427 }
428}
429
430impl fmt::Display for CompilationResult {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 write!(f, "{self:?}")
433 }
434}