Files
sourcekit-lsp/Tests/SourceKitLSPTests/DefinitionTests.swift
Alex Hoppen d5e254137c Make fulfillmentOfOrThrow take a variadic list of expectations
In almost all cases, we pass a single expectation and that looks a lot nicer with a variadic parameter.
2025-03-25 09:16:17 -07:00

591 lines
18 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 - 2023 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 LanguageServerProtocol
@_spi(Testing) import SKLogging
import SKTestSupport
import SwiftExtensions
import XCTest
class DefinitionTests: XCTestCase {
func testJumpToDefinitionAtEndOfIdentifier() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
let 1⃣foo = 1
_ = foo2
""",
uri: uri
)
let response = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2"])
)
XCTAssertEqual(response?.locations, [Location(uri: uri, range: Range(positions["1"]))])
}
func testJumpToDefinitionIncludesOverrides() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
protocol TestProtocol {
func 1⃣doThing()
}
struct TestImpl: TestProtocol {
func 2⃣doThing() { }
}
func anyTestProtocol(value: any TestProtocol) {
value.3⃣doThing()
}
"""
)
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(project.fileURI), position: project.positions["3"])
)
XCTAssertEqual(
response?.locations,
[
Location(uri: project.fileURI, range: Range(project.positions["1"])),
Location(uri: project.fileURI, range: Range(project.positions["2"])),
]
)
}
func testJumpToDefinitionFiltersByReceiver() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
class A {
func 1⃣doThing() {}
}
class B: A {}
class C: B {
override func 2⃣doThing() {}
}
class D: A {
override func doThing() {}
}
func test(value: B) {
value.3⃣doThing()
}
"""
)
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(project.fileURI), position: project.positions["3"])
)
XCTAssertEqual(
response?.locations,
[
Location(uri: project.fileURI, range: Range(project.positions["1"])),
Location(uri: project.fileURI, range: Range(project.positions["2"])),
]
)
}
func testDynamicJumpToDefinitionInClang() async throws {
let project = try await SwiftPMTestProject(
files: [
"Sources/MyLibrary/include/dummy.h": "",
"test.cpp": """
struct Base {
virtual void 1⃣doStuff() {}
};
struct Sub: Base {
void 2⃣doStuff() override {}
};
void test(Base base) {
base.3⃣doStuff();
}
""",
],
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("test.cpp")
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3"])
)
XCTAssertEqual(
response?.locations,
[
Location(uri: uri, range: Range(positions["1"])),
Location(uri: uri, range: Range(positions["2"])),
]
)
}
func testJumpToCDefinitionFromSwift() async throws {
let project = try await SwiftPMTestProject(
files: [
"Sources/MyLibrary/include/test.h": """
void myFunc(void);
""",
"Sources/MyLibrary/test.c": """
#include "test.h"
void 1⃣myFunc(void) {}
""",
"Sources/MySwiftLibrary/main.swift":
"""
import MyLibrary
2⃣myFunc()
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "MyLibrary"),
.target(name: "MySwiftLibrary", dependencies: ["MyLibrary"])
]
)
""",
enableBackgroundIndexing: true
)
let (uri, positions) = try project.openDocument("main.swift")
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2"])
)
let location = try XCTUnwrap(response?.locations?.only)
XCTAssertEqual(
location,
Location(uri: try project.uri(for: "test.c"), range: Range(try project.position(of: "1", in: "test.c")))
)
}
func testReportInitializerOnDefinitionForType() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
struct 1⃣Foo {
2⃣init() {}
}
_ = 3⃣Foo()
""",
uri: uri
)
let response = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3"])
)
XCTAssertEqual(
response,
.locations([
Location(uri: uri, range: Range(positions["1"])),
Location(uri: uri, range: Range(positions["2"])),
])
)
}
func testAmbiguousDefinition() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
func 1⃣foo() -> Int { 1 }
func 2⃣foo() -> String { "" }
func test() {
_ = 3⃣foo()
}
""",
uri: uri
)
let response = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3"])
)
XCTAssertEqual(
response,
.locations([
Location(uri: uri, range: Range(positions["1"])),
Location(uri: uri, range: Range(positions["2"])),
])
)
}
func testDefinitionOfClassBetweenModulesObjC() async throws {
try SkipUnless.platformIsDarwin("@import in Objective-C is not enabled on non-Darwin")
let project = try await SwiftPMTestProject(
files: [
"LibA/include/LibA.h": """
@interface 1⃣LibAClass2
- (void)doSomething;
@end
""",
"LibB/include/dummy.h": "",
"LibB/LibB.m": """
@import LibA;
@interface Test
@end
@implementation Test
- (void)test:(3⃣LibAClass *)libAClass {
[libAClass doSomething];
}
@end
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "LibA"),
.target(name: "LibB", dependencies: ["LibA"]),
]
)
"""
)
let (uri, positions) = try project.openDocument("LibB.m")
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3"])
)
let location = try XCTUnwrap(response?.locations?.only)
XCTAssertEqual(location, try project.location(from: "1", to: "2", in: "LibA.h"))
}
func testDefinitionOfMethodBetweenModulesObjC() async throws {
try SkipUnless.platformIsDarwin("@import in Objective-C is not enabled on non-Darwin")
let project = try await SwiftPMTestProject(
files: [
"LibA/include/LibA.h": """
@interface LibAClass
- (void)1⃣doSomething2⃣;
@end
""",
"LibB/include/dummy.h": "",
"LibB/LibB.m": """
@import LibA;
@interface Test
@end
@implementation Test
- (void)test:(LibAClass *)libAClass {
[libAClass 3⃣doSomething];
}
@end
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "LibA"),
.target(name: "LibB", dependencies: ["LibA"]),
]
)
"""
)
let (uri, positions) = try project.openDocument("LibB.m")
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3"])
)
let location = try XCTUnwrap(response?.locations?.only)
XCTAssertEqual(location, try project.location(from: "1", to: "2", in: "LibA.h"))
}
func testDefinitionOfImplicitInitializer() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
class 1⃣Foo {}
func test() {
2⃣Foo()
}
""",
uri: uri
)
let response = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2"])
)
XCTAssertEqual(
response?.locations,
[Location(uri: uri, range: Range(positions["1"]))]
)
}
func testFileDependencyUpdatedWithinSameModule() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"FileA.swift": "",
"FileB.swift": """
func test() {
1⃣sayHello()
}
""",
],
capabilities: ClientCapabilities(
workspace: WorkspaceClientCapabilities(diagnostics: RefreshRegistrationCapability(refreshSupport: true))
),
enableBackgroundIndexing: true
)
let (bUri, bPositions) = try project.openDocument("FileB.swift")
let beforeChangingFileA = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(bUri), position: bPositions["1"])
)
XCTAssertNil(beforeChangingFileA)
let (aUri, updatedAPositions) = try await project.changeFileOnDisk(
"FileA.swift",
newMarkedContents: "func 2⃣sayHello() {}"
)
// Wait until SourceKit-LSP has handled the `DidChangeWatchedFilesNotification` (which it only does after a delay
// because it debounces these notifications), indicated by it telling us that we should refresh diagnostics.
let diagnosticRefreshRequestReceived = self.expectation(description: "DiagnosticsRefreshRequest received")
project.testClient.handleSingleRequest { (request: DiagnosticsRefreshRequest) in
diagnosticRefreshRequestReceived.fulfill()
return VoidResponse()
}
try await fulfillmentOfOrThrow(diagnosticRefreshRequestReceived)
let afterChangingFileA = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(bUri), position: bPositions["1"])
)
XCTAssertEqual(
afterChangingFileA,
.locations([Location(uri: aUri, range: Range(updatedAPositions["2"]))])
)
let afterChange = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(bUri), position: bPositions["1"])
)
XCTAssertEqual(
afterChange,
.locations([Location(uri: aUri, range: Range(updatedAPositions["2"]))])
)
}
func testDependentModuleGotBuilt() async throws {
try SkipUnless.longTestsEnabled()
let project = try await SwiftPMTestProject(
files: [
"LibA/LibA.swift": """
public func 1⃣sayHello() {}
""",
"LibB/LibB.swift": """
import LibA
func test() {
2⃣sayHello()
}
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "LibA"),
.target(name: "LibB", dependencies: ["LibA"]),
]
)
""",
capabilities: ClientCapabilities(
workspace: WorkspaceClientCapabilities(diagnostics: RefreshRegistrationCapability(refreshSupport: true))
)
)
let (bUri, bPositions) = try project.openDocument("LibB.swift")
let beforeBuilding = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(bUri), position: bPositions["2"])
)
XCTAssertNil(beforeBuilding)
try await SwiftPMTestProject.build(at: project.scratchDirectory)
project.testClient.send(
DidChangeWatchedFilesNotification(
changes:
FileManager.default.findFiles(withExtension: "swiftmodule", in: project.scratchDirectory).map {
FileEvent(uri: DocumentURI($0), type: .created)
}
)
)
// Wait until SourceKit-LSP has handled the `DidChangeWatchedFilesNotification` (which it only does after a delay
// because it debounces these notifications), indicated by it telling us that we should refresh diagnostics.
let diagnosticRefreshRequestReceived = self.expectation(description: "DiagnosticsRefreshRequest received")
project.testClient.handleSingleRequest { (request: DiagnosticsRefreshRequest) in
diagnosticRefreshRequestReceived.fulfill()
return VoidResponse()
}
try await fulfillmentOfOrThrow(diagnosticRefreshRequestReceived)
let afterBuilding = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(bUri), position: bPositions["2"])
)
XCTAssertEqual(
afterBuilding,
.locations([try project.location(from: "1", to: "1", in: "LibA.swift")])
)
}
func testIndexBasedDefinitionAfterFileMove() async throws {
let project = try await SwiftPMTestProject(
files: [
"definition.swift": """
class MyClass {
func 1⃣foo() {}
}
""",
"caller.swift": """
func test(myClass: MyClass) {
myClass.2⃣foo()
}
""",
],
enableBackgroundIndexing: true
)
let definitionUri = try project.uri(for: "definition.swift")
let (callerUri, callerPositions) = try project.openDocument("caller.swift")
// Validate that we get correct rename results before moving the definition file.
let resultBeforeFileMove = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(callerUri), position: callerPositions["2"])
)
XCTAssertEqual(
resultBeforeFileMove,
.locations([
Location(uri: definitionUri, range: Range(try project.position(of: "1", in: "definition.swift")))
])
)
let movedDefinitionUri =
DocumentURI(
definitionUri.fileURL!
.deletingLastPathComponent()
.appendingPathComponent("movedDefinition.swift")
)
try FileManager.default.moveItem(at: XCTUnwrap(definitionUri.fileURL), to: XCTUnwrap(movedDefinitionUri.fileURL))
project.testClient.send(
DidChangeWatchedFilesNotification(changes: [
FileEvent(uri: definitionUri, type: .deleted), FileEvent(uri: movedDefinitionUri, type: .created),
])
)
// Ensure that the DidChangeWatchedFilesNotification is handled before we continue.
try await project.testClient.send(SynchronizeRequest(index: true))
let resultAfterFileMove = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(callerUri), position: callerPositions["2"])
)
XCTAssertEqual(
resultAfterFileMove,
.locations([
Location(uri: movedDefinitionUri, range: Range(try project.position(of: "1", in: "definition.swift")))
])
)
}
func testJumpToDefinitionOnProtocolImplementationJumpsToRequirement() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
protocol TestProtocol {
func 1⃣doThing()
}
struct TestImpl: TestProtocol {
func 2⃣do3⃣Thing() { }
}
"""
)
let definitionFromBaseName = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(project.fileURI), position: project.positions["2"])
)
XCTAssertEqual(
definitionFromBaseName,
.locations([Location(uri: project.fileURI, range: Range(project.positions["1"]))])
)
let definitionFromInsideBaseName = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(project.fileURI), position: project.positions["3"])
)
XCTAssertEqual(
definitionFromInsideBaseName,
.locations([Location(uri: project.fileURI, range: Range(project.positions["1"]))])
)
}
func testJumpToDefinitionOnProtocolImplementationShowsAllFulfilledRequirements() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
protocol TestProtocol {
func 1⃣doThing()
}
protocol OtherProtocol {
func 2⃣doThing()
}
struct TestImpl: TestProtocol, OtherProtocol {
func 3⃣doThing() { }
}
"""
)
let result = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(project.fileURI), position: project.positions["3"])
)
XCTAssertEqual(
result,
.locations([
Location(uri: project.fileURI, range: Range(project.positions["1"])),
Location(uri: project.fileURI, range: Range(project.positions["2"])),
])
)
}
func testJumpToSatisfiedProtocolRequirementInExtension() async throws {
let project = try await IndexedSingleSwiftFileTestProject(
"""
protocol TestProtocol {
func 1⃣doThing()
}
struct TestImpl: TestProtocol {}
extension TestImpl {
func 2⃣doThing() { }
}
"""
)
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(project.fileURI), position: project.positions["1"])
)
XCTAssertEqual(response?.locations, [Location(uri: project.fileURI, range: Range(project.positions["2"]))])
}
}