Skip to content

Commit 4c04b1e

Browse files
authored
Add tests for the jump to definition mapping (#99)
1 parent c3f2818 commit 4c04b1e

File tree

4 files changed

+98
-21
lines changed

4 files changed

+98
-21
lines changed

Sources/SourceKitBazelBSP/RequestHandlers/TargetSourcesHandler.swift

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,33 +61,33 @@ final class TargetSourcesHandler {
6161
return BuildTargetSourcesResponse(items: srcs)
6262
}
6363

64-
private func computeCopyDestinations(for src: URI) -> [DocumentURI]? {
64+
/// The path sourcekit-lsp has is the "real" path of the file,
65+
/// but Bazel works by copying them over to the execroot.
66+
/// This method calculates this fake path so that sourcekit-lsp can
67+
/// map the file back to the original workspace path for features like jump to definition.
68+
func computeCopyDestinations(for src: URI) -> [DocumentURI]? {
6569
guard let srcPath = src.fileURL?.path else {
6670
return nil
6771
}
6872

69-
var destinations: [DocumentURI] = []
73+
let rootUri = initializedConfig.rootUri
7074

71-
let workspaceRoot = initializedConfig.rootUri
72-
let execRoot = initializedConfig.executionRoot
73-
74-
func trimmedRelativePath(fullPath: String, base: String) -> String {
75-
var relativePath = fullPath.dropFirst(base.count)
76-
if relativePath.first == "/" {
77-
relativePath = relativePath.dropFirst()
78-
}
79-
return String(relativePath)
75+
guard srcPath.hasPrefix(rootUri) else {
76+
return nil
8077
}
8178

82-
if srcPath.hasPrefix(workspaceRoot) {
83-
// Workspace file -> single execroot location Bazel copies to.
84-
// We need to tell the LSP about the path of each file in execution root to later help it mapping it again to original Workspace path.
85-
let relativePath = trimmedRelativePath(fullPath: srcPath, base: workspaceRoot)
86-
let destination = DocumentURI(filePath: execRoot + "/" + relativePath, isDirectory: false)
87-
destinations.append(destination)
79+
let execRoot = initializedConfig.executionRoot
80+
81+
var relativePath = srcPath.dropFirst(rootUri.count)
82+
// Not sure how much we can assume about rootUri, so adding this as an edge-case check
83+
if relativePath.first == "/" {
84+
relativePath = relativePath.dropFirst()
8885
}
8986

90-
return destinations.isEmpty ? nil : destinations
87+
let newPath = execRoot + "/" + String(relativePath)
88+
return [
89+
DocumentURI(filePath: newPath, isDirectory: false)
90+
]
9191
}
9292

9393
func convertToSourceItems(_ targetSrcs: [URI]) -> [SourceItem] {
@@ -118,7 +118,7 @@ final class TargetSourcesHandler {
118118
data: SourceKitSourceItemData(
119119
language: language,
120120
kind: kind,
121-
outputPath: nil, // FIXME: Related to the same flag on initialize?
121+
outputPath: nil,
122122
copyDestinations: copyDestinations
123123
).encodeToLSPAny()
124124
)

Sources/SourceKitBazelBSP/Server/LSPConnection.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ package protocol LSPTaskLogger: AnyObject {
3030
/// Extends the original sourcekit-lsp `Connection` type to include JSONRPCConnection's start method
3131
/// and task logging utilities.
3232
package protocol LSPConnection: Connection, LSPTaskLogger, AnyObject {
33-
func start(receiveHandler: MessageHandler, closeHandler: nonisolated(nonsending) @escaping @Sendable () async -> Void)
33+
// Workaround formatter issue: https://github.com/swiftlang/swift-format/issues/1081
34+
// swift-format-ignore
35+
func start(
36+
receiveHandler: MessageHandler,
37+
closeHandler: nonisolated(nonsending) @escaping @Sendable () async -> Void
38+
)
3439
}
3540

3641
extension JSONRPCConnection: LSPConnection {

Tests/SourceKitBazelBSPTests/Fakes/LSPConnectionFake.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ final class LSPConnectionFake: LSPConnection {
2828
nonisolated(unsafe) private(set) var startReceivedHandler: MessageHandler?
2929
nonisolated(unsafe) private(set) var sentNotifications: [any NotificationType] = []
3030

31-
func start(receiveHandler: MessageHandler, closeHandler: nonisolated(nonsending) @escaping @Sendable () async -> Void) {
31+
// Workaround formatter issue: https://github.com/swiftlang/swift-format/issues/1081
32+
// swift-format-ignore
33+
func start(
34+
receiveHandler: MessageHandler,
35+
closeHandler: nonisolated(nonsending) @escaping @Sendable () async -> Void
36+
) {
3237
startCalled = true
3338
startReceivedHandler = receiveHandler
3439
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) 2025 Spotify AB.
2+
//
3+
// Licensed to the Apache Software Foundation (ASF) under one
4+
// or more contributor license agreements. See the NOTICE file
5+
// distributed with this work for additional information
6+
// regarding copyright ownership. The ASF licenses this file
7+
// to you under the Apache License, Version 2.0 (the
8+
// "License"); you may not use this file except in compliance
9+
// with the License. You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing,
14+
// software distributed under the License is distributed on an
15+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
// KIND, either express or implied. See the License for the
17+
// specific language governing permissions and limitations
18+
// under the License.
19+
20+
import BazelProtobufBindings
21+
import BuildServerProtocol
22+
import Foundation
23+
import LanguageServerProtocol
24+
import Testing
25+
26+
@testable import SourceKitBazelBSP
27+
28+
@Suite
29+
struct TargetSourcesHandlerTests {
30+
static func makeHandler() -> TargetSourcesHandler {
31+
let baseConfig = BaseServerConfig(
32+
bazelWrapper: "bazel",
33+
targets: ["//HelloWorld", "//HelloWorld2"],
34+
indexFlags: ["--config=index"],
35+
filesToWatch: nil,
36+
compileTopLevel: false
37+
)
38+
39+
let initializedConfig = InitializedServerConfig(
40+
baseConfig: baseConfig,
41+
rootUri: "/path/to/project",
42+
outputBase: "/tmp/output_base",
43+
outputPath: "/tmp/output_path",
44+
devDir: "/Applications/Xcode.app/Contents/Developer",
45+
devToolchainPath: "/a/b/XcodeDefault.xctoolchain/",
46+
executionRoot: "/tmp/output_path/execroot/_main",
47+
sdkRootPaths: ["iphonesimulator": "bar"]
48+
)
49+
50+
return TargetSourcesHandler(initializedConfig: initializedConfig, targetStore: BazelTargetStoreFake())
51+
}
52+
53+
@Test
54+
func canComputeCopyDestinations() throws {
55+
let handler = Self.makeHandler()
56+
57+
let src = try URI(string: "file:///path/to/project/src/main.swift")
58+
#expect(
59+
handler.computeCopyDestinations(for: src) == [
60+
DocumentURI(filePath: "/tmp/output_path/execroot/_main/src/main.swift", isDirectory: false)
61+
]
62+
)
63+
64+
let externalSrc = try URI(string: "file:///other_path/to/project/src/main.swift")
65+
#expect(handler.computeCopyDestinations(for: externalSrc) == nil)
66+
}
67+
}

0 commit comments

Comments
 (0)