Skip to content

Fix module definition jumping in Swift interface files #2233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
49 changes: 45 additions & 4 deletions Sources/SourceKitLSP/SourceKitLSPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1850,10 +1850,24 @@ extension SourceKitLSPServer {
languageService: LanguageService
) async throws -> [Location] {
// If this symbol is a module then generate a textual interface
if symbol.kind == .module, let name = symbol.name {
if symbol.kind == .module {
// For module symbols, prefer using systemModule information if available
let moduleName: String
let groupName: String?

if let systemModule = symbol.systemModule {
moduleName = systemModule.moduleName
groupName = systemModule.groupName
} else if let name = symbol.name {
moduleName = name
groupName = nil
} else {
return []
}

let interfaceLocation = try await self.definitionInInterface(
moduleName: name,
groupName: nil,
moduleName: moduleName,
groupName: groupName,
symbolUSR: nil,
originatorUri: uri,
languageService: languageService
Expand Down Expand Up @@ -2051,9 +2065,36 @@ extension SourceKitLSPServer {
originatorUri: DocumentURI,
languageService: LanguageService
) async throws -> Location {
// Check if we're already in the target interface with the same module/group/symbol
if case .generatedInterface(let interfaceData) = try? ReferenceDocumentURL(from: originatorUri),
interfaceData.moduleName == moduleName && interfaceData.groupName == groupName
{
// If we have a specific symbol USR, try to find its position in the current interface
if let symbolUSR = symbolUSR,
let swiftLanguageService = languageService as? SwiftLanguageService
{
do {
let position = try await swiftLanguageService.generatedInterfaceManager.position(
ofUsr: symbolUSR,
in: interfaceData
)
return Location(uri: originatorUri, range: Range(position))
} catch {
// If we can't find the symbol, just return the top of the current interface
return Location(uri: originatorUri, range: Range(Position(line: 0, utf16index: 0)))
}
} else {
// No specific symbol, just return the current interface location
return Location(uri: originatorUri, range: Range(Position(line: 0, utf16index: 0)))
}
}

// If the originator URI is already a generated interface, use its primary file for build settings
let documentForBuildSettings = originatorUri.buildSettingsFile

guard
let interfaceDetails = try await languageService.openGeneratedInterface(
document: originatorUri,
document: documentForBuildSettings,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ package struct GeneratedInterfaceDocumentURLData: Hashable, ReferenceURLData {
self.moduleName = moduleName
self.groupName = groupName
self.sourcekitdDocumentName = sourcekitdDocumentName
self.buildSettingsFrom = primaryFile
self.buildSettingsFrom = primaryFile.buildSettingsFile
}

init(queryItems: [URLQueryItem]) throws {
Expand Down
4 changes: 0 additions & 4 deletions Sources/SourceKitLSP/Swift/ReferenceDocumentURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,4 @@ extension DocumentURI {

package struct ReferenceDocumentURLError: Error, CustomStringConvertible {
package var description: String

init(description: String) {
self.description = description
}
}
62 changes: 62 additions & 0 deletions Tests/SourceKitLSPTests/SwiftInterfaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import SourceKitLSP
import SwiftExtensions
import XCTest

private extension Language {
static let swiftinterface = Language(rawValue: "swiftinterface")
}

final class SwiftInterfaceTests: XCTestCase {
func testSystemModuleInterface() async throws {
let testClient = try await TestSourceKitLSPClient()
Expand Down Expand Up @@ -319,6 +323,64 @@ final class SwiftInterfaceTests: XCTestCase {
)
XCTAssertEqual(diagnostics.fullReport?.items, [])
}

func testFoundationImportNavigation() async throws {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything in this test that isn’t already covered by testSwiftInterfaceAcrossModules?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleted

let testClient = try await TestSourceKitLSPClient(
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(true)])
])
)
let uri = DocumentURI(for: .swiftinterface)

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)
// Verify it's a swiftinterface file (can be either file:// or sourcekit-lsp:// scheme)
XCTAssertTrue(foundationLocation.uri.scheme == "sourcekit-lsp")
XCTAssertTrue(foundationLocation.uri.pseudoPath.hasSuffix("Foundation.swiftinterface"))
}

func testFoundationSubmoduleNavigation() async throws {
let testClient = try await TestSourceKitLSPClient(
capabilities: ClientCapabilities(experimental: [
GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(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)
XCTAssertTrue(foundationLocation.uri.pseudoPath.contains("Foundation.swiftinterface"))
XCTAssertTrue(foundationLocation.uri.scheme == "sourcekit-lsp")

// 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
XCTAssertTrue(transformLocation.uri.pseudoPath.contains("Foundation.NSAffineTransform.swiftinterface"))
XCTAssertTrue(transformLocation.uri.scheme == "sourcekit-lsp")
}
}

private func assertSystemSwiftInterface(
Expand Down