1use std::cmp::Ordering;
4use std::fmt;
5
6use rowan::TextRange;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
10pub struct Span {
11 start: usize,
13 end: usize,
15}
16
17impl Span {
18 pub const fn new(start: usize, len: usize) -> Self {
20 Self {
21 start,
22 end: start + len,
23 }
24 }
25
26 pub fn start(&self) -> usize {
28 self.start
29 }
30
31 pub fn end(&self) -> usize {
33 self.end
34 }
35
36 pub fn len(&self) -> usize {
38 self.end - self.start
39 }
40
41 pub fn is_empty(&self) -> bool {
43 self.start == self.end
44 }
45
46 pub fn contains(&self, offset: usize) -> bool {
48 offset >= self.start && offset < self.end
49 }
50
51 #[inline]
67 pub fn intersect(self, other: Self) -> Option<Self> {
68 let start = self.start.max(other.start);
69 let end = self.end.min(other.end);
70 if end < start {
71 return None;
72 }
73
74 Some(Self { start, end })
75 }
76}
77
78impl fmt::Display for Span {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "{start}..{end}", start = self.start, end = self.end)
81 }
82}
83
84impl From<logos::Span> for Span {
85 fn from(value: logos::Span) -> Self {
86 Self::new(value.start, value.len())
87 }
88}
89
90impl From<TextRange> for Span {
91 fn from(value: TextRange) -> Self {
92 let start = usize::from(value.start());
93 Self::new(start, usize::from(value.end()) - start)
94 }
95}
96
97#[derive(
99 Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, serde::Deserialize, serde::Serialize,
100)]
101pub enum Severity {
102 Error,
104 Warning,
106 Note,
108}
109
110impl Severity {
111 #[must_use]
115 pub fn is_error(&self) -> bool {
116 matches!(self, Self::Error)
117 }
118
119 #[must_use]
123 pub fn is_warning(&self) -> bool {
124 matches!(self, Self::Warning)
125 }
126
127 #[must_use]
131 pub fn is_note(&self) -> bool {
132 matches!(self, Self::Note)
133 }
134}
135
136#[derive(Debug, Clone, Eq, PartialEq)]
138pub struct Diagnostic {
139 rule: Option<String>,
141 severity: Severity,
143 message: String,
145 fix: Option<String>,
147 labels: Vec<Label>,
151}
152
153impl Ord for Diagnostic {
154 fn cmp(&self, other: &Self) -> Ordering {
155 match self.labels.cmp(&other.labels) {
156 Ordering::Equal => {}
157 ord => return ord,
158 }
159
160 match self.rule.cmp(&other.rule) {
161 Ordering::Equal => {}
162 ord => return ord,
163 }
164
165 match self.severity.cmp(&other.severity) {
166 Ordering::Equal => {}
167 ord => return ord,
168 }
169
170 match self.message.cmp(&other.message) {
171 Ordering::Equal => {}
172 ord => return ord,
173 }
174
175 self.fix.cmp(&other.fix)
176 }
177}
178
179impl PartialOrd for Diagnostic {
180 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
181 Some(self.cmp(other))
182 }
183}
184
185impl Diagnostic {
186 pub fn error(message: impl Into<String>) -> Self {
188 Self {
189 rule: None,
190 severity: Severity::Error,
191 message: message.into(),
192 fix: None,
193 labels: Default::default(),
194 }
195 }
196
197 pub fn warning(message: impl Into<String>) -> Self {
199 Self {
200 rule: None,
201 severity: Severity::Warning,
202 message: message.into(),
203 fix: None,
204 labels: Default::default(),
205 }
206 }
207
208 pub fn note(message: impl Into<String>) -> Self {
210 Self {
211 rule: None,
212 severity: Severity::Note,
213 message: message.into(),
214 fix: None,
215 labels: Default::default(),
216 }
217 }
218
219 pub fn with_rule(mut self, rule: impl Into<String>) -> Self {
221 self.rule = Some(rule.into());
222 self
223 }
224
225 pub fn with_fix(mut self, fix: impl Into<String>) -> Self {
227 self.fix = Some(fix.into());
228 self
229 }
230
231 pub fn with_highlight(mut self, span: impl Into<Span>) -> Self {
238 self.labels.push(Label::new(String::new(), span.into()));
239 self
240 }
241
242 pub fn with_label(mut self, message: impl Into<String>, span: impl Into<Span>) -> Self {
249 self.labels.push(Label::new(message, span.into()));
250 self
251 }
252
253 pub fn with_severity(mut self, severity: Severity) -> Self {
255 self.severity = severity;
256 self
257 }
258
259 pub fn rule(&self) -> Option<&str> {
261 self.rule.as_deref()
262 }
263
264 pub fn severity(&self) -> Severity {
268 self.severity
269 }
270
271 pub fn message(&self) -> &str {
273 &self.message
274 }
275
276 pub fn fix(&self) -> Option<&str> {
278 self.fix.as_deref()
279 }
280
281 pub fn labels(&self) -> impl Iterator<Item = &Label> {
283 self.labels.iter()
284 }
285
286 pub fn labels_mut(&mut self) -> impl Iterator<Item = &mut Label> {
288 self.labels.iter_mut()
289 }
290
291 #[cfg(feature = "codespan")]
297 pub fn to_codespan<FileId: Copy>(
298 &self,
299 file_id: FileId,
300 ) -> codespan_reporting::diagnostic::Diagnostic<FileId> {
301 use codespan_reporting::diagnostic as codespan;
302
303 let mut diagnostic: codespan::Diagnostic<FileId> = match self.severity {
304 Severity::Error => codespan::Diagnostic::error(),
305 Severity::Warning => codespan::Diagnostic::warning(),
306 Severity::Note => codespan::Diagnostic::note(),
307 };
308
309 if let Some(rule) = &self.rule {
310 diagnostic.code = Some(rule.clone());
311 }
312
313 diagnostic.message.clone_from(&self.message);
314
315 if let Some(fix) = &self.fix {
316 diagnostic.notes.push(format!("fix: {fix}"));
317 }
318
319 if self.labels.is_empty() {
320 diagnostic.labels.push(codespan::Label::new(
324 codespan::LabelStyle::Primary,
325 file_id,
326 usize::MAX - 1..usize::MAX,
327 ))
328 } else {
329 for (i, label) in self.labels.iter().enumerate() {
330 diagnostic.labels.push(
331 codespan::Label::new(
332 if i == 0 {
333 codespan::LabelStyle::Primary
334 } else {
335 codespan::LabelStyle::Secondary
336 },
337 file_id,
338 label.span.start..label.span.end,
339 )
340 .with_message(&label.message),
341 );
342 }
343 }
344
345 diagnostic
346 }
347}
348
349#[derive(Debug, Clone, Eq, PartialEq)]
351pub struct Label {
352 message: String,
354 span: Span,
356}
357
358impl Ord for Label {
359 fn cmp(&self, other: &Self) -> Ordering {
360 match self.span.cmp(&other.span) {
361 Ordering::Equal => {}
362 ord => return ord,
363 }
364
365 self.message.cmp(&other.message)
366 }
367}
368
369impl PartialOrd for Label {
370 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
371 Some(self.cmp(other))
372 }
373}
374
375impl Label {
376 pub fn new(message: impl Into<String>, span: impl Into<Span>) -> Self {
378 Self {
379 message: message.into(),
380 span: span.into(),
381 }
382 }
383
384 pub fn message(&self) -> &str {
386 &self.message
387 }
388
389 pub fn span(&self) -> Span {
391 self.span
392 }
393
394 pub fn set_span(&mut self, span: impl Into<Span>) {
396 self.span = span.into();
397 }
398}