1 //! Diagnostic data structures.
2 
3 #[cfg(feature = "serialization")]
4 use serde::{Deserialize, Serialize};
5 use std::ops::Range;
6 
7 /// A severity level for diagnostic messages.
8 ///
9 /// These are ordered in the following way:
10 ///
11 /// ```rust
12 /// use codespan_reporting::diagnostic::Severity;
13 ///
14 /// assert!(Severity::Bug > Severity::Error);
15 /// assert!(Severity::Error > Severity::Warning);
16 /// assert!(Severity::Warning > Severity::Note);
17 /// assert!(Severity::Note > Severity::Help);
18 /// ```
19 #[derive(Copy, Clone, PartialEq, Hash, Debug)]
20 #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
21 pub enum Severity {
22     /// An unexpected bug.
23     Bug,
24     /// An error.
25     Error,
26     /// A warning.
27     Warning,
28     /// A note.
29     Note,
30     /// A help message.
31     Help,
32 }
33 
34 impl Severity {
35     /// We want bugs to be the maximum severity, errors next, etc...
to_cmp_int(self) -> u836     fn to_cmp_int(self) -> u8 {
37         match self {
38             Severity::Bug => 5,
39             Severity::Error => 4,
40             Severity::Warning => 3,
41             Severity::Note => 2,
42             Severity::Help => 1,
43         }
44     }
45 }
46 
47 impl PartialOrd for Severity {
partial_cmp(&self, other: &Severity) -> Option<std::cmp::Ordering>48     fn partial_cmp(&self, other: &Severity) -> Option<std::cmp::Ordering> {
49         u8::partial_cmp(&self.to_cmp_int(), &other.to_cmp_int())
50     }
51 }
52 
53 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
54 #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
55 pub enum LabelStyle {
56     /// Labels that describe the primary cause of a diagnostic.
57     Primary,
58     /// Labels that provide additional context for a diagnostic.
59     Secondary,
60 }
61 
62 /// A label describing an underlined region of code associated with a diagnostic.
63 #[derive(Clone, Debug)]
64 #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
65 pub struct Label<FileId> {
66     /// The style of the label.
67     pub style: LabelStyle,
68     /// The file that we are labelling.
69     pub file_id: FileId,
70     /// The range in bytes we are going to include in the final snippet.
71     pub range: Range<usize>,
72     /// An optional message to provide some additional information for the
73     /// underlined code. These should not include line breaks.
74     pub message: String,
75 }
76 
77 impl<FileId> Label<FileId> {
78     /// Create a new label.
new( style: LabelStyle, file_id: FileId, range: impl Into<Range<usize>>, ) -> Label<FileId>79     pub fn new(
80         style: LabelStyle,
81         file_id: FileId,
82         range: impl Into<Range<usize>>,
83     ) -> Label<FileId> {
84         Label {
85             style,
86             file_id,
87             range: range.into(),
88             message: String::new(),
89         }
90     }
91 
92     /// Create a new label with a style of [`LabelStyle::Primary`].
93     ///
94     /// [`LabelStyle::Primary`]: LabelStyle::Primary
primary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId>95     pub fn primary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId> {
96         Label::new(LabelStyle::Primary, file_id, range)
97     }
98 
99     /// Create a new label with a style of [`LabelStyle::Secondary`].
100     ///
101     /// [`LabelStyle::Secondary`]: LabelStyle::Secondary
secondary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId>102     pub fn secondary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId> {
103         Label::new(LabelStyle::Secondary, file_id, range)
104     }
105 
106     /// Add a message to the diagnostic.
with_message(mut self, message: impl Into<String>) -> Label<FileId>107     pub fn with_message(mut self, message: impl Into<String>) -> Label<FileId> {
108         self.message = message.into();
109         self
110     }
111 }
112 
113 /// Represents a diagnostic message that can provide information like errors and
114 /// warnings to the user.
115 #[derive(Clone, Debug)]
116 #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
117 pub struct Diagnostic<FileId> {
118     /// The overall severity of the diagnostic
119     pub severity: Severity,
120     /// An optional code that identifies this diagnostic.
121     pub code: Option<String>,
122     /// The main message associated with this diagnostic.
123     ///
124     /// These should not include line breaks, and in order support the 'short'
125     /// diagnostic display mod, the message should be specific enough to make
126     /// sense on its own, without additional context provided by labels and notes.
127     pub message: String,
128     /// Source labels that describe the cause of the diagnostic.
129     pub labels: Vec<Label<FileId>>,
130     /// Notes that are associated with the primary cause of the diagnostic.
131     /// These can include line breaks for improved formatting.
132     pub notes: Vec<String>,
133 }
134 
135 impl<FileId> Diagnostic<FileId> {
136     /// Create a new diagnostic.
new(severity: Severity) -> Diagnostic<FileId>137     pub fn new(severity: Severity) -> Diagnostic<FileId> {
138         Diagnostic {
139             severity,
140             code: None,
141             message: String::new(),
142             labels: Vec::new(),
143             notes: Vec::new(),
144         }
145     }
146 
147     /// Create a new diagnostic with a severity of [`Severity::Bug`].
148     ///
149     /// [`Severity::Bug`]: Severity::Bug
bug() -> Diagnostic<FileId>150     pub fn bug() -> Diagnostic<FileId> {
151         Diagnostic::new(Severity::Bug)
152     }
153 
154     /// Create a new diagnostic with a severity of [`Severity::Error`].
155     ///
156     /// [`Severity::Error`]: Severity::Error
error() -> Diagnostic<FileId>157     pub fn error() -> Diagnostic<FileId> {
158         Diagnostic::new(Severity::Error)
159     }
160 
161     /// Create a new diagnostic with a severity of [`Severity::Warning`].
162     ///
163     /// [`Severity::Warning`]: Severity::Warning
warning() -> Diagnostic<FileId>164     pub fn warning() -> Diagnostic<FileId> {
165         Diagnostic::new(Severity::Warning)
166     }
167 
168     /// Create a new diagnostic with a severity of [`Severity::Note`].
169     ///
170     /// [`Severity::Note`]: Severity::Note
note() -> Diagnostic<FileId>171     pub fn note() -> Diagnostic<FileId> {
172         Diagnostic::new(Severity::Note)
173     }
174 
175     /// Create a new diagnostic with a severity of [`Severity::Help`].
176     ///
177     /// [`Severity::Help`]: Severity::Help
help() -> Diagnostic<FileId>178     pub fn help() -> Diagnostic<FileId> {
179         Diagnostic::new(Severity::Help)
180     }
181 
182     /// Add an error code to the diagnostic.
with_code(mut self, code: impl Into<String>) -> Diagnostic<FileId>183     pub fn with_code(mut self, code: impl Into<String>) -> Diagnostic<FileId> {
184         self.code = Some(code.into());
185         self
186     }
187 
188     /// Add a message to the diagnostic.
with_message(mut self, message: impl Into<String>) -> Diagnostic<FileId>189     pub fn with_message(mut self, message: impl Into<String>) -> Diagnostic<FileId> {
190         self.message = message.into();
191         self
192     }
193 
194     /// Add some labels to the diagnostic.
with_labels(mut self, labels: Vec<Label<FileId>>) -> Diagnostic<FileId>195     pub fn with_labels(mut self, labels: Vec<Label<FileId>>) -> Diagnostic<FileId> {
196         self.labels = labels;
197         self
198     }
199 
200     /// Add some notes to the diagnostic.
with_notes(mut self, notes: Vec<String>) -> Diagnostic<FileId>201     pub fn with_notes(mut self, notes: Vec<String>) -> Diagnostic<FileId> {
202         self.notes = notes;
203         self
204     }
205 }
206