plotnik_lib/diagnostics/
mod.rs1mod message;
2mod printer;
3
4#[cfg(test)]
5mod tests;
6
7use rowan::TextRange;
8
9pub use message::{DiagnosticKind, Severity};
10pub use printer::DiagnosticsPrinter;
11
12use message::{DiagnosticMessage, Fix, RelatedInfo};
13
14#[derive(Debug, Clone, Default)]
15pub struct Diagnostics {
16 messages: Vec<DiagnosticMessage>,
17}
18
19#[must_use = "diagnostic not emitted, call .emit()"]
20pub struct DiagnosticBuilder<'a> {
21 diagnostics: &'a mut Diagnostics,
22 message: DiagnosticMessage,
23}
24
25impl Diagnostics {
26 pub fn new() -> Self {
27 Self {
28 messages: Vec::new(),
29 }
30 }
31
32 pub fn report(&mut self, kind: DiagnosticKind, range: TextRange) -> DiagnosticBuilder<'_> {
36 DiagnosticBuilder {
37 diagnostics: self,
38 message: DiagnosticMessage::with_default_message(kind, range),
39 }
40 }
41
42 pub fn error(&mut self, msg: impl Into<String>, range: TextRange) -> DiagnosticBuilder<'_> {
44 DiagnosticBuilder {
45 diagnostics: self,
46 message: DiagnosticMessage::new(DiagnosticKind::UnexpectedToken, range, msg),
47 }
48 }
49
50 pub fn warning(&mut self, msg: impl Into<String>, range: TextRange) -> DiagnosticBuilder<'_> {
52 DiagnosticBuilder {
53 diagnostics: self,
54 message: DiagnosticMessage::new(DiagnosticKind::UnexpectedToken, range, msg),
55 }
56 }
57
58 pub fn is_empty(&self) -> bool {
59 self.messages.is_empty()
60 }
61
62 pub fn len(&self) -> usize {
63 self.messages.len()
64 }
65
66 pub fn has_errors(&self) -> bool {
67 self.messages.iter().any(|d| d.is_error())
68 }
69
70 pub fn has_warnings(&self) -> bool {
71 self.messages.iter().any(|d| d.is_warning())
72 }
73
74 pub fn error_count(&self) -> usize {
75 self.messages.iter().filter(|d| d.is_error()).count()
76 }
77
78 pub fn warning_count(&self) -> usize {
79 self.messages.iter().filter(|d| d.is_warning()).count()
80 }
81
82 pub(crate) fn filtered(&self) -> Vec<DiagnosticMessage> {
91 if self.messages.is_empty() {
92 return Vec::new();
93 }
94
95 let mut suppressed = vec![false; self.messages.len()];
96
97 let has_primary_error = self.messages.iter().any(|m| !m.kind.is_consequence_error());
99 if has_primary_error {
100 for (i, msg) in self.messages.iter().enumerate() {
101 if msg.kind.is_consequence_error() {
102 suppressed[i] = true;
103 }
104 }
105 }
106
107 for (i, a) in self.messages.iter().enumerate() {
109 for (j, b) in self.messages.iter().enumerate() {
110 if i == j || suppressed[i] || suppressed[j] {
111 continue;
112 }
113
114 let contains = a.suppression_range.start() <= b.range.start()
118 && b.range.end() <= a.suppression_range.end();
119 if contains && a.kind.is_structural_error() && a.kind.suppresses(&b.kind) {
120 suppressed[j] = true;
121 continue;
122 }
123
124 if a.range.start() == b.range.start() {
126 if a.kind.is_root_cause_error() && b.kind.is_structural_error() {
130 suppressed[j] = true;
131 continue;
132 }
133 if a.kind.suppresses(&b.kind) {
134 suppressed[j] = true;
135 continue;
136 }
137 }
138
139 if a.range.end() == b.range.start() {
144 suppressed[j] = true;
145 }
146 }
147 }
148
149 let mut result: Vec<_> = self
150 .messages
151 .iter()
152 .enumerate()
153 .filter(|(i, _)| !suppressed[*i])
154 .map(|(_, m)| m.clone())
155 .collect();
156 result.sort_by_key(|m| m.range.start());
157 result
158 }
159
160 #[allow(dead_code)]
162 pub(crate) fn raw(&self) -> &[DiagnosticMessage] {
163 &self.messages
164 }
165
166 pub fn printer<'a>(&self, source: &'a str) -> DiagnosticsPrinter<'a> {
167 DiagnosticsPrinter::new(self.messages.clone(), source)
168 }
169
170 pub fn filtered_printer<'a>(&self, source: &'a str) -> DiagnosticsPrinter<'a> {
172 DiagnosticsPrinter::new(self.filtered(), source)
173 }
174
175 pub fn render(&self, source: &str) -> String {
176 self.printer(source).render()
177 }
178
179 pub fn render_colored(&self, source: &str, colored: bool) -> String {
180 self.printer(source).colored(colored).render()
181 }
182
183 pub fn render_filtered(&self, source: &str) -> String {
184 self.filtered_printer(source).render()
185 }
186
187 pub fn render_filtered_colored(&self, source: &str, colored: bool) -> String {
188 self.filtered_printer(source).colored(colored).render()
189 }
190
191 pub fn extend(&mut self, other: Diagnostics) {
192 self.messages.extend(other.messages);
193 }
194}
195
196impl<'a> DiagnosticBuilder<'a> {
197 pub fn message(mut self, msg: impl Into<String>) -> Self {
199 let detail = msg.into();
200 self.message.message = self.message.kind.message(Some(&detail));
201 self
202 }
203
204 pub fn related_to(mut self, msg: impl Into<String>, range: TextRange) -> Self {
205 self.message.related.push(RelatedInfo::new(range, msg));
206 self
207 }
208
209 pub fn suppression_range(mut self, range: TextRange) -> Self {
217 self.message.suppression_range = range;
218 self
219 }
220
221 pub fn fix(mut self, description: impl Into<String>, replacement: impl Into<String>) -> Self {
222 self.message.fix = Some(Fix::new(replacement, description));
223 self
224 }
225
226 pub fn hint(mut self, hint: impl Into<String>) -> Self {
227 self.message.hints.push(hint.into());
228 self
229 }
230
231 pub fn emit(self) {
232 self.diagnostics.messages.push(self.message);
233 }
234}