espforge_diagnostics/
lib.rs1use std::ops::Range;
27
28pub mod codes;
29pub mod render;
30
31pub use render::to_human;
32
33pub type Span = Range<usize>;
35
36pub type Diagnostics = Vec<Diagnostic>;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
41pub enum Severity {
42 Error,
43 Warning,
44 Note,
45}
46
47impl Severity {
48 pub fn as_str(self) -> &'static str {
50 match self {
51 Severity::Error => "error",
52 Severity::Warning => "warning",
53 Severity::Note => "note",
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub struct DiagCode(&'static str);
65
66impl DiagCode {
67 pub const fn new(code: &'static str) -> Self {
69 Self(code)
70 }
71
72 pub const fn as_str(self) -> &'static str {
74 self.0
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Label {
81 pub span: Span,
82 pub text: String,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Edit {
88 pub span: Span,
89 pub text: String,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct Fix {
95 pub title: String,
96 pub edits: Vec<Edit>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct Diagnostic {
107 pub code: DiagCode,
109 pub severity: Severity,
110 pub message: String,
112 pub primary: Span,
114 pub labels: Vec<Label>,
116 pub help: Option<String>,
117 pub fixes: Vec<Fix>,
118}
119
120impl Diagnostic {
121 pub fn error(
123 code: DiagCode,
124 primary: Span,
125 message: impl Into<String>,
126 label: impl Into<String>,
127 ) -> Self {
128 Self {
129 code,
130 severity: Severity::Error,
131 message: message.into(),
132 labels: vec![Label {
133 span: primary.clone(),
134 text: label.into(),
135 }],
136 primary,
137 help: None,
138 fixes: Vec::new(),
139 }
140 }
141
142 pub fn warning(
144 code: DiagCode,
145 primary: Span,
146 message: impl Into<String>,
147 label: impl Into<String>,
148 ) -> Self {
149 Self {
150 severity: Severity::Warning,
151 ..Self::error(code, primary, message, label)
152 }
153 }
154
155 pub fn with_label(mut self, span: Span, text: impl Into<String>) -> Self {
157 self.labels.push(Label {
158 span,
159 text: text.into(),
160 });
161 self
162 }
163
164 pub fn with_help(mut self, help: impl Into<String>) -> Self {
166 self.help = Some(help.into());
167 self
168 }
169
170 pub fn with_fix(mut self, fix: Fix) -> Self {
172 self.fixes.push(fix);
173 self
174 }
175
176 pub fn primary_label(&self) -> Option<&Label> {
179 self.labels.iter().find(|label| label.span == self.primary)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn a_diagnostic_has_one_primary_label() {
189 let d = Diagnostic::error(codes::CORE_UNKNOWN_ELEMENT, 4..8, "bad", "no such thing")
190 .with_label(20..24, "declared here");
191 assert_eq!(d.severity, Severity::Error);
192 assert_eq!(d.primary_label().unwrap().text, "no such thing");
193 assert_eq!(d.labels.len(), 2);
194 }
195
196 #[test]
197 fn codes_are_data_not_prefixes() {
198 let d = Diagnostic::error(DiagCode::new("E0701"), 0..1, "boom", "here");
199 assert_eq!(d.code.as_str(), "E0701");
200 }
201
202 #[test]
203 fn the_machine_severity_spelling_is_stable() {
204 assert_eq!(Severity::Error.as_str(), "error");
205 assert_eq!(Severity::Warning.as_str(), "warning");
206 assert_eq!(Severity::Note.as_str(), "note");
207 }
208}