From 35445dcc0e9c5d02e5e9cf48ace008e391f34ea8 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Mon, 9 Sep 2024 15:44:09 +0800 Subject: [PATCH] feat(transformer/react): handle refresh_sig and refresh_reg correctly --- crates/oxc_transformer/src/react/refresh.rs | 104 ++++++++++++---- tasks/transform_conformance/oxc.snap.md | 129 +------------------- 2 files changed, 84 insertions(+), 149 deletions(-) diff --git a/crates/oxc_transformer/src/react/refresh.rs b/crates/oxc_transformer/src/react/refresh.rs index 125eef51b219ae..793e88479ea751 100644 --- a/crates/oxc_transformer/src/react/refresh.rs +++ b/crates/oxc_transformer/src/react/refresh.rs @@ -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; @@ -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. @@ -22,11 +95,12 @@ use crate::context::Ctx; /// * /// * 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>>, /// Used to wrap call expression with signature. /// (eg: hoc(() => {}) -> _s1(hoc(_s1(() => {})))) @@ -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(), @@ -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), @@ -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, @@ -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::::None, ctx.ast.vec(), false, diff --git a/tasks/transform_conformance/oxc.snap.md b/tasks/transform_conformance/oxc.snap.md index 8dd15ff407c6ff..b433fdb20feb47 100644 --- a/tasks/transform_conformance/oxc.snap.md +++ b/tasks/transform_conformance/oxc.snap.md @@ -1,6 +1,6 @@ commit: 3bcfee23 -Passed: 17/51 +Passed: 18/51 # All Passed: * babel-plugin-transform-nullish-coalescing-operator @@ -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 @@ -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 @@ -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