hax_types/diagnostics/
report.rs

1use super::Diagnostics;
2use annotate_snippets::*;
3use miette::SourceOffset;
4use std::collections::HashMap;
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}
13
14/// Translates a line and column position into an absolute offset
15fn compute_offset(src: &str, line: usize, col: usize) -> usize {
16    SourceOffset::from_location(src, line, col).offset() + 1
17}
18
19impl ReportCtx {
20    /// Read the contents of a file. The result is cached.
21    fn file_contents<'a>(&'a mut self, path: PathBuf) -> Rc<String> {
22        self.files
23            .entry(path.clone())
24            .or_insert_with(|| {
25                let s =
26                    std::fs::read_to_string(&path).expect(&format!("Unable to read file {path:?}"));
27                Rc::new(s)
28            })
29            .clone()
30    }
31}
32
33impl Diagnostics {
34    /// Converts a `Diagnostics` to a `annotate_snippets::Message`,
35    /// which can be accessed via `then`, a callback function.
36    pub fn with_message<R, F: for<'a> FnMut(Message<'a>) -> R>(
37        &self,
38        report_ctx: &mut ReportCtx,
39        working_dir: &Path,
40        level: Level,
41        mut then: F,
42    ) -> R {
43        let mut snippets_data = vec![];
44
45        for span in &self.span {
46            if let Some(path) = span.filename.to_path() {
47                let source = {
48                    let mut path = path.to_path_buf();
49                    if path.is_relative() {
50                        path = working_dir.join(&path);
51                    };
52                    report_ctx.file_contents(path)
53                };
54                let start = compute_offset(&source, span.lo.line, span.lo.col);
55                let end = compute_offset(&source, span.hi.line, span.hi.col);
56                let origin = format!("{}", path.display());
57                snippets_data.push((source, origin, start..end));
58            };
59        }
60
61        let title = format!("[{}] {self}", self.kind.code());
62        let message =
63            level
64                .title(&title)
65                .snippets(snippets_data.iter().map(|(source, origin, range)| {
66                    Snippet::source(source)
67                        .line_start(1)
68                        .origin(&origin)
69                        .fold(true)
70                        .annotation(level.span(range.clone()))
71                }));
72
73        then(message)
74    }
75}