@@ -8,7 +8,7 @@ use foundry_common::{
8
8
comments:: { Comment , CommentStyle , Comments , estimate_line_width, line_with_tabs} ,
9
9
iter:: IterDelimited ,
10
10
} ;
11
- use foundry_config:: fmt:: IndentStyle ;
11
+ use foundry_config:: fmt:: { DocCommentStyle , IndentStyle } ;
12
12
use solar:: parse:: {
13
13
ast:: { self , Span } ,
14
14
interface:: { BytePos , SourceMap } ,
@@ -480,9 +480,30 @@ impl<'sess> State<'sess, '_> {
480
480
let config_cache = config;
481
481
let mut buffered_blank = None ;
482
482
while self . peek_comment ( ) . is_some_and ( |c| c. pos ( ) < pos) {
483
- let cmnt = self . next_comment ( ) . unwrap ( ) ;
483
+ let mut cmnt = self . next_comment ( ) . unwrap ( ) ;
484
484
let style_cache = cmnt. style ;
485
485
486
+ // Merge consecutive line doc comments when converting to block style
487
+ if self . config . docs_style == foundry_config:: fmt:: DocCommentStyle :: Block
488
+ && cmnt. is_doc
489
+ && cmnt. kind == ast:: CommentKind :: Line
490
+ {
491
+ let mut ref_line = self . sm . lookup_char_pos ( cmnt. span . hi ( ) ) . line ;
492
+ while let Some ( next_cmnt) = self . peek_comment ( ) {
493
+ if !next_cmnt. is_doc
494
+ || next_cmnt. kind != ast:: CommentKind :: Line
495
+ || ref_line + 1 != self . sm . lookup_char_pos ( next_cmnt. span . lo ( ) ) . line
496
+ {
497
+ break ;
498
+ }
499
+
500
+ let next_to_merge = self . next_comment ( ) . unwrap ( ) ;
501
+ cmnt. lines . extend ( next_to_merge. lines ) ;
502
+ cmnt. span = cmnt. span . to ( next_to_merge. span ) ;
503
+ ref_line += 1 ;
504
+ }
505
+ }
506
+
486
507
// Ensure breaks are never skipped when there are multiple comments
487
508
if self . peek_comment_before ( pos) . is_some ( ) {
488
509
config. iso_no_break = false ;
@@ -662,6 +683,11 @@ impl<'sess> State<'sess, '_> {
662
683
663
684
fn print_comment ( & mut self , mut cmnt : Comment , mut config : CommentConfig ) {
664
685
self . cursor . advance_to ( cmnt. span . hi ( ) , true ) ;
686
+
687
+ if cmnt. is_doc {
688
+ cmnt = style_doc_comment ( self . config . docs_style , cmnt) ;
689
+ }
690
+
665
691
match cmnt. style {
666
692
CommentStyle :: Mixed => {
667
693
let Some ( prefix) = cmnt. prefix ( ) else { return } ;
@@ -1056,3 +1082,47 @@ fn snippet_with_tabs(s: String, tab_width: usize) -> String {
1056
1082
1057
1083
formatted
1058
1084
}
1085
+
1086
+ /// Formats a doc comment with the requested style.
1087
+ ///
1088
+ /// NOTE: assumes comments have already been normalized.
1089
+ fn style_doc_comment ( style : DocCommentStyle , mut cmnt : Comment ) -> Comment {
1090
+ match style {
1091
+ DocCommentStyle :: Line if cmnt. kind == ast:: CommentKind :: Block => {
1092
+ let mut new_lines = Vec :: new ( ) ;
1093
+ for ( pos, line) in cmnt. lines . iter ( ) . delimited ( ) {
1094
+ if pos. is_first || pos. is_last {
1095
+ // Skip the opening '/**' and closing '*/' lines
1096
+ continue ;
1097
+ }
1098
+
1099
+ // Convert ' * {content}' to '/// {content}'
1100
+ let trimmed = line. trim_start ( ) ;
1101
+ if let Some ( content) = trimmed. strip_prefix ( '*' ) {
1102
+ new_lines. push ( format ! ( "///{content}" ) ) ;
1103
+ } else if !trimmed. is_empty ( ) {
1104
+ new_lines. push ( format ! ( "/// {trimmed}" ) ) ;
1105
+ }
1106
+ }
1107
+
1108
+ cmnt. lines = new_lines;
1109
+ cmnt. kind = ast:: CommentKind :: Line ;
1110
+ cmnt
1111
+ }
1112
+ DocCommentStyle :: Block if cmnt. kind == ast:: CommentKind :: Line => {
1113
+ let mut new_lines = vec ! [ "/**" . to_string( ) ] ;
1114
+
1115
+ for line in & cmnt. lines {
1116
+ // Convert '/// {content}' to ' * {content}'
1117
+ new_lines. push ( format ! ( " *{content}" , content = & line[ 3 ..] ) )
1118
+ }
1119
+
1120
+ new_lines. push ( " */" . to_string ( ) ) ;
1121
+ cmnt. lines = new_lines;
1122
+ cmnt. kind = ast:: CommentKind :: Block ;
1123
+ cmnt
1124
+ }
1125
+ // Otherwise, no conversion needed.
1126
+ _ => cmnt,
1127
+ }
1128
+ }
0 commit comments