@@ -8,7 +8,7 @@ use foundry_common::{
88 comments:: { Comment , CommentStyle , Comments , estimate_line_width, line_with_tabs} ,
99 iter:: IterDelimited ,
1010} ;
11- use foundry_config:: fmt:: IndentStyle ;
11+ use foundry_config:: fmt:: { DocCommentStyle , IndentStyle } ;
1212use solar:: parse:: {
1313 ast:: { self , Span } ,
1414 interface:: { BytePos , SourceMap } ,
@@ -480,9 +480,30 @@ impl<'sess> State<'sess, '_> {
480480 let config_cache = config;
481481 let mut buffered_blank = None ;
482482 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 ( ) ;
484484 let style_cache = cmnt. style ;
485485
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+
486507 // Ensure breaks are never skipped when there are multiple comments
487508 if self . peek_comment_before ( pos) . is_some ( ) {
488509 config. iso_no_break = false ;
@@ -662,6 +683,11 @@ impl<'sess> State<'sess, '_> {
662683
663684 fn print_comment ( & mut self , mut cmnt : Comment , mut config : CommentConfig ) {
664685 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+
665691 match cmnt. style {
666692 CommentStyle :: Mixed => {
667693 let Some ( prefix) = cmnt. prefix ( ) else { return } ;
@@ -1056,3 +1082,47 @@ fn snippet_with_tabs(s: String, tab_width: usize) -> String {
10561082
10571083 formatted
10581084}
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