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

660 lines
19 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//===----------------------------------------------------------------------===//
//
// 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)),
]
)
}
}