Skip to content

Commit

Permalink
feat(transformer/react): handle refresh_sig and refresh_reg correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed Sep 9, 2024
1 parent 67eacc1 commit 35445dc
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 149 deletions.
104 changes: 82 additions & 22 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};
use oxc_ast::{ast::*, match_expression, match_member_expression, AstBuilder};
use oxc_semantic::{Reference, ReferenceFlags, ScopeId, SymbolFlags, SymbolId};
use oxc_span::{Atom, GetSpan, SPAN};
use oxc_syntax::operator::AssignmentOperator;
Expand All @@ -13,6 +13,79 @@ use sha1::{Digest, Sha1};
use super::options::ReactRefreshOptions;
use crate::context::Ctx;

/// Parse a string into a `RefreshIdentifierResolver` and convert it into an `Expression`
#[derive(Debug)]
enum RefreshIdentifierResolver<'a> {
/// Simple IdentifierReference (e.g. `$RefreshReg$`)
Identifier(IdentifierReference<'a>),
/// StaticMemberExpression (object, property) (e.g. `window.$RefreshReg$`)
Member((IdentifierReference<'a>, IdentifierName<'a>)),
/// Used for `import.meta` expression (e.g. `import.meta.$RefreshReg$`)
Expression(Expression<'a>),
}

impl<'a> RefreshIdentifierResolver<'a> {
/// Parses a string into a RefreshIdentifierResolver
pub fn parse(input: &str, ast: AstBuilder<'a>) -> Self {
if !input.contains('.') {
// Handle simple identifier reference
return Self::Identifier(ast.identifier_reference(SPAN, input));
}

let mut parts = input.split('.');
let first_part = parts.next().unwrap();

if first_part == "import" {
// Handle import.meta.$RefreshReg$ expression
let mut expr = ast.expression_meta_property(
SPAN,
ast.identifier_name(SPAN, "import"),
ast.identifier_name(SPAN, parts.next().unwrap()),
);
if let Some(property) = parts.next() {
expr = Expression::from(ast.member_expression_static(
SPAN,
expr,
ast.identifier_name(SPAN, property),
false,
));
}
return Self::Expression(expr);
}

// Handle `window.$RefreshReg$` member expression
let object = ast.identifier_reference(SPAN, first_part);
let property = ast.identifier_name(SPAN, parts.next().unwrap());
Self::Member((object, property))
}

/// Converts the RefreshIdentifierResolver into an Expression
pub fn to_expression(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
match self {
Self::Identifier(ident) => {
let ident = ident.clone();
let reference_id =
ctx.create_unbound_reference(ident.name.to_compact_str(), ReferenceFlags::Read);
ident.reference_id.set(Some(reference_id));
ctx.ast.expression_from_identifier_reference(ident)
}
Self::Member((ident, property)) => {
let ident = ident.clone();
let reference_id =
ctx.create_unbound_reference(ident.name.to_compact_str(), ReferenceFlags::Read);
ident.reference_id.set(Some(reference_id));
Expression::from(ctx.ast.member_expression_static(
SPAN,
ctx.ast.expression_from_identifier_reference(ident),
property.clone(),
false,
))
}
Self::Expression(expr) => expr.clone_in(ctx.ast.allocator),
}
}
}

/// React Fast Refresh
///
/// Transform React functional components to integrate Fast Refresh.
Expand All @@ -22,11 +95,12 @@ use crate::context::Ctx;
/// * <https://github.com/facebook/react/issues/16604#issuecomment-528663101>
/// * <https://github.com/facebook/react/blob/main/packages/react-refresh/src/ReactFreshBabelPlugin.js>
pub struct ReactRefresh<'a> {
refresh_reg: Atom<'a>,
refresh_sig: Atom<'a>,
refresh_reg: RefreshIdentifierResolver<'a>,
refresh_sig: RefreshIdentifierResolver<'a>,
emit_full_signatures: bool,
registrations: Vec<(SymbolId, Atom<'a>)>,
ctx: Ctx<'a>,
// States
registrations: Vec<(SymbolId, Atom<'a>)>,
signature_declarator_items: Vec<oxc_allocator::Vec<'a, VariableDeclarator<'a>>>,
/// Used to wrap call expression with signature.
/// (eg: hoc(() => {}) -> _s1(hoc(_s1(() => {}))))
Expand All @@ -39,10 +113,9 @@ pub struct ReactRefresh<'a> {

impl<'a> ReactRefresh<'a> {
pub fn new(options: &ReactRefreshOptions, ctx: Ctx<'a>) -> Self {
// TODO: refresh_reg and refresh_sig need to support MemberExpression
Self {
refresh_reg: ctx.ast.atom(&options.refresh_reg),
refresh_sig: ctx.ast.atom(&options.refresh_sig),
refresh_reg: RefreshIdentifierResolver::parse(&options.refresh_reg, ctx.ast),
refresh_sig: RefreshIdentifierResolver::parse(&options.refresh_sig, ctx.ast),
emit_full_signatures: options.emit_full_signatures,
signature_declarator_items: Vec::new(),
registrations: Vec::default(),
Expand Down Expand Up @@ -99,13 +172,7 @@ impl<'a> Traverse<'a> for ReactRefresh<'a> {
),
);

let refresh_reg_ident = ctx.create_reference_id(
SPAN,
self.refresh_reg.clone(),
Some(symbol_id),
ReferenceFlags::Read,
);
let callee = ctx.ast.expression_from_identifier_reference(refresh_reg_ident);
let callee = self.refresh_reg.to_expression(ctx);
let mut arguments = ctx.ast.vec_with_capacity(2);
arguments.push(ctx.ast.argument_expression(
Self::create_identifier_reference_from_binding_identifier(&binding_identifier, ctx),
Expand Down Expand Up @@ -628,13 +695,6 @@ impl<'a> ReactRefresh<'a> {
symbol_id: Cell::new(Some(symbol_id)),
};

let sig_identifier_reference = ctx.create_reference_id(
SPAN,
self.refresh_sig.clone(),
Some(symbol_id),
ReferenceFlags::Read,
);

// _s();
let call_expression = ctx.ast.statement_expression(
SPAN,
Expand All @@ -660,7 +720,7 @@ impl<'a> ReactRefresh<'a> {
),
Some(ctx.ast.expression_call(
SPAN,
ctx.ast.expression_from_identifier_reference(sig_identifier_reference.clone()),
self.refresh_sig.to_expression(ctx),
Option::<TSTypeParameterInstantiation>::None,
ctx.ast.vec(),
false,
Expand Down
129 changes: 2 additions & 127 deletions tasks/transform_conformance/oxc.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 3bcfee23

Passed: 17/51
Passed: 18/51

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


# babel-plugin-transform-react-jsx (3/29)
# babel-plugin-transform-react-jsx (4/29)
* refresh/can-handle-implicit-arrow-returns/input.jsx
Symbol reference IDs mismatch:
after transform: SymbolId(9): [ReferenceId(23), ReferenceId(24), ReferenceId(25)]
rebuilt : SymbolId(0): [ReferenceId(6), ReferenceId(7)]
Symbol reference IDs mismatch:
after transform: SymbolId(10): [ReferenceId(26), ReferenceId(27), ReferenceId(29)]
rebuilt : SymbolId(1): [ReferenceId(10), ReferenceId(13)]
Symbol reference IDs mismatch:
after transform: SymbolId(11): [ReferenceId(30), ReferenceId(31), ReferenceId(32)]
rebuilt : SymbolId(2): [ReferenceId(18), ReferenceId(19)]
Symbol reference IDs mismatch:
after transform: SymbolId(12): [ReferenceId(33), ReferenceId(34), ReferenceId(36)]
rebuilt : SymbolId(3): [ReferenceId(22), ReferenceId(25)]
Symbol reference IDs mismatch:
after transform: SymbolId(13): [ReferenceId(37), ReferenceId(38), ReferenceId(39), ReferenceId(40)]
rebuilt : SymbolId(4): [ReferenceId(29), ReferenceId(32), ReferenceId(33)]
Symbol reference IDs mismatch:
after transform: SymbolId(14): [ReferenceId(41), ReferenceId(42), ReferenceId(44)]
rebuilt : SymbolId(5): [ReferenceId(38), ReferenceId(41)]
Symbol reference IDs mismatch:
after transform: SymbolId(4): [ReferenceId(14), ReferenceId(45), ReferenceId(46)]
rebuilt : SymbolId(10): [ReferenceId(15), ReferenceId(46)]
Symbol reference IDs mismatch:
after transform: SymbolId(5): [ReferenceId(16), ReferenceId(47), ReferenceId(48)]
rebuilt : SymbolId(11): [ReferenceId(27), ReferenceId(48)]
Symbol reference IDs mismatch:
after transform: SymbolId(6): [ReferenceId(18), ReferenceId(49), ReferenceId(50)]
rebuilt : SymbolId(12): [ReferenceId(31), ReferenceId(50)]
Symbol reference IDs mismatch:
after transform: SymbolId(7): [ReferenceId(19), ReferenceId(51), ReferenceId(52)]
rebuilt : SymbolId(13): [ReferenceId(36), ReferenceId(52)]
Symbol reference IDs mismatch:
after transform: SymbolId(8): [ReferenceId(21), ReferenceId(53), ReferenceId(54)]
rebuilt : SymbolId(14): [ReferenceId(43), ReferenceId(54)]
Reference symbol mismatch:
after transform: ReferenceId(23): Some("_s")
rebuilt : ReferenceId(0): None
Reference symbol mismatch:
after transform: ReferenceId(26): Some("_s2")
rebuilt : ReferenceId(1): None
Reference symbol mismatch:
after transform: ReferenceId(30): Some("_s3")
rebuilt : ReferenceId(2): None
Reference symbol mismatch:
after transform: ReferenceId(33): Some("_s4")
rebuilt : ReferenceId(3): None
Reference symbol mismatch:
after transform: ReferenceId(37): Some("_s5")
rebuilt : ReferenceId(4): None
Reference symbol mismatch:
after transform: ReferenceId(41): Some("_s6")
rebuilt : ReferenceId(5): None
Reference flags mismatch:
after transform: ReferenceId(18): ReferenceFlags(Write)
rebuilt : ReferenceId(31): ReferenceFlags(Read | Write)
Reference symbol mismatch:
after transform: ReferenceId(45): Some("_c")
rebuilt : ReferenceId(45): None
Reference symbol mismatch:
after transform: ReferenceId(47): Some("_c2")
rebuilt : ReferenceId(47): None
Reference symbol mismatch:
after transform: ReferenceId(49): Some("_c3")
rebuilt : ReferenceId(49): None
Reference symbol mismatch:
after transform: ReferenceId(51): Some("_c4")
rebuilt : ReferenceId(51): None
Reference symbol mismatch:
after transform: ReferenceId(53): Some("_c5")
rebuilt : ReferenceId(53): None
Unresolved references mismatch:
after transform: ["X", "memo", "module", "useContext"]
rebuilt : ["$RefreshReg$", "$RefreshSig$", "X", "memo", "module", "useContext"]

* refresh/does-not-consider-require-like-methods-to-be-hocs/input.jsx
x Output mismatch
Expand All @@ -255,15 +186,6 @@ rebuilt : ScopeId(1): []
Symbol scope ID mismatch:
after transform: SymbolId(1): ScopeId(1)
rebuilt : SymbolId(0): ScopeId(0)
Symbol reference IDs mismatch:
after transform: SymbolId(1): [ReferenceId(3), ReferenceId(4), ReferenceId(5)]
rebuilt : SymbolId(0): [ReferenceId(2), ReferenceId(3)]
Reference symbol mismatch:
after transform: ReferenceId(3): Some("_s")
rebuilt : ReferenceId(1): None
Unresolved references mismatch:
after transform: ["item", "useFoo"]
rebuilt : ["$RefreshSig$", "item", "useFoo"]

* refresh/does-not-transform-it-because-it-is-not-used-in-the-AST/input.jsx
x Output mismatch
Expand Down Expand Up @@ -295,53 +217,6 @@ x Output mismatch
* refresh/registers-identifiers-used-in-jsx-at-definition-site/input.jsx
x Output mismatch

* refresh/registers-identifiers-used-in-react-create-element-at-definition-site/input.jsx
Symbol reference IDs mismatch:
after transform: SymbolId(13): [ReferenceId(33), ReferenceId(47), ReferenceId(48)]
rebuilt : SymbolId(13): [ReferenceId(2), ReferenceId(48)]
Symbol reference IDs mismatch:
after transform: SymbolId(14): [ReferenceId(35), ReferenceId(49), ReferenceId(50)]
rebuilt : SymbolId(14): [ReferenceId(5), ReferenceId(50)]
Symbol reference IDs mismatch:
after transform: SymbolId(15): [ReferenceId(37), ReferenceId(51), ReferenceId(52)]
rebuilt : SymbolId(15): [ReferenceId(8), ReferenceId(52)]
Symbol reference IDs mismatch:
after transform: SymbolId(16): [ReferenceId(39), ReferenceId(53), ReferenceId(54)]
rebuilt : SymbolId(16): [ReferenceId(12), ReferenceId(54)]
Symbol reference IDs mismatch:
after transform: SymbolId(17): [ReferenceId(41), ReferenceId(55), ReferenceId(56)]
rebuilt : SymbolId(17): [ReferenceId(35), ReferenceId(56)]
Symbol reference IDs mismatch:
after transform: SymbolId(18): [ReferenceId(43), ReferenceId(57), ReferenceId(58)]
rebuilt : SymbolId(18): [ReferenceId(41), ReferenceId(58)]
Symbol reference IDs mismatch:
after transform: SymbolId(19): [ReferenceId(45), ReferenceId(59), ReferenceId(60)]
rebuilt : SymbolId(19): [ReferenceId(45), ReferenceId(60)]
Reference symbol mismatch:
after transform: ReferenceId(47): Some("_c")
rebuilt : ReferenceId(47): None
Reference symbol mismatch:
after transform: ReferenceId(49): Some("_c2")
rebuilt : ReferenceId(49): None
Reference symbol mismatch:
after transform: ReferenceId(51): Some("_c3")
rebuilt : ReferenceId(51): None
Reference symbol mismatch:
after transform: ReferenceId(53): Some("_c4")
rebuilt : ReferenceId(53): None
Reference symbol mismatch:
after transform: ReferenceId(55): Some("_c5")
rebuilt : ReferenceId(55): None
Reference symbol mismatch:
after transform: ReferenceId(57): Some("_c6")
rebuilt : ReferenceId(57): None
Reference symbol mismatch:
after transform: ReferenceId(59): Some("_c7")
rebuilt : ReferenceId(59): None
Unresolved references mismatch:
after transform: ["React", "funny", "hoc", "jsx", "styled", "wow"]
rebuilt : ["$RefreshReg$", "React", "funny", "hoc", "jsx", "styled", "wow"]

* refresh/registers-likely-hocs-with-inline-functions-1/input.jsx
x Output mismatch

Expand Down

0 comments on commit 35445dc

Please sign in to comment.