orrery_parser/error/
diagnostic.rs1use std::fmt;
7
8use crate::{
9 error::{Severity, error_code::ErrorCode, label::Label},
10 span::Span,
11};
12
13#[derive(Debug, Clone)]
35pub struct Diagnostic {
36 severity: Severity,
37 code: Option<ErrorCode>,
38 message: String,
39 labels: Vec<Label>,
40 help: Option<String>,
41}
42
43impl Diagnostic {
44 pub fn error(message: impl Into<String>) -> Self {
59 Self::new(Severity::Error, message)
60 }
61
62 pub fn warning(message: impl Into<String>) -> Self {
76 Self::new(Severity::Warning, message)
77 }
78
79 pub fn severity(&self) -> Severity {
81 self.severity
82 }
83
84 pub fn code(&self) -> Option<ErrorCode> {
86 self.code
87 }
88
89 pub fn message(&self) -> &str {
91 &self.message
92 }
93
94 pub fn labels(&self) -> &[Label] {
96 &self.labels
97 }
98
99 pub fn help(&self) -> Option<&str> {
101 self.help.as_deref()
102 }
103
104 pub fn with_code(mut self, code: ErrorCode) -> Self {
106 self.code = Some(code);
107 self
108 }
109
110 pub fn with_label(mut self, span: Span, message: impl Into<String>) -> Self {
112 self.labels.push(Label::primary(span, message));
113 self
114 }
115
116 pub fn with_secondary_label(mut self, span: Span, message: impl Into<String>) -> Self {
118 self.labels.push(Label::secondary(span, message));
119 self
120 }
121
122 pub fn with_help(mut self, help: impl Into<String>) -> Self {
124 self.help = Some(help.into());
125 self
126 }
127
128 fn new(severity: Severity, message: impl Into<String>) -> Self {
130 Self {
131 severity,
132 code: None,
133 message: message.into(),
134 labels: Vec::new(),
135 help: None,
136 }
137 }
138}
139
140impl fmt::Display for Diagnostic {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "{}", self.severity)?;
144 if let Some(code) = self.code {
145 write!(f, "[{}]", code)?;
146 }
147 write!(f, ": {}", self.message)
148 }
149}
150
151impl std::error::Error for Diagnostic {}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_diagnostic_new() {
159 let diag = Diagnostic::new(Severity::Error, "test error");
160
161 assert!(diag.severity().is_error());
162 assert!(!diag.severity().is_warning());
163 assert_eq!(diag.message(), "test error");
164 assert!(diag.code().is_none());
165 assert!(diag.labels().is_empty());
166 assert!(diag.help().is_none());
167 }
168
169 #[test]
170 fn test_diagnostic_with_code() {
171 let diag = Diagnostic::new(Severity::Error, "undefined type").with_code(ErrorCode::E300);
172
173 assert_eq!(diag.code(), Some(ErrorCode::E300));
174 }
175
176 #[test]
177 fn test_diagnostic_with_label() {
178 let diag = Diagnostic::new(Severity::Error, "test error")
179 .with_label(Span::new(10..20), "error here");
180
181 assert_eq!(diag.labels().len(), 1);
182 assert!(diag.labels()[0].is_primary());
183 assert_eq!(diag.labels()[0].message(), "error here");
184 }
185
186 #[test]
187 fn test_diagnostic_with_secondary_label() {
188 let diag = Diagnostic::new(Severity::Error, "duplicate definition")
189 .with_label(Span::new(10..20), "duplicate here")
190 .with_secondary_label(Span::new(5..15), "first defined here");
191
192 assert_eq!(diag.labels().len(), 2);
193 assert!(diag.labels()[0].is_primary());
194 assert!(diag.labels()[1].is_secondary());
195 }
196
197 #[test]
198 fn test_diagnostic_with_help() {
199 let diag = Diagnostic::new(Severity::Warning, "unused variable")
200 .with_help("consider removing or prefixing with underscore");
201
202 assert_eq!(
203 diag.help(),
204 Some("consider removing or prefixing with underscore")
205 );
206 }
207
208 #[test]
209 fn test_diagnostic_display_with_code() {
210 let diag =
211 Diagnostic::new(Severity::Error, "undefined type `Foo`").with_code(ErrorCode::E300);
212
213 assert_eq!(diag.to_string(), "error[E300]: undefined type `Foo`");
214 }
215
216 #[test]
217 fn test_diagnostic_display_without_code() {
218 let diag = Diagnostic::new(Severity::Warning, "unused import");
219
220 assert_eq!(diag.to_string(), "warning: unused import");
221 }
222
223 #[test]
224 fn test_diagnostic_builder_chain() {
225 let diag = Diagnostic::new(Severity::Error, "cannot override built-in type `Component`")
226 .with_code(ErrorCode::E301)
227 .with_label(Span::new(100..120), "type override not supported")
228 .with_help("built-in types cannot be redefined");
229
230 assert!(diag.severity().is_error());
231 assert_eq!(diag.code(), Some(ErrorCode::E301));
232 assert_eq!(diag.message(), "cannot override built-in type `Component`");
233 assert_eq!(diag.labels().len(), 1);
234 assert_eq!(diag.help(), Some("built-in types cannot be redefined"));
235 }
236}