Skip to content

Commit

Permalink
Implement transform-react-display-name with bottom-up lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed May 3, 2024
1 parent 3772ce2 commit d8eba8e
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 95 deletions.
23 changes: 2 additions & 21 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x0_typescript.transform_binding_pattern(pat);
}

fn enter_call_expression(&mut self, expr: &mut CallExpression<'a>, _ctx: &TraverseCtx<'a>) {
fn enter_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &TraverseCtx<'a>) {
self.x0_typescript.transform_call_expression(expr);
self.x1_react.transform_call_expression(expr, ctx);
}

fn enter_class(&mut self, class: &mut Class<'a>, _ctx: &TraverseCtx<'a>) {
Expand All @@ -135,14 +136,6 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x0_typescript.transform_class_body(body);
}

fn enter_export_default_declaration(
&mut self,
decl: &mut ExportDefaultDeclaration<'a>,
_ctx: &TraverseCtx<'a>,
) {
self.x1_react.transform_export_default_declaration(decl);
}

fn enter_export_named_declaration(
&mut self,
decl: &mut ExportNamedDeclaration<'a>,
Expand Down Expand Up @@ -194,10 +187,6 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x0_typescript.transform_new_expression(expr);
}

fn enter_object_property(&mut self, prop: &mut ObjectProperty<'a>, _ctx: &TraverseCtx<'a>) {
self.x1_react.transform_object_property(prop);
}

fn enter_property_definition(
&mut self,
def: &mut PropertyDefinition<'a>,
Expand All @@ -219,14 +208,6 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x0_typescript.transform_tagged_template_expression(expr);
}

fn enter_variable_declarator(
&mut self,
declarator: &mut VariableDeclarator<'a>,
_ctx: &TraverseCtx<'a>,
) {
self.x1_react.transform_variable_declarator(declarator);
}

fn enter_identifier_reference(
&mut self,
ident: &mut IdentifierReference<'a>,
Expand Down
105 changes: 55 additions & 50 deletions crates/oxc_transformer/src/react/display_name/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::rc::Rc;

use oxc_allocator::Box;
use oxc_ast::ast::*;
use oxc_ast::{
ast::*,
traverse::{Ancestor, FinderRet, TraverseCtx},
};
use oxc_span::{Atom, SPAN};

use crate::context::Ctx;
Expand All @@ -14,10 +17,6 @@ use crate::context::Ctx;
///
/// In: `var bar = createReactClass({});`
/// Out: `var bar = createReactClass({ displayName: "bar" });`
///
/// NOTE: The current implementation uses the top-down approach on `AssignmentExpression`, `VariableDeclaration`,
/// but can be rewritten with a bottom-up approach.
/// See <https://github.com/babel/babel/blob/08b0472069cd207f043dd40a4d157addfdd36011/packages/babel-plugin-transform-react-display-name/src/index.ts#L88-L98>
pub struct ReactDisplayName<'a> {
ctx: Ctx<'a>,
}
Expand All @@ -30,65 +29,71 @@ impl<'a> ReactDisplayName<'a> {

// Transforms
impl<'a> ReactDisplayName<'a> {
/// `foo = React.createClass({})`
pub fn transform_assignment_expression(&self, assign_expr: &mut AssignmentExpression<'a>) {
let Some(obj_expr) = Self::get_object_from_create_class(&mut assign_expr.right) else {
pub fn transform_call_expression(
&self,
call_expr: &mut CallExpression<'a>,
ctx: &TraverseCtx<'a>,
) {
let Some(obj_expr) = Self::get_object_from_create_class(call_expr) else {
return;
};
let name = match &assign_expr.left {
AssignmentTarget::AssignmentTargetIdentifier(ident) => ident.name.clone(),
target => {
if let Some(target) = target.as_member_expression() {
if let Some(name) = target.static_property_name() {
self.ctx.ast.new_atom(name)

let name = ctx.find_ancestor(|ancestor| {
match ancestor {
// `foo = React.createClass({})`
Ancestor::AssignmentExpressionRight(assign_expr) => match &assign_expr.left() {
AssignmentTarget::AssignmentTargetIdentifier(ident) => {
FinderRet::Found(ident.name.clone())
}
target => {
if let Some(target) = target.as_member_expression() {
if let Some(name) = target.static_property_name() {
FinderRet::Found(ctx.ast.new_atom(name))
} else {
FinderRet::Stop
}
} else {
FinderRet::Stop
}
}
},
// `let foo = React.createClass({})`
Ancestor::VariableDeclaratorInit(declarator) => match &declarator.id().kind {
BindingPatternKind::BindingIdentifier(ident) => {
FinderRet::Found(ident.name.clone())
}
_ => FinderRet::Stop,
},
// `{foo: React.createClass({})}`
Ancestor::ObjectPropertyValue(prop) => {
if let Some(name) = prop.key().static_name() {
FinderRet::Found(ctx.ast.new_atom(&name))
} else {
return;
FinderRet::Stop
}
} else {
return;
}
// `export default React.createClass({})`
// Uses the current file name as the display name.
Ancestor::ExportDefaultDeclarationDeclaration(_) => {
FinderRet::Found(ctx.ast.new_atom(&self.ctx.filename))
}
// Stop crawling up when hit a statement
_ if ancestor.is_statement() => FinderRet::Stop,
_ => FinderRet::Continue,
}
};
self.add_display_name(obj_expr, name);
}

/// `let foo = React.createClass({})`
pub fn transform_variable_declarator(&self, declarator: &mut VariableDeclarator<'a>) {
let Some(init_expr) = declarator.init.as_mut() else { return };
let Some(obj_expr) = Self::get_object_from_create_class(init_expr) else {
return;
};
let name = match &declarator.id.kind {
BindingPatternKind::BindingIdentifier(ident) => ident.name.clone(),
_ => return,
};
self.add_display_name(obj_expr, name);
}

/// `{foo: React.createClass({})}`
pub fn transform_object_property(&self, prop: &mut ObjectProperty<'a>) {
let Some(obj_expr) = Self::get_object_from_create_class(&mut prop.value) else { return };
let Some(name) = prop.key.static_name() else { return };
let name = self.ctx.ast.new_atom(&name);
self.add_display_name(obj_expr, name);
}
});

/// `export default React.createClass({})`
/// Uses the current file name as the display name.
pub fn transform_export_default_declaration(&self, decl: &mut ExportDefaultDeclaration<'a>) {
let Some(expr) = decl.declaration.as_expression_mut() else { return };
let Some(obj_expr) = Self::get_object_from_create_class(expr) else { return };
let name = self.ctx.ast.new_atom(&self.ctx.filename);
self.add_display_name(obj_expr, name);
if let Some(name) = name {
self.add_display_name(obj_expr, name);
}
}
}

impl<'a> ReactDisplayName<'a> {
/// Get the object from `React.createClass({})` or `createReactClass({})`
fn get_object_from_create_class<'b>(
e: &'b mut Expression<'a>,
call_expr: &'b mut CallExpression<'a>,
) -> Option<&'b mut Box<'a, ObjectExpression<'a>>> {
let Expression::CallExpression(call_expr) = e else { return None };
if match &call_expr.callee {
callee @ match_member_expression!(Expression) => {
!callee.to_member_expression().is_specific_member_access("React", "createClass")
Expand Down
27 changes: 7 additions & 20 deletions crates/oxc_transformer/src/react/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod utils;

use std::rc::Rc;

use oxc_ast::ast::*;
use oxc_ast::{ast::*, traverse::TraverseCtx};

use crate::context::Ctx;

Expand Down Expand Up @@ -58,11 +58,6 @@ impl<'a> React<'a> {

pub fn transform_expression(&mut self, expr: &mut Expression<'a>) {
match expr {
Expression::AssignmentExpression(e) => {
if self.options.display_name_plugin {
self.display_name.transform_assignment_expression(e);
}
}
Expression::JSXElement(e) => {
if self.options.is_jsx_plugin_enabled() {
*expr = self.jsx.transform_jsx_element(e);
Expand All @@ -77,21 +72,13 @@ impl<'a> React<'a> {
}
}

pub fn transform_variable_declarator(&self, declarator: &mut VariableDeclarator<'a>) {
if self.options.display_name_plugin {
self.display_name.transform_variable_declarator(declarator);
}
}

pub fn transform_object_property(&self, prop: &mut ObjectProperty<'a>) {
if self.options.display_name_plugin {
self.display_name.transform_object_property(prop);
}
}

pub fn transform_export_default_declaration(&self, decl: &mut ExportDefaultDeclaration<'a>) {
pub fn transform_call_expression(
&self,
call_expr: &mut CallExpression<'a>,
ctx: &TraverseCtx<'a>,
) {
if self.options.display_name_plugin {
self.display_name.transform_export_default_declaration(decl);
self.display_name.transform_call_expression(call_expr, ctx);
}
}

Expand Down
6 changes: 2 additions & 4 deletions tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Passed: 294/363
Passed: 295/363

# All Passed:
* babel-plugin-transform-react-display-name
* babel-plugin-transform-react-jsx-source


Expand Down Expand Up @@ -68,9 +69,6 @@ Passed: 294/363
* autoImport/complicated-scope-module/input.js
* react-automatic/should-throw-when-filter-is-specified/input.js

# babel-plugin-transform-react-display-name (15/16)
* display-name/nested/input.js

# babel-plugin-transform-react-jsx-self (1/3)
* react-source/arrow-function/input.js
* react-source/disable-with-super/input.js
Expand Down

0 comments on commit d8eba8e

Please sign in to comment.