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