From e6df9e7483c44a9ccf4abe000011bd4d7a67db84 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Wed, 4 Jun 2025 12:32:36 +0100 Subject: [PATCH] Update for new `ExpandEditorPlaceholdersToLiteralClosures` API This refactoring now takes a `Syntax` parameter. We don't actually need to change anything else to handle placeholder expansion for macro expansion completions since they get parsed as function calls. --- .../Swift/CodeCompletionSession.swift | 5 +- .../SwiftCompletionTests.swift | 67 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/Sources/SourceKitLSP/Swift/CodeCompletionSession.swift b/Sources/SourceKitLSP/Swift/CodeCompletionSession.swift index 1d77fc41d..d558828b0 100644 --- a/Sources/SourceKitLSP/Swift/CodeCompletionSession.swift +++ b/Sources/SourceKitLSP/Swift/CodeCompletionSession.swift @@ -405,11 +405,14 @@ class CodeCompletionSession { exprToExpand = insertText } + // Note we don't need special handling for macro expansions since + // their insertion text doesn't include the '#', so are parsed as + // function calls here. var parser = Parser(exprToExpand) let expr = ExprSyntax.parse(from: &parser) guard let call = OutermostFunctionCallFinder.findOutermostFunctionCall(in: expr), let expandedCall = ExpandEditorPlaceholdersToLiteralClosures.refactor( - syntax: call, + syntax: Syntax(call), in: ExpandEditorPlaceholdersToLiteralClosures.Context( format: .custom( ClosureCompletionFormat(indentationWidth: indentationWidth), diff --git a/Tests/SourceKitLSPTests/SwiftCompletionTests.swift b/Tests/SourceKitLSPTests/SwiftCompletionTests.swift index 26cd1628d..d08e0233c 100644 --- a/Tests/SourceKitLSPTests/SwiftCompletionTests.swift +++ b/Tests/SourceKitLSPTests/SwiftCompletionTests.swift @@ -1058,6 +1058,73 @@ final class SwiftCompletionTests: XCTestCase { ) } + func testExpandMacroClosurePlaceholder() async throws { + try await SkipUnless.sourcekitdSupportsPlugin() + + let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities) + let uri = DocumentURI(for: .swift) + let positions = testClient.openDocument( + """ + @freestanding(expression) + macro myMacroExpr(fn: (Int) -> String) = #externalMacro(module: "", type: "") + + @freestanding(declaration) + macro myMacroDecl(fn1: (Int) -> String, fn2: () -> Void) = #externalMacro(module: "", type: "") + + func test() { + #1️⃣ + } + """, + uri: uri + ) + let completions = try await testClient.send( + CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"]) + ) + XCTAssertEqual( + completions.items.clearingUnstableValues.filter { $0.label.contains("myMacro") }, + [ + CompletionItem( + label: "myMacroDecl(fn1: (Int) -> String, fn2: () -> Void)", + kind: .value, + detail: "Declaration Macro", + deprecated: false, + filterText: "myMacroDecl(fn1:fn2:)", + insertText: #""" + myMacroDecl(fn1: ${1:{ ${2:Int} in ${3:String} \}}, fn2: ${4:{ ${5:Void} \}}) + """#, + insertTextFormat: .snippet, + textEdit: .textEdit( + TextEdit( + range: Range(positions["1️⃣"]), + newText: #""" + myMacroDecl(fn1: ${1:{ ${2:Int} in ${3:String} \}}, fn2: ${4:{ ${5:Void} \}}) + """# + ) + ) + ), + CompletionItem( + label: "myMacroExpr(fn: (Int) -> String)", + kind: .value, + detail: "Void", + deprecated: false, + filterText: "myMacroExpr(fn:)", + insertText: #""" + myMacroExpr(fn: ${1:{ ${2:Int} in ${3:String} \}}) + """#, + insertTextFormat: .snippet, + textEdit: .textEdit( + TextEdit( + range: Range(positions["1️⃣"]), + newText: #""" + myMacroExpr(fn: ${1:{ ${2:Int} in ${3:String} \}}) + """# + ) + ) + ), + ] + ) + } + func testCompletionScoring() async throws { try await SkipUnless.sourcekitdSupportsPlugin()