Skip to content

Commit bff3f79

Browse files
committed
Make trailingCommas removal more strict
1 parent 82a24fe commit bff3f79

File tree

3 files changed

+58
-26
lines changed

3 files changed

+58
-26
lines changed

Sources/Rules/TrailingCommas.swift

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ public extension FormatRule {
2323
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: true, isCollection: true)
2424

2525
case .subscript, .captureList:
26-
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: formatter.options.swiftVersion >= "6.1")
26+
let trailingCommaSupported = formatter.options.swiftVersion >= "6.1"
27+
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: trailingCommaSupported)
2728

2829
default:
2930
return
3031
}
3132

32-
case .endOfScope(")"):
33-
var trailingCommaSupported = false
33+
case .endOfScope(")") where formatter.options.swiftVersion >= "6.1":
34+
var trailingCommaSupported: Bool?
3435

3536
// Trailing commas are supported in function calls, function definitions, initializers, and attributes.
36-
if formatter.options.swiftVersion >= "6.1",
37-
let identifierIndex = formatter.parseFunctionIdentifier(beforeStartOfScope: startOfScope),
37+
if let identifierIndex = formatter.parseFunctionIdentifier(beforeStartOfScope: startOfScope),
3838
let identifierToken = formatter.token(at: identifierIndex),
3939
identifierToken.isIdentifier || identifierToken.isAttribute || identifierToken.isKeyword,
4040
// If the case of `@escaping` or `@Sendable`, this could be a closure type where trailing commas are not supported.
@@ -59,19 +59,16 @@ public extension FormatRule {
5959

6060
// If the previous token is the closing `>` of a generic list, then this is a function declaration or initializer,
6161
// like `func foo<T>(args...)` or `Foo<Bar>(args...)`.
62-
if formatter.options.swiftVersion >= "6.1",
63-
let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
64-
formatter.tokens[tokenBeforeStartOfScope] == .endOfScope(">")
62+
else if let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
63+
formatter.tokens[tokenBeforeStartOfScope] == .endOfScope(">")
6564
{
6665
trailingCommaSupported = true
6766
}
6867

6968
// In Swift 6.1, trailing commas are also supported in tuple values,
7069
// but not tuple or closure types: https://github.com/swiftlang/swift/issues/81485
7170
// If we know this is a tuple value, then trailing commas are supported.
72-
if formatter.options.swiftVersion >= "6.1",
73-
let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope)
74-
{
71+
else if let tokenBeforeStartOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope) {
7572
// `{ (...) }`, `return (...)` etc are always tuple values
7673
// (except in the case of a typealias, where the rhs is a type)
7774
let tokensPreceedingValuesNotTypes: Set<Token> = [.startOfScope("{"), .keyword("return"), .keyword("throw"), .keyword("switch"), .endOfScope("case")]
@@ -97,14 +94,13 @@ public extension FormatRule {
9794

9895
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: trailingCommaSupported)
9996

100-
case .endOfScope(">"):
97+
case .endOfScope(">") where formatter.options.swiftVersion >= "6.1":
10198
var trailingCommaSupported = false
10299

103100
// In Swift 6.1, only generic lists in concrete type / function / typealias declarations are allowed.
104101
// https://github.com/swiftlang/swift/issues/81474
105102
// All of these cases have the form `keyword identifier<...>`, like `class Foo<...>` or `func foo<...>`.
106-
if formatter.options.swiftVersion >= "6.1",
107-
let identifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
103+
if let identifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope),
108104
formatter.tokens[identifierIndex].isIdentifier,
109105
let keywordIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: identifierIndex),
110106
let keyword = formatter.token(at: keywordIndex),
@@ -116,6 +112,9 @@ public extension FormatRule {
116112

117113
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: trailingCommaSupported)
118114

115+
case .endOfScope(")"), .endOfScope(">"):
116+
formatter.addOrRemoveTrailingComma(beforeEndOfScope: i, trailingCommaSupported: false)
117+
119118
default:
120119
break
121120
}
@@ -180,12 +179,16 @@ extension Formatter {
180179
/// Trailing commas can always be removed.
181180
/// - `trailingCommaSupported` indicates whether or not a trailing comma is allowed by the language at this position.
182181
/// - `isCollection` indicates whether this is an array or dictionary literal.
183-
func addOrRemoveTrailingComma(beforeEndOfScope endOfListIndex: Int, trailingCommaSupported: Bool, isCollection: Bool = false) {
182+
func addOrRemoveTrailingComma(
183+
beforeEndOfScope endOfListIndex: Int,
184+
trailingCommaSupported: Bool?,
185+
isCollection: Bool = false
186+
) {
184187
guard let prevTokenIndex = index(of: .nonSpaceOrComment, before: endOfListIndex),
185188
let startOfScope = startOfScope(at: endOfListIndex)
186189
else { return }
187190

188-
// Decide whether to insert, remove, or preserve the comma in this context based on the enabled option
191+
// Decide whether to insert or remove the comma in this context
189192
enum TrailingCommaMode {
190193
case insert
191194
case remove
@@ -198,9 +201,12 @@ extension Formatter {
198201
trailingCommaMode = .remove
199202

200203
case .always:
201-
if trailingCommaSupported {
204+
switch trailingCommaSupported {
205+
case true?:
202206
trailingCommaMode = .insert
203-
} else {
207+
case false?:
208+
trailingCommaMode = .remove
209+
case nil:
204210
trailingCommaMode = .preserve
205211
}
206212

@@ -212,11 +218,12 @@ extension Formatter {
212218
}
213219

214220
case .multiElementLists:
215-
if commaSeparatedElementsInScope(startOfScope: startOfScope).count <= 1 {
221+
switch trailingCommaSupported {
222+
case _ where commaSeparatedElementsInScope(startOfScope: startOfScope).count <= 1, false?:
216223
trailingCommaMode = .remove
217-
} else if trailingCommaSupported {
224+
case true?:
218225
trailingCommaMode = .insert
219-
} else {
226+
case nil:
220227
trailingCommaMode = .preserve
221228
}
222229
}

Tests/Rules/TrailingCommasTests.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,31 @@ class TrailingCommasTests: XCTestCase {
396396
testFormatting(for: input, output, rule: .trailingCommas, options: options)
397397
}
398398

399+
func testTrailingCommasRemovedFromFunctionParametersOnUnsupportedSwiftVersion() {
400+
let input = """
401+
struct Foo {
402+
func foo(
403+
bar: Int,
404+
baaz: Int,
405+
) -> Int {
406+
bar + baaz
407+
}
408+
}
409+
"""
410+
let output = """
411+
struct Foo {
412+
func foo(
413+
bar: Int,
414+
baaz: Int
415+
) -> Int {
416+
bar + baaz
417+
}
418+
}
419+
"""
420+
let options = FormatOptions(trailingCommas: .always, swiftVersion: "6.0")
421+
testFormatting(for: input, output, rule: .trailingCommas, options: options)
422+
}
423+
399424
func testTrailingCommasAddedToGenericFunctionParameters() {
400425
let input = """
401426
struct Foo {

Tests/Rules/WrapArgumentsTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2974,18 +2974,18 @@ class WrapArgumentsTests: XCTestCase {
29742974
}
29752975
"""
29762976

2977-
testFormatting(for: input, output, rule: .wrapArguments, options: FormatOptions(wrapArguments: .beforeFirst, allowPartialWrapping: false), exclude: [.unusedArguments])
2977+
testFormatting(for: input, output, rule: .wrapArguments, options: FormatOptions(wrapArguments: .beforeFirst, allowPartialWrapping: false), exclude: [.unusedArguments, .trailingCommas])
29782978
}
29792979

29802980
func testWrapPartiallyWrappedFunctionCallTwoLines() {
29812981
let input = """
29822982
func foo(
29832983
foo: Foo, bar: Bar,
2984-
baaz: Baaz, quux: Quux,
2984+
baaz: Baaz, quux: Quux
29852985
) {
29862986
print(
29872987
foo, bar,
2988-
baaz, quux,
2988+
baaz, quux
29892989
)
29902990
}
29912991
"""
@@ -2995,13 +2995,13 @@ class WrapArgumentsTests: XCTestCase {
29952995
foo: Foo,
29962996
bar: Bar,
29972997
baaz: Baaz,
2998-
quux: Quux,
2998+
quux: Quux
29992999
) {
30003000
print(
30013001
foo,
30023002
bar,
30033003
baaz,
3004-
quux,
3004+
quux
30053005
)
30063006
}
30073007
"""

0 commit comments

Comments
 (0)