fix(transformer): use UID for JSX source filename var (#3612)

React JSX Source transform generates a top-level variable `_jsxFileName` containing file path.

Previously this var name was hard-coded. Generate a UID instead to avoid clash with existing var called `_jsxFileName`. Add this binding to scopes.
This commit is contained in:
overlookmotel 2024-06-11 06:41:13 +00:00
parent 60cbdece65
commit 0fb4c35083
4 changed files with 74 additions and 28 deletions

View file

@ -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)),
}
}
}

View file

@ -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));
}
}

View file

@ -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: `<sometag __source={ { fileName: 'this/file.js', lineNumber: 10, columnNumber: 1 } } />`
pub struct ReactJsxSource<'a> {
ctx: Ctx<'a>,
filename_var: Option<BoundIdentifier<'a>>,
}
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> {
/// `<sometag __source={ { fileName: 'this/file.js', lineNumber: 10, columnNumber: 1 } } />`
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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<Statement<'a>> {
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()
}
}

View file

@ -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);
}
}
}