11import SwiftLintCore
22import SwiftSyntax
33
4- @SwiftSyntaxRule
4+ @SwiftSyntaxRule ( explicitRewriter : true )
55struct TrailingClosureRule : OptInRule {
66var configuration = TrailingClosureConfiguration ( )
77
@@ -48,6 +48,59 @@ struct TrailingClosureRule: OptInRule {
4848 n.forEach(↓{ print($0) })
4949 }
5050""" , excludeFromDocumentation: true )
51+ ] ,
52+ corrections: [
53+ Example ( " foo.map(↓{ $0 + 1 }) " ) :
54+ Example ( " foo.map { $0 + 1 } " ) ,
55+ Example ( " foo.reduce(0, combine: ↓{ $0 + 1 }) " ) :
56+ Example ( " foo.reduce(0) { $0 + 1 } " ) ,
57+ Example ( " offsets.sorted(by: ↓{ $0.offset < $1.offset }) " ) :
58+ Example ( " offsets.sorted { $0.offset < $1.offset } " ) ,
59+ Example ( " foo.something(0, ↓{ $0 + 1 }) " ) :
60+ Example ( " foo.something(0) { $0 + 1 } " ) ,
61+ Example ( " foo.something(param1: { _ in true }, param2: 0, param3: ↓{ _ in false }) " ) :
62+ Example ( " foo.something(param1: { _ in true }, param2: 0) { _ in false } " ) ,
63+ Example ( " f(a: ↓{ g(b: ↓{ 1 }) }) " ) :
64+ Example ( " f { g { 1 }} " ) ,
65+ Example ( """
66+ for n in list {
67+ n.forEach(↓{ print($0) })
68+ }
69+ """ ) : Example ( """
70+ for n in list {
71+ n.forEach { print($0) }
72+ }
73+ """ ) ,
74+ Example ( """
75+ f(a: 1,
76+ b: 2,
77+ c: { 3 })
78+ """ ) : Example ( """
79+ f(a: 1,
80+ b: 2) { 3 }
81+ """ ) ,
82+ Example ( """
83+ f(a: 1, // comment
84+ b: 2, /* comment */ c: { 3 })
85+ """ ) : Example ( """
86+ f(a: 1, // comment
87+ b: 2) /* comment */ { 3 }
88+ """ ) ,
89+ Example ( """
90+ f(a: 2, c: /* comment */ { 3 } /* comment */)
91+ """ ) : Example ( """
92+ f(a: 2) /* comment */ { 3 } /* comment */
93+ """ ) ,
94+ Example ( """
95+ f(a: 2, /* comment */ c /* comment */ : /* comment */ { 3 } /* comment */)
96+ """ ) : Example ( """
97+ f(a: 2) /* comment */ { 3 } /* comment */
98+ """ ) ,
99+ Example ( """
100+ f(a: 2, /* comment1 */ c /* comment2 */ : /* comment3 */ { 3 } /* comment4 */)
101+ """ ) : Example ( """
102+ f(a: 2) /* comment1 */ /* comment2 */ /* comment3 */ { 3 } /* comment4 */
103+ """ )
51104]
52105)
53106}
@@ -77,6 +130,39 @@ private extension TrailingClosureRule {
77130}
78131}
79132
133+ private extension TrailingClosureRule {
134+ final class Rewriter : ViolationsSyntaxRewriter < ConfigurationType > {
135+ override func visit( _ node: FunctionCallExprSyntax ) -> ExprSyntax {
136+ guard node. trailingClosure== nil else { return super. visit ( node) }
137+
138+ if configuration. onlySingleMutedParameter{
139+ if let param= node. singleMutedClosureParameter,
140+ let converted= node. convertToTrailingClosure ( ) {
141+ correctionPositions. append ( param. positionAfterSkippingLeadingTrivia)
142+ return super. visit ( converted)
143+ }
144+ } else if let param= node. lastDistinctClosureParameter,
145+ let converted= node. convertToTrailingClosure ( ) {
146+ correctionPositions. append ( param. positionAfterSkippingLeadingTrivia)
147+ return super. visit ( converted)
148+ }
149+ return super. visit ( node)
150+ }
151+
152+ override func visit( _ node: ConditionElementListSyntax ) -> ConditionElementListSyntax {
153+ node
154+ }
155+
156+ override func visit( _ node: ForStmtSyntax ) -> StmtSyntax {
157+ if let body= rewrite ( node. body) . as ( CodeBlockSyntax . self) {
158+ StmtSyntax ( node. with ( \. body, body) )
159+ } else {
160+ StmtSyntax ( node)
161+ }
162+ }
163+ }
164+ }
165+
80166private extension FunctionCallExprSyntax {
81167var singleMutedClosureParameter : ClosureExprSyntax ? {
82168if let onlyArgument= arguments. onlyElement, onlyArgument. label== nil {
@@ -92,10 +178,87 @@ private extension FunctionCallExprSyntax {
92178}
93179return nil
94180}
181+
182+ func dropLastArgument( ) -> Self {
183+ self
184+ . with ( \. arguments, LabeledExprListSyntax ( arguments. dropLast ( ) ) . dropLastTrailingComma ( ) )
185+ . dropParensIfEmpty ( )
186+ }
187+
188+ func dropParensIfEmpty( ) -> Self {
189+ if arguments. isEmpty{
190+ self
191+ . with ( \. rightParen, nil )
192+ . with ( \. leftParen, nil )
193+ } else {
194+ self
195+ }
196+ }
197+
198+ func convertToTrailingClosure( ) -> Self ? {
199+ guard trailingClosure== nil , let lastDistinctClosureParameterelse { return nil }
200+ let leadingTrivia = lastTriviaInArguments?
201+ . removingLeadingNewlines ( )
202+ . appendingMissingSpace ( ) ?? [ ]
203+
204+ return dropLastArgument ( )
205+ . with ( \. trailingClosure, lastDistinctClosureParameter. with ( \. leadingTrivia, leadingTrivia) )
206+ . with ( \. calledExpression. trailingTrivia, [ ] )
207+ }
208+
209+ var lastTriviaInArguments : Trivia ? {
210+ guard let lastArgument= arguments. last,
211+ let previous= lastArgument. previousToken ( viewMode: . sourceAccurate) ? . trailingTriviaelse { return nil }
212+
213+ return previous
214+ . merging ( lastArgument. leadingTrivia)
215+ . merging ( triviaOf: lastArgument. label)
216+ . merging ( triviaOf: lastArgument. colon)
217+ }
95218}
96219
97220private extension LabeledExprSyntax {
98221var isClosureExpr : Bool {
99222 expression. is ( ClosureExprSyntax . self)
100223}
101224}
225+
226+ private extension LabeledExprListSyntax {
227+ func dropLastTrailingComma( ) -> Self {
228+ guard let lastelse { return [ ] }
229+
230+ if last. trailingComma== nil {
231+ return self
232+ }
233+ return LabeledExprListSyntax ( dropLast ( ) ) + CollectionOfOne( last. with ( \. trailingComma, nil ) )
234+ }
235+ }
236+
237+ private extension Trivia {
238+ var endsWithSpace : Bool {
239+ if case. spaces= pieces. last{
240+ return true
241+ }
242+ return false
243+ }
244+
245+ var startsWithNewline : Bool {
246+ first? . isNewline== true
247+ }
248+
249+ func appendingMissingSpace( ) -> Self {
250+ if endsWithSpace{
251+ self
252+ } else {
253+ merging ( . space)
254+ }
255+ }
256+
257+ func removingLeadingNewlines( ) -> Self {
258+ if startsWithNewline{
259+ Trivia ( pieces: pieces. drop ( while: { $0. isNewline} ) )
260+ } else {
261+ self
262+ }
263+ }
264+ }