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, Eq, 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, PartialOrd)] 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, PartialEq, Eq)] 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 /// 116 /// The position of a Diagnostic is considered to be the position of the [`Label`] that has the earliest starting position and has the highest style which appears in all the labels of the diagnostic. 117 #[derive(Clone, Debug, PartialEq, Eq)] 118 #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] 119 pub struct Diagnostic<FileId> { 120 /// The overall severity of the diagnostic 121 pub severity: Severity, 122 /// An optional code that identifies this diagnostic. 123 pub code: Option<String>, 124 /// The main message associated with this diagnostic. 125 /// 126 /// These should not include line breaks, and in order support the 'short' 127 /// diagnostic display mod, the message should be specific enough to make 128 /// sense on its own, without additional context provided by labels and notes. 129 pub message: String, 130 /// Source labels that describe the cause of the diagnostic. 131 /// The order of the labels inside the vector does not have any meaning. 132 /// The labels are always arranged in the order they appear in the source code. 133 pub labels: Vec<Label<FileId>>, 134 /// Notes that are associated with the primary cause of the diagnostic. 135 /// These can include line breaks for improved formatting. 136 pub notes: Vec<String>, 137 } 138 139 impl<FileId> Diagnostic<FileId> { 140 /// Create a new diagnostic. new(severity: Severity) -> Diagnostic<FileId>141 pub fn new(severity: Severity) -> Diagnostic<FileId> { 142 Diagnostic { 143 severity, 144 code: None, 145 message: String::new(), 146 labels: Vec::new(), 147 notes: Vec::new(), 148 } 149 } 150 151 /// Create a new diagnostic with a severity of [`Severity::Bug`]. 152 /// 153 /// [`Severity::Bug`]: Severity::Bug bug() -> Diagnostic<FileId>154 pub fn bug() -> Diagnostic<FileId> { 155 Diagnostic::new(Severity::Bug) 156 } 157 158 /// Create a new diagnostic with a severity of [`Severity::Error`]. 159 /// 160 /// [`Severity::Error`]: Severity::Error error() -> Diagnostic<FileId>161 pub fn error() -> Diagnostic<FileId> { 162 Diagnostic::new(Severity::Error) 163 } 164 165 /// Create a new diagnostic with a severity of [`Severity::Warning`]. 166 /// 167 /// [`Severity::Warning`]: Severity::Warning warning() -> Diagnostic<FileId>168 pub fn warning() -> Diagnostic<FileId> { 169 Diagnostic::new(Severity::Warning) 170 } 171 172 /// Create a new diagnostic with a severity of [`Severity::Note`]. 173 /// 174 /// [`Severity::Note`]: Severity::Note note() -> Diagnostic<FileId>175 pub fn note() -> Diagnostic<FileId> { 176 Diagnostic::new(Severity::Note) 177 } 178 179 /// Create a new diagnostic with a severity of [`Severity::Help`]. 180 /// 181 /// [`Severity::Help`]: Severity::Help help() -> Diagnostic<FileId>182 pub fn help() -> Diagnostic<FileId> { 183 Diagnostic::new(Severity::Help) 184 } 185 186 /// Set the error code of the diagnostic. with_code(mut self, code: impl Into<String>) -> Diagnostic<FileId>187 pub fn with_code(mut self, code: impl Into<String>) -> Diagnostic<FileId> { 188 self.code = Some(code.into()); 189 self 190 } 191 192 /// Set the message of the diagnostic. with_message(mut self, message: impl Into<String>) -> Diagnostic<FileId>193 pub fn with_message(mut self, message: impl Into<String>) -> Diagnostic<FileId> { 194 self.message = message.into(); 195 self 196 } 197 198 /// Add some labels to the diagnostic. with_labels(mut self, mut labels: Vec<Label<FileId>>) -> Diagnostic<FileId>199 pub fn with_labels(mut self, mut labels: Vec<Label<FileId>>) -> Diagnostic<FileId> { 200 self.labels.append(&mut labels); 201 self 202 } 203 204 /// Add some notes to the diagnostic. with_notes(mut self, mut notes: Vec<String>) -> Diagnostic<FileId>205 pub fn with_notes(mut self, mut notes: Vec<String>) -> Diagnostic<FileId> { 206 self.notes.append(&mut notes); 207 self 208 } 209 } 210