@@ -14,7 +14,7 @@ extension Trivia {
1414 /// The contents of the last doc comment piece with any comment markers removed and indentation whitespace stripped.
1515 public var docCommentValue : String ? {
1616 var comments : [ Substring ] = [ ]
17- var currentLineComments : [ Substring ] = [ ]
17+ var currentLineComments : [ String ] = [ ] // Reset line comments when encountering a block comment
1818 var isInsideDocLineCommentSection = false
1919 var consecutiveNewlines = 0
2020
@@ -28,22 +28,19 @@ extension Trivia {
2828 consecutiveNewlines = 0
2929 case . docLineComment( let text) :
3030 if isInsideDocLineCommentSection {
31- currentLineComments. append ( text [ ... ] )
31+ currentLineComments. append ( text)
3232 } else {
33- currentLineComments = [ text [ ... ] ]
33+ currentLineComments = [ text]
3434 isInsideDocLineCommentSection = true
3535 }
3636 consecutiveNewlines = 0
3737
3838 case . newlines( let n) , . carriageReturns( let n) , . carriageReturnLineFeeds( let n) :
39- if n == 1 {
40- consecutiveNewlines += 1
41- } else {
42- consecutiveNewlines = 0
43- }
39+ consecutiveNewlines += n
4440
4541 if consecutiveNewlines != 1 {
4642 processSectionBreak ( )
43+ consecutiveNewlines = 0
4744 }
4845 default :
4946 processSectionBreak ( )
@@ -56,45 +53,60 @@ extension Trivia {
5653 var lines = text. dropPrefix ( " /**").dropSuffix("*/" )
5754 . split ( omittingEmptySubsequences: false , whereSeparator: \. isNewline)
5855
56+ // If the comment content starts on the same line as the `/**` marker or ends on the same line as the `*/` marker,
57+ // it is common to separate the marker and the actual comment using spaces. Strip those spaces if they exists.
58+ // If there are non no-space characters on the first / last line, then the comment doesn't start / end on the line
59+ // with the marker, so don't do the stripping.
5960 if let firstLine = lines. first, firstLine. contains ( where: { $0 != " " } ) {
6061 lines [ 0 ] = firstLine. drop { $0 == " " }
6162 }
6263 if let lastLine = lines. last, lastLine. contains ( where: { $0 != " " } ) {
6364 lines [ lines. count - 1 ] = lastLine. dropLast { $0 == " " }
6465 }
6566
67+ // Find the lowest indentation that is common among all lines in the block comment. Do not consider the first line
68+ // because it won't have any indentation since it starts with /**
6669 let indentation = lines. dropFirst ( )
6770 . map { $0. prefix ( while: { $0 == " " || $0 == " \t " } ) }
6871 . reduce ( nil as Substring ? ) { ( acc: Substring ? , element: Substring . SubSequence ) in
6972 acc. map { commonPrefix ( $0, element) } ?? element
7073 }
7174
7275 guard let firstLine = lines. first else {
76+ // We did not have any lines. This should never happen in practice because `split` never returns an empty array
77+ // but be safe and return `nil` here anyway.
7378 return nil
7479 }
7580
7681 var unindentedLines = [ firstLine] + lines. dropFirst ( ) . map { $0. dropPrefix ( indentation ?? " " ) }
7782
78- while unindentedLines. first? . allSatisfy ( { $0 == " " } ) == true {
83+ // If the first line only contained the comment marker, don't include it. We don't want to start the comment value
84+ // with a newline if `/**` is on its own line. Same for the end marker.
85+ if unindentedLines. first? . allSatisfy ( { $0 == " " } ) ?? false {
7986 unindentedLines. removeFirst ( )
8087 }
81- while unindentedLines. last? . allSatisfy ( { $0 == " " } ) == true {
88+ if unindentedLines. last? . allSatisfy ( { $0 == " " } ) ?? false {
8289 unindentedLines. removeLast ( )
8390 }
8491
92+ // We canonicalize the line endings to `\n` here. This matches how we concatenate the different line comment
93+ // pieces using \n as well.
8594 return Substring ( unindentedLines. joined ( separator: " \n " ) )
8695 }
8796
97+ /// Processes a section break, which is defined as a sequence of newlines or other trivia pieces that are not comments.
8898 func processSectionBreak( ) {
99+ // If we have a section break, we reset the current line comments.
89100 if !currentLineComments. isEmpty {
90- comments = currentLineComments
101+ comments = currentLineComments. map { $0 [ ... ] }
91102 currentLineComments = [ ]
92103 }
93104 isInsideDocLineCommentSection = false
94105 }
95106
107+ // If there are remaining line comments, use them as the last doc comment block.
96108 if !currentLineComments. isEmpty {
97- comments = currentLineComments
109+ comments = currentLineComments. map { $0 [ ... ] }
98110 }
99111
100112 if comments. isEmpty { return nil }
@@ -125,6 +137,6 @@ fileprivate extension StringProtocol where SubSequence == Substring {
125137 }
126138}
127139
128- fileprivate func commonPrefix( _ lhs: Substring , _ rhs: Substring ) -> Substring {
140+ private func commonPrefix( _ lhs: Substring , _ rhs: Substring ) -> Substring {
129141 return lhs [ ..< lhs. index ( lhs. startIndex, offsetBy: zip ( lhs, rhs) . prefix { $0 == $1 } . count) ]
130142}
0 commit comments