Skip to content

Commit f46495f

Browse files
committed
Implement Trivia.docCommentValue to process doc comments
1 parent 5ed8c75 commit f46495f

File tree

4 files changed

+103
-257
lines changed

4 files changed

+103
-257
lines changed

Release Notes/602.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
- Pull request: https://github.com/swiftlang/swift-syntax/pull/3030
2626
- Migration stems: None required.
2727

28-
- `Trivia` has a new `commentValue` property.
29-
- Description: Extracts sanitized comment text from comment trivia pieces, omitting leading comment markers (`//`, `///`, `/*`, `*/`).
28+
- `Trivia` has a new `docCommentValue` property.
29+
- Description: Extracts sanitized comment text from doc comment trivia pieces, omitting leading comment markers (`///`, `/**`).
3030
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/2966
3131

3232
## API Behavior Changes

Sources/SwiftSyntax/Trivia+commentValue.swift

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,27 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
extension Trivia {
14-
/// The contents of all the comment pieces with any comments markers removed and indentation whitespace stripped.
15-
public var commentValue: String? {
14+
/// The contents of the last doc comment piece with any comment markers removed and indentation whitespace stripped.
15+
public var docCommentValue: String? {
1616
var comments: [Substring] = []
1717

18-
/// Keep track of whether we have seen a line or block comment trivia piece. If this `Trivia` contains both a block
19-
/// and a line comment, we don't know how to concatenate them to form the comment value and thus default to
20-
/// returning `nil`.
18+
/// Keep track of whether we have seen a line or block comment trivia piece.
2119
var hasBlockComment = false
22-
var hasLineComment = false
2320

24-
// Determine if all line comments have a space separating the `//` or `///` comment marker and the actual comment.
21+
// Determine if all line comments have a space separating the `///` comment marker and the actual comment.
2522
lazy var allLineCommentsHaveSpace: Bool = pieces.allSatisfy { piece in
2623
switch piece {
27-
case .lineComment(let text): return text.hasPrefix("// ")
2824
case .docLineComment(let text): return text.hasPrefix("/// ")
2925
default: return true
3026
}
3127
}
3228

33-
// Strips /* */ markers and remove any common indentation between the lines in the block comment.
34-
func processBlockComment(_ text: String, isDocComment: Bool) -> String? {
35-
var lines = text.dropPrefix(isDocComment ? "/**" : "/*").dropSuffix("*/")
29+
// Strips /** */ markers and removes any common indentation between the lines in the block comment.
30+
func processBlockComment(_ text: String) -> Substring? {
31+
var lines = text.dropPrefix("/**").dropSuffix("*/")
3632
.split(omittingEmptySubsequences: false, whereSeparator: \.isNewline)
3733

38-
// If the comment content starts on the same line as the `/*` marker or ends on the same line as the `*/` marker,
34+
// If the comment content starts on the same line as the `/**` marker or ends on the same line as the `*/` marker,
3935
// it is common to separate the marker and the actual comment using spaces. Strip those spaces if they exists.
4036
// If there are non no-space characters on the first / last line, then the comment doesn't start / end on the line
4137
// with the marker, so don't do the stripping.
@@ -48,7 +44,7 @@ extension Trivia {
4844

4945
var indentation: Substring? = nil
5046
// Find the lowest indentation that is common among all lines in the block comment. Do not consider the first line
51-
// because it won't have any indentation since it starts with /*
47+
// because it won't have any indentation since it starts with /**
5248
for line in lines.dropFirst() {
5349
let lineIndentation = line.prefix(while: { $0 == " " || $0 == "\t" })
5450
guard let previousIndentation = indentation else {
@@ -67,7 +63,7 @@ extension Trivia {
6763
var unindentedLines = [firstLine] + lines.dropFirst().map { $0.dropPrefix(indentation ?? "") }
6864

6965
// If the first line only contained the comment marker, don't include it. We don't want to start the comment value
70-
// with a newline if `/*` is on its own line. Same for the end marker.
66+
// with a newline if `/**` is on its own line. Same for the end marker.
7167
if unindentedLines.first?.allSatisfy({ $0 == " " }) ?? false {
7268
unindentedLines.removeFirst()
7369
}
@@ -76,34 +72,48 @@ extension Trivia {
7672
}
7773
// We canonicalize the line endings to `\n` here. This matches how we concatenate the different line comment
7874
// pieces using \n as well.
79-
return unindentedLines.joined(separator: "\n")
75+
return unindentedLines.joined(separator: "\n")[...]
8076
}
8177

78+
var currentLineComments: [Substring] = []
79+
var foundStop = false
8280
for piece in pieces {
8381
switch piece {
84-
case .blockComment(let text), .docBlockComment(let text):
85-
if hasBlockComment || hasLineComment {
86-
return nil
82+
case .docBlockComment(let text):
83+
if let processedComment = processBlockComment(text) {
84+
if hasBlockComment {
85+
comments.append(processedComment)
86+
} else {
87+
hasBlockComment = true
88+
comments = [processedComment]
89+
}
8790
}
88-
hasBlockComment = true
89-
guard let processedText = processBlockComment(text, isDocComment: piece.isDocComment) else {
90-
return nil
91+
currentLineComments = [] // Reset line comments when encountering a block comment
92+
case .docLineComment(let text):
93+
let prefixToDrop = ("///") + (allLineCommentsHaveSpace ? " " : "")
94+
if foundStop {
95+
currentLineComments = [text.dropPrefix(prefixToDrop)]
96+
foundStop = false
97+
} else {
98+
currentLineComments.append(text.dropPrefix(prefixToDrop))
9199
}
92-
comments.append(processedText[...])
93-
case .lineComment(let text), .docLineComment(let text):
94-
if hasBlockComment {
95-
return nil
96-
}
97-
hasLineComment = true
98-
let prefixToDrop = (piece.isDocComment ? "///" : "//") + (allLineCommentsHaveSpace ? " " : "")
99-
comments.append(text.dropPrefix(prefixToDrop))
100+
case .newlines(let count) where count == 1:
101+
continue
100102
default:
101-
break
103+
if !currentLineComments.isEmpty {
104+
comments = currentLineComments
105+
}
106+
currentLineComments = []
107+
foundStop = true
102108
}
103109
}
104110

105-
if comments.isEmpty { return nil }
111+
// If there are remaining line comments, use them as the last doc comment block.
112+
if !currentLineComments.isEmpty {
113+
comments = currentLineComments
114+
}
106115

116+
if comments.isEmpty { return nil }
107117
return comments.joined(separator: "\n")
108118
}
109119
}
@@ -132,12 +142,3 @@ fileprivate extension StringProtocol where SubSequence == Substring {
132142
fileprivate func commonPrefix(_ lhs: Substring, _ rhs: Substring) -> Substring {
133143
return lhs[..<lhs.index(lhs.startIndex, offsetBy: zip(lhs, rhs).prefix { $0 == $1 }.count)]
134144
}
135-
136-
fileprivate extension TriviaPiece {
137-
var isDocComment: Bool {
138-
switch self {
139-
case .docBlockComment, .docLineComment: return true
140-
default: return false
141-
}
142-
}
143-
}

0 commit comments

Comments
 (0)