1pub mod code_frame;
2
3use serde::{Serialize, Deserialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum ErrorCategory {
8 Hooks,
9 CapitalizedCalls,
10 StaticComponents,
11 UseMemo,
12 VoidUseMemo,
13 PreserveManualMemo,
14 MemoDependencies,
15 IncompatibleLibrary,
16 Immutability,
17 Globals,
18 Refs,
19 EffectDependencies,
20 EffectExhaustiveDependencies,
21 EffectSetState,
22 EffectDerivationsOfState,
23 ErrorBoundaries,
24 Purity,
25 RenderSetState,
26 Invariant,
27 Todo,
28 Syntax,
29 UnsupportedSyntax,
30 Config,
31 Gating,
32 Suppression,
33 FBT,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum ErrorSeverity {
39 Error,
40 Warning,
41 Hint,
42 Off,
43}
44
45impl ErrorCategory {
46 pub fn severity(&self) -> ErrorSeverity {
47 match self {
48 ErrorCategory::EffectDependencies
50 | ErrorCategory::IncompatibleLibrary
51 | ErrorCategory::PreserveManualMemo
52 | ErrorCategory::UnsupportedSyntax => ErrorSeverity::Warning,
53
54 ErrorCategory::Todo => ErrorSeverity::Hint,
56
57 _ => ErrorSeverity::Error,
59 }
60 }
61
62 pub fn logged_severity(&self) -> ErrorSeverity {
68 match self {
69 ErrorCategory::PreserveManualMemo => ErrorSeverity::Error,
70 _ => self.severity(),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize)]
77pub enum CompilerSuggestionOperation {
78 InsertBefore,
79 InsertAfter,
80 Remove,
81 Replace,
82}
83
84#[derive(Debug, Clone, Serialize)]
86pub struct CompilerSuggestion {
87 pub op: CompilerSuggestionOperation,
88 pub range: (usize, usize),
89 pub description: String,
90 pub text: Option<String>, }
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
97pub struct SourceLocation {
98 pub start: Position,
99 pub end: Position,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
103pub struct Position {
104 pub line: u32,
105 pub column: u32,
106 #[serde(default, skip_serializing)]
108 pub index: Option<u32>,
109}
110
111pub const GENERATED_SOURCE: Option<SourceLocation> = None;
113
114#[derive(Debug, Clone, Serialize)]
116pub enum CompilerDiagnosticDetail {
117 Error {
118 loc: Option<SourceLocation>,
119 message: Option<String>,
120 #[serde(skip)]
124 identifier_name: Option<String>,
125 },
126 Hint {
127 message: String,
128 },
129}
130
131#[derive(Debug, Clone)]
133pub struct CompilerDiagnostic {
134 pub category: ErrorCategory,
135 pub reason: String,
136 pub description: Option<String>,
137 pub details: Vec<CompilerDiagnosticDetail>,
138 pub suggestions: Option<Vec<CompilerSuggestion>>,
139}
140
141impl CompilerDiagnostic {
142 pub fn new(
143 category: ErrorCategory,
144 reason: impl Into<String>,
145 description: Option<String>,
146 ) -> Self {
147 Self {
148 category,
149 reason: reason.into(),
150 description,
151 details: Vec::new(),
152 suggestions: None,
153 }
154 }
155
156 pub fn severity(&self) -> ErrorSeverity {
157 self.category.severity()
158 }
159
160 pub fn logged_severity(&self) -> ErrorSeverity {
161 self.category.logged_severity()
162 }
163
164 pub fn with_detail(mut self, detail: CompilerDiagnosticDetail) -> Self {
165 self.details.push(detail);
166 self
167 }
168
169 pub fn todo(reason: impl Into<String>, loc: Option<SourceLocation>) -> Self {
171 let reason = reason.into();
172 let mut diag = Self::new(ErrorCategory::Todo, reason.clone(), None);
173 diag.details.push(CompilerDiagnosticDetail::Error {
174 loc,
175 message: Some(reason),
176 identifier_name: None,
177 });
178 diag
179 }
180
181 pub fn from_detail(detail: CompilerErrorDetail) -> Self {
183 Self::new(detail.category, detail.reason.clone(), detail.description.clone())
184 .with_detail(CompilerDiagnosticDetail::Error {
185 loc: detail.loc,
186 message: Some(detail.reason),
187 identifier_name: None,
188 })
189 }
190
191 pub fn primary_location(&self) -> Option<&SourceLocation> {
192 self.details.iter().find_map(|d| match d {
193 CompilerDiagnosticDetail::Error { loc, .. } => loc.as_ref(), _ => None,
195 })
196 }
197
198}
199
200#[derive(Debug, Clone, Serialize)]
202pub struct CompilerErrorDetail {
203 pub category: ErrorCategory,
204 pub reason: String,
205 pub description: Option<String>,
206 pub loc: Option<SourceLocation>,
207 pub suggestions: Option<Vec<CompilerSuggestion>>,
208}
209
210impl CompilerErrorDetail {
211 pub fn new(category: ErrorCategory, reason: impl Into<String>) -> Self {
212 Self {
213 category,
214 reason: reason.into(),
215 description: None,
216 loc: None,
217 suggestions: None,
218 }
219 }
220
221 pub fn with_description(mut self, description: impl Into<String>) -> Self {
222 self.description = Some(description.into());
223 self
224 }
225
226 pub fn with_loc(mut self, loc: Option<SourceLocation>) -> Self {
227 self.loc = loc;
228 self
229 }
230
231 pub fn severity(&self) -> ErrorSeverity {
232 self.category.severity()
233 }
234
235 pub fn logged_severity(&self) -> ErrorSeverity {
236 self.category.logged_severity()
237 }
238}
239
240#[derive(Debug, Clone)]
243pub struct CompilerError {
244 pub details: Vec<CompilerErrorOrDiagnostic>,
245 pub is_thrown: bool,
252}
253
254#[derive(Debug, Clone)]
256pub enum CompilerErrorOrDiagnostic {
257 Diagnostic(CompilerDiagnostic),
258 ErrorDetail(CompilerErrorDetail),
259}
260
261impl CompilerErrorOrDiagnostic {
262 pub fn severity(&self) -> ErrorSeverity {
263 match self {
264 Self::Diagnostic(d) => d.severity(),
265 Self::ErrorDetail(d) => d.severity(),
266 }
267 }
268
269 pub fn logged_severity(&self) -> ErrorSeverity {
270 match self {
271 Self::Diagnostic(d) => d.logged_severity(),
272 Self::ErrorDetail(d) => d.logged_severity(),
273 }
274 }
275}
276
277impl CompilerError {
278 pub fn new() -> Self {
279 Self {
280 details: Vec::new(),
281 is_thrown: true,
282 }
283 }
284
285 pub fn push_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {
286 if diagnostic.severity() != ErrorSeverity::Off {
287 self.details
288 .push(CompilerErrorOrDiagnostic::Diagnostic(diagnostic));
289 }
290 }
291
292 pub fn push_error_detail(&mut self, detail: CompilerErrorDetail) {
293 if detail.severity() != ErrorSeverity::Off {
294 self.details
295 .push(CompilerErrorOrDiagnostic::ErrorDetail(detail));
296 }
297 }
298
299 pub fn has_errors(&self) -> bool {
300 self.details
301 .iter()
302 .any(|d| d.severity() == ErrorSeverity::Error)
303 }
304
305 pub fn has_any_errors(&self) -> bool {
306 !self.details.is_empty()
307 }
308
309 pub fn has_invariant_errors(&self) -> bool {
311 self.details.iter().any(|d| {
312 let cat = match d {
313 CompilerErrorOrDiagnostic::Diagnostic(d) => d.category,
314 CompilerErrorOrDiagnostic::ErrorDetail(d) => d.category,
315 };
316 cat == ErrorCategory::Invariant
317 })
318 }
319
320 pub fn merge(&mut self, other: CompilerError) {
321 self.details.extend(other.details);
322 }
323
324 pub fn is_all_non_invariant(&self) -> bool {
328 self.details.iter().all(|d| {
329 let cat = match d {
330 CompilerErrorOrDiagnostic::Diagnostic(d) => d.category,
331 CompilerErrorOrDiagnostic::ErrorDetail(d) => d.category,
332 };
333 cat != ErrorCategory::Invariant
334 })
335 }
336
337 pub fn to_string_for_event(&self) -> String {
343 self.details
344 .iter()
345 .map(|d| {
346 let (category, reason, description, loc) = match d {
347 CompilerErrorOrDiagnostic::Diagnostic(d) => {
348 let loc = d.primary_location().cloned();
349 (d.category, &d.reason, &d.description, loc)
350 }
351 CompilerErrorOrDiagnostic::ErrorDetail(d) => {
352 (d.category, &d.reason, &d.description, d.loc)
353 }
354 };
355 let mut buf = format!("{}: {}", format_category_heading(category), reason);
356 if let Some(desc) = description {
357 buf.push_str(&format!(". {}.", desc));
358 }
359 if let Some(loc) = loc {
360 buf.push_str(&format!(" ({}:{})", loc.start.line, loc.start.column));
361 }
362 buf
363 })
364 .collect::<Vec<_>>()
365 .join("\n\n")
366 }
367}
368
369impl Default for CompilerError {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375impl From<CompilerError> for CompilerDiagnostic {
383 fn from(err: CompilerError) -> Self {
384 if let Some(first) = err.details.into_iter().next() {
385 match first {
386 CompilerErrorOrDiagnostic::Diagnostic(d) => d,
387 CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerDiagnostic::from_detail(d),
388 }
389 } else {
390 CompilerDiagnostic::new(
391 ErrorCategory::Invariant,
392 "Unknown compiler error",
393 None,
394 )
395 }
396 }
397}
398
399impl From<CompilerDiagnostic> for CompilerError {
400 fn from(diagnostic: CompilerDiagnostic) -> Self {
401 let mut error = CompilerError::new();
402 if diagnostic.category == ErrorCategory::Todo {
406 let loc = diagnostic.primary_location().cloned();
407 error.push_error_detail(CompilerErrorDetail {
408 category: diagnostic.category,
409 reason: diagnostic.reason,
410 description: diagnostic.description,
411 loc,
412 suggestions: diagnostic.suggestions,
413 });
414 } else {
415 error.push_diagnostic(diagnostic);
416 }
417 error
418 }
419}
420
421impl std::fmt::Display for CompilerError {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 for detail in &self.details {
424 match detail {
425 CompilerErrorOrDiagnostic::Diagnostic(d) => {
426 write!(f, "{}: {}", format_category_heading(d.category), d.reason)?;
427 if let Some(desc) = &d.description {
428 write!(f, ". {}.", desc)?;
429 }
430 }
431 CompilerErrorOrDiagnostic::ErrorDetail(d) => {
432 write!(f, "{}: {}", format_category_heading(d.category), d.reason)?;
433 if let Some(desc) = &d.description {
434 write!(f, ". {}.", desc)?;
435 }
436 }
437 }
438 writeln!(f)?;
439 }
440 Ok(())
441 }
442}
443
444impl std::error::Error for CompilerError {}
445
446pub fn format_category_heading(category: ErrorCategory) -> &'static str {
447 match category {
448 ErrorCategory::EffectDependencies
449 | ErrorCategory::IncompatibleLibrary
450 | ErrorCategory::PreserveManualMemo
451 | ErrorCategory::UnsupportedSyntax => "Compilation Skipped",
452 ErrorCategory::Invariant => "Invariant",
453 ErrorCategory::Todo => "Todo",
454 _ => "Error",
455 }
456}