Files
sourcekit-lsp/Tests/SourceKitLSPTests/DefinitionTests.swift
Ahmad Ayman Mansour 53e247457b running swift formate
2026-01-11 20:46:51 +02:00

807 lines
26 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 BuildServerIntegration
@_spi(SourceKitLSP) import BuildServerProtocol
@_spi(SourceKitLSP) import LanguageServerProtocol
@_spi(Testing) @_spi(SourceKitLSP) import SKLogging
import SKTestSupport
import SwiftExtensions
import XCTest
class DefinitionTests: SourceKitLSPTestCase {
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()
.appending(component: "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"]))])
}
func testJumpToCopiedHeader() async throws {
actor BuildServer: CustomBuildServer {
let inProgressRequestsTracker = CustomBuildServerInProgressRequestTracker()
private let projectRoot: URL
private var headerCopyDestination: URL {
projectRoot.appending(components: "header-copy", "CopiedTest.h")
}
init(projectRoot: URL, connectionToSourceKitLSP: any Connection) {
self.projectRoot = projectRoot
}
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
return try initializationResponseSupportingBackgroundIndexing(
projectRoot: projectRoot,
outputPathsProvider: false
)
}
func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) -> BuildTargetSourcesResponse {
return BuildTargetSourcesResponse(items: [
SourcesItem(
target: .dummy,
sources: [
SourceItem(
uri: DocumentURI(projectRoot.appending(component: "Test.c")),
kind: .file,
generated: false,
dataKind: .sourceKit,
data: SourceKitSourceItemData(
language: .c,
kind: .source,
outputPath: nil,
copyDestinations: nil
).encodeToLSPAny()
),
SourceItem(
uri: DocumentURI(projectRoot.appending(component: "Test.h")),
kind: .file,
generated: false,
dataKind: .sourceKit,
data: SourceKitSourceItemData(
language: .c,
kind: .header,
outputPath: nil,
copyDestinations: [DocumentURI(headerCopyDestination)]
).encodeToLSPAny()
),
]
)
])
}
func textDocumentSourceKitOptionsRequest(
_ request: TextDocumentSourceKitOptionsRequest
) throws -> TextDocumentSourceKitOptionsResponse? {
return TextDocumentSourceKitOptionsResponse(compilerArguments: [
request.textDocument.uri.pseudoPath, "-I", try headerCopyDestination.deletingLastPathComponent().filePath,
])
}
func prepareTarget(_ request: BuildTargetPrepareRequest) async throws -> VoidResponse {
try FileManager.default.createDirectory(
at: headerCopyDestination.deletingLastPathComponent(),
withIntermediateDirectories: true
)
try FileManager.default.copyItem(
at: projectRoot.appending(component: "Test.h"),
to: headerCopyDestination
)
return VoidResponse()
}
}
let project = try await CustomBuildServerTestProject(
files: [
"Test.h": """
void hello();
""",
"Test.c": """
#include <CopiedTest.h>
void test() {
1⃣hello();
}
""",
],
buildServer: BuildServer.self,
enableBackgroundIndexing: true,
)
try await project.testClient.send(SynchronizeRequest(copyFileMap: true))
let (uri, positions) = try project.openDocument("Test.c")
let response = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1"])
)
XCTAssertEqual(response?.locations?.map(\.uri), [try project.uri(for: "Test.h")])
}
func testJumpToSwiftInterfaceIfIndexLookupFails() async throws {
let project = try await SwiftPMTestProject(
files: [
"Lib/Lib.swift": """
public func abc() {}
""",
"Executable/Executable.swift": """
import Lib
func test() {
1⃣abc()
}
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "Lib"),
.executableTarget(name: "Executable", dependencies: ["Lib"])
]
)
"""
)
try await SwiftPMTestProject.build(
at: project.scratchDirectory,
extraArguments: ["--disable-index-store", "-Xswiftc", "-avoid-emit-module-source-info"]
)
let (uri, positions) = try project.openDocument("Executable.swift")
let definitions = try await project.testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1"])
)
XCTAssertTrue(definitions?.locations?.first?.uri.pseudoPath.hasSuffix("Lib.swiftinterface") ?? false)
}
func testDefinitionOnLiteralsShouldReturnEmpty() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
"""
let message = "1Hello"
let count = 2⃣25
let flag = 3⃣true
let pi = 4⃣3.14
let nothing: Int? = 5⃣nil
""",
uri: uri
)
// Test string literal
let stringResponse = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1"])
)
XCTAssertNil(stringResponse, "Jump-to-definition should not work on string literals")
// Test integer literal
let intResponse = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2"])
)
XCTAssertNil(intResponse, "Jump-to-definition should not work on integer literals")
// Test boolean literal
let boolResponse = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["3"])
)
XCTAssertNil(boolResponse, "Jump-to-definition should not work on boolean literals")
// Test float literal
let floatResponse = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["4"])
)
XCTAssertNil(floatResponse, "Jump-to-definition should not work on float literals")
// Test nil literal
let nilResponse = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["5"])
)
XCTAssertNil(nilResponse, "Jump-to-definition should not work on nil literals")
}
func testDefinitionInsideLiteralsShouldStillWork() async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(
#"""
let name = "Ayman"
let greeting = "Hello \(1name)"
let items = [2⃣name, "other"]
"""#,
uri: uri
)
// Identifier inside string interpolation - should work
let interpolationResponse = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1"])
)
XCTAssertNotNil(
interpolationResponse,
"Jump-to-definition should work for identifiers inside string interpolation"
)
// Identifier inside array literal - should work
let arrayResponse = try await testClient.send(
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2"])
)
XCTAssertNotNil(
arrayResponse,
"Jump-to-definition should work for identifiers inside array literals"
)
}
}