@@ -168,7 +168,7 @@ impl Format<JsFormatContext> for JsxSpace {
168
168
write ! [
169
169
formatter,
170
170
[
171
- if_group_breaks( & format_args![ JsxRawSpace , hard_line_break ( ) ] ) ,
171
+ if_group_breaks( & format_args![ JsxRawSpace , soft_line_break ( ) ] ) ,
172
172
if_group_fits_on_line( & space( ) )
173
173
]
174
174
]
@@ -223,7 +223,7 @@ pub(crate) fn jsx_split_children<I>(
223
223
where
224
224
I : IntoIterator < Item = JsxAnyChild > ,
225
225
{
226
- let mut result = Vec :: new ( ) ;
226
+ let mut builder = JsxSplitChildrenBuilder :: default ( ) ;
227
227
228
228
for child in children. into_iter ( ) {
229
229
match child {
@@ -253,15 +253,15 @@ where
253
253
// </div>
254
254
// ```
255
255
if newlines > 1 {
256
- result . push ( JsxChild :: EmptyLine ) ;
256
+ builder . entry ( JsxChild :: EmptyLine ) ;
257
257
}
258
258
259
259
continue ;
260
260
}
261
261
262
- result . push ( JsxChild :: Newline )
263
- } else if ! matches ! ( result . last ( ) , Some ( JsxChild :: Whitespace ) ) {
264
- result . push ( JsxChild :: Whitespace )
262
+ builder . entry ( JsxChild :: Newline )
263
+ } else {
264
+ builder . entry ( JsxChild :: Whitespace )
265
265
}
266
266
}
267
267
_ => unreachable ! ( ) ,
@@ -274,15 +274,15 @@ where
274
274
// Only handle trailing whitespace. Words must always be joined by new lines
275
275
if chunks. peek ( ) . is_none ( ) {
276
276
if whitespace. contains ( '\n' ) {
277
- result . push ( JsxChild :: Newline ) ;
277
+ builder . entry ( JsxChild :: Newline ) ;
278
278
} else {
279
- result . push ( JsxChild :: Whitespace )
279
+ builder . entry ( JsxChild :: Whitespace )
280
280
}
281
281
}
282
282
}
283
283
284
284
( relative_start, JsxTextChunk :: Word ( word) ) => {
285
- result . push ( JsxChild :: Word ( JsxWord {
285
+ builder . entry ( JsxChild :: Word ( JsxWord {
286
286
text : value_token
287
287
. token_text ( )
288
288
. slice ( TextRange :: at ( relative_start, word. text_len ( ) ) ) ,
@@ -295,23 +295,61 @@ where
295
295
296
296
JsxAnyChild :: JsxExpressionChild ( child) => {
297
297
if is_whitespace_jsx_expression ( & child, comments) {
298
- match result. last ( ) {
299
- Some ( JsxChild :: Whitespace ) => {
300
- // Ignore
301
- }
302
- _ => result. push ( JsxChild :: Whitespace ) ,
303
- }
298
+ builder. entry ( JsxChild :: Whitespace )
304
299
} else {
305
- result . push ( JsxChild :: NonText ( child. into ( ) ) )
300
+ builder . entry ( JsxChild :: NonText ( child. into ( ) ) )
306
301
}
307
302
}
308
303
child => {
309
- result . push ( JsxChild :: NonText ( child) ) ;
304
+ builder . entry ( JsxChild :: NonText ( child) ) ;
310
305
}
311
306
}
312
307
}
313
308
314
- Ok ( result)
309
+ Ok ( builder. finish ( ) )
310
+ }
311
+
312
+ #[ derive( Debug , Default ) ]
313
+ struct JsxSplitChildrenBuilder {
314
+ pending : Option < JsxChild > ,
315
+ buffer : Vec < JsxChild > ,
316
+ }
317
+
318
+ impl JsxSplitChildrenBuilder {
319
+ fn is_last_whitespace ( & self ) -> bool {
320
+ matches ! ( self . buffer. last( ) , Some ( JsxChild :: Whitespace ) )
321
+ }
322
+
323
+ fn entry ( & mut self , child : JsxChild ) {
324
+ if let Some ( pending) = self . pending . take ( ) {
325
+ if matches ! (
326
+ pending,
327
+ JsxChild :: EmptyLine | JsxChild :: Newline | JsxChild :: Whitespace
328
+ ) {
329
+ if !matches ! ( child, JsxChild :: Whitespace ) && !self . is_last_whitespace ( ) {
330
+ self . buffer . push ( pending)
331
+ }
332
+ } else {
333
+ self . buffer . push ( pending) ;
334
+ }
335
+ }
336
+
337
+ self . pending = Some ( child) ;
338
+ }
339
+
340
+ fn finish ( mut self ) -> Vec < JsxChild > {
341
+ if let Some ( pending) = self . pending . take ( ) {
342
+ if matches ! ( pending, JsxChild :: Whitespace ) {
343
+ if !self . is_last_whitespace ( ) {
344
+ self . buffer . push ( pending)
345
+ }
346
+ } else {
347
+ self . buffer . push ( pending) ;
348
+ }
349
+ }
350
+
351
+ self . buffer
352
+ }
315
353
}
316
354
317
355
#[ derive( Debug , Clone , Eq , PartialEq ) ]
@@ -378,6 +416,12 @@ pub(crate) struct JsxWord {
378
416
source_position : TextSize ,
379
417
}
380
418
419
+ impl JsxWord {
420
+ pub fn len ( & self ) -> TextSize {
421
+ self . text . len ( )
422
+ }
423
+ }
424
+
381
425
impl Format < JsFormatContext > for JsxWord {
382
426
fn fmt ( & self , f : & mut Formatter < JsFormatContext > ) -> FormatResult < ( ) > {
383
427
f. write_element ( FormatElement :: Text ( Text :: SyntaxTokenTextSlice {
@@ -645,6 +689,44 @@ mod tests {
645
689
assert_eq ! ( children[ 3 ] , JsxChild :: Whitespace ) ;
646
690
}
647
691
692
+ #[ test]
693
+ fn split_children_remove_in_row_jsx_whitespaces ( ) {
694
+ let child_list = parse_jsx_children ( r#"a{' '}{' '}{' '}c{" "}{' '}{" "}"# ) ;
695
+
696
+ let children = jsx_split_children ( & child_list) . unwrap ( ) ;
697
+
698
+ assert_eq ! (
699
+ 4 ,
700
+ children. len( ) ,
701
+ "Expected to contain four elements. Actual:\n {children:#?} "
702
+ ) ;
703
+ assert_word ( & children[ 0 ] , "a" ) ;
704
+ assert_eq ! ( children[ 1 ] , JsxChild :: Whitespace ) ;
705
+ assert_word ( & children[ 2 ] , "c" ) ;
706
+ assert_eq ! ( children[ 3 ] , JsxChild :: Whitespace ) ;
707
+ }
708
+
709
+ #[ test]
710
+ fn split_children_remove_new_line_before_jsx_whitespaces ( ) {
711
+ let child_list = parse_jsx_children (
712
+ r#"a
713
+ {' '}c{" "}
714
+ "# ,
715
+ ) ;
716
+
717
+ let children = jsx_split_children ( & child_list) . unwrap ( ) ;
718
+
719
+ assert_eq ! (
720
+ 4 ,
721
+ children. len( ) ,
722
+ "Expected to contain four elements. Actual:\n {children:#?} "
723
+ ) ;
724
+ assert_word ( & children[ 0 ] , "a" ) ;
725
+ assert_eq ! ( children[ 1 ] , JsxChild :: Whitespace ) ;
726
+ assert_word ( & children[ 2 ] , "c" ) ;
727
+ assert_eq ! ( children[ 3 ] , JsxChild :: Whitespace ) ;
728
+ }
729
+
648
730
fn assert_word ( child : & JsxChild , text : & str ) {
649
731
match child {
650
732
JsxChild :: Word ( word) => {
0 commit comments