compiler/crates/react_compiler_diagnostics/src/lib.rs RUST 459 lines View on github.com → Search inside
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}

Code quality findings 3

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.
info correctness match-wildcard
match self {
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.
info correctness match-wildcard
self.details.iter().find_map(|d| match d {
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.
info correctness match-wildcard
match category {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.