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