@@ -40,7 +40,9 @@ import SwiftSyntaxBuilder
40
40
/// `type-for-expansion-string`), is parsed into a syntax node. If that node is
41
41
/// a `FunctionTypeSyntax` then the placeholder is expanded into a
42
42
/// `ClosureExprSyntax`. Otherwise it is expanded as is, which is also the case
43
- /// for when only a display string is provided.
43
+ /// for when only a display string is provided. You may customize the formatting
44
+ /// of a closure expansion via ``Context/closureLiteralFormat``, for example to
45
+ /// change whether it is split onto multiple lines.
44
46
///
45
47
/// ## Function Typed Placeholder
46
48
/// ### Before
@@ -78,12 +80,30 @@ import SwiftSyntaxBuilder
78
80
/// ```
79
81
struct ExpandSingleEditorPlaceholder : EditRefactoringProvider {
80
82
struct Context {
81
- let indentationWidth : Trivia ?
82
- let initialIndentation : Trivia
83
-
84
- init ( indentationWidth: Trivia ? = nil , initialIndentation: Trivia = [ ] ) {
85
- self . indentationWidth = indentationWidth
86
- self . initialIndentation = initialIndentation
83
+ /// The formatter to use when expanding a function-typed placeholder.
84
+ let closureLiteralFormat : BasicFormat
85
+ /// When true, the expansion will wrap a function-typed placeholder's entire
86
+ /// expansion in placeholder delimiters, in addition to any placeholders
87
+ /// inside the expanded closure literal.
88
+ ///
89
+ /// ## With nested placeholders allowed
90
+ /// ### Before
91
+ /// ```swift
92
+ /// <#T##(Int) -> String##(Int) -> String##(_ someInt: Int) -> String#>
93
+ /// ```
94
+ ///
95
+ /// ### After
96
+ /// ```swift
97
+ /// <#{ someInt in <#String#> }#>
98
+ /// ```
99
+ let allowNestedPlaceholders : Bool
100
+
101
+ init (
102
+ closureLiteralFormat: BasicFormat = BasicFormat ( ) ,
103
+ allowNestedPlaceholders: Bool = false
104
+ ) {
105
+ self . closureLiteralFormat = closureLiteralFormat
106
+ self . allowNestedPlaceholders = allowNestedPlaceholders
87
107
}
88
108
}
89
109
@@ -94,16 +114,17 @@ struct ExpandSingleEditorPlaceholder: EditRefactoringProvider {
94
114
95
115
let expanded : String
96
116
if let functionType = placeholder. typeForExpansion? . as ( FunctionTypeSyntax . self) {
97
- let basicFormat = BasicFormat (
98
- indentationWidth: context. indentationWidth,
99
- initialIndentation: context. initialIndentation
100
- )
101
- var formattedExpansion = functionType. closureExpansion. formatted ( using: basicFormat) . description
117
+ let format = context. closureLiteralFormat
118
+ let initialIndentation = format. currentIndentationLevel
119
+ var formattedExpansion = functionType. closureExpansion. formatted ( using: format) . description
102
120
// Strip the initial indentation from the placeholder itself. We only introduced the initial indentation to
103
121
// format consecutive lines. We don't want it at the front of the initial line because it replaces an expression
104
122
// that might be in the middle of a line.
105
- if formattedExpansion. hasPrefix ( context. initialIndentation. description) {
106
- formattedExpansion = String ( formattedExpansion. dropFirst ( context. initialIndentation. description. count) )
123
+ if formattedExpansion. hasPrefix ( initialIndentation. description) {
124
+ formattedExpansion = String ( formattedExpansion. dropFirst ( initialIndentation. description. count) )
125
+ }
126
+ if context. allowNestedPlaceholders {
127
+ formattedExpansion = wrapInPlaceholder ( formattedExpansion)
107
128
}
108
129
expanded = formattedExpansion
109
130
} else {
@@ -161,20 +182,24 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
161
182
let arg = placeholder. parent? . as ( LabeledExprSyntax . self) ,
162
183
let argList = arg. parent? . as ( LabeledExprListSyntax . self) ,
163
184
let call = argList. parent? . as ( FunctionCallExprSyntax . self) ,
164
- let expandedTrailingClosures = ExpandEditorPlaceholdersToTrailingClosures . expandTrailingClosurePlaceholders (
185
+ let expandedClosures = ExpandEditorPlaceholdersToLiteralClosures . expandClosurePlaceholders (
165
186
in: call,
166
187
ifIncluded: arg,
167
- indentationWidth: context. indentationWidth
188
+ context: ExpandEditorPlaceholdersToLiteralClosures . Context (
189
+ format: . trailing( indentationWidth: context. indentationWidth)
190
+ )
168
191
)
169
192
else {
170
193
return ExpandSingleEditorPlaceholder . textRefactor ( syntax: token)
171
194
}
172
195
173
- return [ SourceEdit . replace ( call, with: expandedTrailingClosures . description) ]
196
+ return [ SourceEdit . replace ( call, with: expandedClosures . description) ]
174
197
}
175
198
}
176
199
177
- /// Expand all the editor placeholders in the function call that can be converted to trailing closures.
200
+ /// Expand all the editor placeholders in the function call to literal closures.
201
+ /// By default they will be expanded to trailing form; if you provide your own
202
+ /// formatter via ``Context/format`` they will be expanded inline.
178
203
///
179
204
/// ## Before
180
205
/// ```swift
@@ -185,7 +210,7 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
185
210
/// )
186
211
/// ```
187
212
///
188
- /// ## Expansion of `foo`
213
+ /// ## Expansion of `foo`, default behavior
189
214
/// ```swift
190
215
/// foo(
191
216
/// arg: <#T##Int#>,
@@ -195,45 +220,98 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider {
195
220
/// <#T##String#>
196
221
/// }
197
222
/// ```
198
- public struct ExpandEditorPlaceholdersToTrailingClosures : SyntaxRefactoringProvider {
223
+ ///
224
+ /// ## Expansion of `foo` with a basic custom formatter
225
+ /// ```swift
226
+ /// foo(
227
+ /// arg: <#T##Int#>,
228
+ /// firstClosure: { someInt in
229
+ /// <#T##String#>
230
+ /// },
231
+ /// secondClosure: { someInt in
232
+ /// <#T##String#>
233
+ /// }
234
+ /// )
235
+ /// ```
236
+ ///
237
+ /// ## Expansion of `foo`, custom formatter with `allowNestedPlaceholders: true`
238
+ /// ```swift
239
+ /// foo(
240
+ /// arg: <#T##Int#>,
241
+ /// firstClosure: <#{ someInt in
242
+ /// <#T##String#>
243
+ /// }#>,
244
+ /// secondClosure: <#{ someInt in
245
+ /// <#T##String#>
246
+ /// }#>
247
+ /// )
248
+ /// ```
249
+ public struct ExpandEditorPlaceholdersToLiteralClosures : SyntaxRefactoringProvider {
199
250
public struct Context {
200
- public let indentationWidth : Trivia ?
251
+ public enum Format {
252
+ /// Default formatting behavior: expand to trailing closures.
253
+ case trailing( indentationWidth: Trivia ? )
254
+ /// Use the given formatter and expand the placeholder inline, without
255
+ /// moving it to trailing position. If `allowNestedPlaceholders` is true,
256
+ /// the entire closure will also be wrapped as a placeholder.
257
+ case custom( BasicFormat , allowNestedPlaceholders: Bool )
258
+ }
259
+ public let format : Format
260
+
261
+ public init ( format: Format ) {
262
+ self . format = format
263
+ }
201
264
202
265
public init ( indentationWidth: Trivia ? = nil ) {
203
- self . indentationWidth = indentationWidth
266
+ self . init ( format : . trailing ( indentationWidth: indentationWidth) )
204
267
}
205
268
}
206
269
207
270
public static func refactor(
208
271
syntax call: FunctionCallExprSyntax ,
209
272
in context: Context = Context ( )
210
273
) -> FunctionCallExprSyntax ? {
211
- return Self . expandTrailingClosurePlaceholders ( in: call, ifIncluded: nil , indentationWidth: context. indentationWidth)
274
+ return Self . expandClosurePlaceholders (
275
+ in: call,
276
+ ifIncluded: nil ,
277
+ context: context
278
+ )
212
279
}
213
280
214
281
/// If the given argument is `nil` or one of the last arguments that are all
215
282
/// function-typed placeholders and this call doesn't have a trailing
216
283
/// closure, then return a replacement of this call with one that uses
217
284
/// closures based on the function types provided by each editor placeholder.
218
285
/// Otherwise return nil.
219
- fileprivate static func expandTrailingClosurePlaceholders (
286
+ fileprivate static func expandClosurePlaceholders (
220
287
in call: FunctionCallExprSyntax ,
221
288
ifIncluded arg: LabeledExprSyntax ? ,
222
- indentationWidth : Trivia ?
289
+ context : Context
223
290
) -> FunctionCallExprSyntax ? {
224
- guard let expanded = call. expandTrailingClosurePlaceholders ( ifIncluded: arg, indentationWidth: indentationWidth)
225
- else {
226
- return nil
227
- }
291
+ switch context. format {
292
+ case let . custom( formatter, allowNestedPlaceholders: allowNesting) :
293
+ let expanded = call. expandClosurePlaceholders (
294
+ ifIncluded: arg,
295
+ customFormat: formatter,
296
+ allowNestedPlaceholders: allowNesting
297
+ )
298
+ return expanded? . expr
228
299
229
- let callToTrailingContext = CallToTrailingClosures . Context (
230
- startAtArgument: call. arguments. count - expanded. numClosures
231
- )
232
- guard let trailing = CallToTrailingClosures . refactor ( syntax: expanded. expr, in: callToTrailingContext) else {
233
- return nil
234
- }
300
+ case let . trailing( indentationWidth) :
301
+ guard let expanded = call. expandClosurePlaceholders ( ifIncluded: arg, indentationWidth: indentationWidth)
302
+ else {
303
+ return nil
304
+ }
235
305
236
- return trailing
306
+ let callToTrailingContext = CallToTrailingClosures . Context (
307
+ startAtArgument: call. arguments. count - expanded. numClosures
308
+ )
309
+ guard let trailing = CallToTrailingClosures . refactor ( syntax: expanded. expr, in: callToTrailingContext) else {
310
+ return nil
311
+ }
312
+
313
+ return trailing
314
+ }
237
315
}
238
316
}
239
317
@@ -311,9 +389,11 @@ extension FunctionCallExprSyntax {
311
389
/// closure, then return a replacement of this call with one that uses
312
390
/// closures based on the function types provided by each editor placeholder.
313
391
/// Otherwise return nil.
314
- fileprivate func expandTrailingClosurePlaceholders (
392
+ fileprivate func expandClosurePlaceholders (
315
393
ifIncluded: LabeledExprSyntax ? ,
316
- indentationWidth: Trivia ?
394
+ indentationWidth: Trivia ? = nil ,
395
+ customFormat: BasicFormat ? = nil ,
396
+ allowNestedPlaceholders: Bool = false
317
397
) -> ( expr: FunctionCallExprSyntax , numClosures: Int ) ? {
318
398
var includedArg = false
319
399
var argsToExpand = 0
@@ -343,8 +423,12 @@ extension FunctionCallExprSyntax {
343
423
let edits = ExpandSingleEditorPlaceholder . textRefactor (
344
424
syntax: arg. expression. cast ( DeclReferenceExprSyntax . self) . baseName,
345
425
in: ExpandSingleEditorPlaceholder . Context (
346
- indentationWidth: indentationWidth,
347
- initialIndentation: lineIndentation
426
+ closureLiteralFormat: customFormat
427
+ ?? BasicFormat (
428
+ indentationWidth: indentationWidth,
429
+ initialIndentation: lineIndentation
430
+ ) ,
431
+ allowNestedPlaceholders: allowNestedPlaceholders
348
432
)
349
433
)
350
434
guard edits. count == 1 , let edit = edits. first, !edit. replacement. isEmpty else {
0 commit comments