Skip to content

Commit 0fa4da4

Browse files
committed
Add convert to trailing closure and editor placeholder refactorings
Adds three new refactorings: - `ConvertToTrailingClosures` - `ExpandEditorPlaceholder` - `ExpandEditorPlaceholders` `ExpandEditorPlaceholders` is a combination of `ExpandEditorPlaceholder` and `ConvertToTrailingClosures`, ie. it first expands any function-typed closures at the end of a call using `ExpandEditorPlaceholder` and then runs `ConvertToTrailingClosures` on that call. Resolves rdar://107532856.
1 parent 627d033 commit 0fa4da4

File tree

5 files changed

+913
-0
lines changed

5 files changed

+913
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftBasicFormat
14+
import SwiftSyntax
15+
16+
/// Convert a call with inline closures to one that uses trailing closure
17+
/// syntax. Returns `nil` if there's already trailing closures or there are no
18+
/// closures within the call. Pass `conversionStart` to specify an index to
19+
/// start the conversion from, ie. to skip converting closures before
20+
/// `conversionStart`
21+
///
22+
/// ## Before
23+
/// ```
24+
/// someCall(closure1: (String) -> Int, closure2: (String) -> Int)
25+
/// ```
26+
///
27+
/// ## After
28+
/// ```
29+
/// someCall { arg in
30+
/// <#code#>
31+
/// } closure2: { arg in
32+
/// <#code#>
33+
/// }
34+
/// ```
35+
public struct CallToTrailingClosures: SyntaxRefactoringProvider {
36+
public struct Options {
37+
public let startAt: Int
38+
39+
public init(startAt: Int = 0) {
40+
self.startAt = startAt
41+
}
42+
}
43+
public typealias Context = Options
44+
45+
// TODO: Rather than returning nil, we should consider throwing errors with
46+
// appropriate messages instead.
47+
public static func refactor(syntax call: FunctionCallExprSyntax, in context: Context = Context()) -> FunctionCallExprSyntax? {
48+
return call.convertToTrailingClosures(from: context.startAt)?.formatted().as(FunctionCallExprSyntax.self)
49+
}
50+
}
51+
52+
extension FunctionCallExprSyntax {
53+
fileprivate func convertToTrailingClosures(from startAt: Int) -> FunctionCallExprSyntax? {
54+
guard trailingClosure == nil, additionalTrailingClosures == nil, leftParen != nil, rightParen != nil else {
55+
// Already have trailing closures
56+
return nil
57+
}
58+
59+
var closures = [(label: TokenSyntax, closure: ClosureExprSyntax)]()
60+
for arg in argumentList.dropFirst(startAt) {
61+
guard var closure = arg.expression.as(ClosureExprSyntax.self) else {
62+
closures.removeAll()
63+
continue
64+
}
65+
66+
// Trailing comma won't exist any more, move its trivia to the end of
67+
// the closure instead
68+
if let comma = arg.trailingComma {
69+
closure = closure.with(\.trailingTrivia, closure.trailingTrivia.merging(triviaOf: comma))
70+
}
71+
closures.append((arg.label ?? .wildcardToken(), closure))
72+
}
73+
74+
guard !closures.isEmpty else {
75+
return nil
76+
}
77+
78+
let trailingClosure = closures.first!.closure
79+
let additionalTrailingClosures = closures.dropFirst().map {
80+
MultipleTrailingClosureElementSyntax(
81+
label: $0.label,
82+
colon: .colonToken(),
83+
closure: $0.closure
84+
)
85+
}
86+
87+
var converted = self
88+
89+
var argList = Array(argumentList.dropLast(closures.count))
90+
if argList.isEmpty {
91+
converted =
92+
converted
93+
.with(\.leftParen, nil)
94+
.with(\.rightParen, nil)
95+
} else {
96+
// Remove the last comma and move its trivia to after the right paren.
97+
// Also move all the trailing comma trivia of each closure. The right
98+
// paren's trivia was moved to the very end of the call above.
99+
let last = argList.last!
100+
if let comma = last.trailingComma {
101+
converted =
102+
converted
103+
.with(\.rightParen, TokenSyntax.rightParenToken(trailingTrivia: Trivia().merging(triviaOf: comma)))
104+
}
105+
argList[argList.count - 1] =
106+
last
107+
.with(\.trailingComma, nil)
108+
}
109+
110+
converted =
111+
converted
112+
.with(\.argumentList, TupleExprElementListSyntax(argList))
113+
.with(\.trailingClosure, trailingClosure)
114+
if !additionalTrailingClosures.isEmpty {
115+
converted = converted.with(\.additionalTrailingClosures, MultipleTrailingClosureElementListSyntax(additionalTrailingClosures))
116+
}
117+
if let rightParen = rightParen {
118+
converted = converted.with(\.trailingTrivia, converted.trailingTrivia.merging(triviaOf: rightParen))
119+
}
120+
121+
return converted
122+
}
123+
}

0 commit comments

Comments
 (0)