Skip to content

Commit 04f700e

Browse files
committed
Enhance comment trimming and update test cases
Add release note for Trivia.commentValue Address PR review Address second review
1 parent 085f207 commit 04f700e

File tree

4 files changed

+403
-338
lines changed

4 files changed

+403
-338
lines changed

Release Notes/602.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## New APIs
44

5+
- `Trivia` has a new `commentValue` property.
6+
- Description: Extracts sanitized comment text from comment trivia pieces, omitting leading comment markers (`//`, `///`, `/*`, `*/`).
7+
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/2966
8+
59
## API Behavior Changes
610

711
## Deprecations

Sources/SwiftSyntax/Trivia.swift

+86-42
Original file line numberDiff line numberDiff line change
@@ -43,65 +43,109 @@ public struct Trivia: Sendable {
4343
}
4444

4545
/// The string contents of all the comment pieces with any comments tokens trimmed.
46-
///
47-
/// Each element in the array is the trimmed contents of a line comment, or, in the case of a multi-line comment a trimmed, concatenated single string.
48-
public var commentValues: [String] {
46+
public var commentValue: String {
4947
var comments = [String]()
50-
var partialComments = [String]()
5148

52-
var foundStartOfCodeBlock = false
53-
var foundEndOfCodeBlock = false
54-
var isInCodeBlock: Bool { foundStartOfCodeBlock && !foundEndOfCodeBlock }
49+
// Determine if all line comments have a single space
50+
lazy var allLineCommentsHaveSpace: Bool = {
51+
return pieces.allSatisfy { piece in
52+
switch piece {
53+
case .lineComment(let text):
54+
return text.hasPrefix("// ")
55+
case .docLineComment(let text):
56+
return text.hasPrefix("/// ")
57+
default:
58+
return true
59+
}
60+
}
61+
}()
62+
63+
// Helper function to trim leading and trailing whitespace
64+
func trimWhitespace(_ text: String) -> String {
65+
let trimmed = text.drop(while: { $0 == " " })
66+
.reversed()
67+
.drop(while: { $0 == " " })
68+
.reversed()
69+
return String(trimmed)
70+
}
5571

56-
for piece in pieces {
57-
switch piece {
58-
case .blockComment(let text), .docBlockComment(let text):
59-
let text = text.trimmingCharacters(in: "\n")
72+
// Helper function to trim leading and trailing newlines
73+
func trimNewlines(_ text: String) -> String {
74+
let trimmed = text.drop(while: { $0 == "\n" })
75+
.reversed()
76+
.drop(while: { $0 == "\n" })
77+
.reversed()
78+
return String(trimmed)
79+
}
80+
81+
// Helper function to process block comments
82+
func processBlockComment(_ text: String, prefix: String, suffix: String) -> String {
83+
var text = text
84+
text.removeFirst(prefix.count)
85+
text.removeLast(suffix.count)
86+
text = trimWhitespace(text)
87+
text = trimNewlines(text)
88+
return text
89+
}
6090

61-
foundStartOfCodeBlock = text.hasPrefix("/*")
62-
foundEndOfCodeBlock = text.hasSuffix("*/")
91+
// Helper function to process multiline block comments
92+
func processMultilineBlockComment(_ text: String) -> String {
93+
var lines = text.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
6394

64-
let sanitized =
65-
text
66-
.split(separator: "\n")
67-
.map { $0.trimmingAnyCharacters(in: "/*").trimmingAnyCharacters(in: " ") }
68-
.filter { !$0.isEmpty }
69-
.joined(separator: " ")
95+
lines.removeFirst()
7096

71-
appendPartialCommentIfPossible(sanitized)
97+
let minIndentation =
98+
lines
99+
.filter { !$0.isEmpty }
100+
.map { $0.prefix { $0 == " " }.count }
101+
.min() ?? 0
72102

73-
case .lineComment(let text), .docLineComment(let text):
74-
if isInCodeBlock {
75-
appendPartialCommentIfPossible(text)
103+
if let lastLine = lines.last {
104+
if trimWhitespace(lastLine) == "*/" {
105+
lines.removeLast()
76106
} else {
77-
comments.append(String(text.trimmingPrefix("/ ")))
107+
lines[lines.count - 1].removeLast(2)
108+
lines[lines.count - 1] = trimWhitespace(lines[lines.count - 1])
78109
}
79-
80-
default:
81-
break
82110
}
83111

84-
if foundEndOfCodeBlock, !partialComments.isEmpty {
85-
appendSubstringsToLines()
86-
partialComments.removeAll()
112+
let unindentedLines = lines.map { line in
113+
guard line.count >= minIndentation else { return line }
114+
return String(line.dropFirst(minIndentation))
87115
}
88-
}
89116

90-
if !partialComments.isEmpty {
91-
appendSubstringsToLines()
117+
return unindentedLines.joined(separator: "\n")
92118
}
93119

94-
func appendPartialCommentIfPossible(_ text: String) {
95-
guard partialComments.isEmpty || !text.isEmpty else { return }
96-
97-
partialComments.append(text)
98-
}
120+
for piece in pieces {
121+
switch piece {
122+
case .blockComment(let text):
123+
let processedText =
124+
text.hasPrefix("/*\n")
125+
? processMultilineBlockComment(text)
126+
: processBlockComment(text, prefix: "/*", suffix: "*/")
127+
comments.append(processedText)
128+
129+
case .docBlockComment(let text):
130+
let processedText =
131+
text.hasPrefix("/**\n")
132+
? processMultilineBlockComment(text)
133+
: processBlockComment(text, prefix: "/**", suffix: "*/")
134+
comments.append(processedText)
135+
136+
case .lineComment(let text):
137+
let prefix = allLineCommentsHaveSpace ? "// " : "//"
138+
comments.append(String(text.dropFirst(prefix.count)))
139+
140+
case .docLineComment(let text):
141+
let prefix = allLineCommentsHaveSpace ? "/// " : "///"
142+
comments.append(String(text.dropFirst(prefix.count)))
99143

100-
func appendSubstringsToLines() {
101-
comments.append(partialComments.joined(separator: " "))
144+
default:
145+
break
146+
}
102147
}
103-
104-
return comments
148+
return comments.joined(separator: "\n")
105149
}
106150

107151
/// The length of all the pieces in this ``Trivia``.

Sources/SwiftSyntax/Utils.swift

-46
Original file line numberDiff line numberDiff line change
@@ -102,49 +102,3 @@ extension RawUnexpectedNodesSyntax {
102102
self.init(raw: raw)
103103
}
104104
}
105-
106-
extension String {
107-
func trimmingCharacters(in charactersToTrim: any BidirectionalCollection<Character>) -> Substring {
108-
// TODO: adammcarter - this feels a bit dirty
109-
self[startIndex...].trimmingAnyCharacters(in: charactersToTrim)
110-
}
111-
112-
func trimmingPrefix(_ charactersToTrim: any BidirectionalCollection<Character>) -> Substring {
113-
self[startIndex...].trimmingAnyCharactersFromPrefix(in: charactersToTrim)
114-
}
115-
116-
func trimmingSuffix(_ charactersToTrim: any BidirectionalCollection<Character>) -> Substring {
117-
self[startIndex...].trimmingAnyCharactersFromSuffix(in: charactersToTrim)
118-
}
119-
}
120-
121-
extension Substring {
122-
func trimmingAnyCharacters(in charactersToTrim: any BidirectionalCollection<Character>) -> Substring {
123-
trimmingAnyCharactersFromPrefix(in: charactersToTrim).trimmingAnyCharactersFromSuffix(in: charactersToTrim)
124-
}
125-
126-
func trimmingAnyCharactersFromPrefix(in charactersToTrim: any BidirectionalCollection<Character>) -> Self {
127-
dropFirst(countOfSequentialCharacters(charactersToTrim, in: self))
128-
}
129-
130-
func trimmingAnyCharactersFromSuffix(in charactersToTrim: any BidirectionalCollection<Character>) -> Self {
131-
dropLast(countOfSequentialCharacters(charactersToTrim, in: reversed()))
132-
}
133-
}
134-
135-
private func countOfSequentialCharacters(
136-
_ charactersToCount: any BidirectionalCollection<Character>,
137-
in characters: any BidirectionalCollection<Character>
138-
) -> Int {
139-
var count = 0
140-
141-
for character in characters {
142-
if charactersToCount.contains(character) {
143-
count += 1
144-
} else {
145-
break
146-
}
147-
}
148-
149-
return count
150-
}

0 commit comments

Comments
 (0)