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