Skip to content
Open
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
50 changes: 50 additions & 0 deletions Sources/SwiftFormat/Core/Trivia+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
52 changes: 0 additions & 52 deletions Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,58 +97,6 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule {
}
}

extension Trivia {
func trimmingSuperfluousNewlines(fromClosingBrace: Bool) -> (Trivia, Int) {
Comment on lines -100 to -101
Copy link
Member Author

@TTOzzi TTOzzi Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved because it is now used commonly in other places.
(There are no changes to the logic.)

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 '{'"
Expand Down
22 changes: 21 additions & 1 deletion Sources/SwiftFormat/Rules/UseSingleLinePropertyGetter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
174 changes: 172 additions & 2 deletions Tests/SwiftFormatTests/Rules/UseSingleLinePropertyGetterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class UseSingleLinePropertyGetterTests: LintOrFormatRuleTestCase {
var g: Int { return 4 }
var h: Int {
1️⃣get {
return 4
return 4
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the test cases, some whitespace may appear incorrect, but in actual usage the PrettyPrinter will handle the spacing properly.

}
}
var i: Int {
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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"
)
]
)
}
}