Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions Sources/SwiftFormat/Core/Trivia+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ extension Trivia {
return Trivia(pieces: self.pieces.drop(while: \.isSpaceOrTab))
}

/// Returns this trivia with any leading newlines and horizontal whitespace removed, stopping at
/// the first comment so that comments and anything following them are preserved.
func withoutLeadingVerticalWhitespace() -> Trivia {
return Trivia(
pieces: self.pieces.drop {
switch $0 {
case .newlines, .carriageReturns, .carriageReturnLineFeeds, .formfeeds, .spaces, .tabs:
return true
default:
return false
}
}
)
}

func withoutTrailingSpaces() -> Trivia {
guard let lastNonSpaceIndex = self.pieces.lastIndex(where: \.isSpaceOrTab) else {
return self
Expand Down
9 changes: 9 additions & 0 deletions Sources/SwiftFormat/Rules/OneCasePerLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ public final class OneCasePerLine: SyntaxFormatRule {
/// basis declaration, and updates the comment preserving state if needed.
mutating func makeCaseDeclFromBasis(elements: [EnumCaseElementSyntax]) -> EnumCaseDeclSyntax {
var caseDecl = basis

// The first element follows the `case` keyword, so any leading newline it inherited from the
// original (where it was on a continuation line, like `case a,\n b`) would force it onto its
// own line. Drop that whitespace so the element stays on the same line as `case`, but preserve
// any comments that were attached to it.
var elements = elements
if let first = elements.first {
elements[0].leadingTrivia = first.leadingTrivia.withoutLeadingVerticalWhitespace()
}
caseDecl.elements = EnumCaseElementListSyntax(elements)

if shouldKeepLeadingTrivia {
Expand Down
49 changes: 49 additions & 0 deletions Tests/SwiftFormatTests/Rules/OneCasePerLineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,55 @@ final class OneCasePerLineTests: LintOrFormatRuleTestCase {
)
}

func testElementsOnContinuationLinesAreMovedOntoCaseKeyword() {
assertFormatting(
OneCasePerLine.self,
input: """
public enum TestEnum: Int {
case 1️⃣a = 0,
2️⃣b = 1,
3️⃣c = 2
}
""",
expected: """
public enum TestEnum: Int {
case a = 0
case b = 1
case c = 2
}
""",
findings: [
FindingSpec("1️⃣", message: "move 'a' to its own 'case' declaration"),
FindingSpec("2️⃣", message: "move 'b' to its own 'case' declaration"),
FindingSpec("3️⃣", message: "move 'c' to its own 'case' declaration"),
]
)
}

func testCommentBetweenElementsOnContinuationLinesIsPreserved() {
assertFormatting(
OneCasePerLine.self,
input: """
enum Foo: Int {
case 1️⃣a = 1,
// This should stay with `b`.
2️⃣b = 2
}
""",
expected: """
enum Foo: Int {
case a = 1
case // This should stay with `b`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think in this situation, we could do even better: Move the comment/trivia to precede the newly inserted case keyword.

Another question: What if the comment is an end-of-line comment on the previous line, like this?

enum Foo: Int {
  case a = 1,  // a comment about 'a'
    b = 2
}

In that case, since the comment is at the end of the line associated with a, it should stay on that line. Can you add a test for that as well and make sure it behaves as described?

b = 2
}
""",
findings: [
FindingSpec("1️⃣", message: "move 'a' to its own 'case' declaration"),
FindingSpec("2️⃣", message: "move 'b' to its own 'case' declaration"),
]
)
}

func testAttributesArePropagated() {
assertFormatting(
OneCasePerLine.self,
Expand Down