@@ -6,8 +6,8 @@ use oxc_ast::AstBuilder;
66use oxc_ast:: ast:: JSXAttributeItem :: Attribute ;
77use oxc_ast:: ast:: JSXAttributeName :: Identifier ;
88use oxc_ast:: ast:: {
9- Expression , JSXAttributeItem , JSXAttributeValue , JSXExpression , ObjectPropertyKind ,
10- PropertyKey , PropertyKind , TemplateElementValue ,
9+ Expression , JSXAttributeItem , JSXAttributeValue , JSXExpression , LogicalOperator ,
10+ ObjectPropertyKind , PropertyKey , PropertyKind , TemplateElementValue ,
1111} ;
1212use oxc_span:: SPAN ;
1313
@@ -21,6 +21,7 @@ pub fn modify_prop_object<'a>(
2121) {
2222 let mut class_name_prop = None ;
2323 let mut style_prop = None ;
24+ let mut spread_props = vec ! [ ] ;
2425 for idx in ( 0 ..props. len ( ) ) . rev ( ) {
2526 let prop = props. remove ( idx) ;
2627 match prop {
@@ -40,14 +41,20 @@ pub fn modify_prop_object<'a>(
4041 }
4142 props. insert ( idx, ObjectPropertyKind :: ObjectProperty ( attr) ) ;
4243 }
43- _ => {
44- props. insert ( idx, prop) ;
44+ ObjectPropertyKind :: SpreadProperty ( spread) => {
45+ spread_props. push ( spread. argument . clone_in ( ast_builder. allocator ) ) ;
46+ props. insert ( idx, ObjectPropertyKind :: SpreadProperty ( spread) ) ;
4547 }
4648 }
4749 }
4850
49- if let Some ( ex) = get_class_name_expression ( ast_builder, & class_name_prop, styles, style_order)
50- {
51+ if let Some ( ex) = get_class_name_expression (
52+ ast_builder,
53+ & class_name_prop,
54+ styles,
55+ style_order,
56+ & spread_props,
57+ ) {
5158 props. push ( ObjectPropertyKind :: ObjectProperty (
5259 ast_builder. alloc_object_property (
5360 SPAN ,
@@ -60,7 +67,9 @@ pub fn modify_prop_object<'a>(
6067 ) ,
6168 ) ) ;
6269 }
63- if let Some ( ex) = get_style_expression ( ast_builder, & style_prop, styles, & style_vars) {
70+ if let Some ( ex) =
71+ get_style_expression ( ast_builder, & style_prop, styles, & style_vars, & spread_props)
72+ {
6473 props. push ( ObjectPropertyKind :: ObjectProperty (
6574 ast_builder. alloc_object_property (
6675 SPAN ,
@@ -84,6 +93,7 @@ pub fn modify_props<'a>(
8493) {
8594 let mut class_name_prop = None ;
8695 let mut style_prop = None ;
96+ let mut spread_props = vec ! [ ] ;
8797 for idx in ( 0 ..props. len ( ) ) . rev ( ) {
8898 let prop = props. remove ( idx) ;
8999 match prop {
@@ -125,13 +135,19 @@ pub fn modify_props<'a>(
125135 }
126136 props. insert ( idx, Attribute ( attr) ) ;
127137 }
128- _ => {
129- props. insert ( idx, prop) ;
138+ JSXAttributeItem :: SpreadAttribute ( spread) => {
139+ spread_props. push ( spread. argument . clone_in ( ast_builder. allocator ) ) ;
140+ props. insert ( idx, JSXAttributeItem :: SpreadAttribute ( spread) ) ;
130141 }
131142 }
132143 }
133- if let Some ( ex) = get_class_name_expression ( ast_builder, & class_name_prop, styles, style_order)
134- {
144+ if let Some ( ex) = get_class_name_expression (
145+ ast_builder,
146+ & class_name_prop,
147+ styles,
148+ style_order,
149+ & spread_props,
150+ ) {
135151 props. push ( Attribute ( ast_builder. alloc_jsx_attribute (
136152 SPAN ,
137153 Identifier ( ast_builder. alloc_jsx_identifier ( SPAN , "className" ) ) ,
@@ -144,7 +160,9 @@ pub fn modify_props<'a>(
144160 } ) ,
145161 ) ) ) ;
146162 }
147- if let Some ( ex) = get_style_expression ( ast_builder, & style_prop, styles, & style_vars) {
163+ if let Some ( ex) =
164+ get_style_expression ( ast_builder, & style_prop, styles, & style_vars, & spread_props)
165+ {
148166 props. push ( Attribute ( ast_builder. alloc_jsx_attribute (
149167 SPAN ,
150168 Identifier ( ast_builder. alloc_jsx_identifier ( SPAN , "style" ) ) ,
@@ -160,16 +178,30 @@ pub fn get_class_name_expression<'a>(
160178 class_name_prop : & Option < Expression < ' a > > ,
161179 styles : & mut [ ExtractStyleProp < ' a > ] ,
162180 style_order : Option < u8 > ,
181+ spread_props : & [ Expression < ' a > ] ,
163182) -> Option < Expression < ' a > > {
164183 // should modify class name prop
165184 merge_string_expressions (
166185 ast_builder,
167186 [
168- class_name_prop. clone_in ( ast_builder. allocator ) ,
187+ class_name_prop
188+ . as_ref ( )
189+ . map ( |class_name| convert_class_name ( ast_builder, class_name) ) ,
169190 gen_class_names ( ast_builder, styles, style_order) ,
170191 ]
171192 . into_iter ( )
172193 . flatten ( )
194+ . chain ( spread_props. iter ( ) . map ( |ex| {
195+ convert_class_name (
196+ ast_builder,
197+ & Expression :: StaticMemberExpression ( ast_builder. alloc_static_member_expression (
198+ SPAN ,
199+ ex. clone_in ( ast_builder. allocator ) ,
200+ ast_builder. identifier_name ( SPAN , ast_builder. atom ( "className" ) ) ,
201+ true ,
202+ ) ) ,
203+ )
204+ } ) )
173205 . collect :: < Vec < _ > > ( )
174206 . as_slice ( ) ,
175207 )
@@ -180,6 +212,7 @@ pub fn get_style_expression<'a>(
180212 style_prop : & Option < Expression < ' a > > ,
181213 styles : & [ ExtractStyleProp < ' a > ] ,
182214 style_vars : & Option < Expression < ' a > > ,
215+ spread_props : & [ Expression < ' a > ] ,
183216) -> Option < Expression < ' a > > {
184217 merge_object_expressions (
185218 ast_builder,
@@ -192,6 +225,14 @@ pub fn get_style_expression<'a>(
192225 ]
193226 . into_iter ( )
194227 . flatten ( )
228+ . chain ( spread_props. iter ( ) . map ( |ex| {
229+ Expression :: StaticMemberExpression ( ast_builder. alloc_static_member_expression (
230+ SPAN ,
231+ ex. clone_in ( ast_builder. allocator ) ,
232+ ast_builder. identifier_name ( SPAN , ast_builder. atom ( "style" ) ) ,
233+ true ,
234+ ) )
235+ } ) )
195236 . collect :: < Vec < _ > > ( )
196237 . as_slice ( ) ,
197238 )
@@ -210,9 +251,14 @@ fn merge_string_expressions<'a>(
210251
211252 let mut string_literals: std:: vec:: Vec < String > = vec ! [ ] ;
212253 let mut other_expressions = vec ! [ ] ;
254+ let mut prev_str = false ;
213255 for ex in expressions {
214- string_literals. push ( "" . to_string ( ) ) ;
256+ if !prev_str {
257+ string_literals. push ( "" . to_string ( ) ) ;
258+ prev_str = false ;
259+ }
215260 if let Expression :: StringLiteral ( literal) = ex {
261+ prev_str = true ;
216262 if !string_literals. is_empty ( ) {
217263 string_literals
218264 . last_mut ( )
@@ -273,12 +319,14 @@ fn merge_string_expressions<'a>(
273319 let trimmed = s. trim ( ) ;
274320 if trimmed. is_empty ( ) {
275321 "" . to_string ( )
276- } else if idx > 0 && idx == string_literals. len ( ) - 1 {
277- if string_literals. len ( ) == other_expressions. len ( ) {
278- format ! ( " {trimmed} " )
322+ } else if idx == string_literals. len ( ) - 1 {
323+ let prefix = if idx == 0 { "" } else { " " } ;
324+ let suffix = if string_literals. len ( ) == other_expressions. len ( ) {
325+ " "
279326 } else {
280- format ! ( " {trimmed}" )
281- }
327+ ""
328+ } ;
329+ format ! ( "{prefix}{trimmed}{suffix}" )
282330 } else if idx == string_literals. len ( ) - 1 {
283331 trimmed. to_string ( )
284332 } else {
@@ -333,6 +381,35 @@ fn merge_object_expressions<'a>(
333381 ) )
334382}
335383
384+ pub fn convert_class_name < ' a > (
385+ ast_builder : & AstBuilder < ' a > ,
386+ class_name : & Expression < ' a > ,
387+ ) -> Expression < ' a > {
388+ if matches ! (
389+ class_name,
390+ Expression :: StringLiteral ( _)
391+ | Expression :: TemplateLiteral ( _)
392+ | Expression :: NumericLiteral ( _)
393+ ) {
394+ return class_name. clone_in ( ast_builder. allocator ) ;
395+ }
396+
397+ // wrap ( and ?? ''
398+ Expression :: LogicalExpression (
399+ ast_builder. alloc_logical_expression (
400+ SPAN ,
401+ Expression :: ParenthesizedExpression (
402+ ast_builder. alloc_parenthesized_expression (
403+ SPAN ,
404+ class_name. clone_in ( ast_builder. allocator ) ,
405+ ) ,
406+ ) ,
407+ LogicalOperator :: Coalesce ,
408+ Expression :: StringLiteral ( ast_builder. alloc_string_literal ( SPAN , "" , None ) ) ,
409+ ) ,
410+ )
411+ }
412+
336413pub fn convert_style_vars < ' a > (
337414 ast_builder : & AstBuilder < ' a > ,
338415 style_vars : & Expression < ' a > ,
0 commit comments