Skip to content

Add support for re-indenting block comments. #791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion Sources/SwiftFormat/PrettyPrint/Comment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ struct Comment {
let kind: Kind
var text: [String]
var length: Int
// what was the leading indentation, if any, that preceded this comment?
var leadingIndent: Indent?

init(kind: Kind, text: String) {
init(kind: Kind, leadingIndent: Indent?, text: String) {
self.kind = kind
self.leadingIndent = leadingIndent

switch kind {
case .line, .docLine:
Expand Down Expand Up @@ -93,6 +96,25 @@ struct Comment {
return kind.prefix + trimmedLines.joined(separator: separator)
case .block, .docBlock:
let separator = "\n"

// if all the lines after the first matching leadingIndent, replace that prefix with the
// current indentation level
if let leadingIndent {
let rest = self.text.dropFirst()

let hasLeading = rest.allSatisfy {
let result = $0.hasPrefix(leadingIndent.text) || $0.isEmpty
return result
}
if hasLeading, let first = self.text.first, !rest.isEmpty {
let restStr = rest.map {
let stripped = $0.dropFirst(leadingIndent.text.count)
return indent.indentation() + stripped
}.joined(separator: separator)
return kind.prefix + first + separator + restStr + "*/"
}
}

return kind.prefix + self.text.joined(separator: separator) + "*/"
}
}
Expand Down
33 changes: 25 additions & 8 deletions Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3194,7 +3194,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
true,
[
.space(size: config.spacesBeforeEndOfLineComments, flexible: true),
.comment(Comment(kind: .line, text: text), wasEndOfLine: true),
.comment(Comment(kind: .line, leadingIndent: nil, text: text), wasEndOfLine: true),
// There must be a break with a soft newline after the comment, but it's impossible to
// know which kind of break must be used. Adding this newline is deferred until the
// comment is added to the token stream.
Expand All @@ -3205,7 +3205,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
false,
[
.space(size: 1, flexible: true),
.comment(Comment(kind: .block, text: text), wasEndOfLine: false),
.comment(Comment(kind: .block, leadingIndent: nil, text: text), wasEndOfLine: false),
// We place a size-0 break after the comment to allow a discretionary newline after
// the comment if the user places one here but the comment is otherwise adjacent to a
// text token.
Expand Down Expand Up @@ -3294,24 +3294,29 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
// example, even if discretionary newlines are discarded). This is the case when the preceding
// trivia was a line comment or garbage text.
var requiresNextNewline = false
// Tracking whether or not the last piece was leading indentation. A newline is considered
// a 0-space indentation; used for nesting/un-nesting block comments during formatting.
var leadingIndent: Indent? = nil

for (index, piece) in trivia.enumerated() {
if let cutoff = cutoffIndex, index == cutoff { break }

switch piece {
case .lineComment(let text):
if index > 0 || isStartOfFile {
generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength)
appendToken(.comment(Comment(kind: .line, text: text), wasEndOfLine: false))
appendToken(.comment(Comment(kind: .line, leadingIndent: leadingIndent, text: text), wasEndOfLine: false))
generateDisableFormattingIfNecessary(position + piece.sourceLength)
appendNewlines(.soft)
isStartOfFile = false
}
requiresNextNewline = true
leadingIndent = nil

case .blockComment(let text):
if index > 0 || isStartOfFile {
generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength)
appendToken(.comment(Comment(kind: .block, text: text), wasEndOfLine: false))
appendToken(.comment(Comment(kind: .block, leadingIndent: leadingIndent, text: text), wasEndOfLine: false))
generateDisableFormattingIfNecessary(position + piece.sourceLength)
// There is always a break after the comment to allow a discretionary newline after it.
var breakSize = 0
Expand All @@ -3325,24 +3330,28 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
isStartOfFile = false
}
requiresNextNewline = false
leadingIndent = nil

case .docLineComment(let text):
generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength)
appendToken(.comment(Comment(kind: .docLine, text: text), wasEndOfLine: false))
appendToken(.comment(Comment(kind: .docLine, leadingIndent: leadingIndent, text: text), wasEndOfLine: false))
generateDisableFormattingIfNecessary(position + piece.sourceLength)
appendNewlines(.soft)
isStartOfFile = false
requiresNextNewline = true
leadingIndent = nil

case .docBlockComment(let text):
generateEnableFormattingIfNecessary(position ..< position + piece.sourceLength)
appendToken(.comment(Comment(kind: .docBlock, text: text), wasEndOfLine: false))
appendToken(.comment(Comment(kind: .docBlock, leadingIndent: leadingIndent, text: text), wasEndOfLine: false))
generateDisableFormattingIfNecessary(position + piece.sourceLength)
appendNewlines(.soft)
isStartOfFile = false
requiresNextNewline = false
leadingIndent = nil

case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count):
leadingIndent = .spaces(0)
guard !isStartOfFile else { break }

if requiresNextNewline ||
Expand Down Expand Up @@ -3372,9 +3381,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
let isBOM = text == "\u{feff}"
requiresNextNewline = !isBOM
isStartOfFile = isStartOfFile && isBOM
leadingIndent = nil

default:
break
case .backslashes, .formfeeds, .pounds, .verticalTabs:
leadingIndent = nil

case .spaces(let n):
guard leadingIndent == .spaces(0) else { break }
leadingIndent = .spaces(n)
case .tabs(let n):
guard leadingIndent == .spaces(0) else { break }
leadingIndent = .tabs(n)
}
position += piece.sourceLength
}
Expand Down
12 changes: 8 additions & 4 deletions Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,14 @@ final class CommentTests: PrettyPrintTestCase {
func testBlockComments() {
let input =
"""
/* Line Comment1 */
/* Line Comment1 */
/* Line Comment2 */
let a = 123
let b = "456" /* End of line comment */
let c = "More content"

/* Comment 3
Comment 4 */
/* Comment 3
Comment 4 */

let reallyLongVariableName = 123 /* This comment should wrap */

Expand All @@ -603,7 +603,9 @@ final class CommentTests: PrettyPrintTestCase {
}

let d = 123
/* Trailing Comment */
/* Trailing Comment */
/* Trailing
Block Comment */
"""

let expected =
Expand Down Expand Up @@ -633,6 +635,8 @@ final class CommentTests: PrettyPrintTestCase {

let d = 123
/* Trailing Comment */
/* Trailing
Block Comment */

"""

Expand Down