@@ -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 }
0 commit comments