plotnik_compiler/diagnostics/
mod.rs1mod message;
2mod printer;
3
4#[cfg(test)]
5mod diagnostics_tests;
6
7use rowan::TextRange;
8
9pub use message::{DiagnosticKind, Severity};
10pub use printer::DiagnosticsPrinter;
11
12use message::{DiagnosticMessage, Fix, RelatedInfo};
13
14pub use crate::query::{SourceId, SourceMap};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct Span {
20 pub source: SourceId,
21 pub range: TextRange,
22}
23
24impl Span {
25 pub fn new(source: SourceId, range: TextRange) -> Self {
26 Self { source, range }
27 }
28}
29
30#[derive(Debug, Clone, Default)]
31pub struct Diagnostics {
32 messages: Vec<DiagnosticMessage>,
33}
34
35#[must_use = "diagnostic not emitted, call .emit()"]
36pub struct DiagnosticBuilder<'d> {
37 diagnostics: &'d mut Diagnostics,
38 message: DiagnosticMessage,
39}
40
41impl Diagnostics {
42 pub fn new() -> Self {
43 Self {
44 messages: Vec::new(),
45 }
46 }
47
48 pub fn report(
52 &mut self,
53 source: SourceId,
54 kind: DiagnosticKind,
55 range: TextRange,
56 ) -> DiagnosticBuilder<'_> {
57 DiagnosticBuilder {
58 diagnostics: self,
59 message: DiagnosticMessage::with_default_message(source, kind, range),
60 }
61 }
62
63 pub fn is_empty(&self) -> bool {
64 self.messages.is_empty()
65 }
66
67 pub fn len(&self) -> usize {
68 self.messages.len()
69 }
70
71 pub fn has_errors(&self) -> bool {
72 self.messages.iter().any(|d| d.is_error())
73 }
74
75 pub fn has_warnings(&self) -> bool {
76 self.messages.iter().any(|d| d.is_warning())
77 }
78
79 pub fn error_count(&self) -> usize {
80 self.messages.iter().filter(|d| d.is_error()).count()
81 }
82
83 pub fn warning_count(&self) -> usize {
84 self.messages.iter().filter(|d| d.is_warning()).count()
85 }
86
87 pub(crate) fn filtered(&self) -> Vec<DiagnosticMessage> {
96 if self.messages.is_empty() {
97 return Vec::new();
98 }
99
100 let mut suppressed = vec![false; self.messages.len()];
101
102 let has_primary_error = self.messages.iter().any(|m| !m.kind.is_consequence_error());
104 if has_primary_error {
105 for (i, msg) in self.messages.iter().enumerate() {
106 if msg.kind.is_consequence_error() {
107 suppressed[i] = true;
108 }
109 }
110 }
111
112 for (i, a) in self.messages.iter().enumerate() {
114 for (j, b) in self.messages.iter().enumerate() {
115 if i == j || suppressed[i] || suppressed[j] {
116 continue;
117 }
118
119 let contains = a.suppression_range.start() <= b.range.start()
123 && b.range.end() <= a.suppression_range.end();
124 if contains && a.kind.is_structural_error() && a.kind.suppresses(&b.kind) {
125 suppressed[j] = true;
126 continue;
127 }
128
129 if a.range.start() == b.range.start() {
131 if a.kind.is_root_cause_error() && b.kind.is_structural_error() {
135 suppressed[j] = true;
136 continue;
137 }
138 if a.kind.suppresses(&b.kind) {
139 suppressed[j] = true;
140 continue;
141 }
142 }
143
144 if a.range.end() == b.range.start() {
149 suppressed[j] = true;
150 }
151 }
152 }
153
154 let mut result: Vec<_> = self
155 .messages
156 .iter()
157 .enumerate()
158 .filter(|(i, _)| !suppressed[*i])
159 .map(|(_, m)| m.clone())
160 .collect();
161 result.sort_by_key(|m| m.range.start());
162 result
163 }
164
165 #[allow(dead_code)]
167 pub(crate) fn raw(&self) -> &[DiagnosticMessage] {
168 &self.messages
169 }
170
171 pub fn printer<'q>(&self, sources: &'q SourceMap) -> DiagnosticsPrinter<'q> {
173 DiagnosticsPrinter::new(self.messages.clone(), sources)
174 }
175
176 pub fn filtered_printer<'q>(&self, sources: &'q SourceMap) -> DiagnosticsPrinter<'q> {
178 DiagnosticsPrinter::new(self.filtered(), sources)
179 }
180
181 pub fn render(&self, sources: &SourceMap) -> String {
183 self.printer(sources).render()
184 }
185
186 pub fn render_colored(&self, sources: &SourceMap, colored: bool) -> String {
188 self.printer(sources).colored(colored).render()
189 }
190
191 pub fn render_filtered(&self, sources: &SourceMap) -> String {
193 self.filtered_printer(sources).render()
194 }
195
196 pub fn render_filtered_colored(&self, sources: &SourceMap, colored: bool) -> String {
198 self.filtered_printer(sources).colored(colored).render()
199 }
200
201 pub fn extend(&mut self, other: Diagnostics) {
202 self.messages.extend(other.messages);
203 }
204}
205
206impl<'d> DiagnosticBuilder<'d> {
207 pub fn message(mut self, msg: impl Into<String>) -> Self {
209 let detail = msg.into();
210 self.message.message = self.message.kind.message(Some(&detail));
211 self
212 }
213
214 pub fn related_to(
215 mut self,
216 source: SourceId,
217 range: TextRange,
218 msg: impl Into<String>,
219 ) -> Self {
220 self.message
221 .related
222 .push(RelatedInfo::new(source, range, msg));
223 self
224 }
225
226 pub fn suppression_range(mut self, range: TextRange) -> Self {
234 self.message.suppression_range = range;
235 self
236 }
237
238 pub fn fix(mut self, description: impl Into<String>, replacement: impl Into<String>) -> Self {
239 self.message.fix = Some(Fix::new(replacement, description));
240 self
241 }
242
243 pub fn hint(mut self, hint: impl Into<String>) -> Self {
244 self.message.hints.push(hint.into());
245 self
246 }
247
248 pub fn emit(mut self) {
249 if let Some(default_hint) = self.message.kind.default_hint() {
251 self.message.hints.insert(0, default_hint.to_string());
252 }
253 self.diagnostics.messages.push(self.message);
254 }
255}