Skip to content

Commit cc6923f

Browse files
authored
Merge pull request swiftlang#133 from dabelknap/comments
Add support for basic line and documentation comments
2 parents 5bc910b + 9b09a6f commit cc6923f

File tree

5 files changed

+215
-88
lines changed

5 files changed

+215
-88
lines changed

Sources/SwiftFormatPrettyPrint/Comment.swift

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ struct Comment {
3838
}
3939
}
4040
let kind: Kind
41-
var text: String
41+
var text: [String]
42+
public var length: Int
4243

4344
init(kind: Kind, text: String) {
44-
self.text = text
45+
self.text = [text]
4546
self.kind = kind
47+
self.length = text.count + kind.prefixLength + 1
4648

47-
self.text.removeFirst(kind.prefixLength)
49+
self.text[0].removeFirst(kind.prefixLength)
4850

4951
switch kind {
5052
case .docBlock:
@@ -55,51 +57,15 @@ struct Comment {
5557
}
5658
}
5759

58-
mutating func addText(_ text: String) {
59-
self.text += "\n" + text
60+
func print(indent: Int) -> String {
61+
let separator = "\n" + String(repeating: " ", count: indent) + kind.prefix
62+
return kind.prefix + self.text.joined(separator: separator)
6063
}
6164

62-
func wordWrap(lineLength: Int) -> [String] {
63-
let maxLength = lineLength - (kind.prefixLength + 1)
64-
let scanner = Scanner(string: text)
65-
var lines = [String]()
66-
67-
// FIXME: Word wrapping doesn't work for documentation comments, as we need to preserve all the
68-
// intricacies of Markdown formatting.
69-
// FIXME: If there's a totally blank comment line, it doesn't get a prefix for some reason.
70-
// TODO: Allow for leading `*` characters for each line of a block comment.
71-
if kind == .docLine || kind == .docBlock {
72-
lines = text.split(separator: "\n").map { "\(kind.prefix)\($0)" }
73-
} else {
74-
var currentLine = ""
75-
var currentLineLength = 0
76-
var buffer: NSString! = ""
77-
while scanner.scanUpToCharacters(from: .whitespacesAndNewlines, into: &buffer) {
78-
let strBuf = buffer as String
79-
if currentLineLength + strBuf.count > maxLength {
80-
lines.append(currentLine)
81-
currentLine = ""
82-
currentLineLength = 0
83-
}
84-
currentLine += strBuf + " "
85-
currentLineLength += strBuf.count + 1
86-
}
87-
if currentLineLength > 0 {
88-
lines.append(currentLine.trimmingCharacters(in: .whitespaces))
89-
}
90-
for i in 0..<lines.count {
91-
lines[i] = "\(kind.prefix) \(lines[i])"
92-
}
93-
}
94-
switch kind {
95-
case .block:
96-
lines.insert("/*", at: 0)
97-
lines.append(" */")
98-
case .docBlock:
99-
lines.insert("/**", at: 0)
100-
lines.append(" */")
101-
default: break
65+
mutating func addText(_ text: [String]) {
66+
for line in text {
67+
self.text.append(line)
68+
self.length += line.count + self.kind.prefixLength + 1
10269
}
103-
return lines
10470
}
10571
}

Sources/SwiftFormatPrettyPrint/PrettyPrint.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,18 @@ public class PrettyPrinter {
225225
write(syntaxToken.text)
226226
spaceRemaining -= syntaxToken.text.count
227227

228-
// TODO(dabelknap): Implement comments
229-
case .comment: ()
228+
case .comment(let comment):
229+
if lastBreakConsecutive {
230+
// If the last token created a new line, we need to apply indentation.
231+
writeSpaces(lastBreakValue)
232+
233+
lastBreak = false
234+
lastBreakConsecutive = false
235+
lastBreakOffset = 0
236+
lastBreakValue = 0
237+
}
238+
write(comment.print(indent: lastBreakValue))
239+
spaceRemaining -= comment.length
230240
}
231241
}
232242

@@ -317,8 +327,9 @@ public class PrettyPrinter {
317327
lengths.append(syntaxToken.text.count)
318328
total += syntaxToken.text.count
319329

320-
// TODO(dabelknap): Implement comments
321-
case .comment: ()
330+
case .comment(let comment):
331+
lengths.append(comment.length)
332+
total += comment.length
322333
}
323334
}
324335

Sources/SwiftFormatPrettyPrint/Token.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ enum Token {
3333
case `break`(size: Int, offset: Int)
3434
case space(size: Int)
3535
case newlines(Int, offset: Int)
36-
case comment(Comment, hasTrailingSpace: Bool)
36+
case comment(Comment)
3737
case reset
3838

3939
// Convenience overloads for the enum types

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,27 +1205,29 @@ private final class TokenStreamCreator: SyntaxVisitor {
12051205
}
12061206

12071207
override func visit(_ token: TokenSyntax) {
1208-
breakDownTrivia(token.leadingTrivia, before: token)
1208+
let fileStart = token.previousToken == nil
1209+
extractTrivia(token.leadingTrivia, fileStart: fileStart)
12091210
if let before = beforeMap[token] {
12101211
tokens += before
12111212
}
12121213
appendToken(.syntax(token))
1214+
extractTrailingLineComment(token)
12131215
if let afterGroups = afterMap[token] {
12141216
for after in afterGroups.reversed() {
12151217
tokens += after
12161218
}
12171219
}
1218-
breakDownTrivia(token.trailingTrivia)
1220+
extractTrivia(token.trailingTrivia)
12191221
}
12201222

12211223
func appendToken(_ token: Token) {
12221224
if let last = tokens.last {
12231225
switch (last, token) {
1224-
case (.comment(let c1, _), .comment(let c2, _))
1226+
case (.comment(let c1), .comment(let c2))
12251227
where c1.kind == .docLine && c2.kind == .docLine:
12261228
var newComment = c1
12271229
newComment.addText(c2.text)
1228-
tokens[tokens.count - 1] = .comment(newComment, hasTrailingSpace: false)
1230+
tokens[tokens.count - 1] = .comment(newComment)
12291231
return
12301232
default:
12311233
break
@@ -1242,52 +1244,49 @@ private final class TokenStreamCreator: SyntaxVisitor {
12421244
return true
12431245
}
12441246

1245-
private func breakDownTrivia(_ trivia: Trivia, before: TokenSyntax? = nil) {
1247+
private func extractTrailingLineComment(_ token: TokenSyntax) {
1248+
guard let nextToken = token.nextToken else {
1249+
return
1250+
}
1251+
let trivia = nextToken.leadingTrivia
12461252
for (offset, piece) in trivia.enumerated() {
12471253
switch piece {
12481254
case .lineComment(let text):
1249-
appendToken(.comment(Comment(kind: .line, text: text), hasTrailingSpace: false))
1250-
if case .newlines? = trivia[safe: offset + 1],
1251-
case .lineComment? = trivia[safe: offset + 2] {
1252-
/* do nothing */
1255+
if offset > 0, case .newlines? = trivia[safe: offset - 1] {
1256+
return
12531257
} else {
1258+
appendToken(.break(size: 2, offset: 2))
1259+
appendToken(.comment(Comment(kind: .line, text: text)))
1260+
if let parent = nextToken.parent?.parent, !(parent is CodeBlockItemSyntax) {
1261+
appendToken(.newline)
1262+
}
1263+
return
1264+
}
1265+
default:
1266+
break
1267+
}
1268+
}
1269+
}
1270+
1271+
private func extractTrivia(_ trivia: Trivia, fileStart: Bool = false) {
1272+
for (offset, piece) in trivia.enumerated() {
1273+
switch piece {
1274+
case .lineComment(let text):
1275+
if fileStart {
1276+
appendToken(.comment(Comment(kind: .line, text: text)))
1277+
appendToken(.newline)
1278+
} else if offset > 0, case .newlines? = trivia[safe: offset - 1] {
1279+
appendToken(.comment(Comment(kind: .line, text: text)))
12541280
appendToken(.newline)
12551281
}
12561282
case .docLineComment(let text):
1257-
appendToken(.comment(Comment(kind: .docLine, text: text), hasTrailingSpace: false))
1283+
appendToken(.comment(Comment(kind: .docLine, text: text)))
12581284
if case .newlines? = trivia[safe: offset + 1],
12591285
case .docLineComment? = trivia[safe: offset + 2] {
12601286
/* do nothing */
12611287
} else {
12621288
appendToken(.newline)
12631289
}
1264-
case .blockComment(let text), .docBlockComment(let text):
1265-
var hasTrailingSpace = false
1266-
var hasTrailingNewline = false
1267-
1268-
// Detect if a newline or trailing space comes after this comment and preserve it.
1269-
if let next = trivia[safe: offset + 1] {
1270-
switch next {
1271-
case .newlines, .carriageReturns, .carriageReturnLineFeeds:
1272-
hasTrailingNewline = true
1273-
case .spaces, .tabs:
1274-
hasTrailingSpace = true
1275-
default:
1276-
break
1277-
}
1278-
}
1279-
1280-
let commentKind: Comment.Kind
1281-
if case .blockComment = piece {
1282-
commentKind = .block
1283-
} else {
1284-
commentKind = .docBlock
1285-
}
1286-
let comment = Comment(kind: commentKind, text: text)
1287-
appendToken(.comment(comment, hasTrailingSpace: hasTrailingSpace))
1288-
if hasTrailingNewline {
1289-
appendToken(.newline)
1290-
}
12911290
case .newlines(let n), .carriageReturns(let n), .carriageReturnLineFeeds(let n):
12921291
if n > 1 {
12931292
appendToken(.newlines(min(n - 1, config.maximumBlankLines), offset: 0))
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
public class CommentTests: PrettyPrintTestCase {
2+
public func testDocumentationComments() {
3+
let input =
4+
"""
5+
/// This is a documentation comment
6+
///
7+
/// - Parameters:
8+
/// - param1: Param1 comment
9+
/// - param2: Param2 comment
10+
/// - Returns: The output
11+
func myfun(param1: Int, param2: Double) -> String {
12+
let out = "123"
13+
return out
14+
}
15+
16+
/// A brief doc comment
17+
func myfun() {
18+
let a = 123
19+
let b = "456"
20+
}
21+
22+
public class MyClass {
23+
/// Doc comment
24+
var myVariable: Int
25+
26+
/// Method doc comment
27+
///
28+
/// - Parameters:
29+
/// - param1: Param1 comment
30+
/// - param2: Param2 comment
31+
/// - Returns: The output
32+
func myFun(param1: Int, param2: Int) -> String {
33+
let a = 123
34+
let b = "456"
35+
return b
36+
}
37+
}
38+
"""
39+
40+
let expected =
41+
"""
42+
/// This is a documentation comment
43+
///
44+
/// - Parameters:
45+
/// - param1: Param1 comment
46+
/// - param2: Param2 comment
47+
/// - Returns: The output
48+
func myfun(param1: Int, param2: Double) -> String {
49+
let out = "123"
50+
return out
51+
}
52+
53+
/// A brief doc comment
54+
func myfun() {
55+
let a = 123
56+
let b = "456"
57+
}
58+
59+
public class MyClass {
60+
/// Doc comment
61+
var myVariable: Int
62+
63+
/// Method doc comment
64+
///
65+
/// - Parameters:
66+
/// - param1: Param1 comment
67+
/// - param2: Param2 comment
68+
/// - Returns: The output
69+
func myFun(param1: Int, param2: Int) -> String {
70+
let a = 123
71+
let b = "456"
72+
return b
73+
}
74+
}
75+
76+
"""
77+
78+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 55)
79+
}
80+
81+
public func testLineComments() {
82+
let input =
83+
"""
84+
// Line Comment1
85+
// Line Comment2
86+
let a = 123
87+
let b = "456" // End of line comment
88+
let c = "More content"
89+
90+
// Comment 3
91+
// Comment 4
92+
93+
let reallyLongVariableName = 123 // This comment should wrap
94+
95+
let d = 123
96+
// Trailing Comment
97+
"""
98+
99+
let expected =
100+
"""
101+
// Line Comment1
102+
// Line Comment2
103+
let a = 123
104+
let b = "456" // End of line comment
105+
let c = "More content"
106+
107+
// Comment 3
108+
// Comment 4
109+
110+
let reallyLongVariableName = 123
111+
// This comment should wrap
112+
113+
let d = 123
114+
// Trailing Comment
115+
116+
"""
117+
118+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 45)
119+
}
120+
121+
public func testContainerLineComments() {
122+
let input =
123+
"""
124+
// Array comment
125+
let a = [456, // small comment
126+
789]
127+
128+
// Dictionary comment
129+
let b = ["abc": 456, // small comment
130+
"def": 789]
131+
"""
132+
133+
let expected =
134+
"""
135+
// Array comment
136+
let a = [
137+
456, // small comment
138+
789
139+
]
140+
141+
// Dictionary comment
142+
let b = [
143+
"abc": 456, // small comment
144+
"def": 789
145+
]
146+
147+
"""
148+
149+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80)
150+
}
151+
}

0 commit comments

Comments
 (0)