@@ -52,6 +52,27 @@ fn custom_opener(s: &str) -> &str {
52
52
}
53
53
54
54
impl < ' a > CommentStyle < ' a > {
55
+ /// Returns true if the commenting style covers a line only.
56
+ pub fn is_line_comment ( & self ) -> bool {
57
+ match * self {
58
+ CommentStyle :: DoubleSlash
59
+ | CommentStyle :: TripleSlash
60
+ | CommentStyle :: Doc
61
+ | CommentStyle :: Custom ( _) => true ,
62
+ _ => false ,
63
+ }
64
+ }
65
+
66
+ /// Returns true if the commenting style can span over multiple lines.
67
+ pub fn is_block_comment ( & self ) -> bool {
68
+ match * self {
69
+ CommentStyle :: SingleBullet | CommentStyle :: DoubleBullet | CommentStyle :: Exclamation => {
70
+ true
71
+ }
72
+ _ => false ,
73
+ }
74
+ }
75
+
55
76
pub fn is_doc_comment ( & self ) -> bool {
56
77
match * self {
57
78
CommentStyle :: TripleSlash | CommentStyle :: Doc => true ,
@@ -213,7 +234,7 @@ pub fn combine_strs_with_missing_comments(
213
234
}
214
235
215
236
pub fn rewrite_doc_comment ( orig : & str , shape : Shape , config : & Config ) -> Option < String > {
216
- _rewrite_comment ( orig, false , shape, config, true )
237
+ identify_comment ( orig, false , shape, config, true )
217
238
}
218
239
219
240
pub fn rewrite_comment (
@@ -222,32 +243,7 @@ pub fn rewrite_comment(
222
243
shape : Shape ,
223
244
config : & Config ,
224
245
) -> Option < String > {
225
- _rewrite_comment ( orig, block_style, shape, config, false )
226
- }
227
-
228
- fn _rewrite_comment (
229
- orig : & str ,
230
- block_style : bool ,
231
- shape : Shape ,
232
- config : & Config ,
233
- is_doc_comment : bool ,
234
- ) -> Option < String > {
235
- // If there are lines without a starting sigil, we won't format them correctly
236
- // so in that case we won't even re-align (if !config.normalize_comments()) and
237
- // we should stop now.
238
- let num_bare_lines = orig
239
- . lines ( )
240
- . map ( |line| line. trim ( ) )
241
- . filter ( |l| !( l. starts_with ( '*' ) || l. starts_with ( "//" ) || l. starts_with ( "/*" ) ) )
242
- . count ( ) ;
243
- if num_bare_lines > 0 && !config. normalize_comments ( ) {
244
- return Some ( orig. to_owned ( ) ) ;
245
- }
246
- if !config. normalize_comments ( ) && !config. wrap_comments ( ) {
247
- return light_rewrite_comment ( orig, shape. indent , config, is_doc_comment) ;
248
- }
249
-
250
- identify_comment ( orig, block_style, shape, config, is_doc_comment)
246
+ identify_comment ( orig, block_style, shape, config, false )
251
247
}
252
248
253
249
fn identify_comment (
@@ -258,8 +254,8 @@ fn identify_comment(
258
254
is_doc_comment : bool ,
259
255
) -> Option < String > {
260
256
let style = comment_style ( orig, false ) ;
261
- let mut first_group_ending = 0 ;
262
257
258
+ // Computes the len of line taking into account a newline if the line is part of a paragraph.
263
259
fn compute_len ( orig : & str , line : & str ) -> usize {
264
260
if orig. len ( ) > line. len ( ) {
265
261
if orig. as_bytes ( ) [ line. len ( ) ] == b'\r' {
@@ -272,62 +268,145 @@ fn identify_comment(
272
268
}
273
269
}
274
270
275
- match style {
271
+ // Get the first group of line comments having the same commenting style.
272
+ //
273
+ // Returns a tuple with:
274
+ // - a boolean indicating if there is a blank line
275
+ // - a number indicating the size of the first group of comments
276
+ fn consume_same_line_comments (
277
+ style : CommentStyle ,
278
+ orig : & str ,
279
+ line_start : & str ,
280
+ ) -> ( bool , usize ) {
281
+ let mut first_group_ending = 0 ;
282
+ let mut hbl = false ;
283
+
284
+ for line in orig. lines ( ) {
285
+ let trimmed_line = line. trim_left ( ) ;
286
+ if trimmed_line. is_empty ( ) {
287
+ hbl = true ;
288
+ break ;
289
+ } else if trimmed_line. starts_with ( line_start)
290
+ || comment_style ( trimmed_line, false ) == style
291
+ {
292
+ first_group_ending += compute_len ( & orig[ first_group_ending..] , line) ;
293
+ } else {
294
+ break ;
295
+ }
296
+ }
297
+ ( hbl, first_group_ending)
298
+ }
299
+
300
+ let ( has_bare_lines, first_group_ending) = match style {
276
301
CommentStyle :: DoubleSlash | CommentStyle :: TripleSlash | CommentStyle :: Doc => {
277
302
let line_start = style. line_start ( ) . trim_left ( ) ;
278
- for line in orig. lines ( ) {
279
- if line. trim_left ( ) . starts_with ( line_start) || comment_style ( line, false ) == style {
280
- first_group_ending += compute_len ( & orig[ first_group_ending..] , line) ;
281
- } else {
282
- break ;
283
- }
284
- }
303
+ consume_same_line_comments ( style, orig, line_start)
285
304
}
286
305
CommentStyle :: Custom ( opener) => {
287
306
let trimmed_opener = opener. trim_right ( ) ;
288
- for line in orig. lines ( ) {
289
- if line. trim_left ( ) . starts_with ( trimmed_opener) {
290
- first_group_ending += compute_len ( & orig[ first_group_ending..] , line) ;
291
- } else {
292
- break ;
293
- }
294
- }
307
+ consume_same_line_comments ( style, orig, trimmed_opener)
295
308
}
296
309
// for a block comment, search for the closing symbol
297
310
CommentStyle :: DoubleBullet | CommentStyle :: SingleBullet | CommentStyle :: Exclamation => {
298
311
let closer = style. closer ( ) . trim_left ( ) ;
312
+ let mut closing_symbol_offset = 0 ;
313
+ let mut hbl = false ;
299
314
for line in orig. lines ( ) {
300
- first_group_ending += compute_len ( & orig[ first_group_ending..] , line) ;
301
- if line. trim_left ( ) . ends_with ( closer) {
315
+ closing_symbol_offset += compute_len ( & orig[ closing_symbol_offset..] , line) ;
316
+ let trimmed_line = line. trim_left ( ) ;
317
+ if !trimmed_line. starts_with ( '*' )
318
+ && !trimmed_line. starts_with ( "//" )
319
+ && !trimmed_line. starts_with ( "/*" )
320
+ {
321
+ hbl = true ;
322
+ }
323
+ if trimmed_line. ends_with ( closer) {
302
324
break ;
303
325
}
304
326
}
327
+ ( hbl, closing_symbol_offset)
305
328
}
306
- }
329
+ } ;
307
330
308
331
let ( first_group, rest) = orig. split_at ( first_group_ending) ;
309
- let first_group_str = rewrite_comment_inner (
310
- first_group,
311
- block_style,
312
- style,
313
- shape,
314
- config,
315
- is_doc_comment || style. is_doc_comment ( ) ,
316
- ) ?;
332
+ let rewritten_first_group =
333
+ if !config. normalize_comments ( ) && has_bare_lines && style. is_block_comment ( ) {
334
+ light_rewrite_block_comment_with_bare_lines ( first_group, shape, config) ?
335
+ } else if !config. normalize_comments ( ) && !config. wrap_comments ( ) {
336
+ light_rewrite_comment ( first_group, shape. indent , config, is_doc_comment) ?
337
+ } else {
338
+ rewrite_comment_inner (
339
+ first_group,
340
+ block_style,
341
+ style,
342
+ shape,
343
+ config,
344
+ is_doc_comment || style. is_doc_comment ( ) ,
345
+ ) ?
346
+ } ;
317
347
if rest. is_empty ( ) {
318
- Some ( first_group_str )
348
+ Some ( rewritten_first_group )
319
349
} else {
320
- identify_comment ( rest, block_style, shape, config, is_doc_comment) . map ( |rest_str| {
321
- format ! (
322
- "{}\n {}{}" ,
323
- first_group_str,
324
- shape. indent. to_string( config) ,
325
- rest_str
326
- )
327
- } )
350
+ identify_comment ( rest. trim_left ( ) , block_style, shape, config, is_doc_comment) . map (
351
+ |rest_str| {
352
+ format ! (
353
+ "{}\n {}{}{}" ,
354
+ rewritten_first_group,
355
+ // insert back the blank line
356
+ if has_bare_lines && style. is_line_comment( ) {
357
+ "\n "
358
+ } else {
359
+ ""
360
+ } ,
361
+ shape. indent. to_string( config) ,
362
+ rest_str
363
+ )
364
+ } ,
365
+ )
328
366
}
329
367
}
330
368
369
+ /// Trims a minimum of leading whitespaces so that the content layout is kept and aligns to indent.
370
+ fn light_rewrite_block_comment_with_bare_lines (
371
+ orig : & str ,
372
+ shape : Shape ,
373
+ config : & Config ,
374
+ ) -> Option < String > {
375
+ let prefix_whitespace_min = orig
376
+ . lines ( )
377
+ // skip the line with the starting sigil since the leading whitespace is removed
378
+ // otherwise, the minimum would always be zero
379
+ . skip ( 1 )
380
+ . filter ( |line| !line. is_empty ( ) )
381
+ . map ( |line| {
382
+ let mut width = 0 ;
383
+ for c in line. chars ( ) {
384
+ match c {
385
+ ' ' => width += 1 ,
386
+ '\t' => width += config. tab_spaces ( ) ,
387
+ _ => break ,
388
+ }
389
+ }
390
+ width
391
+ } )
392
+ . min ( ) ?;
393
+
394
+ let indent_str = shape. indent . to_string ( config) ;
395
+ let mut lines = orig. lines ( ) ;
396
+ let first_line = lines. next ( ) ?;
397
+ let rest = lines
398
+ . map ( |line| {
399
+ if line. is_empty ( ) {
400
+ line
401
+ } else {
402
+ & line[ prefix_whitespace_min..]
403
+ }
404
+ } )
405
+ . collect :: < Vec < & str > > ( )
406
+ . join ( & format ! ( "\n {}" , indent_str) ) ;
407
+ Some ( format ! ( "{}\n {}{}" , first_line, indent_str, rest) )
408
+ }
409
+
331
410
fn rewrite_comment_inner (
332
411
orig : & str ,
333
412
block_style : bool ,
@@ -1413,26 +1492,29 @@ mod test {
1413
1492
#[ test]
1414
1493
#[ rustfmt:: skip]
1415
1494
fn format_comments ( ) {
1416
- let mut config: :: config:: Config = Default :: default ( ) ;
1417
- config. set ( ) . wrap_comments ( true ) ;
1418
- config. set ( ) . normalize_comments ( true ) ;
1495
+ let mut wrap_normalize_config: :: config:: Config = Default :: default ( ) ;
1496
+ wrap_normalize_config. set ( ) . wrap_comments ( true ) ;
1497
+ wrap_normalize_config. set ( ) . normalize_comments ( true ) ;
1498
+
1499
+ let mut wrap_config: :: config:: Config = Default :: default ( ) ;
1500
+ wrap_config. set ( ) . wrap_comments ( true ) ;
1419
1501
1420
1502
let comment = rewrite_comment ( " //test" ,
1421
1503
true ,
1422
1504
Shape :: legacy ( 100 , Indent :: new ( 0 , 100 ) ) ,
1423
- & config ) . unwrap ( ) ;
1505
+ & wrap_normalize_config ) . unwrap ( ) ;
1424
1506
assert_eq ! ( "/* test */" , comment) ;
1425
1507
1426
1508
let comment = rewrite_comment ( "// comment on a" ,
1427
1509
false ,
1428
1510
Shape :: legacy ( 10 , Indent :: empty ( ) ) ,
1429
- & config ) . unwrap ( ) ;
1511
+ & wrap_normalize_config ) . unwrap ( ) ;
1430
1512
assert_eq ! ( "// comment\n // on a" , comment) ;
1431
1513
1432
1514
let comment = rewrite_comment ( "// A multi line comment\n // between args." ,
1433
1515
false ,
1434
1516
Shape :: legacy ( 60 , Indent :: new ( 0 , 12 ) ) ,
1435
- & config ) . unwrap ( ) ;
1517
+ & wrap_normalize_config ) . unwrap ( ) ;
1436
1518
assert_eq ! ( "// A multi line comment\n // between args." , comment) ;
1437
1519
1438
1520
let input = "// comment" ;
@@ -1441,14 +1523,55 @@ mod test {
1441
1523
let comment = rewrite_comment ( input,
1442
1524
true ,
1443
1525
Shape :: legacy ( 9 , Indent :: new ( 0 , 69 ) ) ,
1444
- & config ) . unwrap ( ) ;
1526
+ & wrap_normalize_config ) . unwrap ( ) ;
1445
1527
assert_eq ! ( expected, comment) ;
1446
1528
1447
1529
let comment = rewrite_comment ( "/* trimmed */" ,
1448
1530
true ,
1449
1531
Shape :: legacy ( 100 , Indent :: new ( 0 , 100 ) ) ,
1450
- & config ) . unwrap ( ) ;
1532
+ & wrap_normalize_config ) . unwrap ( ) ;
1451
1533
assert_eq ! ( "/* trimmed */" , comment) ;
1534
+
1535
+ // check that different comment style are properly recognised
1536
+ let comment = rewrite_comment ( r#"/// test1
1537
+ /// test2
1538
+ /*
1539
+ * test3
1540
+ */"# ,
1541
+ false ,
1542
+ Shape :: legacy ( 100 , Indent :: new ( 0 , 0 ) ) ,
1543
+ & wrap_normalize_config) . unwrap ( ) ;
1544
+ assert_eq ! ( "/// test1\n /// test2\n // test3" , comment) ;
1545
+
1546
+ // check that the blank line marks the end of a commented paragraph
1547
+ let comment = rewrite_comment ( r#"// test1
1548
+
1549
+ // test2"# ,
1550
+ false ,
1551
+ Shape :: legacy ( 100 , Indent :: new ( 0 , 0 ) ) ,
1552
+ & wrap_normalize_config) . unwrap ( ) ;
1553
+ assert_eq ! ( "// test1\n \n // test2" , comment) ;
1554
+
1555
+ // check that the blank line marks the end of a custom-commented paragraph
1556
+ let comment = rewrite_comment ( r#"//@ test1
1557
+
1558
+ //@ test2"# ,
1559
+ false ,
1560
+ Shape :: legacy ( 100 , Indent :: new ( 0 , 0 ) ) ,
1561
+ & wrap_normalize_config) . unwrap ( ) ;
1562
+ assert_eq ! ( "//@ test1\n \n //@ test2" , comment) ;
1563
+
1564
+ // check that bare lines are just indented but left unchanged otherwise
1565
+ let comment = rewrite_comment ( r#"// test1
1566
+ /*
1567
+ a bare line!
1568
+
1569
+ another bare line!
1570
+ */"# ,
1571
+ false ,
1572
+ Shape :: legacy ( 100 , Indent :: new ( 0 , 0 ) ) ,
1573
+ & wrap_config) . unwrap ( ) ;
1574
+ assert_eq ! ( "// test1\n /*\n a bare line!\n \n another bare line!\n */" , comment) ;
1452
1575
}
1453
1576
1454
1577
// This is probably intended to be a non-test fn, but it is not used. I'm
0 commit comments