Skip to content

Commit

Permalink
fix(transformer): use UID for JSX source filename var
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Jun 10, 2024
1 parent 2cc4913 commit 0a08dae
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 28 deletions.
20 changes: 18 additions & 2 deletions crates/oxc_transformer/src/helpers/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::cell::Cell;

use oxc_ast::ast::IdentifierReference;
use oxc_ast::ast::{BindingIdentifier, IdentifierReference};
use oxc_span::{Atom, Span, SPAN};
use oxc_syntax::{
reference::{ReferenceFlag, ReferenceId},
symbol::SymbolId,
symbol::{SymbolFlags, SymbolId},
};
use oxc_traverse::TraverseCtx;

Expand All @@ -16,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> {
Expand All @@ -26,6 +33,15 @@ impl<'a> BoundIdentifier<'a> {
);
create_read_identifier_reference(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)),
}
}
}

/// Create `IdentifierReference` which is read from
Expand Down
16 changes: 6 additions & 10 deletions crates/oxc_transformer/src/react/jsx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ pub struct ReactJsx<'a> {

// States
bindings: Bindings<'a>,
can_add_filename_statement: bool,
}

/// Bindings for different import options
Expand Down Expand Up @@ -366,7 +365,6 @@ impl<'a> ReactJsx<'a> {
jsx_self: ReactJsxSelf::new(Rc::clone(&ctx)),
jsx_source: ReactJsxSource::new(ctx),
bindings,
can_add_filename_statement: false,
}
}

Expand Down Expand Up @@ -403,8 +401,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;
}
Expand All @@ -416,8 +414,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() {
Expand Down Expand Up @@ -602,10 +600,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),
);
}
}
Expand Down Expand Up @@ -658,9 +655,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));
}
}
Expand Down
57 changes: 43 additions & 14 deletions crates/oxc_transformer/src/react/jsx_source.rs
Original file line number Diff line number Diff line change
@@ -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)
///
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -70,21 +81,26 @@ 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));
elem.attributes.push(JSXAttributeItem::Attribute(attribute_item));
}

#[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)
};
Expand Down Expand Up @@ -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)
};
Expand All @@ -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()
}
}
4 changes: 2 additions & 2 deletions crates/oxc_transformer/src/react/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

0 comments on commit 0a08dae

Please sign in to comment.