Files
sourcekit-lsp/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift
Alex Hoppen 2a0f8c79b9 A couple of improvements for generated interfaces
- Rename methods to highlight that we’re talking about generated interfaces here, not `.swiftinterface` files
- Don’t open the generated interface in `documentManager`. Opening documents in `documentManager` should only be done by the `textDocument/didOpen` notification from the LSP client. Otherwise we might indefinitely keep the document in the document manager
- After getting the generated interface from sourcekitd, close the document in sourcekitd again. We don’t provide semantic functionality in the generated interface yet, so we can’t interact with the generated interface path. Before, we left it open in sourcekitd indefinitely.
- A couple of code simplifications.

Fixes #878
rdar://116705653
2024-06-08 07:56:19 -07:00

270 lines
8.3 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) 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
import ISDBTestSupport
import LSPLogging
import LSPTestSupport
import LanguageServerProtocol
import SKSupport
import SKTestSupport
import SourceKitLSP
import XCTest
final class SwiftInterfaceTests: XCTestCase {
func testSystemModuleInterface() async throws {
let testClient = try await TestSourceKitLSPClient()
let url = URL(fileURLWithPath: "/\(UUID())/a.swift")
let uri = DocumentURI(url)
testClient.openDocument("import Foundation", uri: uri)
let _resp = try await testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(url),
position: Position(line: 0, utf16index: 10)
)
)
let resp = try XCTUnwrap(_resp)
guard case .locations(let locations) = resp else {
XCTFail("Unexpected response: \(resp)")
return
}
let location = try XCTUnwrap(locations.only)
XCTAssertTrue(location.uri.pseudoPath.hasSuffix("/Foundation.swiftinterface"))
let fileContents = try XCTUnwrap(location.uri.fileURL.flatMap({ try String(contentsOf: $0, encoding: .utf8) }))
// Sanity-check 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 testOpenInterface() async throws {
let project = try await SwiftPMTestProject(
files: [
"MyLibrary/MyLibrary.swift": """
public struct Lib {
public func foo() {}
public init() {}
}
""",
"Exec/main.swift": "import MyLibrary",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "MyLibrary"),
.executableTarget(name: "Exec", dependencies: ["MyLibrary"])
]
)
""",
enableBackgroundIndexing: true
)
let (mainUri, _) = try project.openDocument("main.swift")
let openInterface = OpenGeneratedInterfaceRequest(
textDocument: TextDocumentIdentifier(mainUri),
name: "MyLibrary",
groupName: nil,
symbolUSR: nil
)
let interfaceDetails = try unwrap(await project.testClient.send(openInterface))
XCTAssert(interfaceDetails.uri.pseudoPath.hasSuffix("/MyLibrary.swiftinterface"))
let fileContents = try XCTUnwrap(
interfaceDetails.uri.fileURL.flatMap({ try String(contentsOf: $0, encoding: .utf8) })
)
XCTAssertTrue(
fileContents.contains(
"""
public struct Lib {
public func foo()
public init()
}
"""
)
)
}
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,
swiftInterfaceFile: "/Swift.String.swiftinterface",
linePrefix: "@frozen public struct String"
)
// Test stdlib with two submodules
try await assertSystemSwiftInterface(
uri: project.fileURI,
position: project.positions["2"],
testClient: project.testClient,
swiftInterfaceFile: "/Swift.Math.Integers.swiftinterface",
linePrefix: "@frozen public struct Int"
)
// Test concurrency
try await assertSystemSwiftInterface(
uri: project.fileURI,
position: project.positions["3"],
testClient: project.testClient,
swiftInterfaceFile: "/_Concurrency.swiftinterface",
linePrefix: "@inlinable public func withTaskGroup"
)
}
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 _resp =
try await project.testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(mainUri),
position: mainPositions["1"]
)
)
let resp = try XCTUnwrap(_resp)
guard case .locations(let locations) = resp else {
XCTFail("Unexpected response: \(resp)")
return
}
let location = try XCTUnwrap(locations.only)
XCTAssertTrue(location.uri.pseudoPath.hasSuffix("/MyLibrary.swiftinterface"))
let fileContents = try XCTUnwrap(location.uri.fileURL.flatMap({ try String(contentsOf: $0, encoding: .utf8) }))
XCTAssertTrue(
fileContents.contains(
"""
public struct Lib {
public func foo()
public init()
}
"""
),
"Generated interface did not contain expected text.\n\(fileContents)"
)
}
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,
swiftInterfaceFile: "/Swift.Collection.Array.swiftinterface",
linePrefix: "@inlinable public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [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,
swiftInterfaceFile: "/Swift.Collection.Array.swiftinterface",
linePrefix: "@inlinable public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]"
)
}
}
private func assertSystemSwiftInterface(
uri: DocumentURI,
position: Position,
testClient: TestSourceKitLSPClient,
swiftInterfaceFile: String,
linePrefix: String,
line: UInt = #line
) async throws {
let definition = try await testClient.send(
DefinitionRequest(
textDocument: TextDocumentIdentifier(uri),
position: position
)
)
guard case .locations(let jump) = definition else {
XCTFail("Response is not locations", line: line)
return
}
let location = try XCTUnwrap(jump.only)
XCTAssertTrue(
location.uri.pseudoPath.hasSuffix(swiftInterfaceFile),
"Path was: '\(location.uri.pseudoPath)'",
line: line
)
// load contents of swiftinterface
let contents = try XCTUnwrap(location.uri.fileURL.flatMap({ try String(contentsOf: $0, encoding: .utf8) }))
let lineTable = LineTable(contents)
let destinationLine = lineTable[location.range.lowerBound.line].trimmingCharacters(in: .whitespaces)
XCTAssert(destinationLine.hasPrefix(linePrefix), "Full line was: '\(destinationLine)'", line: line)
}