1
+use std::{
2
+ error::Error,
3
+ fmt::{Debug, Display, Write},
4
+};
5
+
6
+use forget_estree::SourceRange;
7
+use miette::{ByteOffset, SourceSpan};
8
+use static_assertions::assert_impl_all;
9
+use thiserror::Error;
10
+
11
+pub type Diagnostics = Vec<Diagnostic>;
12
+pub type DiagnosticsResult<T> = Result<T, Diagnostics>;
13
+
14
+#[derive(Debug)]
15
+pub struct WithDiagnostics<T> {
16
+ pub item: T,
17
+ pub diagnostics: Vec<Diagnostic>,
18
+}
19
+
20
+impl<T> From<WithDiagnostics<T>> for Result<T, Diagnostics> {
21
+ fn from(s: WithDiagnostics<T>) -> Result<T, Diagnostics> {
22
+ if s.diagnostics.is_empty() {
23
+ Ok(s.item)
24
+ } else {
25
+ Err(s.diagnostics)
26
+ }
27
+ }
28
+}
29
+
30
+pub fn diagnostics_result<T>(result: T, diagnostics: Diagnostics) -> DiagnosticsResult<T> {
31
+ if diagnostics.is_empty() {
32
+ Ok(result)
33
+ } else {
34
+ Err(diagnostics)
35
+ }
36
+}
37
+
38
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)]
39
+pub enum DiagnosticSeverity {
40
+ /// A feature that is intended to work but not yet implemented
41
+ #[error("Not implemented")]
42
+ Todo,
43
+
44
+ /// Syntax that is valid but intentionally not supported
45
+ #[error("Unsupported")]
46
+ Unsupported,
47
+
48
+ /// Invalid syntax
49
+ #[error("Invalid JavaScript")]
50
+ InvalidSyntax,
51
+
52
+ /// Valid syntax, but invalid React
53
+ #[error("Invalid React")]
54
+ InvalidReact,
55
+
56
+ /// Internal compiler error (ICE)
57
+ #[error("Internal error")]
58
+ Invariant,
59
+}
60
+
61
+/// A diagnostic message as a result of validating some code. This struct is
62
+/// modeled after the LSP Diagnostic type:
63
+/// https://microsoft.github.io/language-server-protocol/specification#diagnostic
64
+///
65
+/// Changes from LSP:
66
+/// - `location` is different from LSP in that it's a file + span instead of
67
+/// just a span.
68
+/// - Unused fields are omitted.
69
+/// - Severity is a custom enum that represents Forget-specific categories of error.
70
+/// The translation to an LSP error/warning/etc depends on compiler settings and
71
+/// invocation context.
72
+#[derive(Debug)]
73
+pub struct Diagnostic(Box<DiagnosticData>);
74
+
75
+impl Diagnostic {
76
+ fn with_severity<T: 'static + DiagnosticDisplay>(
77
+ severity: DiagnosticSeverity,
78
+ message: T,
79
+ range: Option<SourceRange>,
80
+ ) -> Self {
81
+ Self(Box::new(DiagnosticData {
82
+ message: Box::new(message),
83
+ span: range.map(source_span_from_range),
84
+ related_information: Vec::new(),
85
+ severity,
86
+ data: Vec::new(),
87
+ }))
88
+ }
89
+
90
+ /// Creates a new Todo Diagnostic.
91
+ /// Additional locations can be added with the `.annotate()` function.
92
+ pub fn todo<T: 'static + DiagnosticDisplay>(message: T, range: Option<SourceRange>) -> Self {
93
+ Diagnostic::with_severity(DiagnosticSeverity::Todo, message, range)
94
+ }
95
+
96
+ /// Creates a new Unsupported Diagnostic.
97
+ /// Additional locations can be added with the `.annotate()` function.
98
+ pub fn unsupported<T: 'static + DiagnosticDisplay>(
99
+ message: T,
100
+ range: Option<SourceRange>,
101
+ ) -> Self {
102
+ Diagnostic::with_severity(DiagnosticSeverity::Unsupported, message, range)
103
+ }
104
+
105
+ /// Creates a new InvalidSyntax Diagnostic.
106
+ /// Additional locations can be added with the `.annotate()` function.
107
+ pub fn invalid_syntax<T: 'static + DiagnosticDisplay>(
108
+ message: T,
109
+ range: Option<SourceRange>,
110
+ ) -> Self {
111
+ Diagnostic::with_severity(DiagnosticSeverity::InvalidSyntax, message, range)
112
+ }
113
+
114
+ /// Creates a new InvalidReact Diagnostic.
115
+ /// Additional locations can be added with the `.annotate()` function.
116
+ pub fn invalid_react<T: 'static + DiagnosticDisplay>(
117
+ message: T,
118
+ range: Option<SourceRange>,
119
+ ) -> Self {
120
+ Diagnostic::with_severity(DiagnosticSeverity::InvalidReact, message, range)
121
+ }
122
+
123
+ /// Creates a new InvalidReact Diagnostic.
124
+ /// Additional locations can be added with the `.annotate()` function.
125
+ pub fn invariant<T: 'static + DiagnosticDisplay>(
126
+ message: T,
127
+ range: Option<SourceRange>,
128
+ ) -> Self {
129
+ Diagnostic::with_severity(DiagnosticSeverity::Invariant, message, range)
130
+ }
131
+
132
+ /// Annotates this error with an additional location and associated message.
133
+ pub fn annotate<T: 'static + DiagnosticDisplay>(
134
+ mut self,
135
+ message: T,
136
+ range: Option<SourceRange>,
137
+ ) -> Self {
138
+ self.0
139
+ .related_information
140
+ .push(DiagnosticRelatedInformation {
141
+ message: Box::new(message),
142
+ span: range.map(source_span_from_range),
143
+ });
144
+ self
145
+ }
146
+
147
+ pub fn message(&self) -> &impl DiagnosticDisplay {
148
+ &self.0.message
149
+ }
150
+
151
+ pub fn span(&self) -> Option<SourceSpan> {
152
+ self.0.span
153
+ }
154
+
155
+ pub fn get_data(&self) -> &[impl DiagnosticDisplay] {
156
+ &self.0.data
157
+ }
158
+
159
+ pub fn severity(&self) -> DiagnosticSeverity {
160
+ self.0.severity
161
+ }
162
+
163
+ pub fn related_information(&self) -> &[DiagnosticRelatedInformation] {
164
+ &self.0.related_information
165
+ }
166
+
167
+ pub fn print_without_source(&self) -> String {
168
+ let mut result = String::new();
169
+ writeln!(
170
+ result,
171
+ "{message}:{span:?}",
172
+ message = &self.0.message,
173
+ span = self.0.span
174
+ )
175
+ .unwrap();
176
+ if !self.0.related_information.is_empty() {
177
+ for (ix, related) in self.0.related_information.iter().enumerate() {
178
+ writeln!(
179
+ result,
180
+ "[related {ix}] {message}:{span:?}",
181
+ ix = ix + 1,
182
+ message = related.message,
183
+ span = related.span
184
+ )
185
+ .unwrap();
186
+ }
187
+ };
188
+ result
189
+ }
190
+}
191
+
192
+impl Display for Diagnostic {
193
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194
+ write!(f, "{}", self.0.message.to_string())
195
+ }
196
+}
197
+
198
+impl Error for Diagnostic {}
199
+
200
+impl miette::Diagnostic for Diagnostic {
201
+ fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
202
+ Some(Box::new(self.0.message.to_string()))
203
+ }
204
+
205
+ fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
206
+ let related_items = &self.0.related_information;
207
+ let mut spans: Vec<miette::LabeledSpan> = Vec::new();
208
+ for related in related_items {
209
+ if let Some(span) = related.span {
210
+ spans.push(miette::LabeledSpan::new_with_span(
211
+ Some(related.message.to_string()),
212
+ span,
213
+ ))
214
+ }
215
+ }
216
+ if spans.is_empty() {
217
+ if let Some(span) = self.0.span {
218
+ spans.push(miette::LabeledSpan::new_with_span(
219
+ Some(self.0.message.to_string()),
220
+ span,
221
+ ))
222
+ }
223
+ }
224
+ Some(Box::new(spans.into_iter()))
225
+ }
226
+}
227
+
228
+// Ensure Diagnostic is thread-safe
229
+assert_impl_all!(Diagnostic: Send, Sync);
230
+
231
+#[derive(Debug)]
232
+struct DiagnosticData {
233
+ /// Human readable error message.
234
+ message: Box<dyn DiagnosticDisplay>,
235
+
236
+ /// The primary location of this diagnostic.
237
+ span: Option<SourceSpan>,
238
+
239
+ /// Related diagnostic information, such as other definitions in the case of
240
+ /// a duplicate definition error.
241
+ related_information: Vec<DiagnosticRelatedInformation>,
242
+
243
+ severity: DiagnosticSeverity,
244
+
245
+ /// A list with data that can be passed to the code actions
246
+ /// `data` is used in the LSP protocol:
247
+ /// @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
248
+ data: Vec<Box<dyn DiagnosticDisplay>>,
249
+}
250
+
251
+/// Secondary locations attached to a diagnostic.
252
+#[derive(Debug)]
253
+pub struct DiagnosticRelatedInformation {
254
+ /// The message of this related diagnostic information.
255
+ pub message: Box<dyn DiagnosticDisplay>,
256
+
257
+ /// The location of this related diagnostic information.
258
+ pub span: Option<SourceSpan>,
259
+}
260
+
261
+/// Trait for diagnostic messages to allow structs that capture
262
+/// some data and can lazily convert it to a message.
263
+pub trait DiagnosticDisplay: Debug + Display + Send + Sync {}
264
+
265
+/// Automatically implement the trait if constraints are met, so that
266
+/// implementors don't need to.
267
+impl<T> DiagnosticDisplay for T where T: Debug + Display + Send + Sync {}
268
+
269
+impl From<Diagnostic> for Diagnostics {
270
+ fn from(diagnostic: Diagnostic) -> Self {
271
+ vec![diagnostic]
272
+ }
273
+}
274
+
275
+fn source_span_from_range(range: SourceRange) -> SourceSpan {
276
+ SourceSpan::new(
277
+ ByteOffset::from(range.start as usize - 1).into(),
278
+ ByteOffset::from((u32::from(range.end) - range.start) as usize).into(),
279
+ )
280
+}