Skip to main content

react_compiler/entrypoint/
compile_result.rs

1use react_compiler_ast::expressions::Identifier as AstIdentifier;
2use react_compiler_ast::patterns::PatternLike;
3use react_compiler_ast::statements::BlockStatement;
4use react_compiler_diagnostics::SourceLocation;
5use react_compiler_hir::ReactFunctionType;
6use serde::Serialize;
7
8use crate::timing::TimingEntry;
9
10/// Source location with index and filename fields for logger event serialization.
11/// Matches the Babel SourceLocation format that the TS compiler emits in logger events.
12#[derive(Debug, Clone, Serialize)]
13pub struct LoggerSourceLocation {
14    pub start: LoggerPosition,
15    pub end: LoggerPosition,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub filename: Option<String>,
18    #[serde(rename = "identifierName", skip_serializing_if = "Option::is_none")]
19    pub identifier_name: Option<String>,
20}
21
22#[derive(Debug, Clone, Serialize)]
23pub struct LoggerPosition {
24    pub line: u32,
25    pub column: u32,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub index: Option<u32>,
28}
29
30impl LoggerSourceLocation {
31    /// Create from a diagnostics SourceLocation, adding index and filename.
32    pub fn from_loc(loc: &SourceLocation, filename: Option<&str>, start_index: Option<u32>, end_index: Option<u32>) -> Self {
33        Self {
34            start: LoggerPosition {
35                line: loc.start.line,
36                column: loc.start.column,
37                index: start_index,
38            },
39            end: LoggerPosition {
40                line: loc.end.line,
41                column: loc.end.column,
42                index: end_index,
43            },
44            filename: filename.map(|s| s.to_string()),
45            identifier_name: None,
46        }
47    }
48
49    /// Create from a diagnostics SourceLocation without index or filename.
50    pub fn from_loc_simple(loc: &SourceLocation) -> Self {
51        Self {
52            start: LoggerPosition {
53                line: loc.start.line,
54                column: loc.start.column,
55                index: None,
56            },
57            end: LoggerPosition {
58                line: loc.end.line,
59                column: loc.end.column,
60                index: None,
61            },
62            filename: None,
63            identifier_name: None,
64        }
65    }
66}
67
68/// A variable rename from lowering, serialized for the JS shim.
69#[derive(Debug, Clone, Serialize)]
70pub struct BindingRenameInfo {
71    pub original: String,
72    pub renamed: String,
73    #[serde(rename = "declarationStart")]
74    pub declaration_start: u32,
75}
76
77/// Main result type returned by the compile function.
78/// Serialized to JSON and returned to the JS shim.
79#[derive(Debug, Serialize)]
80#[serde(tag = "kind", rename_all = "lowercase")]
81pub enum CompileResult {
82    /// Compilation succeeded (or no functions needed compilation).
83    /// `ast` is None if no changes were made to the program. The compiled Babel
84    /// AST is returned by value so in-process Rust consumers (the oxc/swc
85    /// front-ends) use it directly instead of round-tripping through JSON.
86    /// `CompileResult` still derives `Serialize`, so a JS consumer can serialize
87    /// the whole result as before.
88    Success {
89        ast: Option<react_compiler_ast::File>,
90        events: Vec<LoggerEvent>,
91        /// Unified ordered log interleaving events and debug entries.
92        /// Items appear in the order they were emitted during compilation.
93        /// The JS side uses this as the single source of truth (preferred over
94        /// separate events/debugLogs arrays).
95        #[serde(rename = "orderedLog", skip_serializing_if = "Vec::is_empty")]
96        ordered_log: Vec<OrderedLogItem>,
97        /// Variable renames from lowering, for applying back to the Babel AST.
98        /// Each entry maps an original binding name to its renamed version,
99        /// identified by the binding's declaration start position in the source.
100        #[serde(skip_serializing_if = "Vec::is_empty")]
101        renames: Vec<BindingRenameInfo>,
102        /// Timing data for profiling. Only populated when __profiling is enabled.
103        #[serde(skip_serializing_if = "Vec::is_empty")]
104        timing: Vec<TimingEntry>,
105    },
106    /// A fatal error occurred and panicThreshold dictates it should throw.
107    Error {
108        error: CompilerErrorInfo,
109        events: Vec<LoggerEvent>,
110        #[serde(rename = "orderedLog", skip_serializing_if = "Vec::is_empty")]
111        ordered_log: Vec<OrderedLogItem>,
112        /// Timing data for profiling. Only populated when __profiling is enabled.
113        #[serde(skip_serializing_if = "Vec::is_empty")]
114        timing: Vec<TimingEntry>,
115    },
116}
117
118/// An item in the ordered log, which can be either a logger event or a debug entry.
119#[derive(Debug, Clone, Serialize)]
120#[serde(tag = "type", rename_all = "camelCase")]
121pub enum OrderedLogItem {
122    Event { event: LoggerEvent },
123    Debug { entry: DebugLogEntry },
124}
125
126/// Structured error information for the JS shim.
127#[derive(Debug, Clone, Serialize)]
128pub struct CompilerErrorInfo {
129    pub reason: String,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub description: Option<String>,
132    pub details: Vec<CompilerErrorDetailInfo>,
133    /// When set, the JS shim should throw an Error with this exact message
134    /// instead of formatting through formatCompilerError(). This is used
135    /// for simulated unknown exceptions (throwUnknownException__testonly)
136    /// which in the TS compiler are plain Error objects, not CompilerErrors.
137    #[serde(rename = "rawMessage", skip_serializing_if = "Option::is_none")]
138    pub raw_message: Option<String>,
139    /// Pre-formatted error message produced by Rust, matching the JS
140    /// formatCompilerError() output. When present, the JS shim uses this
141    /// directly instead of calling formatCompilerError() on the JS side.
142    #[serde(rename = "formattedMessage", skip_serializing_if = "Option::is_none")]
143    pub formatted_message: Option<String>,
144}
145
146/// Serializable error detail — flat plain object matching the TS
147/// `formatDetailForLogging()` output. All fields are direct properties.
148#[derive(Debug, Clone, Serialize)]
149pub struct CompilerErrorDetailInfo {
150    pub category: String,
151    pub reason: String,
152    pub description: Option<String>,
153    pub severity: String,
154    pub suggestions: Option<Vec<LoggerSuggestionInfo>>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub details: Option<Vec<CompilerErrorItemInfo>>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub loc: Option<LoggerSourceLocation>,
159}
160
161/// Serializable suggestion info for logger events.
162#[derive(Debug, Clone, Serialize)]
163pub struct LoggerSuggestionInfo {
164    pub description: String,
165    pub op: LoggerSuggestionOp,
166    pub range: (usize, usize),
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub text: Option<String>,
169}
170
171/// Numeric enum matching TS `CompilerSuggestionOperation`.
172#[derive(Debug, Clone, Copy)]
173pub enum LoggerSuggestionOp {
174    InsertBefore = 0,
175    InsertAfter = 1,
176    Remove = 2,
177    Replace = 3,
178}
179
180impl serde::Serialize for LoggerSuggestionOp {
181    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
182        serializer.serialize_u8(*self as u8)
183    }
184}
185
186/// Individual error or hint item within a CompilerErrorDetailInfo.
187#[derive(Debug, Clone, Serialize)]
188pub struct CompilerErrorItemInfo {
189    pub kind: String,
190    pub loc: Option<LoggerSourceLocation>,
191    /// Serialized as `null` when None (not omitted), matching TS behavior.
192    pub message: Option<String>,
193}
194
195/// Debug log entry for debugLogIRs support.
196/// Currently only supports the 'debug' variant (string values).
197#[derive(Debug, Clone, Serialize)]
198pub struct DebugLogEntry {
199    pub kind: &'static str,
200    pub name: String,
201    pub value: String,
202}
203
204impl DebugLogEntry {
205    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
206        Self {
207            kind: "debug",
208            name: name.into(),
209            value: value.into(),
210        }
211    }
212}
213
214/// Codegen output for a single compiled function.
215/// Carries the generated AST fields needed to replace the original function.
216#[derive(Debug, Clone)]
217pub struct CodegenFunction {
218    pub loc: Option<SourceLocation>,
219    pub id: Option<AstIdentifier>,
220    pub name_hint: Option<String>,
221    pub params: Vec<PatternLike>,
222    pub body: BlockStatement,
223    pub generator: bool,
224    pub is_async: bool,
225    pub memo_slots_used: u32,
226    pub memo_blocks: u32,
227    pub memo_values: u32,
228    pub pruned_memo_blocks: u32,
229    pub pruned_memo_values: u32,
230    pub outlined: Vec<OutlinedFunction>,
231}
232
233/// An outlined function extracted during compilation.
234#[derive(Debug, Clone)]
235pub struct OutlinedFunction {
236    pub func: CodegenFunction,
237    pub fn_type: Option<ReactFunctionType>,
238}
239
240/// Logger events emitted during compilation.
241/// These are returned to JS for the logger callback.
242#[derive(Debug, Clone, Serialize)]
243#[serde(tag = "kind")]
244pub enum LoggerEvent {
245    CompileSuccess {
246        #[serde(rename = "fnLoc")]
247        fn_loc: Option<LoggerSourceLocation>,
248        #[serde(rename = "fnName")]
249        fn_name: Option<String>,
250        #[serde(rename = "memoSlots")]
251        memo_slots: u32,
252        #[serde(rename = "memoBlocks")]
253        memo_blocks: u32,
254        #[serde(rename = "memoValues")]
255        memo_values: u32,
256        #[serde(rename = "prunedMemoBlocks")]
257        pruned_memo_blocks: u32,
258        #[serde(rename = "prunedMemoValues")]
259        pruned_memo_values: u32,
260    },
261    CompileError {
262        detail: CompilerErrorDetailInfo,
263        #[serde(rename = "fnLoc")]
264        fn_loc: Option<LoggerSourceLocation>,
265    },
266    /// Same as CompileError but serializes fnLoc before detail (matching TS program.ts output)
267    #[serde(rename = "CompileError")]
268    CompileErrorWithLoc {
269        #[serde(rename = "fnLoc")]
270        fn_loc: LoggerSourceLocation,
271        detail: CompilerErrorDetailInfo,
272    },
273    CompileSkip {
274        #[serde(rename = "fnLoc")]
275        fn_loc: Option<LoggerSourceLocation>,
276        reason: String,
277        #[serde(skip_serializing_if = "Option::is_none")]
278        loc: Option<LoggerSourceLocation>,
279    },
280    CompileUnexpectedThrow {
281        #[serde(rename = "fnLoc")]
282        fn_loc: Option<LoggerSourceLocation>,
283        data: String,
284    },
285    PipelineError {
286        #[serde(rename = "fnLoc")]
287        fn_loc: Option<LoggerSourceLocation>,
288        data: String,
289    },
290}