@@ -122,7 +122,7 @@ impl Format<JsFormatContext> for JsxSpace {
122
122
write ! [
123
123
formatter,
124
124
[
125
- if_group_breaks( & format_args![ JsxRawSpace , hard_line_break ( ) ] ) ,
125
+ if_group_breaks( & format_args![ JsxRawSpace , soft_line_break ( ) ] ) ,
126
126
if_group_fits_on_line( & space( ) )
127
127
]
128
128
]
@@ -173,7 +173,7 @@ pub(crate) fn jsx_split_children<I>(children: I) -> SyntaxResult<Vec<JsxChild>>
173
173
where
174
174
I : IntoIterator < Item = JsxAnyChild > ,
175
175
{
176
- let mut result = Vec :: new ( ) ;
176
+ let mut builder = JsxSplitChildrenBuilder :: default ( ) ;
177
177
178
178
for child in children. into_iter ( ) {
179
179
match child {
@@ -203,15 +203,15 @@ where
203
203
// </div>
204
204
// ```
205
205
if newlines > 1 {
206
- result . push ( JsxChild :: EmptyLine ) ;
206
+ builder . entry ( JsxChild :: EmptyLine ) ;
207
207
}
208
208
209
209
continue ;
210
210
}
211
211
212
- result . push ( JsxChild :: Newline )
213
- } else if ! matches ! ( result . last ( ) , Some ( JsxChild :: Whitespace ) ) {
214
- result . push ( JsxChild :: Whitespace )
212
+ builder . entry ( JsxChild :: Newline )
213
+ } else {
214
+ builder . entry ( JsxChild :: Whitespace )
215
215
}
216
216
}
217
217
_ => unreachable ! ( ) ,
@@ -224,15 +224,15 @@ where
224
224
// Only handle trailing whitespace. Words must always be joined by new lines
225
225
if chunks. peek ( ) . is_none ( ) {
226
226
if whitespace. contains ( '\n' ) {
227
- result . push ( JsxChild :: Newline ) ;
227
+ builder . entry ( JsxChild :: Newline ) ;
228
228
} else {
229
- result . push ( JsxChild :: Whitespace )
229
+ builder . entry ( JsxChild :: Whitespace )
230
230
}
231
231
}
232
232
}
233
233
234
234
( relative_start, JsxTextChunk :: Word ( word) ) => {
235
- result . push ( JsxChild :: Word ( JsxWord {
235
+ builder . entry ( JsxChild :: Word ( JsxWord {
236
236
text : value_token
237
237
. token_text ( )
238
238
. slice ( TextRange :: at ( relative_start, word. text_len ( ) ) ) ,
@@ -245,23 +245,61 @@ where
245
245
246
246
JsxAnyChild :: JsxExpressionChild ( child) => {
247
247
if is_whitespace_jsx_expression ( & child) {
248
- match result. last ( ) {
249
- Some ( JsxChild :: Whitespace ) => {
250
- // Ignore
251
- }
252
- _ => result. push ( JsxChild :: Whitespace ) ,
253
- }
248
+ builder. entry ( JsxChild :: Whitespace )
254
249
} else {
255
- result . push ( JsxChild :: NonText ( child. into ( ) ) )
250
+ builder . entry ( JsxChild :: NonText ( child. into ( ) ) )
256
251
}
257
252
}
258
253
child => {
259
- result. push ( JsxChild :: NonText ( child) ) ;
254
+ builder. entry ( JsxChild :: NonText ( child) ) ;
255
+ }
256
+ }
257
+ }
258
+
259
+ Ok ( builder. finish ( ) )
260
+ }
261
+
262
+ #[ derive( Debug , Default ) ]
263
+ struct JsxSplitChildrenBuilder {
264
+ pending : Option < JsxChild > ,
265
+ buffer : Vec < JsxChild > ,
266
+ }
267
+
268
+ impl JsxSplitChildrenBuilder {
269
+ fn is_last_whitespace ( & self ) -> bool {
270
+ matches ! ( self . buffer. last( ) , Some ( JsxChild :: Whitespace ) )
271
+ }
272
+
273
+ fn entry ( & mut self , child : JsxChild ) {
274
+ if let Some ( pending) = self . pending . take ( ) {
275
+ if matches ! (
276
+ pending,
277
+ JsxChild :: EmptyLine | JsxChild :: Newline | JsxChild :: Whitespace
278
+ ) {
279
+ if !matches ! ( child, JsxChild :: Whitespace ) && !self . is_last_whitespace ( ) {
280
+ self . buffer . push ( pending)
281
+ }
282
+ } else {
283
+ self . buffer . push ( pending) ;
260
284
}
261
285
}
286
+
287
+ self . pending = Some ( child) ;
262
288
}
263
289
264
- Ok ( result)
290
+ fn finish ( mut self ) -> Vec < JsxChild > {
291
+ if let Some ( pending) = self . pending . take ( ) {
292
+ if matches ! ( pending, JsxChild :: Whitespace ) {
293
+ if !self . is_last_whitespace ( ) {
294
+ self . buffer . push ( pending)
295
+ }
296
+ } else {
297
+ self . buffer . push ( pending) ;
298
+ }
299
+ }
300
+
301
+ self . buffer
302
+ }
265
303
}
266
304
267
305
#[ derive( Debug , Clone , Eq , PartialEq ) ]
@@ -328,6 +366,12 @@ pub(crate) struct JsxWord {
328
366
source_position : TextSize ,
329
367
}
330
368
369
+ impl JsxWord {
370
+ pub fn len ( & self ) -> TextSize {
371
+ self . text . len ( )
372
+ }
373
+ }
374
+
331
375
impl Format < JsFormatContext > for JsxWord {
332
376
fn fmt ( & self , f : & mut Formatter < JsFormatContext > ) -> FormatResult < ( ) > {
333
377
f. write_element ( FormatElement :: Text ( Text :: SyntaxTokenTextSlice {
@@ -585,6 +629,44 @@ mod tests {
585
629
assert_eq ! ( children[ 3 ] , JsxChild :: Whitespace ) ;
586
630
}
587
631
632
+ #[ test]
633
+ fn split_children_remove_in_row_jsx_whitespaces ( ) {
634
+ let child_list = parse_jsx_children ( r#"a{' '}{' '}{' '}c{" "}{' '}{" "}"# ) ;
635
+
636
+ let children = jsx_split_children ( & child_list) . unwrap ( ) ;
637
+
638
+ assert_eq ! (
639
+ 4 ,
640
+ children. len( ) ,
641
+ "Expected to contain four elements. Actual:\n {children:#?} "
642
+ ) ;
643
+ assert_word ( & children[ 0 ] , "a" ) ;
644
+ assert_eq ! ( children[ 1 ] , JsxChild :: Whitespace ) ;
645
+ assert_word ( & children[ 2 ] , "c" ) ;
646
+ assert_eq ! ( children[ 3 ] , JsxChild :: Whitespace ) ;
647
+ }
648
+
649
+ #[ test]
650
+ fn split_children_remove_new_line_before_jsx_whitespaces ( ) {
651
+ let child_list = parse_jsx_children (
652
+ r#"a
653
+ {' '}c{" "}
654
+ "# ,
655
+ ) ;
656
+
657
+ let children = jsx_split_children ( & child_list) . unwrap ( ) ;
658
+
659
+ assert_eq ! (
660
+ 4 ,
661
+ children. len( ) ,
662
+ "Expected to contain four elements. Actual:\n {children:#?} "
663
+ ) ;
664
+ assert_word ( & children[ 0 ] , "a" ) ;
665
+ assert_eq ! ( children[ 1 ] , JsxChild :: Whitespace ) ;
666
+ assert_word ( & children[ 2 ] , "c" ) ;
667
+ assert_eq ! ( children[ 3 ] , JsxChild :: Whitespace ) ;
668
+ }
669
+
588
670
fn assert_word ( child : & JsxChild , text : & str ) {
589
671
match child {
590
672
JsxChild :: Word ( word) => {
0 commit comments