@@ -4,14 +4,18 @@ use base64::{
44 encoded_len as base64_encoded_len,
55 prelude:: { BASE64_STANDARD , Engine } ,
66} ;
7- use rustc_hash:: FxHashMap ;
7+ use rustc_hash:: { FxHashMap , FxHashSet } ;
88use sha1:: { Digest , Sha1 } ;
99
1010use oxc_allocator:: {
1111 Address , CloneIn , GetAddress , StringBuilder as ArenaStringBuilder , TakeIn , Vec as ArenaVec ,
1212} ;
1313use oxc_ast:: { AstBuilder , NONE , ast:: * , match_expression} ;
14- use oxc_semantic:: { Reference , ReferenceFlags , ScopeFlags , ScopeId , SymbolFlags } ;
14+ use oxc_ast_visit:: {
15+ Visit ,
16+ walk:: { walk_call_expression, walk_declaration} ,
17+ } ;
18+ use oxc_semantic:: { ReferenceFlags , ScopeFlags , ScopeId , SymbolFlags , SymbolId } ;
1519use oxc_span:: { Atom , GetSpan , SPAN } ;
1620use oxc_syntax:: operator:: AssignmentOperator ;
1721use oxc_traverse:: { Ancestor , BoundIdentifier , Traverse } ;
@@ -120,6 +124,8 @@ pub struct ReactRefresh<'a, 'ctx> {
120124 // (function_scope_id, key)
121125 function_signature_keys : FxHashMap < ScopeId , String > ,
122126 non_builtin_hooks_callee : FxHashMap < ScopeId , Vec < Option < Expression < ' a > > > > ,
127+ /// Used to determine which bindings are used in JSX calls.
128+ used_in_jsx_bindings : FxHashSet < SymbolId > ,
123129}
124130
125131impl < ' a , ' ctx > ReactRefresh < ' a , ' ctx > {
@@ -137,12 +143,15 @@ impl<'a, 'ctx> ReactRefresh<'a, 'ctx> {
137143 last_signature : None ,
138144 function_signature_keys : FxHashMap :: default ( ) ,
139145 non_builtin_hooks_callee : FxHashMap :: default ( ) ,
146+ used_in_jsx_bindings : FxHashSet :: default ( ) ,
140147 }
141148 }
142149}
143150
144151impl < ' a > Traverse < ' a , TransformState < ' a > > for ReactRefresh < ' a , ' _ > {
145152 fn enter_program ( & mut self , program : & mut Program < ' a > , ctx : & mut TraverseCtx < ' a > ) {
153+ self . used_in_jsx_bindings = UsedInJSXBindingsCollector :: collect ( program, ctx) ;
154+
146155 let mut new_statements = ctx. ast . vec_with_capacity ( program. body . len ( ) * 2 ) ;
147156 for mut statement in program. body . take_in ( ctx. ast ) {
148157 let next_statement = self . process_statement ( & mut statement, ctx) ;
@@ -808,13 +817,8 @@ impl<'a> ReactRefresh<'a, '_> {
808817 let found_inside = self
809818 . replace_inner_components ( & id. name , init, /* is_variable_declarator */ true , ctx) ;
810819
811- if !found_inside {
812- // See if this identifier is used in JSX. Then it's a component.
813- // TODO: Here we should check if the variable is used in JSX. But now we only check if it has value references.
814- // https://github.com/facebook/react/blob/ba6a9e94edf0db3ad96432804f9931ce9dc89fec/packages/react-refresh/src/ReactFreshBabelPlugin.js#L161-L199
815- if !ctx. scoping ( ) . get_resolved_references ( symbol_id) . any ( Reference :: is_value) {
816- return None ;
817- }
820+ if !found_inside && !self . used_in_jsx_bindings . contains ( & symbol_id) {
821+ return None ;
818822 }
819823
820824 Some ( self . create_assignment_expression ( id, ctx) )
@@ -913,3 +917,84 @@ fn is_builtin_hook(hook_name: &str) -> bool {
913917 "useOptimistic"
914918 )
915919}
920+
921+ /// Collects all bindings that are used in JSX elements or JSX-like calls.
922+ ///
923+ /// For <https://github.com/facebook/react/blob/ba6a9e94edf0db3ad96432804f9931ce9dc89fec/packages/react-refresh/src/ReactFreshBabelPlugin.js#L161-L199>
924+ struct UsedInJSXBindingsCollector < ' a , ' b > {
925+ ctx : & ' b TraverseCtx < ' a > ,
926+ bindings : FxHashSet < SymbolId > ,
927+ }
928+
929+ impl < ' a , ' b > UsedInJSXBindingsCollector < ' a , ' b > {
930+ fn collect ( program : & Program < ' a > , ctx : & ' b TraverseCtx < ' a > ) -> FxHashSet < SymbolId > {
931+ let mut visitor = Self { ctx, bindings : FxHashSet :: default ( ) } ;
932+ visitor. visit_program ( program) ;
933+ visitor. bindings
934+ }
935+
936+ fn is_jsx_like_call ( name : & str ) -> bool {
937+ matches ! ( name, "createElement" | "jsx" | "jsxDEV" | "jsxs" )
938+ }
939+ }
940+
941+ impl < ' a > Visit < ' a > for UsedInJSXBindingsCollector < ' a , ' _ > {
942+ fn visit_call_expression ( & mut self , it : & CallExpression < ' a > ) {
943+ walk_call_expression ( self , it) ;
944+
945+ let is_jsx_call = match & it. callee {
946+ Expression :: Identifier ( ident) => Self :: is_jsx_like_call ( & ident. name ) ,
947+ Expression :: StaticMemberExpression ( member) => {
948+ Self :: is_jsx_like_call ( & member. property . name )
949+ }
950+ _ => false ,
951+ } ;
952+
953+ if is_jsx_call {
954+ if let Some ( Argument :: Identifier ( ident) ) = it. arguments . first ( ) {
955+ if let Some ( symbol_id) =
956+ self . ctx . scoping ( ) . get_reference ( ident. reference_id ( ) ) . symbol_id ( )
957+ {
958+ self . bindings . insert ( symbol_id) ;
959+ }
960+ }
961+ }
962+ }
963+
964+ fn visit_jsx_opening_element ( & mut self , it : & JSXOpeningElement < ' _ > ) {
965+ if let Some ( ident) = it. name . get_identifier ( ) {
966+ if let Some ( symbol_id) =
967+ self . ctx . scoping ( ) . get_reference ( ident. reference_id ( ) ) . symbol_id ( )
968+ {
969+ self . bindings . insert ( symbol_id) ;
970+ }
971+ }
972+ }
973+
974+ #[ inline]
975+ fn visit_ts_type_annotation ( & mut self , _it : & TSTypeAnnotation < ' a > ) {
976+ // Skip type annotations because it definitely doesn't have any JSX bindings
977+ }
978+
979+ #[ inline]
980+ fn visit_declaration ( & mut self , it : & Declaration < ' a > ) {
981+ if matches ! (
982+ it,
983+ Declaration :: TSTypeAliasDeclaration ( _) | Declaration :: TSInterfaceDeclaration ( _)
984+ ) {
985+ // Skip type-only declarations because it definitely doesn't have any JSX bindings
986+ return ;
987+ }
988+ walk_declaration ( self , it) ;
989+ }
990+
991+ #[ inline]
992+ fn visit_import_declaration ( & mut self , _it : & ImportDeclaration < ' a > ) {
993+ // Skip import declarations because it definitely doesn't have any JSX bindings
994+ }
995+
996+ #[ inline]
997+ fn visit_export_all_declaration ( & mut self , _it : & ExportAllDeclaration < ' a > ) {
998+ // Skip export all declarations because it definitely doesn't have any JSX bindings
999+ }
1000+ }
0 commit comments