Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
match self {
1pub mod code_frame;2pub mod js_string;34pub use js_string::JsString;56use serde::{Deserialize, Serialize};78/// Error categories matching the TS ErrorCategory enum9#[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}3839/// Error severity levels40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]41pub enum ErrorSeverity {42 Error,43 Warning,44 Hint,45 Off,46}4748impl ErrorCategory {49 pub fn severity(&self) -> ErrorSeverity {50 match self {51 // These map to "Compilation Skipped" (Warning severity)52 ErrorCategory::EffectDependencies53 | ErrorCategory::IncompatibleLibrary54 | ErrorCategory::PreserveManualMemo55 | ErrorCategory::UnsupportedSyntax => ErrorSeverity::Warning,5657 // Todo is Hint58 ErrorCategory::Todo => ErrorSeverity::Hint,5960 // Invariant and all others are Error severity61 _ => ErrorSeverity::Error,62 }63 }6465 /// The severity to use in logged output, matching the TS compiler's66 /// `getRuleForCategory()`. This may differ from the internal `severity()`67 /// used for panicThreshold logic. In particular, `PreserveManualMemo` is68 /// `Warning` internally (so it doesn't trigger panicThreshold throws) but69 /// `Error` in logged output (matching TS behavior).70 pub fn logged_severity(&self) -> ErrorSeverity {71 match self {72 ErrorCategory::PreserveManualMemo => ErrorSeverity::Error,73 _ => self.severity(),74 }75 }76}7778/// Suggestion operations for auto-fixes79#[derive(Debug, Clone, Serialize)]80pub enum CompilerSuggestionOperation {81 InsertBefore,82 InsertAfter,83 Remove,84 Replace,85}8687/// A compiler suggestion for fixing an error88#[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>, // None for Remove operations94}9596/// Source location (matches Babel's SourceLocation format)97/// This is the HIR source location, separate from AST's BaseNode location.98/// GeneratedSource is represented as None.99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]100pub struct SourceLocation {101 pub start: Position,102 pub end: Position,103}104105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]106pub struct Position {107 pub line: u32,108 pub column: u32,109 /// Byte offset in the source file. Preserved for logger event serialization.110 #[serde(default, skip_serializing)]111 pub index: Option<u32>,112}113114/// Sentinel value for generated/synthetic source locations115pub const GENERATED_SOURCE: Option<SourceLocation> = None;116117/// Detail for a diagnostic118#[derive(Debug, Clone, Serialize)]119pub enum CompilerDiagnosticDetail {120 Error {121 loc: Option<SourceLocation>,122 message: Option<String>,123 /// The identifier name from the AST source location, if this error124 /// points to an identifier node. Preserved for logger event serialization125 /// to match Babel's SourceLocation.identifierName field.126 #[serde(skip)]127 identifier_name: Option<String>,128 },129 Hint {130 message: String,131 },132}133134/// A single compiler diagnostic (new-style)135#[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}143144impl 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 }158159 pub fn severity(&self) -> ErrorSeverity {160 self.category.severity()161 }162163 pub fn logged_severity(&self) -> ErrorSeverity {164 self.category.logged_severity()165 }166167 pub fn with_detail(mut self, detail: CompilerDiagnosticDetail) -> Self {168 self.details.push(detail);169 self170 }171172 /// Create a Todo diagnostic (matches TS `CompilerError.throwTodo()`).173 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 diag182 }183184 /// Create a diagnostic from a CompilerErrorDetail.185 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 }197198 pub fn primary_location(&self) -> Option<&SourceLocation> {199 self.details.iter().find_map(|d| match d {200 CompilerDiagnosticDetail::Error { loc, .. } => loc.as_ref(), // identifier_name covered by ..201 _ => None,202 })203 }204}205206/// Legacy-style error detail (matches CompilerErrorDetail in TS)207#[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}215216impl 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 }226227 pub fn with_description(mut self, description: impl Into<String>) -> Self {228 self.description = Some(description.into());229 self230 }231232 pub fn with_loc(mut self, loc: Option<SourceLocation>) -> Self {233 self.loc = loc;234 self235 }236237 pub fn severity(&self) -> ErrorSeverity {238 self.category.severity()239 }240241 pub fn logged_severity(&self) -> ErrorSeverity {242 self.category.logged_severity()243 }244}245246/// Aggregate compiler error - can contain multiple diagnostics.247/// This is the main error type thrown/returned by the compiler.248#[derive(Debug, Clone)]249pub struct CompilerError {250 pub details: Vec<CompilerErrorOrDiagnostic>,251 /// When false, this error was accumulated on the Environment via252 /// `record_error()` / `record_diagnostic()` and returned at the end253 /// of the pipeline. In TS, `CompileUnexpectedThrow` is only emitted254 /// for errors that are **thrown** (not accumulated). Defaults to `true`255 /// because errors created directly (e.g., via `?` from a pass) are256 /// analogous to thrown errors in the TS code.257 pub is_thrown: bool,258}259260/// Either a new-style diagnostic or legacy error detail261#[derive(Debug, Clone)]262pub enum CompilerErrorOrDiagnostic {263 Diagnostic(CompilerDiagnostic),264 ErrorDetail(CompilerErrorDetail),265}266267impl CompilerErrorOrDiagnostic {268 pub fn severity(&self) -> ErrorSeverity {269 match self {270 Self::Diagnostic(d) => d.severity(),271 Self::ErrorDetail(d) => d.severity(),272 }273 }274275 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}282283impl CompilerError {284 pub fn new() -> Self {285 Self {286 details: Vec::new(),287 is_thrown: true,288 }289 }290291 pub fn push_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {292 if diagnostic.severity() != ErrorSeverity::Off {293 self.details294 .push(CompilerErrorOrDiagnostic::Diagnostic(diagnostic));295 }296 }297298 pub fn push_error_detail(&mut self, detail: CompilerErrorDetail) {299 if detail.severity() != ErrorSeverity::Off {300 self.details301 .push(CompilerErrorOrDiagnostic::ErrorDetail(detail));302 }303 }304305 pub fn has_errors(&self) -> bool {306 self.details307 .iter()308 .any(|d| d.severity() == ErrorSeverity::Error)309 }310311 pub fn has_any_errors(&self) -> bool {312 !self.details.is_empty()313 }314315 /// Check if any error detail has Invariant category.316 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::Invariant323 })324 }325326 pub fn merge(&mut self, other: CompilerError) {327 self.details.extend(other.details);328 }329330 /// Check if all error details are non-invariant.331 /// In TS, this is used to determine if an error thrown during compilation332 /// should be logged as CompileUnexpectedThrow.333 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::Invariant340 })341 }342343 /// Format as a string matching the TS `CompilerError.toString()` output.344 /// Used for the `data` field of `CompileUnexpectedThrow` events.345 ///346 /// Format per detail: `"Category: reason. Description. (line:column)"`347 /// Multiple details are joined with `"\n\n"`.348 pub fn to_string_for_event(&self) -> String {349 self.details350 .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 buf369 })370 .collect::<Vec<_>>()371 .join("\n\n")372 }373}374375impl Default for CompilerError {376 fn default() -> Self {377 Self::new()378 }379}380381/// Allow `?` to convert a `CompilerError` into a `CompilerDiagnostic`382/// when the enclosing function returns `Result<T, CompilerDiagnostic>`.383///384/// This typically happens when `record_error()` returns `Err(CompilerError)`385/// for an Invariant error, and the calling function already returns386/// `Result<T, CompilerDiagnostic>`. The conversion extracts the first387/// error detail from the aggregate error.388impl 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}400401impl From<CompilerDiagnostic> for CompilerError {402 fn from(diagnostic: CompilerDiagnostic) -> Self {403 let mut error = CompilerError::new();404 // Todo diagnostics should produce ErrorDetail (flat loc format), matching405 // the TS behavior where CompilerError.throwTodo() creates a CompilerErrorDetail406 // with loc directly on it, not a CompilerDiagnostic with sub-details.407 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 error420 }421}422423impl 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}445446impl std::error::Error for CompilerError {}447448pub fn format_category_heading(category: ErrorCategory) -> &'static str {449 match category {450 ErrorCategory::EffectDependencies451 | ErrorCategory::IncompatibleLibrary452 | ErrorCategory::PreserveManualMemo453 | ErrorCategory::UnsupportedSyntax => "Compilation Skipped",454 ErrorCategory::Invariant => "Invariant",455 ErrorCategory::Todo => "Todo",456 _ => "Error",457 }458}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.