Files
sourcekit-lsp/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift
T
2026-05-18 15:27:18 +02:00

466 lines
16 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 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 Foundation
@_spi(SourceKitLSP) import LanguageServerProtocol
@_spi(SourceKitLSP) import SKLogging
import SKOptions
import SKTestSupport
import SKUtilities
import SourceKitLSP
import SwiftExtensions
import XCTest
final class SwiftInterfaceTests: SourceKitLSPTestCase {
func testSystemModuleInterface() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
testClient.openDocument("import Foundation", uri: uri)
let resp = try await testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(uri),
position: Position(line: 0, utf16index: 10)
)
)
let location = try XCTUnwrap(resp?.locations?.only)
XCTAssertTrue(location.uri.pseudoPath.hasSuffix("Foundation.swiftinterface"))
let fileContents = try XCTUnwrap(String(contentsOf: try XCTUnwrap(location.uri.fileURL), encoding: .utf8))
// Smoke test that the generated Swift Interface contains Swift code
XCTAssert(
fileContents.hasPrefix("import "),
"Expected that the foundation swift interface starts with 'import ' but got '\(fileContents.prefix(100))'"
)
}
func testSystemModuleInterfaceReferenceDocument() async throws {
let testClient = try await TestSourceKitLSPClient(
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: ["supported": true]
])
)
let uri = DocumentURI(for: .swift)
testClient.openDocument("import Foundation", uri: uri)
let response = try await testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(uri),
position: Position(line: 0, utf16index: 10)
)
)
let location = try XCTUnwrap(response?.locations?.only)
let referenceDocument = try await testClient.send(GetReferenceDocumentRequest(uri: location.uri))
XCTAssert(
referenceDocument.content.hasPrefix("import "),
"Expected that the foundation swift interface starts with 'import ' but got '\(referenceDocument.content.prefix(100))'"
)
}
func testDefinitionInSystemModuleInterface() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
public func libFunc() async {
let a: 1️⃣String = "test"
let i: 2️⃣Int = 2
await 3️⃣withTaskGroup(of: Void.self) { group in
group.addTask {
print(a)
print(i)
}
}
}
""",
indexSystemModules: true
)
// Test stdlib with one submodule
try await assertSystemSwiftInterface(
uri: project.fileURI,
position: project.positions["1️⃣"],
testClient: project.testClient,
swiftInterfaceFiles: ["Swift.String.swiftinterface", "String.swift"],
lineContains: "public struct String"
)
// Test stdlib with two submodules
try await assertSystemSwiftInterface(
uri: project.fileURI,
position: project.positions["2️⃣"],
testClient: project.testClient,
swiftInterfaceFiles: ["Swift.Math.Integers.swiftinterface", "IntegerTypes.swift"],
lineContains: "public struct Int"
)
// Test concurrency
try await assertSystemSwiftInterface(
uri: project.fileURI,
position: project.positions["3️⃣"],
testClient: project.testClient,
swiftInterfaceFiles: ["Swift.swiftinterface", "TaskGroup.swift"],
lineContains: "public func withTaskGroup"
)
}
func testDefinitionInSystemModuleInterfaceWithReferenceDocument() async throws {
var options = try await SourceKitLSPOptions.testDefault()
options.swiftPMOrDefault.swiftCompilerFlags = Self.ignoreModuleSourceInfoFlags
let project = try await IndexedSingleSwiftFileTestProject(
"""
public func libFunc() async {
let a: 1️⃣String = "test"
}
""",
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: ["supported": true]
]),
indexSystemModules: true,
extraCompilerArguments: Self.ignoreModuleSourceInfoFlags
)
let definition = try await project.testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(project.fileURI),
position: project.positions["1️⃣"]
)
)
let location = try XCTUnwrap(definition?.locations?.only)
let referenceDocument = try await project.testClient.send(GetReferenceDocumentRequest(uri: location.uri))
let contents = referenceDocument.content
let lineTable = LineTable(contents)
let destinationLine = try XCTUnwrap(lineTable.line(at: location.range.lowerBound.line))
.trimmingCharacters(in: .whitespaces)
XCTAssert(
destinationLine.hasPrefix("@frozen public struct String"),
"Full line was: '\(destinationLine)'"
)
}
func testSwiftInterfaceAcrossModules() async throws {
let project = try await SwiftPMTestProject(
files: [
"MyLibrary/MyLibrary.swift": """
public struct Lib {
public func foo() {}
public init() {}
}
""",
"Exec/main.swift": "import 1️⃣MyLibrary",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "MyLibrary"),
.executableTarget(name: "Exec", dependencies: ["MyLibrary"])
]
)
""",
enableBackgroundIndexing: true
)
let (mainUri, mainPositions) = try project.openDocument("main.swift")
let response =
try await project.testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(mainUri),
position: mainPositions["1️⃣"]
)
)
let location = try XCTUnwrap(response?.locations?.only)
XCTAssertTrue(location.uri.pseudoPath.hasSuffix("MyLibrary.swiftinterface"))
let fileContents = try XCTUnwrap(String(contentsOf: try XCTUnwrap(location.uri.fileURL), encoding: .utf8))
XCTAssertTrue(
fileContents.contains(
"""
public struct Lib {
public func foo()
public init()
}
"""
),
"Generated interface did not contain expected text.\n\(fileContents)"
)
}
func testSemanticFunctionalityInGeneratedInterface() async throws {
let project = try await SwiftPMTestProject(
files: [
"MyLibrary/MyLibrary.swift": """
public struct Lib {
public func foo() -> String {}
public init() {}
}
""",
"Exec/main.swift": "import 1️⃣MyLibrary",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "MyLibrary"),
.executableTarget(name: "Exec", dependencies: ["MyLibrary"])
]
)
""",
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: ["supported": true]
]),
enableBackgroundIndexing: true
)
let (mainUri, mainPositions) = try project.openDocument("main.swift")
let response =
try await project.testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(mainUri),
position: mainPositions["1️⃣"]
)
)
let referenceDocumentUri = try XCTUnwrap(response?.locations?.only).uri
let referenceDocument = try await project.testClient.send(GetReferenceDocumentRequest(uri: referenceDocumentUri))
let stringIndex = try XCTUnwrap(referenceDocument.content.firstRange(of: "-> String"))
let (stringLine, stringColumn) = LineTable(referenceDocument.content)
.lineAndUTF16ColumnOf(referenceDocument.content.index(stringIndex.lowerBound, offsetBy: 3))
project.testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: referenceDocumentUri,
language: .swift,
version: 0,
text: referenceDocument.content
)
)
)
let hover = try await project.testClient.send(
HoverRequest(
textDocument: TextDocumentIdentifier(referenceDocumentUri),
position: Position(line: stringLine, utf16index: stringColumn)
)
)
XCTAssertNotNil(hover)
}
func testJumpToSynthesizedExtensionMethodInSystemModuleWithoutIndex() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
func test(x: [String]) {
let rows = x.1️⃣filter { !$0.isEmpty }
}
""",
uri: uri
)
try await assertSystemSwiftInterface(
uri: uri,
position: positions["1️⃣"],
testClient: testClient,
swiftInterfaceFiles: ["Swift.Collection.Array.swiftinterface"],
lineContains: "func filter<E>(_ isIncluded: (Element) throws(E) -> Bool) throws(E) -> [Element]"
)
}
func testJumpToSynthesizedExtensionMethodInSystemModuleWithIndex() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
func test(x: [String]) {
let rows = x.1️⃣filter { !$0.isEmpty }
}
""",
indexSystemModules: true
)
try await assertSystemSwiftInterface(
uri: project.fileURI,
position: project.positions["1️⃣"],
testClient: project.testClient,
swiftInterfaceFiles: ["Swift.Collection.Array.swiftinterface"],
lineContains: "func filter<E>(_ isIncluded: (Element) throws(E) -> Bool) throws(E) -> [Element]"
)
}
/// Flags needed to ignore module source info.
static var ignoreModuleSourceInfoFlags: [String] {
[
"-Xfrontend",
"-ignore-module-source-info",
]
}
func testNoDiagnosticsInGeneratedInterface() async throws {
var options = try await SourceKitLSPOptions.testDefault()
options.fallbackBuildSystemOrDefault.swiftCompilerFlags = Self.ignoreModuleSourceInfoFlags
let testClient = try await TestSourceKitLSPClient(
options: options,
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: ["supported": true]
])
)
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
func test(x: 1️⃣String) {}
""",
uri: uri
)
let definition = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
)
let interfaceUri = try XCTUnwrap(definition?.locations?.only?.uri)
let interfaceContents = try await testClient.send(GetReferenceDocumentRequest(uri: interfaceUri))
testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(uri: interfaceUri, language: .swift, version: 0, text: interfaceContents.content)
)
)
let diagnostics = try await testClient.send(
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(interfaceUri))
)
XCTAssertEqual(diagnostics.fullReport?.items, [])
}
func testFoundationImportNavigation() async throws {
let testClient = try await TestSourceKitLSPClient(
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: ["supported": true]
])
)
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import 1️⃣Foundation
""",
uri: uri,
language: .swift
)
// Test navigation to Foundation module
let foundationDefinition = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
)
let foundationLocation = try XCTUnwrap(foundationDefinition?.locations?.only)
XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp")
assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface")
}
func testFoundationSubmoduleNavigation() async throws {
try SkipUnless.platformIsDarwin("Non-Darwin platforms don't have Foundation submodules")
let testClient = try await TestSourceKitLSPClient(
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: ["supported": true]
])
)
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
import 1️⃣Foundation.2️⃣NSAffineTransform
""",
uri: uri
)
let foundationDefinition = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
)
let foundationLocation = try XCTUnwrap(foundationDefinition?.locations?.only)
XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp")
assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface")
// Test navigation to NSAffineTransform
let transformDefinition = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
)
let transformLocation = try XCTUnwrap(transformDefinition?.locations?.only)
// Verify we can identify this as a swiftinterface file
XCTAssertEqual(transformLocation.uri.scheme, "sourcekit-lsp")
assertContains(transformLocation.uri.pseudoPath, "Foundation.NSAffineTransform.swiftinterface")
}
func testClosingGeneratedInterfacePreservesOriginatingLanguageService() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
func test(x: 1️⃣String) {
let y: 2️⃣Int = 1
}
""",
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: .dictionary(["supported": true])
]),
extraCompilerArguments: Self.ignoreModuleSourceInfoFlags
)
let definition = try await project.testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(project.fileURI),
position: project.positions["1️⃣"]
)
)
let interfaceUri = try XCTUnwrap(definition?.locations?.only?.uri)
let interfaceContents = try await project.testClient.send(GetReferenceDocumentRequest(uri: interfaceUri))
project.testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(uri: interfaceUri, language: .swift, version: 1, text: interfaceContents.content)
)
)
project.testClient.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(interfaceUri)))
let hover = try await project.testClient.send(
HoverRequest(
textDocument: TextDocumentIdentifier(project.fileURI),
position: project.positions["2️⃣"]
)
)
XCTAssertNotNil(hover)
}
}
private func assertSystemSwiftInterface(
uri: DocumentURI,
position: Position,
testClient: TestSourceKitLSPClient,
swiftInterfaceFiles: [String],
linePrefix: String? = nil,
lineContains: String? = nil,
line: UInt = #line
) async throws {
let definition = try await testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(uri),
position: position
)
)
let location = try XCTUnwrap(definition?.locations?.only)
assertContains(swiftInterfaceFiles, try XCTUnwrap(location.uri.fileURL?.lastPathComponent), line: line)
// load contents of swiftinterface
let contents = try XCTUnwrap(String(contentsOf: try XCTUnwrap(location.uri.fileURL), encoding: .utf8))
let lineTable = LineTable(contents)
let destinationLine = try XCTUnwrap(lineTable.line(at: location.range.lowerBound.line))
.trimmingCharacters(in: .whitespaces)
if let linePrefix {
XCTAssert(destinationLine.hasPrefix(linePrefix), "Full line was: '\(destinationLine)'", line: line)
}
if let lineContains {
XCTAssert(destinationLine.contains(lineContains), "Full line was: '\(destinationLine)'", line: line)
}
}