Skip to content

Commit 5179e40

Browse files
committed
Update trailingCommas to support @escaping / @sendable closures, protocol primary associated types
1 parent 81f9e42 commit 5179e40

File tree

3 files changed

+47
-4
lines changed

3 files changed

+47
-4
lines changed

Sources/ParsingHelpers.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,12 @@ extension Formatter {
637637
}
638638
}
639639

640+
/// Whether or not the index is the start of a valid closure type
641+
func isStartOfClosureType(at i: Int) -> Bool {
642+
guard let type = parseType(at: i) else { return false }
643+
return index(of: .operator("->", .infix), in: Range(type.range)) != nil
644+
}
645+
640646
func isInClosureArguments(at i: Int) -> Bool {
641647
var i = i
642648
while let token = token(at: i) {

Sources/Rules/TrailingCommas.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,16 @@ public extension FormatRule {
3333
let startOfScope = formatter.startOfScope(at: i),
3434
let identifierBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
3535
let identifierToken = formatter.token(at: identifierBeforeStartOfScope),
36-
identifierToken.isIdentifier || identifierToken.isAttribute || (identifierToken.isKeyword && identifierToken.string.hasPrefix("#"))
36+
identifierToken.isIdentifier || identifierToken.isAttribute || (identifierToken.isKeyword && identifierToken.string.hasPrefix("#")),
37+
// If the case of `@escaping` or `@Sendable`, this could be a closure type where trailing commas are not supported.
38+
!formatter.isStartOfClosureType(at: startOfScope)
3739
{
3840
// In Swift 6.1, built-in attributes unexpectedly don't support trailing commas.
3941
// Other attributes like property wrappers and macros do support trailing commas.
4042
// https://github.com/swiftlang/swift/issues/81475
4143
// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/
42-
let unsupportedBuiltInAttributes = ["@available", "@backDeployed", "@objc", "@freestanding", "@attached"]
44+
// Some attributes like `@objc`, `@inline` that have parens but not comma-separated lists don't support trailing commas.
45+
let unsupportedBuiltInAttributes = ["@available", "@backDeployed", "@freestanding", "@attached", "@objc", "@inline"]
4346
if identifierToken.isAttribute, unsupportedBuiltInAttributes.contains(identifierToken.string)
4447
|| identifierToken.string.hasPrefix("@_")
4548
{
@@ -78,7 +81,7 @@ public extension FormatRule {
7881
case .endOfScope(">"):
7982
var trailingCommaSupported = false
8083

81-
// In Swift 6.1, only generic lists in type / function / typealias declarations are allowed.
84+
// In Swift 6.1, only generic lists in concrete type / function / typealias declarations are allowed.
8285
// https://github.com/swiftlang/swift/issues/81474
8386
// All of these cases have the form `keyword identifier<...>`, like `class Foo<...>` or `func foo<...>`.
8487
if formatter.options.swiftVersion >= "6.1",
@@ -88,7 +91,7 @@ public extension FormatRule {
8891
let keywordIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: identifierIndex),
8992
let keyword = formatter.token(at: keywordIndex),
9093
keyword.isKeyword,
91-
["class", "actor", "struct", "enum", "protocol", "typealias", "func"].contains(keyword.string)
94+
["class", "actor", "struct", "enum", "typealias", "func"].contains(keyword.string)
9295
{
9396
trailingCommaSupported = true
9497
}

Tests/Rules/TrailingCommasTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,35 @@ class TrailingCommasTests: XCTestCase {
649649
bar: String,
650650
quux: String // trailing comma not supported
651651
)
652+
653+
let closure: @Sendable (
654+
String,
655+
String // trailing comma not supported
656+
) -> (
657+
bar: String,
658+
quux: String // trailing comma not supported
659+
)
660+
661+
let closure: (
662+
String,
663+
String // trailing comma not supported
664+
) async -> (
665+
bar: String,
666+
quux: String // trailing comma not supported
667+
)
668+
669+
let closure: (
670+
String,
671+
String // trailing comma not supported
672+
) async throws -> (
673+
bar: String,
674+
quux: String // trailing comma not supported
675+
)
676+
677+
func foo(_: @escaping (
678+
String,
679+
String // trailing comma not supported
680+
) -> Void) {}
652681
"""
653682

654683
let options = FormatOptions(trailingCommas: true, swiftVersion: "6.1")
@@ -1086,6 +1115,11 @@ class TrailingCommasTests: XCTestCase {
10861115
String,
10871116
Any
10881117
> {}
1118+
1119+
protocol MyProtocolWithAssociatedTypes<
1120+
Foo,
1121+
Bar
1122+
> {}
10891123
"""
10901124

10911125
let options = FormatOptions(trailingCommas: true, swiftVersion: "6.1")

0 commit comments

Comments
 (0)