Skip to content

Commit

Permalink
fix(transformer/react): the refresh plugin cannot handle member expre…
Browse files Browse the repository at this point in the history
…ssions with React hooks (#5655)

The previous implementation doesn't handle nested StaticMemberExpression. For example: `A.B.C.useHook`.
  • Loading branch information
Dunqing committed Sep 11, 2024
1 parent 0739b5f commit 3e8b96f
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 49 deletions.
87 changes: 40 additions & 47 deletions crates/oxc_transformer/src/react/refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{cell::Cell, iter::once};

use base64::prelude::{Engine, BASE64_STANDARD};
use oxc_allocator::CloneIn;
use oxc_ast::{ast::*, match_expression, match_member_expression, AstBuilder};
use oxc_ast::{ast::*, match_expression, AstBuilder};
use oxc_semantic::{Reference, ReferenceFlags, ScopeId, SymbolFlags, SymbolId};
use oxc_span::{Atom, GetSpan, SPAN};
use oxc_syntax::operator::AssignmentOperator;
Expand Down Expand Up @@ -384,65 +384,58 @@ impl<'a> Traverse<'a> for ReactRefresh<'a> {
return;
}

let name = match &call_expr.callee {
Expression::Identifier(ident) => Some(ident.name.clone()),
Expression::StaticMemberExpression(ref member) => Some(member.property.name.clone()),
_ => None,
};

let Some(hook_name) = name else {
return;
let hook_name = match &call_expr.callee {
Expression::Identifier(ident) => ident.name.clone(),
Expression::StaticMemberExpression(ref member) => member.property.name.clone(),
_ => return,
};

if !is_use_hook_name(&hook_name) {
return;
}

if !is_builtin_hook(&hook_name) {
let (binding_name, hook_name) = match &call_expr.callee {
Expression::Identifier(ident) => (ident.name.clone(), None),
callee @ match_member_expression!(Expression) => {
let member_expr = callee.to_member_expression();
match member_expr.object() {
Expression::Identifier(ident) => {
(ident.name.clone(), Some(hook_name.clone()))
}
_ => unreachable!(),
let (binding_name, is_member_expression) = match &call_expr.callee {
Expression::Identifier(ident) => (Some(ident.name.clone()), false),
Expression::StaticMemberExpression(member) => {
if let Expression::Identifier(object) = member.get_first_object() {
(Some(object.name.clone()), true)
} else {
(None, false)
}
}
_ => unreachable!(),
};

let callees = self.non_builtin_hooks_callee.entry(current_scope_id).or_default();

callees.push(
ctx.scopes()
.find_binding(
ctx.scopes().get_parent_id(ctx.current_scope_id()).unwrap(),
binding_name.as_str(),
)
.map(|symbol_id| {
let ident = ctx.create_reference_id(
SPAN,
binding_name.clone(),
Some(symbol_id),
ReferenceFlags::Read,
);

let mut expr = self.ctx.ast.expression_from_identifier_reference(ident);

if let Some(hook_name) = hook_name {
// binding_name.hook_name
expr = Expression::from(self.ctx.ast.member_expression_static(
if let Some(binding_name) = binding_name {
self.non_builtin_hooks_callee.entry(current_scope_id).or_default().push(
ctx.scopes()
.find_binding(
ctx.scopes().get_parent_id(ctx.current_scope_id()).unwrap(),
binding_name.as_str(),
)
.map(|symbol_id| {
let ident = ctx.create_reference_id(
SPAN,
expr,
self.ctx.ast.identifier_name(SPAN, hook_name),
false,
));
}
expr
}),
);
binding_name,
Some(symbol_id),
ReferenceFlags::Read,
);
let mut expr = self.ctx.ast.expression_from_identifier_reference(ident);

if is_member_expression {
// binding_name.hook_name
expr = Expression::from(self.ctx.ast.member_expression_static(
SPAN,
expr,
self.ctx.ast.identifier_name(SPAN, hook_name.clone()),
false,
));
}
expr
}),
);
}
}

let key = if let Ancestor::VariableDeclaratorInit(declarator) = ctx.parent() {
Expand Down
4 changes: 2 additions & 2 deletions tasks/transform_conformance/oxc.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 3bcfee23

Passed: 18/51
Passed: 19/52

# All Passed:
* babel-plugin-transform-nullish-coalescing-operator
Expand Down Expand Up @@ -167,7 +167,7 @@ rebuilt : SymbolId(2): []
x Output mismatch


# babel-plugin-transform-react-jsx (4/29)
# babel-plugin-transform-react-jsx (5/30)
* refresh/can-handle-implicit-arrow-returns/input.jsx
Reference flags mismatch:
after transform: ReferenceId(18): ReferenceFlags(Write)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Bar () {
A.B.C.useHook()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var _s = $RefreshSig$();
export function Bar() {
_s();
A.B.C.useHook();
}
_s(Bar, "useHook{}", true);
_c = Bar;
var _c;
$RefreshReg$(_c, "Bar");

0 comments on commit 3e8b96f

Please sign in to comment.