Files
sourcekit-lsp/Tests/SourceKitLSPTests/SwiftSignatureHelpTests.swift
T
2025-10-31 14:11:11 -07:00

660 lines
19 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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
//
//===----------------------------------------------------------------------===//
@_spi(SourceKitLSP) import LanguageServerProtocol
import SKLogging
import SKTestSupport
import SourceKitLSP
import SwiftExtensions
import XCTest
final class SwiftSignatureHelpTests: SourceKitLSPTestCase {
func testSignatureHelpFunction() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
/// This is a test function
/// - Parameters:
/// - a: The first parameter
/// - b: The second parameter
/// - Returns: The result of the test
func test(a: Int, b: String) -> Double { 0 }
func main() {
test(1️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 0)
XCTAssertEqual(signature.label, "test(a: Int, b: String) -> Double")
XCTAssertEqual(
signature.documentation,
.markupContent(
MarkupContent(
kind: .markdown,
value: """
This is a test function
- Parameters:
- a: The first parameter
- b: The second parameter
- Returns: The result of the test
"""
)
)
)
XCTAssertEqual(
signature.parameters,
[
ParameterInformation(label: .offsets(start: 5, end: 11)),
ParameterInformation(label: .offsets(start: 13, end: 22)),
]
)
}
func testSignatureHelpSubscript() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
struct Matrix {
/// Returns the element at the given row and column
/// - Parameters:
/// - row: The row index
/// - column: The column index
/// - Returns: The element at the given row and column
subscript(row: Int, column: Int) -> Int { 0 }
}
func main(matrix: Matrix) {
matrix[1, 1️⃣]
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 1)
XCTAssertEqual(signature.label, "subscript(row: Int, column: Int) -> Int")
XCTAssertEqual(
signature.documentation,
.markupContent(
MarkupContent(
kind: .markdown,
value: """
Returns the element at the given row and column
- Parameters:
- row: The row index
- column: The column index
- Returns: The element at the given row and column
"""
)
)
)
XCTAssertEqual(
signature.parameters,
[
ParameterInformation(label: .offsets(start: 10, end: 18)),
ParameterInformation(label: .offsets(start: 20, end: 31)),
]
)
}
func testSignatureHelpInitializer() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
struct Matrix {
/// Initializes a matrix with 0 elements in each row and column
///
/// - Precondition: `rows` and `columns` must be positive
init(rows: Int, columns: Int) { }
}
func main() {
let matrix = Matrix(rows: 3, 1️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 1)
XCTAssertEqual(signature.label, "init(rows: Int, columns: Int)")
XCTAssertEqual(
signature.documentation,
.markupContent(
MarkupContent(
kind: .markdown,
value: """
Initializes a matrix with 0 elements in each row and column
- Precondition: `rows` and `columns` must be positive
"""
)
)
)
XCTAssertEqual(
signature.parameters,
[
ParameterInformation(label: .offsets(start: 5, end: 14)),
ParameterInformation(label: .offsets(start: 16, end: 28)),
]
)
}
func testSignatureHelpEnumCase() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
enum Label {
/// The label as an offset within the signature label
/// - Parameters:
/// - start: The start offset
/// - end: The end offset
case offset(start: Int, end: Int)
}
func main() {
let label = Label.offset(start: 11️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 0)
XCTAssertEqual(signature.label, "offset(start: Int, end: Int) -> Label")
XCTAssertEqual(
signature.documentation,
.markupContent(
MarkupContent(
kind: .markdown,
value: """
The label as an offset within the signature label
- Parameters:
- start: The start offset
- end: The end offset
"""
)
)
)
XCTAssertEqual(
signature.parameters,
[
ParameterInformation(label: .offsets(start: 7, end: 17)),
ParameterInformation(label: .offsets(start: 19, end: 27)),
]
)
}
func testSignatureHelpNoParameters() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
/// This is a test function
func test() -> Double { 0 }
func main() {
test(1️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertNil(signatureHelp.activeParameter)
XCTAssertEqual(signature.label, "test() -> Double")
XCTAssertEqual(
signature.documentation,
.markupContent(
MarkupContent(kind: .markdown, value: "This is a test function")
)
)
XCTAssertEqual(signature.parameters?.isEmpty, true)
}
func testSignatureHelpNoSignatures() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
func main() {
test(1️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
XCTAssertNil(result)
}
func testSignatureHelpNoActiveParameter() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
/// This is a test function
func test(x: Int) -> Double { 0 }
func main() {
test(x: 1, 1️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 1)
XCTAssertEqual(signature.label, "test(x: Int) -> Double")
XCTAssertEqual(
signature.documentation,
.markupContent(
MarkupContent(kind: .markdown, value: "This is a test function")
)
)
XCTAssertEqual(
signature.parameters,
[ParameterInformation(label: .offsets(start: 5, end: 11))]
)
}
func testSignatureHelpAdjustToStartOfArgument() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
struct Adder {
func add(first: Double!, second: Float, third: Int) -> Double { 0 }
func clone() -> Adder { self }
}
func main(adder: Adder) {
adder.add(1️⃣fir2️⃣st: 13️⃣, second: 2 + 4️⃣Float.pi, 5️⃣third: 3)
adder.add(first: adder.add(first: 1, second: 6️⃣))
adder.clone().add(7️⃣)
}
""",
uri: uri
)
func getSignature(marker: String, line: UInt = #line) async throws -> SignatureInformation {
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions[marker]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signature.label, "add(first: Double!, second: Float, third: Int) -> Double", line: line)
XCTAssertNil(signature.documentation, line: line)
XCTAssertEqual(
signature.parameters,
[
ParameterInformation(label: .offsets(start: 4, end: 18)),
ParameterInformation(label: .offsets(start: 20, end: 33)),
ParameterInformation(label: .offsets(start: 35, end: 45)),
],
line: line
)
return signature
}
let firstPosition = try await getSignature(marker: "1️⃣")
XCTAssertEqual(firstPosition.activeParameter, 0)
let secondPosition = try await getSignature(marker: "2️⃣")
XCTAssertEqual(secondPosition.activeParameter, 0)
let thirdPosition = try await getSignature(marker: "3️⃣")
XCTAssertEqual(thirdPosition.activeParameter, 0)
let fourthPosition = try await getSignature(marker: "4️⃣")
XCTAssertEqual(fourthPosition.activeParameter, 1)
let fifthPosition = try await getSignature(marker: "5️⃣")
XCTAssertEqual(fifthPosition.activeParameter, 2)
let sixthPosition = try await getSignature(marker: "6️⃣")
XCTAssertEqual(sixthPosition.activeParameter, 1)
let seventhPosition = try await getSignature(marker: "7️⃣")
XCTAssertEqual(seventhPosition.activeParameter, 0)
}
func testSignatureHelpMultipleOverloads() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
struct Adder {
func add(_ x: Int, to y: Int) -> Int { 0 }
/// Adds one to an integer
func add(oneTo x: inout Int) { }
}
func test(adder: Adder) {
adder.add(1️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 0)
guard signatureHelp.signatures.count == 2 else {
XCTFail("expected 2 signatures, got \(signatureHelp.signatures)")
return
}
let firstSignature = signatureHelp.signatures[0]
XCTAssertEqual(firstSignature.label, "add(_ x: Int, to: Int) -> Int")
XCTAssertEqual(firstSignature.activeParameter, 0)
XCTAssertNil(firstSignature.documentation)
XCTAssertEqual(
firstSignature.parameters,
[
ParameterInformation(label: .offsets(start: 4, end: 12)),
ParameterInformation(label: .offsets(start: 14, end: 21)),
]
)
let secondSignature = signatureHelp.signatures[1]
XCTAssertEqual(secondSignature.label, "add(oneTo: inout Int)")
XCTAssertEqual(secondSignature.activeParameter, 0)
XCTAssertEqual(
secondSignature.parameters,
[ParameterInformation(label: .offsets(start: 4, end: 20))]
)
XCTAssertEqual(
secondSignature.documentation,
.markupContent(MarkupContent(kind: .markdown, value: "Adds one to an integer"))
)
}
func testSignatureHelpPreservesActiveSignature() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
struct Adder {
func add(x: Int, y: Int) -> Int { 0 }
/// Adds two doubles
func add(y: Double = 0.0, x: Double) -> Double { 0 }
}
func test(adder: Adder) {
adder.add(1️⃣x: 2️⃣)
}
""",
uri: uri
)
let initialResult = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"],
context: SignatureHelpContext(
triggerKind: .triggerCharacter,
isRetrigger: false,
)
)
)
var activeSignatureHelp = try XCTUnwrap(initialResult)
// Simulate the user selecting the second signature.
activeSignatureHelp.activeSignature = 1
activeSignatureHelp.activeParameter = 0
let retriggerResult = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["2️⃣"],
context: SignatureHelpContext(
triggerKind: .contentChange,
isRetrigger: true,
activeSignatureHelp: activeSignatureHelp
)
)
)
let retriggerSignatureHelp = try XCTUnwrap(retriggerResult)
XCTAssertEqual(retriggerSignatureHelp.activeSignature, 1)
XCTAssertEqual(retriggerSignatureHelp.activeParameter, 1)
}
func testSignatureHelpNonASCII() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
/// This is a test function
func 🧑‍🧑‍🧒‍🧒🧑‍🧑‍🧒‍🧒(🧑🏽‍🚀🧑🏽‍🚀: Int, `🕵🏻‍♀️🕵🏻‍♀️`: String) -> Double { 0 }
func main() {
🧑‍🧑‍🧒‍🧒🧑‍🧑‍🧒‍🧒(🧑🏽‍🚀🧑🏽‍🚀: 1️⃣)
}
""",
uri: uri
)
let result = try await testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 0)
XCTAssertEqual(signature.label, "🧑‍🧑‍🧒‍🧒🧑‍🧑‍🧒‍🧒(🧑🏽‍🚀🧑🏽‍🚀: Int, `🕵🏻‍♀️🕵🏻‍♀️`: String) -> Double")
XCTAssertEqual(
signature.documentation,
.markupContent(MarkupContent(kind: .markdown, value: "This is a test function"))
)
XCTAssertEqual(
signature.parameters,
[
ParameterInformation(label: .offsets(start: 23, end: 42)),
ParameterInformation(label: .offsets(start: 44, end: 68)),
]
)
}
func testSignatureHelpSwiftPMProject() async throws {
try await SkipUnless.sourcekitdSupportsSignatureHelp()
let project = try await SwiftPMTestProject(
files: [
"utils.swift": #"""
/// A utility function that combines values
/// - Parameters:
/// - first: The first value
/// - second: The second value
/// - Returns: The combined result
func combine(first: String, second: Int) -> String {
return "\(first)-\(second)"
}
"""#,
"main.swift": """
func test() {
combine(1️⃣)
}
""",
]
)
let (uri, positions) = try project.openDocument("main.swift")
let result = try await project.testClient.send(
SignatureHelpRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions["1️⃣"]
)
)
let signatureHelp = try XCTUnwrap(result)
let signature = try XCTUnwrap(signatureHelp.signatures.only)
XCTAssertEqual(signature.label, "combine(first: String, second: Int) -> String")
XCTAssertEqual(signatureHelp.activeSignature, 0)
XCTAssertEqual(signatureHelp.activeParameter, 0)
XCTAssertEqual(
signature.documentation,
.markupContent(
MarkupContent(
kind: .markdown,
value: """
A utility function that combines values
- Parameters:
- first: The first value
- second: The second value
- Returns: The combined result
"""
)
)
)
XCTAssertEqual(
signature.parameters,
[
ParameterInformation(label: .offsets(start: 8, end: 21)),
ParameterInformation(label: .offsets(start: 23, end: 34)),
]
)
}
}