hax_types/diagnostics/
report.rs

1use super::Diagnostics;
2use annotate_snippets::*;
3use miette::SourceOffset;
4use std::collections::{HashMap, HashSet};
5use std::path::{Path, PathBuf};
6use std::rc::Rc;
7
8/// A context for reporting diagnostics
9#[derive(Clone, Debug, Default)]
10pub struct ReportCtx {
11    files: HashMap<PathBuf, Rc<String>>,
12    seen: HashSet<Diagnostics>,
13}
14
15/// Translates a line and column position into an absolute offset
16fn compute_offset(src: &str, line: usize, col: usize) -> usize {
17    SourceOffset::from_location(src, line, col).offset() + 1
18}
19
20impl ReportCtx {
21    /// Read the contents of a file. The result is cached.
22    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    /// Check if `diagnostic` have been seen already, and mark `diagnostic` as seen.
34    pub fn seen_already(&mut self, diagnostic: Diagnostics) -> bool {
35        !self.seen.insert(diagnostic)
36    }
37}
38
39impl Diagnostics {
40    /// Converts a `Diagnostics` to a `annotate_snippets::Message`,
41    /// which can be accessed via `then`, a callback function.
42    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}