whitaker_common/
diagnostics.rs1#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]
3
4use crate::span::SourceSpan;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum Applicability {
9 MachineApplicable,
11 MaybeIncorrect,
13 HasPlaceholders,
15 Unspecified,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct Suggestion {
22 message: String,
23 replacement: String,
24 applicability: Applicability,
25}
26
27impl Suggestion {
28 #[must_use]
39 pub fn new(
40 message: impl Into<String>,
41 replacement: impl Into<String>,
42 applicability: Applicability,
43 ) -> Self {
44 Self {
45 message: message.into(),
46 replacement: replacement.into(),
47 applicability,
48 }
49 }
50
51 #[must_use]
53 pub fn message(&self) -> &str {
54 &self.message
55 }
56
57 #[must_use]
59 pub fn replacement(&self) -> &str {
60 &self.replacement
61 }
62
63 #[must_use]
65 pub const fn applicability(&self) -> Applicability {
66 self.applicability
67 }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct Diagnostic {
73 code: String,
74 message: String,
75 span: SourceSpan,
76 notes: Vec<String>,
77 helps: Vec<String>,
78 suggestions: Vec<Suggestion>,
79}
80
81impl Diagnostic {
82 #[must_use]
84 pub fn code(&self) -> &str {
85 &self.code
86 }
87
88 #[must_use]
90 pub fn message(&self) -> &str {
91 &self.message
92 }
93
94 #[must_use]
96 pub const fn span(&self) -> SourceSpan {
97 self.span
98 }
99
100 #[must_use]
102 pub fn notes(&self) -> &[String] {
103 &self.notes
104 }
105
106 #[must_use]
108 pub fn helps(&self) -> &[String] {
109 &self.helps
110 }
111
112 #[must_use]
114 pub fn suggestions(&self) -> &[Suggestion] {
115 &self.suggestions
116 }
117}
118
119pub struct DiagnosticBuilder {
121 diagnostic: Diagnostic,
122}
123
124impl DiagnosticBuilder {
125 fn new(code: impl Into<String>, message: impl Into<String>, span: SourceSpan) -> Self {
126 Self {
127 diagnostic: Diagnostic {
128 code: code.into(),
129 message: message.into(),
130 span,
131 notes: Vec::new(),
132 helps: Vec::new(),
133 suggestions: Vec::new(),
134 },
135 }
136 }
137
138 #[must_use]
140 pub fn note(mut self, note: impl Into<String>) -> Self {
141 self.diagnostic.notes.push(note.into());
142 self
143 }
144
145 #[must_use]
147 pub fn help(mut self, help: impl Into<String>) -> Self {
148 self.diagnostic.helps.push(help.into());
149 self
150 }
151
152 #[must_use]
154 pub fn suggestion(mut self, suggestion: Suggestion) -> Self {
155 self.diagnostic.suggestions.push(suggestion);
156 self
157 }
158
159 #[must_use]
161 pub fn build(self) -> Diagnostic {
162 self.diagnostic
163 }
164}
165
166#[must_use]
182pub fn span_lint(
183 code: impl Into<String>,
184 message: impl Into<String>,
185 span: SourceSpan,
186) -> DiagnosticBuilder {
187 DiagnosticBuilder::new(code, message, span)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use crate::span::{SourceLocation, SourceSpan};
194 use rstest::rstest;
195
196 #[rstest]
197 fn builds_diagnostic() {
198 let span = SourceSpan::new(SourceLocation::new(2, 1), SourceLocation::new(2, 5))
199 .expect("valid span for diagnostic test");
200 let diagnostic = span_lint("lint", "Message", span)
201 .note("Note")
202 .help("Help")
203 .suggestion(Suggestion::new(
204 "Fix",
205 "fix()",
206 Applicability::MachineApplicable,
207 ))
208 .build();
209
210 assert_eq!(diagnostic.code(), "lint");
211 assert_eq!(diagnostic.notes(), &[String::from("Note")]);
212 assert_eq!(diagnostic.helps(), &[String::from("Help")]);
213 assert_eq!(diagnostic.suggestions().len(), 1);
214 }
215}