Skip to content

Commit ebfb348

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 92178c0 commit ebfb348

File tree

5 files changed

+951
-0
lines changed

5 files changed

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

0 commit comments

Comments
 (0)