summaryrefslogtreecommitdiff
path: root/dhall/src/error/builder.rs
blob: e29322069f4fb2cbbdcab1d4c19936e271cd1629 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use annotate_snippets::{
    display_list::DisplayList,
    formatter::DisplayListFormatter,
    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
};

use crate::syntax::Span;

#[derive(Debug, Clone)]
pub struct ErrorBuilder {
    message: String,
    annotation_type: AnnotationType,
    annotations: Vec<BuilderAnnotation>,
}

#[derive(Debug, Clone)]
struct BuilderAnnotation {
    span: Span,
    message: String,
    annotation_type: AnnotationType,
}

/// A builder that uses the annotate_snippets library to display nice error messages about source
/// code locations.
impl ErrorBuilder {
    pub fn new(message: String) -> Self {
        ErrorBuilder {
            message,
            annotation_type: AnnotationType::Error,
            annotations: Vec::new(),
        }
    }
    pub fn span_err(span: &Span, message: String) -> Self {
        let mut builder = Self::new(message.clone());
        builder.annotate_err(span, message);
        builder
    }

    pub fn annotate_err(&mut self, span: &Span, message: String) {
        self.annotations.push(BuilderAnnotation {
            span: span.clone(),
            message,
            annotation_type: AnnotationType::Error,
        })
    }
    pub fn annotate_info(&mut self, span: &Span, message: String) {
        self.annotations.push(BuilderAnnotation {
            span: span.clone(),
            message,
            annotation_type: AnnotationType::Help,
        })
    }

    // TODO: handle multiple files
    pub fn format(self) -> String {
        let mut input = None;
        let annotations = self
            .annotations
            .into_iter()
            .filter_map(|annot| {
                let span = match annot.span {
                    Span::Parsed(span) => span,
                    _ => return None,
                };
                if input.is_none() {
                    input = Some(span.to_input());
                }
                Some(SourceAnnotation {
                    label: annot.message,
                    annotation_type: annot.annotation_type,
                    range: span.as_char_range(),
                })
            })
            .collect();

        let input = match input {
            Some(input) => input,
            None => return format!("[unknown location] {}", self.message),
        };

        let snippet = Snippet {
            title: Some(Annotation {
                label: Some(self.message),
                id: None,
                annotation_type: self.annotation_type,
            }),
            footer: vec![],
            slices: vec![Slice {
                source: input,
                line_start: 1, // TODO
                origin: Some("<current file>".to_string()),
                fold: true,
                annotations,
            }],
        };
        let dl = DisplayList::from(snippet);
        let dlf = DisplayListFormatter::new(true, false);
        format!("{}", dlf.format(&dl))
    }
}