Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transformer/react): handle refresh_sig and refresh_reg options correctly #5638

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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