use oxc_ast::ast::*;
use oxc_span::SPAN;
use oxc_str::Str;
use oxc_traverse::{Ancestor, Traverse};
use crate::{context::TraverseCtx, state::TransformState};
pub struct ReactDisplayName;
impl ReactDisplayName {
pub fn new() -> Self {
Self
}
}
impl<'a> Traverse<'a, TransformState<'a>> for ReactDisplayName {
fn enter_call_expression(
&mut self,
call_expr: &mut CallExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Some(obj_expr) = Self::get_object_from_create_class(call_expr) else {
return;
};
let mut ancestors = ctx.ancestors();
let name = loop {
let Some(ancestor) = ancestors.next() else {
return;
};
match ancestor {
Ancestor::AssignmentExpressionRight(assign_expr) => match assign_expr.left() {
AssignmentTarget::AssignmentTargetIdentifier(ident) => {
break ident.name.into();
}
AssignmentTarget::StaticMemberExpression(expr) => {
break expr.property.name.into();
}
AssignmentTarget::ComputedMemberExpression(expr) => {
if let Some(name) = expr.static_property_name() {
break name;
}
return;
}
_ => return,
},
Ancestor::VariableDeclaratorInit(declarator) => {
if let BindingPattern::BindingIdentifier(ident) = &declarator.id() {
break ident.name.into();
}
return;
}
Ancestor::ObjectPropertyValue(prop) => {
if let Some(name) = prop.key().static_name() {
break ctx.ast.str(&name);
}
return;
}
Ancestor::ExportDefaultDeclarationDeclaration(_) => {
break ctx.ast.str(&ctx.state.filename);
}
_ if ancestor.is_parent_of_statement() => return,
_ => {}
}
};
Self::add_display_name(obj_expr, name, ctx);
}
}
impl<'a> ReactDisplayName {
fn get_object_from_create_class<'b>(
call_expr: &'b mut CallExpression<'a>,
) -> Option<&'b mut ObjectExpression<'a>> {
if match &call_expr.callee {
callee @ match_member_expression!(Expression) => {
!callee.to_member_expression().is_specific_member_access("React", "createClass")
}
Expression::Identifier(ident) => ident.name != "createReactClass",
_ => true,
} {
return None;
}
if call_expr.arguments.len() != 1 {
return None;
}
let arg = call_expr.arguments.get_mut(0)?;
match arg {
Argument::ObjectExpression(obj_expr) => Some(obj_expr),
_ => None,
}
}
fn add_display_name(obj_expr: &mut ObjectExpression<'a>, name: Str<'a>, ctx: &TraverseCtx<'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;
}
obj_expr.properties.insert(
0,
ctx.ast.object_property_kind_object_property(
SPAN,
PropertyKind::Init,
ctx.ast.property_key_static_identifier(SPAN, DISPLAY_NAME),
ctx.ast.expression_string_literal(SPAN, name, None),
false,
false,
false,
),
);
}
}