diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs index 3a0a6d6164df..dcfab38f1078 100644 --- a/crates/swc/src/config/mod.rs +++ b/crates/swc/src/config/mod.rs @@ -357,6 +357,7 @@ impl Options { &program, source_type, &fm.src, + fm.start_pos, comments, options, ); diff --git a/crates/swc_ecma_react_compiler/examples/react_compiler.rs b/crates/swc_ecma_react_compiler/examples/react_compiler.rs index c5960f6ee0ee..a38db91010d4 100644 --- a/crates/swc_ecma_react_compiler/examples/react_compiler.rs +++ b/crates/swc_ecma_react_compiler/examples/react_compiler.rs @@ -8,7 +8,7 @@ use std::{env, path::Path}; -use swc_common::{comments::SingleThreadedComments, sync::Lrc, FileName, SourceMap}; +use swc_common::{comments::SingleThreadedComments, sync::Lrc, BytePos, FileName, SourceMap}; use swc_ecma_ast::{EsVersion, Program}; use swc_ecma_codegen::{text_writer::JsWriter, Emitter, Node}; use swc_ecma_parser::{parse_file_as_program, EsSyntax, Syntax, TsSyntax}; @@ -33,7 +33,8 @@ fn run() -> Result<(), String> { let source_text = std::fs::read_to_string(path) .map_err(|err| format!("failed to read {}: {err}", path.display()))?; - let (program, comments, source_type) = parse_program(path, &source_text)?; + let (program, comments, source_type, source_file_start_pos) = + parse_program(path, &source_text)?; let mut options = default_plugin_options(); options.filename = Some(path.display().to_string()); @@ -41,6 +42,7 @@ fn run() -> Result<(), String> { &program, source_type, &source_text, + source_file_start_pos, Some(&comments), options, ); @@ -55,7 +57,7 @@ fn run() -> Result<(), String> { fn parse_program( path: &Path, source_text: &str, -) -> Result<(Program, SingleThreadedComments, SourceType), String> { +) -> Result<(Program, SingleThreadedComments, SourceType, BytePos), String> { let cm = Lrc::new(SourceMap::default()); let fm = cm.new_source_file( Lrc::new(FileName::Real(path.to_path_buf())), @@ -85,7 +87,7 @@ fn parse_program( let program = program.map_err(|error| format!("failed to parse input: {}", error.kind().msg()))?; let source_type = SourceType::from_program(&program).with_typescript(is_typescript); - Ok((program, comments, source_type)) + Ok((program, comments, source_type, fm.start_pos)) } fn syntax_for_path(path: &Path) -> Syntax { diff --git a/crates/swc_ecma_react_compiler/src/convert_ast.rs b/crates/swc_ecma_react_compiler/src/convert_ast.rs index 2ad9cf9e7f2d..d9acc251b90a 100644 --- a/crates/swc_ecma_react_compiler/src/convert_ast.rs +++ b/crates/swc_ecma_react_compiler/src/convert_ast.rs @@ -19,7 +19,7 @@ use react_compiler_ast::{ use swc_common::{ comments::{Comment as SwcComment, CommentKind, SingleThreadedComments}, util::take::Take, - Span, Spanned, + BytePos, Span, Spanned, }; use swc_ecma_ast as swc; @@ -28,16 +28,17 @@ use crate::preserved_ast::PreservedAst; pub struct ConvertResult { pub file: File, pub preserved_ast: PreservedAst, - pub source_file_start_pos: swc_common::BytePos, + pub source_file_start_pos: BytePos, } /// Converts an SWC AST to the React compiler's Babel-compatible AST. pub fn convert_program( program: &swc::Program, source_text: &str, + source_file_start_pos: BytePos, comments: Option<&SingleThreadedComments>, ) -> ConvertResult { - let ctx = ConvertCtx::new(source_text); + let ctx = ConvertCtx::new(source_text, source_file_start_pos); let comments = convert_swc_comments(&ctx, comments); let mut ctx = ctx.with_comments(comments); let file = ctx.convert_program(program); @@ -45,19 +46,20 @@ pub fn convert_program( ConvertResult { file, preserved_ast: ctx.preserved_ast.into_inner(), - source_file_start_pos: program.span().lo, + source_file_start_pos, } } struct ConvertCtx<'a> { source_text: &'a str, + source_file_start_pos: BytePos, line_offsets: Vec, comments: Vec, preserved_ast: RefCell, } impl<'a> ConvertCtx<'a> { - fn new(source_text: &'a str) -> Self { + fn new(source_text: &'a str, source_file_start_pos: BytePos) -> Self { let mut line_offsets = vec![0u32]; for (i, ch) in source_text.char_indices() { let next = i + ch.len_utf8(); @@ -67,6 +69,7 @@ impl<'a> ConvertCtx<'a> { } Self { source_text, + source_file_start_pos, line_offsets, comments: Default::default(), preserved_ast: Default::default(), @@ -78,6 +81,9 @@ impl<'a> ConvertCtx<'a> { self } + /// `start`/`end`/`node_id` keep absolute SWC `BytePos` values so reverse + /// conversion and scope-key/rename logic can continue to use them directly. + /// `loc` remains Babel-style and file-relative. fn make_base_node(&self, span: Span) -> BaseNode { BaseNode { node_type: None, @@ -95,15 +101,17 @@ impl<'a> ConvertCtx<'a> { /// Converts a SWC span to a Babel-compatible position. /// - /// SWC `BytePos` values are byte offsets and 1-based. Base node offsets - /// stay 1-based so scope keys can use `span.lo` directly, while `loc` - /// follows Babel's 1-based lines and 0-based columns/indices. + /// SWC `BytePos` values are absolute and 1-based. `start`/`end`/`node_id` + /// keep these absolute offsets, while `loc` follows Babel's file-relative + /// 1-based lines and 0-based columns/indices. /// /// Assumption: the React Compiler does not receive the original source text /// from this bridge, so UTF-8 byte offsets are enough for `column` and /// `index`. If that changes, switch these offsets to UTF-16 code units. fn position(&self, offset: u32) -> Position { - let offset = offset.saturating_sub(1).min(self.source_text.len() as u32); + let offset = offset + .saturating_sub(self.source_file_start_pos.0) + .min(self.source_text.len() as u32); let line_idx = match self.line_offsets.binary_search(&offset) { Ok(idx) => idx, Err(idx) => idx.saturating_sub(1), @@ -153,7 +161,10 @@ impl<'a> ConvertCtx<'a> { .source_text .find('\n') .unwrap_or(self.source_text.len()) as u32; - let span = Span::new(swc_common::BytePos(1), swc_common::BytePos(1 + end)); + let span = Span::new( + self.source_file_start_pos, + self.source_file_start_pos + swc_common::BytePos(end), + ); InterpreterDirective { base: self.make_base_node(span), value: shebang.to_string(), diff --git a/crates/swc_ecma_react_compiler/src/convert_ast_reverse.rs b/crates/swc_ecma_react_compiler/src/convert_ast_reverse.rs index 77764bb0bb86..6090901aef21 100644 --- a/crates/swc_ecma_react_compiler/src/convert_ast_reverse.rs +++ b/crates/swc_ecma_react_compiler/src/convert_ast_reverse.rs @@ -109,8 +109,11 @@ impl ReverseCtx { } fn span_from_base(&self, base: &BaseNode) -> Span { - // `start` and `end` preserve absolute SWC `BytePos` values from the - // forward conversion, while `loc` indices are file-relative. + // Invariant with the forward converter: + // - `start`/`end` keep absolute SWC `BytePos` values. + // - `loc.*.index` values are Babel-style file-relative offsets. + // So the fallback path must rebase `loc.index` with + // `source_file_start_pos` to recover absolute SWC spans. match (base.start, base.end) { (Some(start), Some(end)) => Span::new(BytePos(start), BytePos(end)), (Some(start), None) => Span::new(BytePos(start), BytePos(start)), diff --git a/crates/swc_ecma_react_compiler/src/lib.rs b/crates/swc_ecma_react_compiler/src/lib.rs index 55f28d018a17..9bce072a00c5 100644 --- a/crates/swc_ecma_react_compiler/src/lib.rs +++ b/crates/swc_ecma_react_compiler/src/lib.rs @@ -28,7 +28,7 @@ pub use react_compiler::entrypoint::plugin_options::{ }; use react_compiler_hir::environment_config::EnvironmentConfig; pub use source_type::SourceType; -use swc_common::{comments::SingleThreadedComments, sync::Lrc, FileName}; +use swc_common::{comments::SingleThreadedComments, sync::Lrc, BytePos, FileName}; use swc_ecma_ast::Program; use crate::{convert_ast::ConvertResult, convert_scope::SemanticBuilder}; @@ -81,6 +81,7 @@ pub fn transform( program: &Program, source_type: SourceType, source_text: &str, + source_file_start_pos: BytePos, comments: Option<&SingleThreadedComments>, options: PluginOptions, ) -> TransformResult { @@ -98,7 +99,7 @@ pub fn transform( file, preserved_ast, source_file_start_pos, - } = convert_program(program, source_text, comments); + } = convert_program(program, source_text, source_file_start_pos, comments); let emit_success_error_diagnostics = options.no_emit; let result = react_compiler::entrypoint::program::compile_program(file, scope_info.clone(), options); @@ -138,9 +139,14 @@ pub(crate) fn transform_source( options: PluginOptions, ) -> TransformResult { match parse_source(source_text, syntax) { - Ok((program, comments, source_type)) => { - transform(&program, source_type, source_text, Some(&comments), options) - } + Ok((program, comments, source_type, source_file_start_pos)) => transform( + &program, + source_type, + source_text, + source_file_start_pos, + Some(&comments), + options, + ), Err(diagnostic) => TransformResult { program: None, diagnostics: vec![*diagnostic], @@ -159,12 +165,20 @@ pub fn lint( program: &Program, source_type: SourceType, source_text: &str, + source_file_start_pos: BytePos, comments: Option<&SingleThreadedComments>, options: PluginOptions, ) -> LintResult { let mut opts = options; opts.no_emit = true; - let result = transform(program, source_type, source_text, comments, opts); + let result = transform( + program, + source_type, + source_text, + source_file_start_pos, + comments, + opts, + ); LintResult { diagnostics: result.diagnostics, } @@ -179,9 +193,14 @@ pub fn lint_source( options: PluginOptions, ) -> LintResult { match parse_source(source_text, syntax) { - Ok((program, comments, source_type)) => { - lint(&program, source_type, source_text, Some(&comments), options) - } + Ok((program, comments, source_type, source_file_start_pos)) => lint( + &program, + source_type, + source_text, + source_file_start_pos, + Some(&comments), + options, + ), Err(diagnostic) => LintResult { diagnostics: vec![*diagnostic], }, @@ -191,7 +210,7 @@ pub fn lint_source( fn parse_source( source_text: &str, syntax: swc_ecma_parser::Syntax, -) -> Result<(Program, SingleThreadedComments, SourceType), Box> { +) -> Result<(Program, SingleThreadedComments, SourceType, BytePos), Box> { let cm = Lrc::new(swc_common::SourceMap::default()); let fm = cm.new_source_file(Lrc::new(FileName::Anon), source_text.to_string()); let comments = SingleThreadedComments::default(); @@ -206,7 +225,7 @@ fn parse_source( ) { Ok(program) => { let source_type = SourceType::from_program(&program).with_typescript(is_typescript); - Ok((program, comments, source_type)) + Ok((program, comments, source_type, fm.start_pos)) } Err(error) => Err(Box::new(DiagnosticMessage { severity: diagnostics::Severity::Error, diff --git a/crates/swc_ecma_react_compiler/src/tests/integration.rs b/crates/swc_ecma_react_compiler/src/tests/integration.rs index 11dc3911b086..d2e2d39bf94f 100644 --- a/crates/swc_ecma_react_compiler/src/tests/integration.rs +++ b/crates/swc_ecma_react_compiler/src/tests/integration.rs @@ -38,7 +38,7 @@ fn convert_program_to_swc(file: &File) -> swc_ecma_ast::Program { fn convert_module(module: &swc_ecma_ast::Module, source_text: &str) -> File { let program = swc_ecma_ast::Program::Module(module.clone()); - convert_program(&program, source_text, None).file + convert_program(&program, source_text, BytePos(1), None).file } fn assert_file_serializes_to_json(file: &File) { @@ -165,7 +165,7 @@ fn default_options() -> PluginOptions { fn convert_variable_declaration() { let source = "const x = 1;"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 1); assert!(matches!( &file.program.body[0], @@ -177,7 +177,7 @@ fn convert_variable_declaration() { fn convert_function_declaration() { let source = "function foo() { return 42; }"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 1); assert!(matches!( &file.program.body[0], @@ -189,7 +189,7 @@ fn convert_function_declaration() { fn convert_arrow_function_expression() { let source = "const f = (x) => x + 1;"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 1); assert!(matches!( &file.program.body[0], @@ -201,7 +201,7 @@ fn convert_arrow_function_expression() { fn convert_jsx_element() { let source = "const el =
hello
;"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 1); assert!(matches!( &file.program.body[0], @@ -213,7 +213,7 @@ fn convert_jsx_element() { fn convert_import_declaration() { let source = "import { useState } from 'react';"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 1); assert!(matches!( &file.program.body[0], @@ -225,7 +225,7 @@ fn convert_import_declaration() { fn convert_export_named_declaration() { let source = "export const x = 1;"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 1); assert!(matches!( &file.program.body[0], @@ -237,7 +237,7 @@ fn convert_export_named_declaration() { fn convert_export_default_declaration() { let source = "export default function App() { return
; }"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 1); assert!(matches!( &file.program.body[0], @@ -254,7 +254,7 @@ fn convert_multiple_statements() { export default App; "#; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.body.len(), 4); assert!(matches!( &file.program.body[0], @@ -278,7 +278,7 @@ fn convert_multiple_statements() { fn convert_directive() { let source = "'use strict';\nconst x = 1;"; let program = parse_program(source); - let file = convert_program(&program, source, None).file; + let file = convert_program(&program, source, BytePos(1), None).file; assert_eq!(file.program.directives.len(), 1); assert_eq!(file.program.body.len(), 1); } @@ -1767,7 +1767,7 @@ fn parse_ts_module(source: &str) -> swc_ecma_ast::Module { fn convert_ts_source(source: &str) -> crate::convert_ast::ConvertResult { let module = parse_ts_module(source); let program = swc_ecma_ast::Program::Module(module); - convert_program(&program, source, None) + convert_program(&program, source, BytePos(1), None) } fn round_trip_convert_result(result: crate::convert_ast::ConvertResult) -> swc_ecma_ast::Program { @@ -2004,6 +2004,7 @@ fn react_compiler_recovers_source_map_density_from_locations() { &program, SourceType::module(), source, + file.start_pos, None, default_options(), ); @@ -2056,9 +2057,220 @@ fn react_compiler_recovers_source_map_density_from_locations() { // Ensure we have more than one mapping per mapped line (helps catch regressions // that collapse many node spans to DUMMY_SP). assert!( - mappings.len() >= generated_lines.len() * 2, + mappings.len() * 2 >= generated_lines.len() * 3, "expected dense source map, got {} mappings across {} mapped lines", mappings.len(), generated_lines.len() ); } + +fn set_program_base_to_loc_only_index_zero(file: &mut File) { + file.program.base.start = None; + file.program.base.end = None; + let loc = file + .program + .base + .loc + .as_mut() + .expect("program base should include a location"); + loc.start.index = Some(0); + loc.end.index = Some(0); +} + +#[test] +fn reverse_loc_only_index_zero_recovers_file_start_for_leading_comment() { + let prefix = "\n\n// comment\n"; + let source = format!("{prefix}const value = 1;"); + let cm = Lrc::new(SourceMap::default()); + let fm = cm.new_source_file(Lrc::new(FileName::Anon), source.clone()); + let mut errors = vec![]; + let program = parse_file_as_program( + &fm, + Syntax::Es(EsSyntax::default()), + EsVersion::latest(), + None, + &mut errors, + ) + .expect("should parse"); + let first_token = fm.start_pos + BytePos(prefix.len() as u32); + assert!(first_token > fm.start_pos); + + let mut result = convert_program(&program, &source, fm.start_pos, None); + set_program_base_to_loc_only_index_zero(&mut result.file); + let round_tripped = round_trip_convert_result(result); + let recovered_span = match &round_tripped { + swc_ecma_ast::Program::Module(module) => module.span, + swc_ecma_ast::Program::Script(script) => script.span, + }; + + assert_eq!(recovered_span.lo, fm.start_pos); + assert_ne!(recovered_span.lo, first_token); +} + +#[test] +fn reverse_loc_only_index_zero_recovers_file_start_for_shebang() { + let prefix = "#!/usr/bin/env node\n"; + let source = format!("{prefix}const value = 1;"); + let cm = Lrc::new(SourceMap::default()); + let fm = cm.new_source_file(Lrc::new(FileName::Anon), source.clone()); + let mut errors = vec![]; + let program = parse_file_as_program( + &fm, + Syntax::Es(EsSyntax::default()), + EsVersion::latest(), + None, + &mut errors, + ) + .expect("should parse"); + let first_token = fm.start_pos + BytePos(prefix.len() as u32); + assert!(first_token > fm.start_pos); + + let mut result = convert_program(&program, &source, fm.start_pos, None); + set_program_base_to_loc_only_index_zero(&mut result.file); + let round_tripped = round_trip_convert_result(result); + let recovered_span = match &round_tripped { + swc_ecma_ast::Program::Module(module) => module.span, + swc_ecma_ast::Program::Script(script) => script.span, + }; + + assert_eq!(recovered_span.lo, fm.start_pos); + assert_ne!(recovered_span.lo, first_token); +} + +fn assert_loc_indices_are_file_relative(value: &serde_json::Value, source_file_start_pos: u64) { + if let Some(array) = value.as_array() { + for item in array { + assert_loc_indices_are_file_relative(item, source_file_start_pos); + } + return; + } + + let Some(object) = value.as_object() else { + return; + }; + + let loc = object.get("loc").and_then(serde_json::Value::as_object); + // `start`/`end` serialize the absolute SWC `BytePos` values from + // `BaseNode`, while `loc.*.index` is file-relative. + if let Some(start_index) = loc + .and_then(|loc| loc.get("start")) + .and_then(|start| start.get("index")) + .and_then(serde_json::Value::as_u64) + { + if let Some(start) = object.get("start").and_then(serde_json::Value::as_u64) { + assert_eq!(start, source_file_start_pos + start_index); + } + } + + if let Some(end_index) = loc + .and_then(|loc| loc.get("end")) + .and_then(|end| end.get("index")) + .and_then(serde_json::Value::as_u64) + { + if let Some(end) = object.get("end").and_then(serde_json::Value::as_u64) { + assert_eq!(end, source_file_start_pos + end_index); + } + } + + for child in object.values() { + assert_loc_indices_are_file_relative(child, source_file_start_pos); + } +} + +#[test] +fn convert_program_loc_round_trip_invariant_for_non_first_source_file() { + let first_source = "const first = 1;"; + let second_source = "\nconst second = first + 1;\nexport { second };"; + let cm = Lrc::new(SourceMap::default()); + let _first_file = cm.new_source_file(Lrc::new(FileName::Anon), first_source.to_string()); + let second_file = cm.new_source_file(Lrc::new(FileName::Anon), second_source.to_string()); + let mut errors = vec![]; + let program = parse_file_as_program( + &second_file, + Syntax::Es(EsSyntax::default()), + EsVersion::latest(), + None, + &mut errors, + ) + .expect("should parse"); + + let result = convert_program(&program, second_source, second_file.start_pos, None); + let json = serde_json::to_value(&result.file).expect("file should serialize"); + assert_loc_indices_are_file_relative(&json, result.source_file_start_pos.0 as u64); +} + +#[test] +fn transform_preserves_sourcemap_spans_for_second_file_in_shared_source_map() { + let first_source = "const first = 1;"; + let second_source = r#" + import { useMemo } from "react"; + export function App({ items }) { + const visible = useMemo(() => items.filter(Boolean), [items]); + return
{visible.length}
; + } + "#; + + let cm = Lrc::new(SourceMap::default()); + let _first_file = cm.new_source_file(Lrc::new(FileName::Anon), first_source.to_string()); + let second_file = cm.new_source_file(Lrc::new(FileName::Anon), second_source.to_string()); + let comments = swc_common::comments::SingleThreadedComments::default(); + let mut errors = vec![]; + let program = parse_file_as_program( + &second_file, + Syntax::Es(EsSyntax { + jsx: true, + ..Default::default() + }), + EsVersion::latest(), + Some(&comments), + &mut errors, + ) + .expect("should parse"); + + let result = crate::transform( + &program, + SourceType::module(), + second_source, + second_file.start_pos, + Some(&comments), + default_options(), + ); + assert!( + result.diagnostics.is_empty(), + "unexpected diagnostics: {:#?}", + result.diagnostics + ); + let compiled = result.program.expect("component should compile"); + + let mut code = Vec::new(); + let mut mappings = Vec::new(); + { + let writer = swc_ecma_codegen::text_writer::JsWriter::new( + cm.clone(), + "\n", + &mut code, + Some(&mut mappings), + ); + let mut emitter = swc_ecma_codegen::Emitter { + cfg: swc_ecma_codegen::Config::default(), + cm: cm.clone(), + comments: None, + wr: Box::new(writer), + }; + compiled + .emit_with(&mut emitter) + .expect("should emit transformed program"); + } + + assert!( + !mappings.is_empty(), + "expected source mappings for transformed output" + ); + assert!( + mappings + .iter() + .all(|(source_pos, _)| *source_pos >= second_file.start_pos + && *source_pos < second_file.end_pos), + "found source map positions outside the second file range" + ); +} diff --git a/crates/swc_ecma_react_compiler/tests/fixture.rs b/crates/swc_ecma_react_compiler/tests/fixture.rs index f9555fc35185..dd90b63a2ce4 100644 --- a/crates/swc_ecma_react_compiler/tests/fixture.rs +++ b/crates/swc_ecma_react_compiler/tests/fixture.rs @@ -61,7 +61,12 @@ fn read_syntax(input: &Path) -> Syntax { fn parse_program( input: &Path, cm: Lrc, -) -> (Program, SingleThreadedComments, SourceType) { +) -> ( + Program, + SingleThreadedComments, + SourceType, + swc_common::BytePos, +) { let fm = cm .load_file(input) .unwrap_or_else(|err| panic!("failed to load {}: {err}", input.display())); @@ -97,7 +102,7 @@ fn parse_program( }); let source_type = SourceType::from_program(&program).with_typescript(is_typescript); - (program, comments, source_type) + (program, comments, source_type, fm.start_pos) } fn emit_program(program: &Program, cm: Lrc) -> String { @@ -121,7 +126,7 @@ fn emit_program(program: &Program, cm: Lrc) -> String { fn transform_fixture(input: &Path, cm: Lrc) -> TransformResult { let source_text = read_to_string(input) .unwrap_or_else(|err| panic!("failed to read {}: {err}", input.display())); - let (program, comments, source_type) = parse_program(input, cm); + let (program, comments, source_type, source_file_start_pos) = parse_program(input, cm); let mut options = default_plugin_options(); options.filename = Some(input.display().to_string()); @@ -129,6 +134,7 @@ fn transform_fixture(input: &Path, cm: Lrc) -> TransformResult { &program, source_type, &source_text, + source_file_start_pos, Some(&comments), options, ) diff --git a/crates/swc_ecma_react_compiler/tests/fixture/compile-pass/ast-roundtrip/output/jsx.tsx b/crates/swc_ecma_react_compiler/tests/fixture/compile-pass/ast-roundtrip/output/jsx.tsx index 98cbe304882f..924e3e354c17 100644 --- a/crates/swc_ecma_react_compiler/tests/fixture/compile-pass/ast-roundtrip/output/jsx.tsx +++ b/crates/swc_ecma_react_compiler/tests/fixture/compile-pass/ast-roundtrip/output/jsx.tsx @@ -31,7 +31,7 @@ export function App(x: T): React.ReactElement { } let t4; if ($[4] !== x) { - t4 =
{t1}{t2}{t3}{dashboard.actions}
; + t4 =
x={x} title={`Status: ${Status.Ready}`}>{t1}{t2}{t3}{dashboard.actions}
; $[4] = x; $[5] = t4; } else {