@@ -387,6 +387,16 @@ extension Formatter {
387387 let markDeclaration = tokenize ( " \( indentation) // \( markCommentBody) " )
388388 let eligibleCommentRange = declaration. range. lowerBound ..< self . index ( of: . nonSpaceOrCommentOrLinebreak, after: declaration. range. lowerBound - 1 ) !
389389
390+ // Remove any comments other than the expected mark comment if present
391+ removeExistingCategorySeparators (
392+ from: declaration,
393+ previousDeclaration: index == 0 ? nil : sortedDeclarations [ index - 1 ] . declaration,
394+ order: order,
395+ preserving: { commentBody in
396+ commentBody == markCommentBody
397+ }
398+ )
399+
390400 let matchingComments = singleLineComments ( in: eligibleCommentRange, matching: { commentBody in
391401 commentBody == markCommentBody
392402 } )
@@ -403,12 +413,6 @@ extension Formatter {
403413 insertLinebreak ( at: tokenAfterComment)
404414 }
405415 } else {
406- removeExistingCategorySeparators (
407- from: declaration,
408- previousDeclaration: index == 0 ? nil : sortedDeclarations [ index - 1 ] . declaration,
409- order: order
410- )
411-
412416 insertLinebreak ( at: declaration. range. lowerBound)
413417 if options. lineAfterMarks {
414418 insertLinebreak ( at: declaration. range. lowerBound)
@@ -461,13 +465,25 @@ extension Formatter {
461465 func removeExistingCategorySeparators(
462466 from declaration: Declaration ,
463467 previousDeclaration: Declaration ? ,
464- order: ParsedOrder
468+ order: ParsedOrder ,
469+ preserving shouldPreserveComment: ( _ commentBody: String ) -> Bool = { _ in false }
465470 ) {
466471 var matchingComments = matchingCategorySeparatorComments ( in: declaration. leadingCommentRange, order: order)
472+ . map { $0. autoUpdating ( in: self ) }
473+ var preservedComment = false
467474
468475 while !matchingComments. isEmpty {
469476 let commentRange = matchingComments. removeFirst ( )
470477
478+ // Preserve the first comment matching the given closure
479+ if !preservedComment,
480+ let commentBody = index ( after: commentRange. lowerBound, where: \. isCommentBody) ,
481+ shouldPreserveComment ( tokens [ commentBody] . string)
482+ {
483+ preservedComment = true
484+ continue
485+ }
486+
471487 // Makes sure there are only whitespace or other comments before this comment.
472488 // Otherwise, we don't want to remove it.
473489 let tokensBeforeComment = tokens [ declaration. range. lowerBound ..< commentRange. lowerBound]
@@ -483,16 +499,10 @@ extension Formatter {
483499 let rangeToRemove = startOfCommentLine ..< startOfNextDeclaration
484500 removeTokens ( in: rangeToRemove)
485501
486- // We specifically iterate from start to end here, instead of in reverse,
487- // so we have to manually keep the existing indices up to date.
488- matchingComments = matchingComments. map { commentRange in
489- ( commentRange. lowerBound - rangeToRemove. count)
490- ... ( commentRange. upperBound - rangeToRemove. count)
491- }
492-
493502 // Move any tokens from before the category separator into the previous declaration.
494503 // This makes sure that things like comments stay grouped in the same category.
495- if let previousDeclaration, startOfCommentLine != 0 {
504+ // Don't do this is we preserved a previous comment, since this following comment is no longer the first one.
505+ if let previousDeclaration, startOfCommentLine != 0 , !preservedComment {
496506 // Remove the tokens before the category separator from this declaration...
497507 let rangeBeforeComment = min ( startOfCommentLine, declaration. range. lowerBound) ..< startOfCommentLine
498508 let tokensBeforeCommentLine = Array ( tokens [ rangeBeforeComment] )
0 commit comments