Skip to content

Commit c23e3dc

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

File tree

4 files changed

+359
-342
lines changed

4 files changed

+359
-342
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

+59-45
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import Foundation
14+
1315
public enum TriviaPosition {
1416
case leading
1517
case trailing
@@ -43,65 +45,77 @@ public struct Trivia: Sendable {
4345
}
4446

4547
/// 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] {
48+
public var commentValue: String {
4949
var comments = [String]()
50-
var partialComments = [String]()
51-
52-
var foundStartOfCodeBlock = false
53-
var foundEndOfCodeBlock = false
54-
var isInCodeBlock: Bool { foundStartOfCodeBlock && !foundEndOfCodeBlock }
5550

56-
for piece in pieces {
57-
switch piece {
58-
case .blockComment(let text), .docBlockComment(let text):
59-
let text = text.trimmingCharacters(in: "\n")
51+
// Determine if all line comments have a single space
52+
let allLineCommentsHaveSpace = pieces.allSatisfy { piece in
53+
guard case .lineComment(let text) = piece else { return true }
54+
return text.hasPrefix("// ")
55+
}
6056

61-
foundStartOfCodeBlock = text.hasPrefix("/*")
62-
foundEndOfCodeBlock = text.hasSuffix("*/")
57+
// Determine if all doc line comments have a single space
58+
let allDocLineCommentsHaveSpace = pieces.allSatisfy { piece in
59+
guard case .docLineComment(let text) = piece else { return true }
60+
return text.hasPrefix("/// ")
61+
}
6362

64-
let sanitized =
65-
text
66-
.split(separator: "\n")
67-
.map { $0.trimmingAnyCharacters(in: "/*").trimmingAnyCharacters(in: " ") }
68-
.filter { !$0.isEmpty }
69-
.joined(separator: " ")
63+
// Helper function to process block comments
64+
func processBlockComment(_ text: String, prefix: String, suffix: String) -> String {
65+
var text = text
66+
text.removeFirst(prefix.count)
67+
text.removeLast(suffix.count)
68+
text = text.trimmingCharacters(in: .whitespaces)
69+
let newlineCharacterSet = CharacterSet(charactersIn: "\n")
70+
return text.trimmingCharacters(in: newlineCharacterSet)
71+
}
7072

71-
appendPartialCommentIfPossible(sanitized)
73+
// Helper function to process multiline block comments
74+
func processMultilineBlockComment(_ text: String) -> String {
75+
var lines = text.components(separatedBy: .newlines)
7276

73-
case .lineComment(let text), .docLineComment(let text):
74-
if isInCodeBlock {
75-
appendPartialCommentIfPossible(text)
76-
} else {
77-
comments.append(String(text.trimmingPrefix("/ ")))
78-
}
77+
let lastLine = lines.last ?? ""
78+
let leadingSpaces = lastLine.prefix { $0 == " " }.count
7979

80-
default:
81-
break
82-
}
80+
lines.removeFirst()
81+
lines.removeLast()
8382

84-
if foundEndOfCodeBlock, !partialComments.isEmpty {
85-
appendSubstringsToLines()
86-
partialComments.removeAll()
83+
let processedLines = lines.map { line in
84+
String(line.dropFirst(leadingSpaces))
8785
}
88-
}
8986

90-
if !partialComments.isEmpty {
91-
appendSubstringsToLines()
87+
return processedLines.joined(separator: "\n")
9288
}
9389

94-
func appendPartialCommentIfPossible(_ text: String) {
95-
guard partialComments.isEmpty || !text.isEmpty else { return }
96-
97-
partialComments.append(text)
98-
}
90+
for piece in pieces {
91+
switch piece {
92+
case .blockComment(let text):
93+
let processedText =
94+
text.hasPrefix("/*\n")
95+
? processMultilineBlockComment(text)
96+
: processBlockComment(text, prefix: "/*", suffix: "*/")
97+
comments.append(processedText)
98+
99+
case .docBlockComment(let text):
100+
let processedText =
101+
text.hasPrefix("/**\n")
102+
? processMultilineBlockComment(text)
103+
: processBlockComment(text, prefix: "/**", suffix: "*/")
104+
comments.append(processedText)
105+
106+
case .lineComment(let text):
107+
let prefix = allLineCommentsHaveSpace ? "// " : "//"
108+
comments.append(String(text.dropFirst(prefix.count)))
109+
110+
case .docLineComment(let text):
111+
let prefix = allDocLineCommentsHaveSpace ? "/// " : "///"
112+
comments.append(String(text.dropFirst(prefix.count)))
99113

100-
func appendSubstringsToLines() {
101-
comments.append(partialComments.joined(separator: " "))
114+
default:
115+
break
116+
}
102117
}
103-
104-
return comments
118+
return comments.joined(separator: "\n")
105119
}
106120

107121
/// 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)