Skip to content

Commit ed0e753

Browse files
committed
Update trailingCommas rule to support bug fixes in Swift 6.2
1 parent ae96a86 commit ed0e753

File tree

3 files changed

+1494
-28
lines changed

3 files changed

+1494
-28
lines changed

Sources/Rules/TrailingCommas.swift

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,27 @@ public extension FormatRule {
3030
return
3131
}
3232

33-
case .endOfScope(")") where formatter.options.swiftVersion >= "6.1":
33+
case .endOfScope(")"):
3434
var trailingCommaSupported: Bool?
3535

36+
if formatter.options.swiftVersion < "6.1" {
37+
trailingCommaSupported = false
38+
}
39+
3640
// Trailing commas are supported in function calls, function definitions, initializers, and attributes.
37-
if let identifierIndex = formatter.parseFunctionIdentifier(beforeStartOfScope: startOfScope),
41+
if formatter.options.swiftVersion >= "6.1",
42+
let identifierIndex = formatter.parseFunctionIdentifier(beforeStartOfScope: startOfScope),
3843
let identifierToken = formatter.token(at: identifierIndex),
3944
identifierToken.isIdentifier || identifierToken.isAttribute || identifierToken.isKeyword,
4045
// If the case of `@escaping` or `@Sendable`, this could be a closure type where trailing commas are not supported.
4146
!formatter.isStartOfClosureType(at: startOfScope)
4247
{
43-
// In Swift 6.1, built-in attributes unexpectedly don't support trailing commas.
44-
// Other attributes like property wrappers and macros do support trailing commas.
45-
// https://github.com/swiftlang/swift/issues/81475
46-
// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/
47-
// Some attributes like `@objc`, `@inline` that have parens but not comma-separated lists don't support trailing commas.
48-
let unsupportedBuiltInAttributes = ["@available", "@backDeployed", "@freestanding", "@attached", "@objc", "@inline"]
49-
if identifierToken.isAttribute, unsupportedBuiltInAttributes.contains(identifierToken.string)
50-
|| identifierToken.string.hasPrefix("@_")
48+
// Built-in attributes like `@available`, `@backDeployed` don't support trailing commas.
49+
// Assume any attribute with a lowercase first letter or a leading underscore is a built-in attribute.
50+
// https://github.com/swiftlang/swift/issues/81475#issuecomment-2894879640
51+
if identifierToken.isAttribute,
52+
let firstCharacterInAttribute = identifierToken.string.dropFirst().first,
53+
firstCharacterInAttribute.isLowercase || firstCharacterInAttribute == "_"
5154
{
5255
trailingCommaSupported = false
5356
}
@@ -59,16 +62,31 @@ public extension FormatRule {
5962

6063
// If the previous token is the closing `>` of a generic list, then this is a function declaration or initializer,
6164
// like `func foo<T>(args...)` or `Foo<Bar>(args...)`.
62-
else if let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
65+
else if formatter.options.swiftVersion >= "6.1",
66+
let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
6367
formatter.tokens[tokenBeforeStartOfScope] == .endOfScope(">")
6468
{
6569
trailingCommaSupported = true
6670
}
6771

68-
// In Swift 6.1, trailing commas are also supported in tuple values,
72+
// In Swift 6.2 and later, trailing commas are supported in tuple values and tuple types.
73+
// If there are multiple values in the parens, and it's not one of the scope types we already handled above,
74+
// then we know it's a tuple.
75+
else if formatter.options.swiftVersion >= "6.2",
76+
formatter.commaSeparatedElementsInScope(startOfScope: startOfScope).count > 1
77+
{
78+
trailingCommaSupported = true
79+
}
80+
81+
// In Swift 6.1, trailing commas are only supported in tuple values,
6982
// but not tuple or closure types: https://github.com/swiftlang/swift/issues/81485
7083
// If we know this is a tuple value, then trailing commas are supported.
71-
else if let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope) {
84+
//
85+
// This also handles paren scopes with only a single element (so, not a tuple)
86+
// where trailing commas are allowed in Swift 6.2 and later.
87+
else if formatter.options.swiftVersion >= "6.1",
88+
let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope)
89+
{
7290
// `{ (...) }`, `return (...)` etc are always tuple values
7391
// (except in the case of a typealias, where the rhs is a type)
7492
let tokensPreceedingValuesNotTypes: Set<Token> = [.startOfScope("{"), .keyword("return"), .keyword("throw"), .keyword("switch"), .endOfScope("case")]
@@ -92,29 +110,38 @@ public extension FormatRule {
92110
}
93111
}
94112

113+
// In Swift 6.2 and later, trailing commas are always supported in closure argument lists.
114+
if formatter.options.swiftVersion >= "6.2",
115+
let tokenAfterEndOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i),
116+
[.identifier("async"), .keyword("throws"), .operator("->", .infix)].contains(formatter.tokens[tokenAfterEndOfScope])
117+
{
118+
trailingCommaSupported = true
119+
}
120+
95121
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: trailingCommaSupported)
96122

97-
case .endOfScope(">") where formatter.options.swiftVersion >= "6.1":
123+
case .endOfScope(">"):
98124
var trailingCommaSupported = false
99125

100-
// In Swift 6.1, only generic lists in concrete type / function / typealias declarations are allowed.
101-
// https://github.com/swiftlang/swift/issues/81474
102-
// All of these cases have the form `keyword identifier<...>`, like `class Foo<...>` or `func foo<...>`.
103-
if let identifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
104-
formatter.tokens[identifierIndex].isIdentifier,
105-
let keywordIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: identifierIndex),
106-
let keyword = formatter.token(at: keywordIndex),
107-
keyword.isKeyword,
108-
["class", "actor", "struct", "enum", "typealias", "func"].contains(keyword.string)
109-
{
126+
if formatter.options.swiftVersion >= "6.2" {
110127
trailingCommaSupported = true
128+
} else if formatter.options.swiftVersion == "6.1" {
129+
// In Swift 6.1, only generic lists in concrete type / function / typealias declarations are allowed.
130+
// https://github.com/swiftlang/swift/issues/81474
131+
// All of these cases have the form `keyword identifier<...>`, like `class Foo<...>` or `func foo<...>`.
132+
if let identifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
133+
formatter.tokens[identifierIndex].isIdentifier,
134+
let keywordIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: identifierIndex),
135+
let keyword = formatter.token(at: keywordIndex),
136+
keyword.isKeyword,
137+
["class", "actor", "struct", "enum", "typealias", "func"].contains(keyword.string)
138+
{
139+
trailingCommaSupported = true
140+
}
111141
}
112142

113143
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: trailingCommaSupported)
114144

115-
case .endOfScope(")"), .endOfScope(">"):
116-
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: false)
117-
118145
default:
119146
break
120147
}

Sources/SwiftFormat.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public let swiftVersionFile = ".swift-version"
4545
public let swiftVersions = [
4646
"3.x", "4.0", "4.1", "4.2",
4747
"5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7", "5.8", "5.9", "5.10",
48-
"6.0", "6.1", "6.2",
48+
"6.0", "6.1", "6.2", "6.3",
4949
]
5050

5151
/// Supported Swift language modes

0 commit comments

Comments
 (0)