From 12135d06872ea70330a681d03ac582f140001a87 Mon Sep 17 00:00:00 2001 From: Wang Lun Date: Mon, 11 Aug 2025 20:46:32 +0900 Subject: [PATCH 1/5] fixed group of module can't navi, interface name strategy --- .../GeneratedInterfaceDocumentURLData.swift | 2 +- .../SourceKitLSP/ReferenceDocumentURL.swift | 4 -- Sources/SourceKitLSP/SourceKitLSPServer.swift | 25 ++++++-- .../SwiftLanguageService/OpenInterface.swift | 8 ++- .../SwiftInterfaceTests.swift | 57 +++++++++++++++++++ 5 files changed, 86 insertions(+), 10 deletions(-) diff --git a/Sources/SourceKitLSP/GeneratedInterfaceDocumentURLData.swift b/Sources/SourceKitLSP/GeneratedInterfaceDocumentURLData.swift index 5e90b35ef..275a12b1d 100644 --- a/Sources/SourceKitLSP/GeneratedInterfaceDocumentURLData.swift +++ b/Sources/SourceKitLSP/GeneratedInterfaceDocumentURLData.swift @@ -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 { diff --git a/Sources/SourceKitLSP/ReferenceDocumentURL.swift b/Sources/SourceKitLSP/ReferenceDocumentURL.swift index bcf19c5b4..11ed7adff 100644 --- a/Sources/SourceKitLSP/ReferenceDocumentURL.swift +++ b/Sources/SourceKitLSP/ReferenceDocumentURL.swift @@ -170,8 +170,4 @@ extension DocumentURI { package struct ReferenceDocumentURLError: Error, CustomStringConvertible { package var description: String - - init(description: String) { - self.description = description - } } diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 8bc1c4fb7..4aa68337b 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -1857,10 +1857,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 @@ -2058,9 +2072,12 @@ extension SourceKitLSPServer { originatorUri: DocumentURI, languageService: LanguageService ) async throws -> Location { + // Let openGeneratedInterface handle all the logic, including checking if we're already in the right interface + let documentForBuildSettings = originatorUri.buildSettingsFile + guard let interfaceDetails = try await languageService.openGeneratedInterface( - document: originatorUri, + document: documentForBuildSettings, moduleName: moduleName, groupName: groupName, symbolUSR: symbolUSR diff --git a/Sources/SwiftLanguageService/OpenInterface.swift b/Sources/SwiftLanguageService/OpenInterface.swift index f0921580c..2f78e7b80 100644 --- a/Sources/SwiftLanguageService/OpenInterface.swift +++ b/Sources/SwiftLanguageService/OpenInterface.swift @@ -22,10 +22,16 @@ extension SwiftLanguageService { groupName: String?, symbolUSR symbol: String? ) async throws -> GeneratedInterfaceDetails? { + // Include build settings context to distinguish different versions/configurations + let buildSettingsFileHash = "\(abs(document.buildSettingsFile.stringValue.hashValue))" + let sourcekitdDocumentName = [moduleName, groupName, buildSettingsFileHash].compactMap(\.self).joined( + separator: "." + ) + let urlData = GeneratedInterfaceDocumentURLData( moduleName: moduleName, groupName: groupName, - sourcekitdDocumentName: "\(moduleName)-\(UUID())", + sourcekitdDocumentName: sourcekitdDocumentName, primaryFile: document ) let position: Position? = diff --git a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift index 4997c5942..beeb2bbb9 100644 --- a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift +++ b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift @@ -319,6 +319,63 @@ final class SwiftInterfaceTests: XCTestCase { ) XCTAssertEqual(diagnostics.fullReport?.items, []) } + + func testFoundationImportNavigation() 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 + """, + 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) + XCTAssertTrue(foundationLocation.uri.scheme == "sourcekit-lsp") + XCTAssertTrue(foundationLocation.uri.pseudoPath.contains("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( From a86ed02a11ff3b6886609ce3d4999a4fab76e903 Mon Sep 17 00:00:00 2001 From: Wang Lun Date: Tue, 19 Aug 2025 11:49:03 +0900 Subject: [PATCH 2/5] fixed format issue, path of interface file --- Sources/SwiftLanguageService/OpenInterface.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftLanguageService/OpenInterface.swift b/Sources/SwiftLanguageService/OpenInterface.swift index 2f78e7b80..bfa6b1555 100644 --- a/Sources/SwiftLanguageService/OpenInterface.swift +++ b/Sources/SwiftLanguageService/OpenInterface.swift @@ -24,9 +24,8 @@ extension SwiftLanguageService { ) async throws -> GeneratedInterfaceDetails? { // Include build settings context to distinguish different versions/configurations let buildSettingsFileHash = "\(abs(document.buildSettingsFile.stringValue.hashValue))" - let sourcekitdDocumentName = [moduleName, groupName, buildSettingsFileHash].compactMap(\.self).joined( - separator: "." - ) + let sourcekitdDocumentName = [moduleName, groupName, buildSettingsFileHash].compactMap(\.self) + .joined(separator: ".") let urlData = GeneratedInterfaceDocumentURLData( moduleName: moduleName, @@ -46,7 +45,13 @@ extension SwiftLanguageService { if self.capabilityRegistry.clientHasExperimentalCapability(GetReferenceDocumentRequest.method) { return GeneratedInterfaceDetails(uri: try urlData.uri, position: position) } - let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent(urlData.displayName) + let interfaceFilePath = self.generatedInterfacesPath + .appendingPathComponent(buildSettingsFileHash) + .appendingPathComponent(urlData.displayName) + try FileManager.default.createDirectory( + at: interfaceFilePath.deletingLastPathComponent(), + withIntermediateDirectories: true + ) try await generatedInterfaceManager.snapshot(of: urlData).text.write( to: interfaceFilePath, atomically: true, From 717f0119ced83401b39cd0a62326f9a8ba8396f3 Mon Sep 17 00:00:00 2001 From: Wang Lun Date: Tue, 19 Aug 2025 11:50:26 +0900 Subject: [PATCH 3/5] fix unit-test assert pattern --- Tests/SourceKitLSPTests/SwiftInterfaceTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift index beeb2bbb9..61de03e66 100644 --- a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift +++ b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift @@ -341,8 +341,8 @@ final class SwiftInterfaceTests: XCTestCase { DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"]) ) let foundationLocation = try XCTUnwrap(foundationDefinition?.locations?.only) - XCTAssertTrue(foundationLocation.uri.scheme == "sourcekit-lsp") - XCTAssertTrue(foundationLocation.uri.pseudoPath.contains("Foundation.swiftinterface")) + XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp") + assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface") } func testFoundationSubmoduleNavigation() async throws { @@ -364,8 +364,8 @@ final class SwiftInterfaceTests: XCTestCase { 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") + XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp") + assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface") // Test navigation to NSAffineTransform let transformDefinition = try await testClient.send( @@ -373,8 +373,8 @@ final class SwiftInterfaceTests: XCTestCase { ) 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") + XCTAssertEqual(transformLocation.uri.scheme, "sourcekit-lsp") + assertContains(transformLocation.uri.pseudoPath, "Foundation.NSAffineTransform.swiftinterface") } } From e39642192d767b9c02b1fbbcead78cca11240988 Mon Sep 17 00:00:00 2001 From: Wang Lun Date: Tue, 19 Aug 2025 21:34:50 +0900 Subject: [PATCH 4/5] Fixed format issues --- Tests/SourceKitLSPTests/SwiftInterfaceTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift index 61de03e66..a430647d8 100644 --- a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift +++ b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift @@ -341,8 +341,8 @@ final class SwiftInterfaceTests: XCTestCase { DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"]) ) let foundationLocation = try XCTUnwrap(foundationDefinition?.locations?.only) - XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp") - assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface") + XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp") + assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface") } func testFoundationSubmoduleNavigation() async throws { @@ -364,8 +364,8 @@ final class SwiftInterfaceTests: XCTestCase { DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"]) ) let foundationLocation = try XCTUnwrap(foundationDefinition?.locations?.only) - XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp") - assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface") + XCTAssertEqual(foundationLocation.uri.scheme, "sourcekit-lsp") + assertContains(foundationLocation.uri.pseudoPath, "Foundation.swiftinterface") // Test navigation to NSAffineTransform let transformDefinition = try await testClient.send( From a2a3388711b6f6496601bc86aa912cf060451ace Mon Sep 17 00:00:00 2001 From: Wang Lun Date: Wed, 20 Aug 2025 11:04:43 +0900 Subject: [PATCH 5/5] skip FoundationSubmodule test for non-darwnin --- Tests/SourceKitLSPTests/SwiftInterfaceTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift index a430647d8..d0c6f338f 100644 --- a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift +++ b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift @@ -346,6 +346,8 @@ final class SwiftInterfaceTests: XCTestCase { } func testFoundationSubmoduleNavigation() async throws { + try SkipUnless.platformIsDarwin("Non-Darwin platforms don't have Foundation submodules") + let testClient = try await TestSourceKitLSPClient( capabilities: ClientCapabilities(experimental: [ GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(true)])