diff --git a/Sources/SwiftFormat/Core/Trivia+Convenience.swift b/Sources/SwiftFormat/Core/Trivia+Convenience.swift index 13820423..1841f397 100644 --- a/Sources/SwiftFormat/Core/Trivia+Convenience.swift +++ b/Sources/SwiftFormat/Core/Trivia+Convenience.swift @@ -95,4 +95,54 @@ extension Trivia { } return result.isEmpty ? nil : result } + + func trimmingSuperfluousNewlines(fromClosingBrace: Bool) -> (Trivia, Int) { + var trimmmed = 0 + var pendingNewlineCount = 0 + let pieces = self.indices.reduce([TriviaPiece]()) { (partialResult, index) in + let piece = self[index] + // Collapse consecutive newlines into a single one + if case .newlines(let count) = piece { + if fromClosingBrace { + if index == self.count - 1 { + // For the last index(newline right before the closing brace), collapse into a single newline + trimmmed += count - 1 + return partialResult + [.newlines(1)] + } else { + pendingNewlineCount += count + return partialResult + } + } else { + if let last = partialResult.last, last.isNewline { + trimmmed += count + return partialResult + } else if index == 0 { + // For leading trivia not associated with a closing brace, collapse the first newline into a single one + trimmmed += count - 1 + return partialResult + [.newlines(1)] + } else { + return partialResult + [piece] + } + } + } + // Remove spaces/tabs surrounded by newlines + if piece.isSpaceOrTab, index > 0, index < self.count - 1, self[index - 1].isNewline, self[index + 1].isNewline { + return partialResult + } + // Handle pending newlines if there are any + if pendingNewlineCount > 0 { + if index < self.count - 1 { + let newlines = TriviaPiece.newlines(pendingNewlineCount) + pendingNewlineCount = 0 + return partialResult + [newlines] + [piece] + } else { + return partialResult + [.newlines(1)] + [piece] + } + } + // Retain other trivia pieces + return partialResult + [piece] + } + + return (Trivia(pieces: pieces), trimmmed) + } } diff --git a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift index 5158dbb9..3251a92b 100644 --- a/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift +++ b/Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift @@ -97,58 +97,6 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule { } } -extension Trivia { - func trimmingSuperfluousNewlines(fromClosingBrace: Bool) -> (Trivia, Int) { - var trimmmed = 0 - var pendingNewlineCount = 0 - let pieces = self.indices.reduce([TriviaPiece]()) { (partialResult, index) in - let piece = self[index] - // Collapse consecutive newlines into a single one - if case .newlines(let count) = piece { - if fromClosingBrace { - if index == self.count - 1 { - // For the last index(newline right before the closing brace), collapse into a single newline - trimmmed += count - 1 - return partialResult + [.newlines(1)] - } else { - pendingNewlineCount += count - return partialResult - } - } else { - if let last = partialResult.last, last.isNewline { - trimmmed += count - return partialResult - } else if index == 0 { - // For leading trivia not associated with a closing brace, collapse the first newline into a single one - trimmmed += count - 1 - return partialResult + [.newlines(1)] - } else { - return partialResult + [piece] - } - } - } - // Remove spaces/tabs surrounded by newlines - if piece.isSpaceOrTab, index > 0, index < self.count - 1, self[index - 1].isNewline, self[index + 1].isNewline { - return partialResult - } - // Handle pending newlines if there are any - if pendingNewlineCount > 0 { - if index < self.count - 1 { - let newlines = TriviaPiece.newlines(pendingNewlineCount) - pendingNewlineCount = 0 - return partialResult + [newlines] + [piece] - } else { - return partialResult + [.newlines(1)] + [piece] - } - } - // Retain other trivia pieces - return partialResult + [piece] - } - - return (Trivia(pieces: pieces), trimmmed) - } -} - extension Finding.Message { fileprivate static func removeEmptyLinesAfter(_ count: Int) -> Finding.Message { "remove empty \(count > 1 ? "lines" : "line") after '{'" diff --git a/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift index abc72257..81d4c0a6 100644 --- a/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift +++ b/Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift @@ -35,7 +35,27 @@ public final class UseSingleLinePropertyGetter: SyntaxFormatRule { diagnose(.removeExtraneousGetBlock, on: acc) var result = node - result.accessorBlock?.accessors = .getter(body.statements) + result.accessorBlock?.accessors = .getter(body.statements.trimmed) + + var triviaBeforeStatements = Trivia() + if let accessorPrecedingTrivia = node.accessorBlock?.accessors.allPrecedingTrivia { + triviaBeforeStatements += accessorPrecedingTrivia + } + if acc.accessorSpecifier.trailingTrivia.hasAnyComments { + triviaBeforeStatements += acc.accessorSpecifier.trailingTrivia + triviaBeforeStatements += .newline + } + triviaBeforeStatements += body.statements.allPrecedingTrivia + result.accessorBlock?.leftBrace.trailingTrivia = + triviaBeforeStatements.trimmingSuperfluousNewlines(fromClosingBrace: false).0 + + var triviaAfterStatements = body.statements.allFollowingTrivia + if let accessorsFollowingTrivia = node.accessorBlock?.accessors.allFollowingTrivia { + triviaAfterStatements += accessorsFollowingTrivia + } + result.accessorBlock?.accessors.trailingTrivia = + triviaAfterStatements.trimmingSuperfluousNewlines(fromClosingBrace: true).0 + result.accessorBlock?.rightBrace.leadingTrivia = [] return result } } diff --git a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift index 6a521736..dcf8c4a0 100644 --- a/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift +++ b/Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift @@ -21,7 +21,7 @@ final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { var g: Int { return 4 } var h: Int { 1️⃣get { - return 4 + return 4 } } var i: Int { @@ -50,7 +50,7 @@ final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { expected: """ var g: Int { return 4 } var h: Int { - return 4 + return 4 } var i: Int { get { return 0 } @@ -83,4 +83,174 @@ final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase { ] ) } + + func testSingleLineGetterWithInlineComments() { + assertFormatting( + UseSingleLinePropertyGetter.self, + input: """ + var x: Int { + // A comment + 1️⃣get { 1 } + } + var y: Int { + 2️⃣get { 1 } // A comment + } + var z: Int { + 3️⃣get { 1 } + // A comment + } + """, + expected: """ + var x: Int { + // A comment + 1 + } + var y: Int { + 1 // A comment + } + var z: Int { + 1 + // A comment + } + """, + findings: [ + FindingSpec( + "1️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + FindingSpec( + "2️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + FindingSpec( + "3️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + ] + ) + } + + func testMultiLineGetterWithCommentsInsideBody() { + assertFormatting( + UseSingleLinePropertyGetter.self, + input: """ + var x: Int { + 1️⃣get { + // A comment + 1 + } + } + var x: Int { + 2️⃣get { + 1 // A comment + } + } + var x: Int { + 3️⃣get { + 1 + // A comment + } + } + """, + expected: """ + var x: Int { + // A comment + 1 + } + var x: Int { + 1 // A comment + } + var x: Int { + 1 + // A comment + } + """, + findings: [ + FindingSpec( + "1️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + FindingSpec( + "2️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + FindingSpec( + "3️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + ] + ) + } + + func testGetterWithCommentsAfterGetKeyword() { + assertFormatting( + UseSingleLinePropertyGetter.self, + input: """ + var x: Int { + 1️⃣get // hello + { 1 } + } + + var x: Int { + 2️⃣get /* hello */ { 1 } + } + """, + expected: """ + var x: Int { + // hello + 1 + } + + var x: Int { + /* hello */ + 1 + } + """, + findings: [ + FindingSpec( + "1️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + FindingSpec( + "2️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ), + ] + ) + } + + func testGetterWithCommentsAroundBracesAndBody() { + assertFormatting( + UseSingleLinePropertyGetter.self, + input: """ + var x: Int { // A comment + // B comment + 1️⃣get /* C comment */ { // D comment + // E comment + 1 // F comment + // G comment + } // H comment + // I comment + } + """, + expected: """ + var x: Int { // A comment + // B comment + /* C comment */ + // D comment + // E comment + 1 // F comment + // G comment + // H comment + // I comment + } + """, + findings: [ + FindingSpec( + "1️⃣", + message: "remove 'get {...}' around the accessor and move its body directly into the computed property" + ) + ] + ) + } }