diff --git a/crates/oxc_transformer/src/helpers/bindings.rs b/crates/oxc_transformer/src/helpers/bindings.rs index 575c8428e..3d93786f9 100644 --- a/crates/oxc_transformer/src/helpers/bindings.rs +++ b/crates/oxc_transformer/src/helpers/bindings.rs @@ -1,6 +1,11 @@ -use oxc_ast::ast::IdentifierReference; +use std::cell::Cell; + +use oxc_ast::ast::{BindingIdentifier, IdentifierReference}; use oxc_span::{Atom, SPAN}; -use oxc_syntax::{reference::ReferenceFlag, symbol::SymbolId}; +use oxc_syntax::{ + reference::ReferenceFlag, + symbol::{SymbolFlags, SymbolId}, +}; use oxc_traverse::TraverseCtx; /// Store for a created binding identifier @@ -11,6 +16,13 @@ pub struct BoundIdentifier<'a> { } impl<'a> BoundIdentifier<'a> { + /// Create `BoundIdentifier` for new binding in root scope + pub fn new_root_uid(name: &str, flags: SymbolFlags, ctx: &mut TraverseCtx<'a>) -> Self { + let symbol_id = ctx.generate_uid_in_root_scope(name, flags); + let name = ctx.ast.new_atom(&ctx.symbols().names[symbol_id]); + Self { name, symbol_id } + } + /// Create `IdentifierReference` referencing this binding which is read from /// in current scope pub fn create_read_reference(&self, ctx: &mut TraverseCtx) -> IdentifierReference<'a> { @@ -21,4 +33,13 @@ impl<'a> BoundIdentifier<'a> { ); IdentifierReference::new_read(SPAN, self.name.clone(), Some(reference_id)) } + + /// Create `BindingIdentifier` for this binding + pub fn create_binding_identifier(&self) -> BindingIdentifier<'a> { + BindingIdentifier { + span: SPAN, + name: self.name.clone(), + symbol_id: Cell::new(Some(self.symbol_id)), + } + } } diff --git a/crates/oxc_transformer/src/react/jsx.rs b/crates/oxc_transformer/src/react/jsx.rs index afca9709e..33223ecf3 100644 --- a/crates/oxc_transformer/src/react/jsx.rs +++ b/crates/oxc_transformer/src/react/jsx.rs @@ -44,7 +44,6 @@ pub struct ReactJsx<'a> { // States bindings: Bindings<'a>, - can_add_filename_statement: bool, } /// Bindings for different import options @@ -363,7 +362,6 @@ impl<'a> ReactJsx<'a> { jsx_self: ReactJsxSelf::new(Rc::clone(&ctx)), jsx_source: ReactJsxSource::new(ctx), bindings, - can_add_filename_statement: false, } } @@ -400,8 +398,8 @@ impl<'a> ReactJsx<'a> { impl<'a> ReactJsx<'a> { pub fn add_runtime_imports(&mut self, program: &mut Program<'a>) { if self.bindings.is_classic() { - if self.can_add_filename_statement { - program.body.insert(0, self.jsx_source.get_var_file_name_statement()); + if let Some(stmt) = self.jsx_source.get_var_file_name_statement() { + program.body.insert(0, stmt); } return; } @@ -413,8 +411,8 @@ impl<'a> ReactJsx<'a> { .rposition(|stmt| matches!(stmt, Statement::ImportDeclaration(_))) .map_or(0, |i| i + 1); - if self.can_add_filename_statement { - program.body.insert(index, self.jsx_source.get_var_file_name_statement()); + if let Some(stmt) = self.jsx_source.get_var_file_name_statement() { + program.body.insert(index, stmt); // If source type is module then we need to add the import statement after the var file name statement // Follow the same behavior as babel if !self.is_script() { @@ -599,10 +597,9 @@ impl<'a> ReactJsx<'a> { if let Some(span) = source_attr_span { self.jsx_source.report_error(span); } else { - self.can_add_filename_statement = true; let (line, column) = get_line_column(e.span().start, self.ctx.source_text); properties.push( - self.jsx_source.get_object_property_kind_for_jsx_plugin(line, column), + self.jsx_source.get_object_property_kind_for_jsx_plugin(line, column, ctx), ); } } @@ -655,9 +652,8 @@ impl<'a> ReactJsx<'a> { if let Some(span) = source_attr_span { self.jsx_source.report_error(span); } else { - self.can_add_filename_statement = true; let (line, column) = get_line_column(e.span().start, self.ctx.source_text); - let expr = self.jsx_source.get_source_object(line, column); + let expr = self.jsx_source.get_source_object(line, column, ctx); arguments.push(Argument::from(expr)); } } diff --git a/crates/oxc_transformer/src/react/jsx_source.rs b/crates/oxc_transformer/src/react/jsx_source.rs index 8ca4f9d3b..14ecd0945 100644 --- a/crates/oxc_transformer/src/react/jsx_source.rs +++ b/crates/oxc_transformer/src/react/jsx_source.rs @@ -1,14 +1,15 @@ use oxc_ast::ast::*; use oxc_diagnostics::OxcDiagnostic; use oxc_span::{Span, SPAN}; -use oxc_syntax::number::NumberBase; +use oxc_syntax::{number::NumberBase, symbol::SymbolFlags}; +use oxc_traverse::TraverseCtx; -use crate::context::Ctx; +use crate::{context::Ctx, helpers::bindings::BoundIdentifier}; use super::utils::get_line_column; const SOURCE: &str = "__source"; -const FILE_NAME_VAR: &str = "_jsxFileName"; +const FILE_NAME_VAR: &str = "jsxFileName"; /// [plugin-transform-react-jsx-source](https://babeljs.io/docs/babel-plugin-transform-react-jsx-source) /// @@ -20,26 +21,32 @@ const FILE_NAME_VAR: &str = "_jsxFileName"; /// Out: `` pub struct ReactJsxSource<'a> { ctx: Ctx<'a>, + filename_var: Option>, } impl<'a> ReactJsxSource<'a> { pub fn new(ctx: Ctx<'a>) -> Self { - Self { ctx } + Self { ctx, filename_var: None } } - pub fn transform_jsx_opening_element(&mut self, elem: &mut JSXOpeningElement<'a>) { - self.add_source_attribute(elem); + pub fn transform_jsx_opening_element( + &mut self, + elem: &mut JSXOpeningElement<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.add_source_attribute(elem, ctx); } pub fn get_object_property_kind_for_jsx_plugin( &mut self, line: usize, column: usize, + ctx: &mut TraverseCtx<'a>, ) -> ObjectPropertyKind<'a> { let kind = PropertyKind::Init; let ident = IdentifierName::new(SPAN, SOURCE.into()); let key = self.ctx.ast.property_key_identifier(ident); - let value = self.get_source_object(line, column); + let value = self.get_source_object(line, column, ctx); let obj = self.ctx.ast.object_property(SPAN, kind, key, value, None, false, false, false); ObjectPropertyKind::ObjectProperty(obj) } @@ -53,7 +60,11 @@ impl<'a> ReactJsxSource<'a> { impl<'a> ReactJsxSource<'a> { /// `` /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - fn add_source_attribute(&mut self, elem: &mut JSXOpeningElement<'a>) { + fn add_source_attribute( + &mut self, + elem: &mut JSXOpeningElement<'a>, + ctx: &mut TraverseCtx<'a>, + ) { // Check if `__source` attribute already exists for item in &elem.attributes { if let JSXAttributeItem::Attribute(attribute) = item { @@ -70,7 +81,7 @@ impl<'a> ReactJsxSource<'a> { self.ctx.ast.alloc(self.ctx.ast.jsx_identifier(SPAN, SOURCE.into())), ); let (line, column) = get_line_column(elem.span.start, self.ctx.source_text); - let object = self.get_source_object(line, column); + let object = self.get_source_object(line, column, ctx); let expr = self.ctx.ast.jsx_expression_container(SPAN, JSXExpression::from(object)); let value = JSXAttributeValue::ExpressionContainer(expr); let attribute_item = self.ctx.ast.jsx_attribute(SPAN, key, Some(value)); @@ -78,13 +89,18 @@ impl<'a> ReactJsxSource<'a> { } #[allow(clippy::cast_precision_loss)] - pub fn get_source_object(&mut self, line: usize, column: usize) -> Expression<'a> { + pub fn get_source_object( + &mut self, + line: usize, + column: usize, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { let kind = PropertyKind::Init; let filename = { let name = IdentifierName::new(SPAN, "fileName".into()); let key = self.ctx.ast.property_key_identifier(name); - let ident = self.ctx.ast.identifier_reference(SPAN, FILE_NAME_VAR); + let ident = self.get_filename_var(ctx).create_read_reference(ctx); let value = self.ctx.ast.identifier_reference_expression(ident); self.ctx.ast.object_property(SPAN, kind, key, value, None, false, false, false) }; @@ -122,10 +138,12 @@ impl<'a> ReactJsxSource<'a> { self.ctx.ast.object_expression(SPAN, properties, None) } - pub fn get_var_file_name_statement(&self) -> Statement<'a> { + pub fn get_var_file_name_statement(&mut self) -> Option> { + let filename_var = self.filename_var.as_ref()?; + let var_kind = VariableDeclarationKind::Var; let id = { - let ident = BindingIdentifier::new(SPAN, FILE_NAME_VAR.into()); + let ident = filename_var.create_binding_identifier(); let ident = self.ctx.ast.binding_pattern_identifier(ident); self.ctx.ast.binding_pattern(ident, None, false) }; @@ -136,6 +154,17 @@ impl<'a> ReactJsxSource<'a> { self.ctx.ast.new_vec_single(decl) }; let var_decl = self.ctx.ast.variable_declaration(SPAN, var_kind, decl, Modifiers::empty()); - Statement::VariableDeclaration(var_decl) + Some(Statement::VariableDeclaration(var_decl)) + } + + fn get_filename_var(&mut self, ctx: &mut TraverseCtx<'a>) -> BoundIdentifier<'a> { + if self.filename_var.is_none() { + self.filename_var = Some(BoundIdentifier::new_root_uid( + FILE_NAME_VAR, + SymbolFlags::FunctionScopedVariable, + ctx, + )); + } + self.filename_var.as_ref().unwrap().clone() } } diff --git a/crates/oxc_transformer/src/react/mod.rs b/crates/oxc_transformer/src/react/mod.rs index 0beacfc67..76cafb764 100644 --- a/crates/oxc_transformer/src/react/mod.rs +++ b/crates/oxc_transformer/src/react/mod.rs @@ -96,13 +96,13 @@ impl<'a> React<'a> { pub fn transform_jsx_opening_element( &mut self, elem: &mut JSXOpeningElement<'a>, - ctx: &TraverseCtx<'a>, + ctx: &mut TraverseCtx<'a>, ) { if self.jsx_self_plugin && self.jsx.jsx_self.can_add_self_attribute(ctx) { self.jsx.jsx_self.transform_jsx_opening_element(elem); } if self.jsx_source_plugin { - self.jsx.jsx_source.transform_jsx_opening_element(elem); + self.jsx.jsx_source.transform_jsx_opening_element(elem, ctx); } } }