From 897ac1a6563569e99bcf0dac0e8f9cad40a885d2 Mon Sep 17 00:00:00 2001 From: Josh Caswell Date: Sun, 10 Nov 2024 16:24:20 -0800 Subject: [PATCH] Refine expansion of function-typed placeholders Per , these placeholders no longer expand to multi-line trailing closures. Instead they become inline closures, with nested placeholders for the signature and body. This introduces a new formatter, `ClosureLiteralFormat`, so that other uses of `BasicFormat` are not impacted. `ClosureLiteralFormat` does not insert newlines into a closure when there is only a single statement in the body. --- .../ClosureLiteralFormat.swift | 65 +++++++++ .../ExpandEditorPlaceholder.swift | 74 ++++------ Sources/SwiftSyntax/SyntaxProtocol.swift | 5 +- .../ClosureLiteralFormatTests.swift | 129 +++++++++++++++++ .../ExpandEditorPlaceholderTests.swift | 136 ++++++------------ 5 files changed, 271 insertions(+), 138 deletions(-) create mode 100644 Sources/SwiftBasicFormat/ClosureLiteralFormat.swift create mode 100644 Tests/SwiftBasicFormatTest/ClosureLiteralFormatTests.swift diff --git a/Sources/SwiftBasicFormat/ClosureLiteralFormat.swift b/Sources/SwiftBasicFormat/ClosureLiteralFormat.swift new file mode 100644 index 00000000000..0c1368cb1ab --- /dev/null +++ b/Sources/SwiftBasicFormat/ClosureLiteralFormat.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if swift(>=6) +@_spi(RawSyntax) public import SwiftSyntax +#else +@_spi(RawSyntax) import SwiftSyntax +#endif + +/// A specialization of `BasicFormat` for closure literals, which is more +/// conservative about newline insertion. +/// +/// A closure that seems to be a simple predicate or transform — based on the +/// number of enclosed statements — will not be reformatted to multiple lines. +open class ClosureLiteralFormat: BasicFormat { + open override func requiresNewline(between first: TokenSyntax?, and second: TokenSyntax?) -> Bool { + if let first, isEndOfSmallClosureSignature(first) { + return false + } else if let first, isSmallClosureDelimiter(first, kind: \.leftBrace) { + return false + } else if let second, isSmallClosureDelimiter(second, kind: \.rightBrace) { + return false + } else { + return super.requiresNewline(between: first, and: second) + } + } + + /// Returns `true` if `token` is an opening or closing brace (according to + /// `kind`) of a closure, and that closure has no more than one statement in + /// its body. + private func isSmallClosureDelimiter( + _ token: TokenSyntax, + kind: KeyPath + ) -> Bool { + guard token.keyPathInParent == kind, + let closure = token.parent?.as(ClosureExprSyntax.self) + else { + return false + } + + return closure.statements.count <= 1 + } + + /// Returns `true` if `token` is the last token in the signature of a closure, + /// and that closure has no more than one statement in its body. + private func isEndOfSmallClosureSignature(_ token: TokenSyntax) -> Bool { + guard let signature = token.ancestorOrSelf(mapping: { $0.as(ClosureSignatureSyntax.self) }), + let closure = signature.parent?.as(ClosureExprSyntax.self) + else { + return false + } + + return signature.lastToken(viewMode: viewMode) == token + && closure.statements.count <= 1 + } +} diff --git a/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift b/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift index 87c250a7101..cec0dd17eea 100644 --- a/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift +++ b/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift @@ -50,9 +50,7 @@ import SwiftSyntaxBuilder /// /// ### After /// ```swift -/// { someInt in -/// <#T##String#> -/// } +/// <#{ <#someInt#> in <#T##String#> }#> /// ``` /// /// ## Other Type Placeholder @@ -94,18 +92,18 @@ struct ExpandSingleEditorPlaceholder: EditRefactoringProvider { let expanded: String if let functionType = placeholder.typeForExpansion?.as(FunctionTypeSyntax.self) { - let basicFormat = BasicFormat( + let format = ClosureLiteralFormat( indentationWidth: context.indentationWidth, initialIndentation: context.initialIndentation ) - var formattedExpansion = functionType.closureExpansion.formatted(using: basicFormat).description + var formattedExpansion = functionType.closureExpansion.formatted(using: format).description // Strip the initial indentation from the placeholder itself. We only introduced the initial indentation to // format consecutive lines. We don't want it at the front of the initial line because it replaces an expression // that might be in the middle of a line. if formattedExpansion.hasPrefix(context.initialIndentation.description) { formattedExpansion = String(formattedExpansion.dropFirst(context.initialIndentation.description.count)) } - expanded = formattedExpansion + expanded = wrapInPlaceholder(formattedExpansion) } else { expanded = placeholder.displayText } @@ -117,9 +115,9 @@ struct ExpandSingleEditorPlaceholder: EditRefactoringProvider { } /// If a function-typed placeholder is the argument to a non-trailing closure -/// call, expands it and any adjacent function-typed placeholders to trailing -/// closures on that call. All other placeholders will expand as per -/// `ExpandEditorPlaceholder`. +/// call, expands it and any adjacent function-typed placeholders to literal +/// closures with inner placeholders on that call. All other placeholders will +/// expand as per `ExpandEditorPlaceholder`. /// /// ## Before /// ```swift @@ -137,12 +135,10 @@ struct ExpandSingleEditorPlaceholder: EditRefactoringProvider { /// foo( /// closure1: <#T##(Int) -> String##(Int) -> String##(_ someInt: Int) -> String#>, /// normalArg: <#T##Int#>, -/// closure2: { ... } -/// ) { someInt in -/// <#T##String#> -/// } closure2: { someInt in -/// <#T##String#> -/// } +/// closure2: { ... }, +/// closure3: { <#someInt#> in <#T##String#> }, +/// closure4: { <#someInt#> in <#T##String#> } +/// ) /// ``` /// /// Expansion on `closure1` and `normalArg` is the same as `ExpandSingleEditorPlaceholder`. @@ -161,7 +157,7 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider { let arg = placeholder.parent?.as(LabeledExprSyntax.self), let argList = arg.parent?.as(LabeledExprListSyntax.self), let call = argList.parent?.as(FunctionCallExprSyntax.self), - let expandedTrailingClosures = ExpandEditorPlaceholdersToTrailingClosures.expandTrailingClosurePlaceholders( + let expandedClosures = ExpandEditorPlaceholdersToLiteralClosures.expandClosurePlaceholders( in: call, ifIncluded: arg, indentationWidth: context.indentationWidth @@ -170,11 +166,11 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider { return ExpandSingleEditorPlaceholder.textRefactor(syntax: token) } - return [SourceEdit.replace(call, with: expandedTrailingClosures.description)] + return [SourceEdit.replace(call, with: expandedClosures.description)] } } -/// Expand all the editor placeholders in the function call that can be converted to trailing closures. +/// Expand all the editor placeholders in the function call to literal closures. /// /// ## Before /// ```swift @@ -189,13 +185,11 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider { /// ```swift /// foo( /// arg: <#T##Int#>, -/// ) { someInt in -/// <#T##String#> -/// } secondClosure: { someInt in -/// <#T##String#> -/// } +/// firstClosure: { <#someInt#> in <#T##String#> }, +/// secondClosure: { <#someInt#> in <#T##String#> } +/// ) /// ``` -public struct ExpandEditorPlaceholdersToTrailingClosures: SyntaxRefactoringProvider { +public struct ExpandEditorPlaceholdersToLiteralClosures: SyntaxRefactoringProvider { public struct Context { public let indentationWidth: Trivia? @@ -208,7 +202,8 @@ public struct ExpandEditorPlaceholdersToTrailingClosures: SyntaxRefactoringProvi syntax call: FunctionCallExprSyntax, in context: Context = Context() ) -> FunctionCallExprSyntax? { - return Self.expandTrailingClosurePlaceholders(in: call, ifIncluded: nil, indentationWidth: context.indentationWidth) + return Self.expandClosurePlaceholders(in: call, ifIncluded: nil, indentationWidth: context.indentationWidth + ) } /// If the given argument is `nil` or one of the last arguments that are all @@ -216,24 +211,12 @@ public struct ExpandEditorPlaceholdersToTrailingClosures: SyntaxRefactoringProvi /// closure, then return a replacement of this call with one that uses /// closures based on the function types provided by each editor placeholder. /// Otherwise return nil. - fileprivate static func expandTrailingClosurePlaceholders( + fileprivate static func expandClosurePlaceholders( in call: FunctionCallExprSyntax, ifIncluded arg: LabeledExprSyntax?, indentationWidth: Trivia? ) -> FunctionCallExprSyntax? { - guard let expanded = call.expandTrailingClosurePlaceholders(ifIncluded: arg, indentationWidth: indentationWidth) - else { - return nil - } - - let callToTrailingContext = CallToTrailingClosures.Context( - startAtArgument: call.arguments.count - expanded.numClosures - ) - guard let trailing = CallToTrailingClosures.refactor(syntax: expanded.expr, in: callToTrailingContext) else { - return nil - } - - return trailing + return call.expandClosurePlaceholders(ifIncluded: arg, indentationWidth: indentationWidth) } } @@ -244,9 +227,7 @@ extension FunctionTypeSyntax { /// ``` /// would become /// ``` - /// { someInt in - /// <#T##String#> - /// } + /// { <#someInt#> in <#T##String#> } /// ``` fileprivate var closureExpansion: ClosureExprSyntax { let closureSignature: ClosureSignatureSyntax? @@ -311,10 +292,10 @@ extension FunctionCallExprSyntax { /// closure, then return a replacement of this call with one that uses /// closures based on the function types provided by each editor placeholder. /// Otherwise return nil. - fileprivate func expandTrailingClosurePlaceholders( + fileprivate func expandClosurePlaceholders( ifIncluded: LabeledExprSyntax?, indentationWidth: Trivia? - ) -> (expr: FunctionCallExprSyntax, numClosures: Int)? { + ) -> FunctionCallExprSyntax? { var includedArg = false var argsToExpand = 0 for arg in arguments.reversed() { @@ -359,10 +340,7 @@ extension FunctionCallExprSyntax { } let originalArgs = arguments.dropLast(argsToExpand) - return ( - detached.with(\.arguments, LabeledExprListSyntax(originalArgs + expandedArgs)), - expandedArgs.count - ) + return detached.with(\.arguments, LabeledExprListSyntax(originalArgs + expandedArgs)) } } diff --git a/Sources/SwiftSyntax/SyntaxProtocol.swift b/Sources/SwiftSyntax/SyntaxProtocol.swift index d2edbddc478..1eba89b48f5 100644 --- a/Sources/SwiftSyntax/SyntaxProtocol.swift +++ b/Sources/SwiftSyntax/SyntaxProtocol.swift @@ -235,7 +235,10 @@ extension SyntaxProtocol { return self.previousToken(viewMode: .sourceAccurate) } - /// Returns this node or the first ancestor that satisfies `condition`. + /// Applies `map` to this node and each of its ancestors until a non-`nil` + /// value is produced, then returns that value. + /// + /// If no node has a non-`nil` mapping, returns `nil`. public func ancestorOrSelf(mapping map: (Syntax) -> T?) -> T? { return self.withUnownedSyntax { var node = $0 diff --git a/Tests/SwiftBasicFormatTest/ClosureLiteralFormatTests.swift b/Tests/SwiftBasicFormatTest/ClosureLiteralFormatTests.swift new file mode 100644 index 00000000000..8e60808bba9 --- /dev/null +++ b/Tests/SwiftBasicFormatTest/ClosureLiteralFormatTests.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftBasicFormat +import SwiftParser +import SwiftSyntax +@_spi(Testing) import SwiftSyntaxBuilder +import XCTest +import _SwiftSyntaxTestSupport + +fileprivate func assertFormatted( + tree: T, + expected: String, + using format: ClosureLiteralFormat = ClosureLiteralFormat(indentationWidth: .spaces(4)), + file: StaticString = #filePath, + line: UInt = #line +) { + assertStringsEqualWithDiff(tree.formatted(using: format).description, expected, file: file, line: line) +} + +fileprivate func assertFormatted( + source: String, + expected: String, + using format: ClosureLiteralFormat = ClosureLiteralFormat(indentationWidth: .spaces(4)), + file: StaticString = #filePath, + line: UInt = #line +) { + assertFormatted( + tree: Parser.parse(source: source), + expected: expected, + using: format, + file: file, + line: line + ) +} + +final class ClosureLiteralFormatTests: XCTestCase { + func testSingleStatementClosureArg() { + assertFormatted( + source: """ + foo(bar: { baz in baz.quux }) + """, + expected: """ + foo(bar: { baz in baz.quux }) + """ + ) + } + + func testSingleStatementClosureArgAlreadyMultiLine() { + assertFormatted( + source: """ + foo( + bar: { baz in + baz.quux + } + ) + """, + expected: """ + foo( + bar: { baz in + baz.quux + } + ) + """ + ) + } + + func testMultiStatmentClosureArg() { + assertFormatted( + source: """ + foo( + bar: { baz in print(baz); return baz.quux } + ) + """, + expected: """ + foo( + bar: { baz in + print(baz); + return baz.quux + } + ) + """ + ) + } + + func testMultiStatementClosureArgAlreadyMultiLine() { + assertFormatted( + source: """ + foo( + bar: { baz in + print(baz) + return baz.quux + } + ) + """, + expected: """ + foo( + bar: { baz in + print(baz) + return baz.quux + } + ) + """ + ) + } + + func testFormatClosureWithInitialIndentation() throws { + assertFormatted( + tree: ClosureExprSyntax( + statements: CodeBlockItemListSyntax([ + CodeBlockItemSyntax(item: CodeBlockItemSyntax.Item(IntegerLiteralExprSyntax(integerLiteral: 2))) + ]) + ), + expected: """ + { 2 } + """, + using: ClosureLiteralFormat(initialIndentation: .spaces(4)) + ) + } +} diff --git a/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift b/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift index 3818c772aaa..5f4c6256215 100644 --- a/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift +++ b/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift @@ -18,6 +18,12 @@ import SwiftSyntaxBuilder import XCTest import _SwiftSyntaxTestSupport +fileprivate extension DefaultStringInterpolation { + mutating func appendInterpolation(placeholder string: String) { + self.appendLiteral(wrapInPlaceholder(string)) + } +} + fileprivate let closurePlaceholder = wrapInPlaceholder("T##closure##() -> Void") fileprivate let closureWithArgPlaceholder = wrapInPlaceholder( "T##(Int) -> String##(Int) -> String##(_ someInt: Int) -> String" @@ -47,27 +53,21 @@ final class ExpandEditorPlaceholderTests: XCTestCase { func testVoidClosure() throws { let expected = """ - { - \(voidPlaceholder) - } + \(placeholder: "{ \(voidPlaceholder) }") """ try assertRefactorPlaceholder("T##display##() -> Void", expected: expected) } func testTypedReturnClosure() throws { let expected = """ - { - \(intPlaceholder) - } + \(placeholder: "{ \(intPlaceholder) }") """ try assertRefactorPlaceholder("T##display##() -> Int", expected: expected) } func testClosureWithArg() throws { let expected = """ - { arg in - \(intPlaceholder) - } + \(placeholder: "{ arg in \(intPlaceholder) }") """ try assertRefactorPlaceholder("T##display##(arg: String) -> Int", expected: expected) try assertRefactorPlaceholder("T##display##(_ arg: String) -> Int", expected: expected) @@ -75,9 +75,7 @@ final class ExpandEditorPlaceholderTests: XCTestCase { func testClosureWithMultipleArgs() throws { let expected = """ - { arg, arg2 in - \(intPlaceholder) - } + \(placeholder: "{ arg, arg2 in \(intPlaceholder) }") """ try assertRefactorPlaceholder("T##display##(arg: String, arg2: String) -> Int", expected: expected) } @@ -90,9 +88,7 @@ final class ExpandEditorPlaceholderTests: XCTestCase { func testClosureComments() throws { let placeholder = wrapInPlaceholder("T##display##(arg: String) -> Int") let expected = """ - /*c1*/{ arg in - \(intPlaceholder) - }/*c2*/ + /*c1*/\(placeholder: "{ arg in \(intPlaceholder) }")/*c2*/ """ try assertRefactorPlaceholder("/*c1*/\(placeholder)/*c2*/", wrap: false, expected: expected) } @@ -101,9 +97,7 @@ final class ExpandEditorPlaceholderTests: XCTestCase { let baseline = "call(\(closurePlaceholder))" let expected: String = """ - call { - \(voidPlaceholder) - } + call(\(placeholder: "{ \(voidPlaceholder) }")) """ try assertRefactorPlaceholderCall(baseline, expected: expected) @@ -118,9 +112,7 @@ final class ExpandEditorPlaceholderTests: XCTestCase { let baseline = "call(\(placeholder))" let expected: String = """ - call { - \(intPlaceholder) - } + call(\(placeholder: "{ \(intPlaceholder) }")) """ try assertRefactorPlaceholderCall(baseline, expected: expected) @@ -132,9 +124,7 @@ final class ExpandEditorPlaceholderTests: XCTestCase { let int = wrapInPlaceholder("Int") let expected: String = """ - call { \(int) in - \(voidPlaceholder) - } + call(\(placeholder: "{ \(int) in \(voidPlaceholder) }")) """ try assertRefactorPlaceholderCall(baseline, expected: expected) @@ -144,11 +134,7 @@ final class ExpandEditorPlaceholderTests: XCTestCase { let baseline = "call(arg1: \(closurePlaceholder), arg2: \(closurePlaceholder))" let expected: String = """ - call { - \(voidPlaceholder) - } arg2: { - \(voidPlaceholder) - } + call(arg1: \(placeholder: "{ \(voidPlaceholder) }"), arg2: \(placeholder: "{ \(voidPlaceholder) }")) """ try assertRefactorPlaceholderCall(baseline, expected: expected) @@ -158,11 +144,7 @@ final class ExpandEditorPlaceholderTests: XCTestCase { func testNonClosureAfterClosure() throws { let baseline = "call(arg1: \(closurePlaceholder), arg2: \(intPlaceholder))" - let expected: String = """ - { - \(voidPlaceholder) - } - """ + let expected: String = "\(placeholder: "{ \(voidPlaceholder) }")" try assertRefactorPlaceholderToken(baseline, expected: expected) } @@ -173,106 +155,84 @@ final class ExpandEditorPlaceholderTests: XCTestCase { /*c8*/\(closurePlaceholder)/*c9*/)/*c10*/ """ - // TODO: Should we remove whitespace from the merged trivia? The space - // between c2 and c3 is the one added for the `{`. The space between c4 - // and c5 is the space between the `:` and c5 (added by merging the - // colon's trivia since it was removed). let expected: String = """ - /*c1*/foo/*c2*/ /*c3*//*c4*/ /*c5*/{ - \(voidPlaceholder) - }/*c6*//*c7*/ _: - /*c8*/{ - \(voidPlaceholder) - }/*c9*//*c10*/ + /*c1*/foo/*c2*/(/*c3*/arg/*c4*/: /*c5*/\(placeholder: "{ \(voidPlaceholder) }")/*c6*/,/*c7*/ + /*c8*/\(placeholder: "{ \(voidPlaceholder) }")/*c9*/)/*c10*/ """ try assertRefactorPlaceholderCall(baseline, placeholder: 1, expected: expected) } - func testExpandEditorPlaceholdersToSingleTrailingClosures() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + func testExpandEditorPlaceholdersToSingleClosures() throws { + try assertExpandEditorPlaceholdersToClosures( """ foo(arg: \(intPlaceholder), closure: \(closureWithArgPlaceholder)) """, expected: """ - foo(arg: \(intPlaceholder)) { someInt in - \(stringPlaceholder) - } + foo(arg: \(intPlaceholder), closure: \(placeholder: "{ someInt in \(stringPlaceholder) }")) """ ) } - func testExpandEditorPlaceholdersToMultipleTrailingClosures() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + func testExpandEditorPlaceholdersToMultipleClosures() throws { + try assertExpandEditorPlaceholdersToClosures( """ foo(arg: \(intPlaceholder), firstClosure: \(closureWithArgPlaceholder), secondClosure: \(closureWithArgPlaceholder)) """, expected: """ - foo(arg: \(intPlaceholder)) { someInt in - \(stringPlaceholder) - } secondClosure: { someInt in - \(stringPlaceholder) - } + foo(arg: \(intPlaceholder), firstClosure: \(placeholder: "{ someInt in \(stringPlaceholder) }"), secondClosure: \(placeholder: "{ someInt in \(stringPlaceholder) }")) """ ) } func testExpandEditorPlaceholdersDoesntExpandClosureBeforeNormalArgs() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + try assertExpandEditorPlaceholdersToClosures( """ foo(pre: \(closurePlaceholder), arg: \(intPlaceholder), closure: \(closureWithArgPlaceholder)) """, expected: """ - foo(pre: \(closurePlaceholder), arg: \(intPlaceholder)) { someInt in - \(stringPlaceholder) - } + foo(pre: \(closurePlaceholder), arg: \(intPlaceholder), closure: \(placeholder: "{ someInt in \(stringPlaceholder) }")) """ ) } func testCallHasInitialIndentation() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + try assertExpandEditorPlaceholdersToClosures( """ foo(arg: 1, closure: \(closureWithArgPlaceholder)) """, expected: """ - foo(arg: 1) { someInt in - \(stringPlaceholder) - } + foo(arg: 1, closure: \(placeholder: "{ someInt in \(stringPlaceholder) }")) """ ) } func testCustomIndentationWidth() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + try assertExpandEditorPlaceholdersToClosures( """ foo(arg: 1, closure: \(closureWithArgPlaceholder)) """, expected: """ - foo(arg: 1) { someInt in - \(stringPlaceholder) - } + foo(arg: 1, closure: \(placeholder: "{ someInt in \(stringPlaceholder) }")) """, indentationWidth: .spaces(2) ) } func testCustomIndentationWidthWithInitialIndentation() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + try assertExpandEditorPlaceholdersToClosures( """ foo(arg: 1, closure: \(closureWithArgPlaceholder)) """, expected: """ - foo(arg: 1) { someInt in - \(stringPlaceholder) - } + foo(arg: 1, closure: \(placeholder: "{ someInt in \(stringPlaceholder) }")) """, indentationWidth: .spaces(2) ) } func testMultilineCall() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + try assertExpandEditorPlaceholdersToClosures( """ foo( arg: 1, @@ -281,16 +241,15 @@ final class ExpandEditorPlaceholderTests: XCTestCase { """, expected: """ foo( - arg: 1 - ) { someInt in - \(stringPlaceholder) - } + arg: 1, + closure: \(placeholder: "{ someInt in \(stringPlaceholder) }") + ) """ ) } func testMultilineIndentedCall() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + try assertExpandEditorPlaceholdersToClosures( """ foo( arg: 1, @@ -299,25 +258,24 @@ final class ExpandEditorPlaceholderTests: XCTestCase { """, expected: """ foo( - arg: 1 - ) { someInt in - \(stringPlaceholder) - } + arg: 1, + closure: \(placeholder: "{ someInt in \(stringPlaceholder) }") + ) """ ) } func testMultilineCallWithNoAdditionalArguments() throws { - try assertExpandEditorPlaceholdersToTrailingClosures( + try assertExpandEditorPlaceholdersToClosures( """ foo( closure: \(closureWithArgPlaceholder) ) """, expected: """ - foo { someInt in - \(stringPlaceholder) - } + foo( + closure: \(placeholder: "{ someInt in \(stringPlaceholder) }") + ) """ ) } @@ -398,7 +356,7 @@ fileprivate func assertRefactorPlaceholderToken( ) } -fileprivate func assertExpandEditorPlaceholdersToTrailingClosures( +fileprivate func assertExpandEditorPlaceholdersToClosures( _ expr: String, expected: String, indentationWidth: Trivia? = nil, @@ -410,8 +368,8 @@ fileprivate func assertExpandEditorPlaceholdersToTrailingClosures( try assertRefactor( call, - context: ExpandEditorPlaceholdersToTrailingClosures.Context(indentationWidth: indentationWidth), - provider: ExpandEditorPlaceholdersToTrailingClosures.self, + context: ExpandEditorPlaceholdersToLiteralClosures.Context(indentationWidth: indentationWidth), + provider: ExpandEditorPlaceholdersToLiteralClosures.self, expected: [SourceEdit.replace(call, with: expected)], file: file, line: line