Skip to content

Commit 2ca2c46

Browse files
committed
[Lint/Format] Extend empty literal rewritting rule to support parameter with default values
1 parent 7463405 commit 2ca2c46

File tree

3 files changed

+129
-34
lines changed

3 files changed

+129
-34
lines changed

Sources/SwiftFormat/Core/Pipelines+Generated.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ class LintPipeline: SyntaxVisitor {
159159
}
160160

161161
override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind {
162+
visitIfEnabled(AlwaysUseLiteralForEmptyCollectionInit.visit, for: node)
162163
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
163164
return .visitChildren
164165
}

Sources/SwiftFormat/Rules/AlwaysUseLiteralForEmptyCollectionInit.swift

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,58 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule {
2525
public override class var isOptIn: Bool { return true }
2626

2727
public override func visit(_ node: PatternBindingSyntax) -> PatternBindingSyntax {
28-
// Check whether the initializer is `[<Type>]()`
2928
guard let initializer = node.initializer,
30-
let initCall = initializer.value.as(FunctionCallExprSyntax.self),
31-
initCall.arguments.isEmpty else {
29+
let type = isRewritable(initializer) else {
3230
return node
3331
}
3432

35-
if let arrayLiteral = initCall.calledExpression.as(ArrayExprSyntax.self),
36-
let type = getLiteralType(arrayLiteral) {
33+
if let type = type.as(ArrayTypeSyntax.self) {
3734
return rewrite(node, type: type)
3835
}
3936

40-
if let dictLiteral = initCall.calledExpression.as(DictionaryExprSyntax.self),
41-
let type = getLiteralType(dictLiteral) {
37+
if let type = type.as(DictionaryTypeSyntax.self) {
4238
return rewrite(node, type: type)
4339
}
4440

4541
return node
4642
}
4743

44+
public override func visit(_ param: FunctionParameterSyntax) -> FunctionParameterSyntax {
45+
guard let initializer = param.defaultValue,
46+
let type = isRewritable(initializer) else {
47+
return param
48+
}
49+
50+
if let type = type.as(ArrayTypeSyntax.self) {
51+
return rewrite(param, type: type)
52+
}
53+
54+
if let type = type.as(DictionaryTypeSyntax.self) {
55+
return rewrite(param, type: type)
56+
}
57+
58+
return param
59+
}
60+
61+
/// Check whether the initializer is `[<Type>]()` and, if so, it could be rewritten to use an empty collection literal.
62+
/// Return a type of the collection.
63+
public func isRewritable(_ initializer: InitializerClauseSyntax) -> TypeSyntax? {
64+
guard let initCall = initializer.value.as(FunctionCallExprSyntax.self),
65+
initCall.arguments.isEmpty else {
66+
return nil
67+
}
68+
69+
if let arrayLiteral = initCall.calledExpression.as(ArrayExprSyntax.self) {
70+
return getLiteralType(arrayLiteral)
71+
}
72+
73+
if let dictLiteral = initCall.calledExpression.as(DictionaryExprSyntax.self) {
74+
return getLiteralType(dictLiteral)
75+
}
76+
77+
return nil
78+
}
79+
4880
private func rewrite(_ node: PatternBindingSyntax,
4981
type: ArrayTypeSyntax) -> PatternBindingSyntax {
5082
var replacement = node
@@ -83,14 +115,32 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule {
83115
}
84116

85117
let initializer = node.initializer!
86-
let emptyDictExpr = DictionaryExprSyntax(content: .colon(.colonToken()))
87-
88118
// Replace initializer call with empty dictionary literal: `[<Type>]()` -> `[]`
89-
replacement.initializer = initializer.with(\.value, ExprSyntax(emptyDictExpr))
119+
replacement.initializer = initializer.with(\.value, ExprSyntax(getEmptyDictionaryLiteral()))
90120

91121
return replacement
92122
}
93123

124+
private func rewrite(_ param: FunctionParameterSyntax,
125+
type: ArrayTypeSyntax) -> FunctionParameterSyntax {
126+
guard let initializer = param.defaultValue else {
127+
return param
128+
}
129+
130+
emitDiagnostic(replace: "\(initializer.value)", with: "[]", on: initializer.value)
131+
return param.with(\.defaultValue, initializer.with(\.value, getEmptyArrayLiteral()))
132+
}
133+
134+
private func rewrite(_ param: FunctionParameterSyntax,
135+
type: DictionaryTypeSyntax) -> FunctionParameterSyntax {
136+
guard let initializer = param.defaultValue else {
137+
return param
138+
}
139+
140+
emitDiagnostic(replace: "\(initializer.value)", with: "[:]", on: initializer.value)
141+
return param.with(\.defaultValue, initializer.with(\.value, getEmptyDictionaryLiteral()))
142+
}
143+
94144
private func diagnose(_ node: PatternBindingSyntax, type: ArrayTypeSyntax) {
95145
var withFixIt = "[]"
96146
if node.typeAnnotation == nil {
@@ -115,21 +165,39 @@ public final class AlwaysUseLiteralForEmptyCollectionInit : SyntaxFormatRule {
115165
diagnose(.refactorIntoEmptyLiteral(replace: replace, with: fixIt), on: on)
116166
}
117167

118-
private func getLiteralType(_ arrayLiteral: ArrayExprSyntax) -> ArrayTypeSyntax? {
168+
private func getLiteralType(_ arrayLiteral: ArrayExprSyntax) -> TypeSyntax? {
119169
guard let elementExpr = arrayLiteral.elements.firstAndOnly,
120170
elementExpr.is(ArrayElementSyntax.self) else {
121171
return nil
122172
}
123173

124174
var parser = Parser(arrayLiteral.description)
125175
let elementType = TypeSyntax.parse(from: &parser)
126-
return elementType.hasError ? nil : elementType.as(ArrayTypeSyntax.self)
176+
177+
guard !elementType.hasError, elementType.is(ArrayTypeSyntax.self) else {
178+
return nil
179+
}
180+
181+
return elementType
127182
}
128183

129-
private func getLiteralType(_ dictLiteral: DictionaryExprSyntax) -> DictionaryTypeSyntax? {
184+
private func getLiteralType(_ dictLiteral: DictionaryExprSyntax) -> TypeSyntax? {
130185
var parser = Parser(dictLiteral.description)
131186
let elementType = TypeSyntax.parse(from: &parser)
132-
return elementType.hasError ? nil : elementType.as(DictionaryTypeSyntax.self)
187+
188+
guard !elementType.hasError, elementType.is(DictionaryTypeSyntax.self) else {
189+
return nil
190+
}
191+
192+
return elementType
193+
}
194+
195+
private func getEmptyArrayLiteral() -> ExprSyntax {
196+
ExprSyntax(ArrayExprSyntax(elements: ArrayElementListSyntax.init([])))
197+
}
198+
199+
private func getEmptyDictionaryLiteral() -> ExprSyntax {
200+
ExprSyntax(DictionaryExprSyntax(content: .colon(.colonToken())))
133201
}
134202
}
135203

Tests/SwiftFormatTests/Rules/AlwaysUseLiteralForEmptyCollectionInitTests.swift

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,27 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas
1010
public struct Test {
1111
var value1 = 1️⃣[Int]()
1212
13-
func test(v: [Double] = [Double]()) {
14-
let _ = 2️⃣[String]()
13+
func test(v: [Double] = 2️⃣[Double]()) {
14+
let _ = 3️⃣[String]()
1515
}
1616
}
1717
18-
var _: [Category<Int>] = 3️⃣[Category<Int>]()
19-
let _ = 4️⃣[(Int, Array<String>)]()
20-
let _: [(String, Int, Float)] = 5️⃣[(String, Int, Float)]()
18+
var _: [Category<Int>] = 4️⃣[Category<Int>]()
19+
let _ = 5️⃣[(Int, Array<String>)]()
20+
let _: [(String, Int, Float)] = 6️⃣[(String, Int, Float)]()
2121
2222
let _ = [(1, 2, String)]()
23+
24+
class TestSubscript {
25+
subscript(_: [A] = 7️⃣[A](), x: [(Int, B)] = 8️⃣[(Int, B)]()) {
26+
}
27+
}
2328
""",
2429
expected: """
2530
public struct Test {
2631
var value1: [Int] = []
2732
28-
func test(v: [Double] = [Double]()) {
33+
func test(v: [Double] = []) {
2934
let _: [String] = []
3035
}
3136
}
@@ -35,13 +40,21 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas
3540
let _: [(String, Int, Float)] = []
3641
3742
let _ = [(1, 2, String)]()
43+
44+
class TestSubscript {
45+
subscript(_: [A] = [], x: [(Int, B)] = []) {
46+
}
47+
}
3848
""",
3949
findings: [
4050
FindingSpec("1️⃣", message: "replace '[Int]()' with ': [Int] = []'"),
41-
FindingSpec("2️⃣", message: "replace '[String]()' with ': [String] = []'"),
42-
FindingSpec("3️⃣", message: "replace '[Category<Int>]()' with '[]'"),
43-
FindingSpec("4️⃣", message: "replace '[(Int, Array<String>)]()' with ': [(Int, Array<String>)] = []'"),
44-
FindingSpec("5️⃣", message: "replace '[(String, Int, Float)]()' with '[]'"),
51+
FindingSpec("2️⃣", message: "replace '[Double]()' with '[]'"),
52+
FindingSpec("3️⃣", message: "replace '[String]()' with ': [String] = []'"),
53+
FindingSpec("4️⃣", message: "replace '[Category<Int>]()' with '[]'"),
54+
FindingSpec("5️⃣", message: "replace '[(Int, Array<String>)]()' with ': [(Int, Array<String>)] = []'"),
55+
FindingSpec("6️⃣", message: "replace '[(String, Int, Float)]()' with '[]'"),
56+
FindingSpec("7️⃣", message: "replace '[A]()' with '[]'"),
57+
FindingSpec("8️⃣", message: "replace '[(Int, B)]()' with '[]'"),
4558
]
4659
)
4760
}
@@ -53,22 +66,27 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas
5366
public struct Test {
5467
var value1 = 1️⃣[Int: String]()
5568
56-
func test(v: [Double: Int] = [Double: Int]()) {
57-
let _ = 2️⃣[String: Int]()
69+
func test(v: [Double: Int] = 2️⃣[Double: Int]()) {
70+
let _ = 3️⃣[String: Int]()
5871
}
5972
}
6073
61-
var _: [Category<Int>: String] = 3️⃣[Category<Int>: String]()
62-
let _ = 4️⃣[(Int, Array<String>): Int]()
63-
let _: [String: (String, Int, Float)] = 5️⃣[String: (String, Int, Float)]()
74+
var _: [Category<Int>: String] = 4️⃣[Category<Int>: String]()
75+
let _ = 5️⃣[(Int, Array<String>): Int]()
76+
let _: [String: (String, Int, Float)] = 6️⃣[String: (String, Int, Float)]()
6477
6578
let _ = [String: (1, 2, String)]()
79+
80+
class TestSubscript {
81+
subscript(_: [A: Int] = 7️⃣[A: Int](), x: [(Int, B): String] = 8️⃣[(Int, B): String]()) {
82+
}
83+
}
6684
""",
6785
expected: """
6886
public struct Test {
6987
var value1: [Int: String] = [:]
7088
71-
func test(v: [Double: Int] = [Double: Int]()) {
89+
func test(v: [Double: Int] = [:]) {
7290
let _: [String: Int] = [:]
7391
}
7492
}
@@ -78,13 +96,21 @@ final class AlwaysUseLiteralForEmptyCollectionInitTests: LintOrFormatRuleTestCas
7896
let _: [String: (String, Int, Float)] = [:]
7997
8098
let _ = [String: (1, 2, String)]()
99+
100+
class TestSubscript {
101+
subscript(_: [A: Int] = [:], x: [(Int, B): String] = [:]) {
102+
}
103+
}
81104
""",
82105
findings: [
83106
FindingSpec("1️⃣", message: "replace '[Int: String]()' with ': [Int: String] = [:]'"),
84-
FindingSpec("2️⃣", message: "replace '[String: Int]()' with ': [String: Int] = [:]'"),
85-
FindingSpec("3️⃣", message: "replace '[Category<Int>: String]()' with '[:]'"),
86-
FindingSpec("4️⃣", message: "replace '[(Int, Array<String>): Int]()' with ': [(Int, Array<String>): Int] = [:]'"),
87-
FindingSpec("5️⃣", message: "replace '[String: (String, Int, Float)]()' with '[:]'"),
107+
FindingSpec("2️⃣", message: "replace '[Double: Int]()' with '[:]'"),
108+
FindingSpec("3️⃣", message: "replace '[String: Int]()' with ': [String: Int] = [:]'"),
109+
FindingSpec("4️⃣", message: "replace '[Category<Int>: String]()' with '[:]'"),
110+
FindingSpec("5️⃣", message: "replace '[(Int, Array<String>): Int]()' with ': [(Int, Array<String>): Int] = [:]'"),
111+
FindingSpec("6️⃣", message: "replace '[String: (String, Int, Float)]()' with '[:]'"),
112+
FindingSpec("7️⃣", message: "replace '[A: Int]()' with '[:]'"),
113+
FindingSpec("8️⃣", message: "replace '[(Int, B): String]()' with '[:]'"),
88114
]
89115
)
90116
}

0 commit comments

Comments
 (0)