Skip to content

Commit a442daa

Browse files
LaurenWhiteallevato
authored andcommitted
Implement one case per line (swiftlang#59)
1 parent 19f9aba commit a442daa

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

tools/swift-format/Sources/Rules/OneCasePerLine.swift

+84
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,89 @@ import SwiftSyntax
1111
///
1212
/// - SeeAlso: https://google.github.io/swift#enum-cases
1313
public final class OneCasePerLine: SyntaxFormatRule {
14+
15+
public override func visit(_ node: EnumDeclSyntax) -> DeclSyntax {
16+
let enumMembers = node.members.members
17+
var newMembers: [MemberDeclListItemSyntax] = []
18+
var newIndx = 0
19+
20+
for member in enumMembers {
21+
var numNewMembers = 0
22+
if let caseMember = member.decl as? EnumCaseDeclSyntax {
23+
var otherDecl: EnumCaseDeclSyntax? = caseMember
24+
// Add and skip single element case declarations
25+
guard caseMember.elements.count > 1 else {
26+
let newMember = SyntaxFactory.makeMemberDeclListItem(decl: caseMember, semicolon: nil)
27+
newMembers.append(newMember)
28+
newIndx += 1
29+
continue
30+
}
31+
// Move all cases with associated/raw values to new declarations
32+
for element in caseMember.elements {
33+
if element.associatedValue != nil || element.rawValue != nil {
34+
diagnose(.moveAssociatedOrRawValueCase(name: element.identifier.text), on: element)
35+
let newRemovedDecl = createAssociateOrRawCaseDecl(fullDecl: caseMember,
36+
removedElement: element)
37+
otherDecl = removeAssociateOrRawCaseDecl(fullDecl: otherDecl)
38+
let newMember = SyntaxFactory.makeMemberDeclListItem(decl: newRemovedDecl,
39+
semicolon: nil)
40+
newMembers.append(newMember)
41+
numNewMembers += 1
42+
}
43+
}
44+
// Add case declaration of remaining elements without associated/raw values, if any
45+
if let otherDecl = otherDecl {
46+
let newMember = SyntaxFactory.makeMemberDeclListItem(decl: otherDecl, semicolon: nil)
47+
newMembers.insert(newMember, at: newIndx)
48+
newIndx += 1
49+
}
50+
// Add any member that isn't an enum case declaration
51+
} else {
52+
newMembers.append(member)
53+
newIndx += 1
54+
}
55+
newIndx += numNewMembers
56+
}
1457

58+
let newDeclList = SyntaxFactory.makeMemberDeclList(newMembers)
59+
let newMemberBlock = SyntaxFactory.makeMemberDeclBlock(leftBrace: node.members.leftBrace,
60+
members: newDeclList,
61+
rightBrace: node.members.rightBrace)
62+
return node.withMembers(newMemberBlock)
63+
}
64+
65+
func createAssociateOrRawCaseDecl(fullDecl: EnumCaseDeclSyntax,
66+
removedElement: EnumCaseElementSyntax) -> EnumCaseDeclSyntax {
67+
let formattedElement = removedElement.withTrailingComma(nil)
68+
let newElementList = SyntaxFactory.makeEnumCaseElementList([formattedElement])
69+
let newDecl = SyntaxFactory.makeEnumCaseDecl(attributes: fullDecl.attributes,
70+
modifiers: fullDecl.modifiers,
71+
caseKeyword: fullDecl.caseKeyword,
72+
elements: newElementList)
73+
return newDecl
74+
}
75+
76+
// Returns formatted declaration of cases without associated/raw values, or nil if all cases had
77+
// a raw or associate value
78+
func removeAssociateOrRawCaseDecl(fullDecl: EnumCaseDeclSyntax?) -> EnumCaseDeclSyntax? {
79+
guard let fullDecl = fullDecl else { return nil }
80+
var newList: [EnumCaseElementSyntax] = []
81+
82+
for element in fullDecl.elements {
83+
if element.associatedValue == nil && element.rawValue == nil { newList.append(element) }
84+
}
85+
86+
guard newList.count > 0 else { return nil }
87+
let (last, indx) = (newList[newList.count - 1], newList.count - 1)
88+
if last.trailingComma != nil {
89+
newList[indx] = last.withTrailingComma(nil)
90+
}
91+
return fullDecl.withElements(SyntaxFactory.makeEnumCaseElementList(newList))
92+
}
93+
}
94+
95+
extension Diagnostic.Message {
96+
static func moveAssociatedOrRawValueCase(name: String) -> Diagnostic.Message {
97+
return .init(.warning, "move \(name) case to a new line")
98+
}
1599
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import Foundation
2+
import XCTest
3+
import SwiftSyntax
4+
5+
@testable import Rules
6+
7+
public class OneCasePerLineTests: DiagnosingTestCase {
8+
func testInvalidCasesOnLine() {
9+
XCTAssertFormatting(OneCasePerLine.self,
10+
input: """
11+
public enum Token {
12+
case arrow
13+
case comma, identifier(String), semicolon, stringSegment(String)
14+
case period
15+
case ifKeyword(String), forKeyword(String)
16+
indirect case guardKeyword, elseKeyword, contextualKeyword(String)
17+
var x: Bool
18+
case leftParen, rightParen = ")", leftBrace, rightBrace = "}"
19+
}
20+
""",
21+
expected: """
22+
public enum Token {
23+
case arrow
24+
case comma, semicolon
25+
case identifier(String)
26+
case stringSegment(String)
27+
case period
28+
case ifKeyword(String)
29+
case forKeyword(String)
30+
indirect case guardKeyword, elseKeyword
31+
indirect case contextualKeyword(String)
32+
var x: Bool
33+
case leftParen, leftBrace
34+
case rightParen = ")"
35+
case rightBrace = "}"
36+
}
37+
""")
38+
}
39+
40+
#if !os(macOS)
41+
static let allTests = [
42+
OneCasePerLineTests.testInvalidCasesOnLine,
43+
]
44+
#endif
45+
}

0 commit comments

Comments
 (0)