hax_types/diagnostics/
report.rs1use super::Diagnostics;
2use annotate_snippets::*;
3use miette::SourceOffset;
4use std::collections::{HashMap, HashSet};
5use std::path::{Path, PathBuf};
6use std::rc::Rc;
7
8#[derive(Clone, Debug, Default)]
10pub struct ReportCtx {
11 files: HashMap<PathBuf, Rc<String>>,
12 seen: HashSet<Diagnostics>,
13}
14
15fn compute_offset(src: &str, line: usize, col: usize) -> usize {
17 SourceOffset::from_location(src, line, col).offset() + 1
18}
19
20impl ReportCtx {
21 fn file_contents<'a>(&'a mut self, path: PathBuf) -> Rc<String> {
23 self.files
24 .entry(path.clone())
25 .or_insert_with(|| {
26 let s =
27 std::fs::read_to_string(&path).expect(&format!("Unable to read file {path:?}"));
28 Rc::new(s)
29 })
30 .clone()
31 }
32
33 pub fn seen_already(&mut self, diagnostic: Diagnostics) -> bool {
35 !self.seen.insert(diagnostic)
36 }
37}
38
39impl Diagnostics {
40 pub fn with_message<R, F: for<'a> FnMut(Message<'a>) -> R>(
43 &self,
44 report_ctx: &mut ReportCtx,
45 working_dir: Option<&Path>,
46 level: Level,
47 mut then: F,
48 ) -> R {
49 let mut snippets_data = vec![];
50
51 for span in &self.span {
52 if let Some(path) = span.filename.to_path() {
53 let source = {
54 let mut path = path.to_path_buf();
55 if let Some(working_dir) = working_dir
56 && path.is_relative()
57 {
58 path = working_dir.join(&path);
59 };
60 report_ctx.file_contents(path)
61 };
62 let start = compute_offset(&source, span.lo.line, span.lo.col);
63 let end = compute_offset(&source, span.hi.line, span.hi.col);
64 let origin = format!("{}", path.display());
65 snippets_data.push((source, origin, start..end));
66 };
67 }
68
69 let title = format!("[{}] {self}", self.kind.code());
70 let message =
71 level
72 .title(&title)
73 .snippets(snippets_data.iter().map(|(source, origin, range)| {
74 Snippet::source(source)
75 .line_start(1)
76 .origin(&origin)
77 .fold(true)
78 .annotation(level.span(range.clone()))
79 }));
80
81 then(message)
82 }
83}