diff --git a/Sources/SwiftFormat/PrettyPrint/Comment.swift b/Sources/SwiftFormat/PrettyPrint/Comment.swift index 60d63af10..3dff9480f 100644 --- a/Sources/SwiftFormat/PrettyPrint/Comment.swift +++ b/Sources/SwiftFormat/PrettyPrint/Comment.swift @@ -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: @@ -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) + "*/" } } diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 1e4629916..e88f1dccb 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -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. @@ -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. @@ -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 @@ -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 || @@ -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 } diff --git a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift index 64f9aa039..52226bed5 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/CommentTests.swift @@ -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 */ @@ -603,7 +603,9 @@ final class CommentTests: PrettyPrintTestCase { } let d = 123 - /* Trailing Comment */ + /* Trailing Comment */ + /* Trailing + Block Comment */ """ let expected = @@ -633,6 +635,8 @@ final class CommentTests: PrettyPrintTestCase { let d = 123 /* Trailing Comment */ + /* Trailing + Block Comment */ """