1use ariadne::{Color, Config, Label, Report, ReportKind};
9
10use crate::span::Span;
11
12#[derive(Debug, Clone)]
14pub struct CompileError {
15 pub category: &'static str,
16 pub span: Span,
17 pub message: String,
18 pub labels: Vec<(Span, String)>,
19 pub notes: Vec<String>,
20 pub suggestions: Vec<Suggestion>,
24}
25
26#[derive(Debug, Clone)]
32pub struct Suggestion {
33 pub message: String,
35 pub edits: Vec<(Span, String)>,
36 pub applicability: Applicability,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Applicability {
43 MachineApplicable,
45 HasPlaceholders,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum Severity {
57 Error,
58 Warning,
59}
60
61impl Severity {
62 pub fn for_error(err: &CompileError) -> Severity {
69 match err.category {
70 "bynk.parse.orphan_doc_block"
71 | "bynk.given.unused_capability"
72 | "bynk.list.deprecated_function"
73 | "bynk.index.missing"
74 | "bynk.index.unused" => Severity::Warning,
75 _ => Severity::Error,
76 }
77 }
78}
79
80pub fn partition_by_severity(
84 diagnostics: Vec<CompileError>,
85) -> (Vec<CompileError>, Vec<CompileError>) {
86 diagnostics
87 .into_iter()
88 .partition(|d| Severity::for_error(d) == Severity::Error)
89}
90
91impl CompileError {
92 pub fn new(category: &'static str, span: Span, message: impl Into<String>) -> Self {
93 Self {
94 category,
95 span,
96 message: message.into(),
97 labels: Vec::new(),
98 notes: Vec::new(),
99 suggestions: Vec::new(),
100 }
101 }
102
103 pub fn with_label(mut self, span: Span, label: impl Into<String>) -> Self {
104 self.labels.push((span, label.into()));
105 self
106 }
107
108 pub fn with_note(mut self, note: impl Into<String>) -> Self {
109 self.notes.push(note.into());
110 self
111 }
112
113 pub fn with_suggestion(
116 mut self,
117 message: impl Into<String>,
118 edits: Vec<(Span, String)>,
119 applicability: Applicability,
120 ) -> Self {
121 self.suggestions.push(Suggestion {
122 message: message.into(),
123 edits,
124 applicability,
125 });
126 self
127 }
128
129 pub fn report<'a>(
132 &'a self,
133 filename: &'a str,
134 ) -> Report<'a, (&'a str, std::ops::Range<usize>)> {
135 self.report_with_config(filename, Config::default())
136 }
137
138 pub fn report_plain<'a>(
141 &'a self,
142 filename: &'a str,
143 ) -> Report<'a, (&'a str, std::ops::Range<usize>)> {
144 self.report_with_config(filename, Config::default().with_color(false))
145 }
146
147 fn report_with_config<'a>(
148 &'a self,
149 filename: &'a str,
150 config: Config,
151 ) -> Report<'a, (&'a str, std::ops::Range<usize>)> {
152 let primary_span = (filename, self.span.range());
153 let mut builder = Report::build(ReportKind::Error, primary_span.clone())
154 .with_config(config)
155 .with_code(self.category)
156 .with_message(&self.message)
157 .with_label(
158 Label::new(primary_span)
159 .with_message(&self.message)
160 .with_color(Color::Red),
161 );
162
163 for (span, label) in &self.labels {
164 builder = builder.with_label(
165 Label::new((filename, span.range()))
166 .with_message(label)
167 .with_color(Color::Yellow),
168 );
169 }
170
171 for note in &self.notes {
172 builder = builder.with_note(note);
173 }
174
175 builder.finish()
176 }
177}
178
179#[cfg(test)]
180mod warning_channel_tests {
181 use super::*;
182 use crate::span::Span;
183
184 #[test]
185 fn partition_splits_by_severity() {
186 let warn = CompileError::new("bynk.given.unused_capability", Span::default(), "unused");
187 let err = CompileError::new("bynk.types.argument_mismatch", Span::default(), "bad");
188 let (errors, warnings) = partition_by_severity(vec![warn, err]);
189 assert_eq!(errors.len(), 1);
190 assert_eq!(errors[0].category, "bynk.types.argument_mismatch");
191 assert_eq!(warnings.len(), 1);
192 assert_eq!(warnings[0].category, "bynk.given.unused_capability");
193 }
194}