1use codespan_reporting::diagnostic::{Diagnostic, Label};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 errors::{ParseError, ReqlangError, ResolverError},
6 span::{Span, Spanned},
7 str_idxpos::index_to_position,
8};
9
10pub fn get_diagnostics(errs: &[Spanned<ReqlangError>], source: &str) -> Vec<Diagnosis> {
12 errs.iter()
13 .map(|(err, span)| Diagnosis {
14 range: get_range(source, span),
15 severity: Some(DiagnosisSeverity::ERROR),
16 message: err.to_string(),
17 })
18 .collect()
19}
20
21fn get_range(source: &str, span: &Span) -> DiagnosisRange {
22 DiagnosisRange {
23 start: get_position(source, span.start),
24 end: get_position(source, span.end),
25 }
26}
27
28fn get_position(source: &str, idx: usize) -> DiagnosisPosition {
29 let (line, character) = index_to_position(source, idx);
30
31 DiagnosisPosition {
32 line: line as u32,
33 character: character as u32,
34 }
35}
36
37#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
38#[serde(rename_all = "camelCase")]
39pub struct Diagnosis {
40 pub range: DiagnosisRange,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub severity: Option<DiagnosisSeverity>,
44
45 pub message: String,
46}
47
48#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Deserialize, Serialize)]
49#[serde(transparent)]
50pub struct DiagnosisSeverity(i32);
51impl DiagnosisSeverity {
52 pub const ERROR: DiagnosisSeverity = DiagnosisSeverity(1);
53 pub const WARNING: DiagnosisSeverity = DiagnosisSeverity(2);
54 pub const INFORMATION: DiagnosisSeverity = DiagnosisSeverity(3);
55 pub const HINT: DiagnosisSeverity = DiagnosisSeverity(4);
56}
57
58#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default, Deserialize, Serialize)]
59pub struct DiagnosisPosition {
60 pub line: u32,
61 pub character: u32,
62}
63
64impl DiagnosisPosition {
65 pub fn new(line: u32, character: u32) -> DiagnosisPosition {
66 DiagnosisPosition { line, character }
67 }
68}
69
70#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Deserialize, Serialize)]
71pub struct DiagnosisRange {
72 pub start: DiagnosisPosition,
74 pub end: DiagnosisPosition,
76}
77
78impl DiagnosisRange {
79 pub fn new(start: DiagnosisPosition, end: DiagnosisPosition) -> DiagnosisRange {
80 DiagnosisRange { start, end }
81 }
82}
83
84#[allow(dead_code)]
85trait AsDiagnostic {
86 fn as_diagnostic(&self, span: &Span) -> Diagnostic<()>;
87}
88
89macro_rules! impl_as_dianostic {
90 ($($error:tt),+) => {$(
91 impl AsDiagnostic for $error {
92 fn as_diagnostic(&self, span: &Span) -> Diagnostic<()> {
93 Diagnostic::error()
94 .with_code(stringify!($error))
95 .with_message(self.to_string())
96 .with_labels(vec![Label::primary((), span.clone())])
97 }
98 }
99 )+};
100}
101
102impl_as_dianostic!(ParseError, ResolverError);
103
104impl AsDiagnostic for ReqlangError {
105 fn as_diagnostic(&self, span: &Span) -> Diagnostic<()> {
106 match self {
107 ReqlangError::ParseError(e) => e.as_diagnostic(span),
108 ReqlangError::ResolverError(e) => e.as_diagnostic(span),
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115
116 use crate::{
117 ast::Ast,
118 diagnostics::{
119 Diagnosis, DiagnosisPosition, DiagnosisRange, DiagnosisSeverity, get_diagnostics,
120 },
121 parser::parse,
122 };
123
124 #[test]
125 fn it_works() {
126 let source = String::from("");
127
128 let ast = Ast::from(&source);
129
130 let errs = parse(&ast).unwrap_err();
131
132 assert_eq!(
133 vec![Diagnosis {
134 range: DiagnosisRange {
135 start: DiagnosisPosition {
136 line: 0,
137 character: 0,
138 },
139 end: DiagnosisPosition {
140 line: 0,
141 character: 0,
142 },
143 },
144 severity: Some(DiagnosisSeverity::ERROR),
145 message: String::from("ParseError: Request file requires a request be defined")
146 }],
147 get_diagnostics(&errs, &source)
148 );
149 }
150}