Skip to content

Commit

Permalink
feat(transformer): implement plugin-transform-react-display-name top-…
Browse files Browse the repository at this point in the history
…down
  • Loading branch information
Boshen committed Apr 11, 2024
1 parent 07bd85e commit f4f472f
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 17 deletions.
15 changes: 15 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,19 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
self.x1_react.transform_expression(expr);
walk_mut::walk_expression_mut(self, expr);
}

fn visit_variable_declarator(&mut self, declarator: &mut VariableDeclarator<'a>) {
self.x1_react.transform_variable_declarator(declarator);
walk_mut::walk_variable_declarator_mut(self, declarator);
}

fn visit_object_property(&mut self, prop: &mut ObjectProperty<'a>) {
self.x1_react.transform_object_property(prop);
walk_mut::walk_object_property_mut(self, prop);
}

fn visit_export_default_declaration(&mut self, decl: &mut ExportDefaultDeclaration<'a>) {
self.x1_react.transform_export_default_declaration(decl);
walk_mut::walk_export_default_declaration_mut(self, decl);
}
}
116 changes: 115 additions & 1 deletion crates/oxc_transformer/src/react/display_name/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::rc::Rc;

use oxc_allocator::Box;
use oxc_ast::ast::*;
use oxc_span::{Atom, SPAN};

use crate::context::Ctx;

/// [plugin-transform-react-display-name](https://babeljs.io/docs/babel-plugin-transform-react-display-name)
Expand All @@ -10,7 +14,10 @@ use crate::context::Ctx;
///
/// In: `var bar = createReactClass({});`
/// Out: `var bar = createReactClass({ displayName: "bar" });`
#[allow(unused)]
///
/// 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 @@ -20,3 +27,110 @@ impl<'a> ReactDisplayName<'a> {
Self { ctx: Rc::clone(ctx) }
}
}

// 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 {
return;
};
let name = match &assign_expr.left {
AssignmentTarget::SimpleAssignmentTarget(
SimpleAssignmentTarget::AssignmentTargetIdentifier(ident),
) => ident.name.clone(),
AssignmentTarget::SimpleAssignmentTarget(
SimpleAssignmentTarget::MemberAssignmentTarget(target),
) => {
if let Some(name) = target.static_property_name() {
self.ctx.ast.new_atom(name)
} else {
return;
}
}
_ => return,
};
self.add_display_name(obj_expr, name);
}

/// `let foo = React.createClass({})`
pub fn transform_variable_declarator(&mut 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(&mut 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(
&mut self,
decl: &mut ExportDefaultDeclaration<'a>,
) {
let ExportDefaultDeclarationKind::Expression(expr) = &mut decl.declaration else { return };
let Some(obj_expr) = Self::get_object_from_create_class(expr) else { return };
let name = self.ctx.ast.new_atom("input"); // TODO: use the filename
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>,
) -> Option<&'b mut Box<'a, ObjectExpression<'a>>> {
let Expression::CallExpression(call_expr) = e else { return None };
if match &call_expr.callee {
Expression::MemberExpression(e) => !e.is_specific_member_access("React", "createClass"),
Expression::Identifier(ident) => ident.name != "createReactClass",
_ => true,
} {
return None;
}
// Only 1 argument being the object expression.
if call_expr.arguments.len() != 1 {
return None;
}
let arg = call_expr.arguments.get_mut(0)?;
match arg {
Argument::SpreadElement(_) => None,
Argument::Expression(e) => match e {
Expression::ObjectExpression(obj_expr) => Some(obj_expr),
_ => None,
},
}
}

/// Add key value `displayName: name` to the `React.createClass` object.
fn add_display_name(&self, obj_expr: &mut ObjectExpression<'a>, name: Atom<'a>) {
const DISPLAY_NAME: &str = "displayName";
let not_safe = obj_expr.properties.iter().any(|prop| {
matches!(prop, ObjectPropertyKind::ObjectProperty(p) if p.key.static_name().is_some_and(|name| name == DISPLAY_NAME))
});
if not_safe {
return;
}
let prop = {
let kind = PropertyKind::Init;
let identifier_name = IdentifierName::new(SPAN, self.ctx.ast.new_atom(DISPLAY_NAME));
let key = self.ctx.ast.property_key_identifier(identifier_name);
let string_literal = StringLiteral::new(SPAN, name);
let value = self.ctx.ast.literal_string_expression(string_literal);
self.ctx.ast.object_property(SPAN, kind, key, value, None, false, false, false)
};
obj_expr.properties.insert(0, ObjectPropertyKind::ObjectProperty(prop));
}
}
20 changes: 19 additions & 1 deletion crates/oxc_transformer/src/react/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ impl<'a> React<'a> {
}
}

// Transformers
// Transforms
impl<'a> React<'a> {
pub fn transform_expression(&mut self, expr: &mut Expression<'a>) {
match expr {
Expression::AssignmentExpression(e) => {
self.display_name.transform_assignment_expression(e);
}
Expression::JSXElement(_e) => {
// *expr = unimplemented!();
}
Expand All @@ -58,4 +61,19 @@ impl<'a> React<'a> {
_ => {}
}
}

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

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

pub fn transform_export_default_declaration(
&mut self,
decl: &mut ExportDefaultDeclaration<'a>,
) {
self.display_name.transform_export_default_declaration(decl);
}
}
17 changes: 2 additions & 15 deletions tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
Passed: 2/16
Passed: 15/16

# All Passed:



# babel-plugin-transform-react-display-name (2/16)
* display-name/assignment-expression/input.js
# babel-plugin-transform-react-display-name (15/16)
* display-name/nested/input.js
* display-name/object-property/input.js
* display-name/variable-declarator/input.js
* with-jsx-plugin/assignment-expression/input.js
* with-jsx-plugin/export-default/input.mjs
* with-jsx-plugin/object-declaration/input.js
* with-jsx-plugin/property-assignment/input.js
* with-jsx-plugin/variable-declaration/input.js
* with-jsx-plugin-automatic/display-name-assignment-expression/input.js
* with-jsx-plugin-automatic/display-name-export-default/input.mjs
* with-jsx-plugin-automatic/display-name-object-declaration/input.js
* with-jsx-plugin-automatic/display-name-property-assignment/input.js
* with-jsx-plugin-automatic/display-name-variable-declaration/input.js

0 comments on commit f4f472f

Please sign in to comment.