diff --git a/Sources/DocumentationLanguageService/DocCDocumentationManager.swift b/Sources/DocumentationLanguageService/DocCDocumentationManager.swift index 39fd0a549..7d5de99d5 100644 --- a/Sources/DocumentationLanguageService/DocCDocumentationManager.swift +++ b/Sources/DocumentationLanguageService/DocCDocumentationManager.swift @@ -19,20 +19,13 @@ import SwiftDocC struct DocCDocumentationManager: Sendable { private let doccServer: DocCServer - private let referenceResolutionService: DocCReferenceResolutionService private let catalogIndexManager: DocCCatalogIndexManager private let buildServerManager: BuildServerManager init(buildServerManager: BuildServerManager) { - let symbolResolutionServer = DocumentationServer(qualityOfService: .unspecified) - doccServer = DocCServer( - peer: symbolResolutionServer, - qualityOfService: .default - ) - catalogIndexManager = DocCCatalogIndexManager(server: doccServer) - referenceResolutionService = DocCReferenceResolutionService() - symbolResolutionServer.register(service: referenceResolutionService) + self.doccServer = DocCServer(qualityOfService: .default) + self.catalogIndexManager = DocCCatalogIndexManager(server: doccServer) self.buildServerManager = buildServerManager } @@ -95,17 +88,6 @@ struct DocCDocumentationManager: Sendable { } // Store the convert request identifier in order to fulfill index requests from SwiftDocC let convertRequestIdentifier = UUID().uuidString - var catalogIndex: DocCCatalogIndex? = nil - if let catalogURL { - catalogIndex = try await catalogIndexManager.index(for: catalogURL) - } - referenceResolutionService.addContext( - DocCReferenceResolutionContext( - catalogURL: catalogURL, - catalogIndex: catalogIndex - ), - withKey: convertRequestIdentifier - ) // Send the convert request to SwiftDocC and wait for the response let convertResponse = try await doccServer.convert( externalIDsToConvert: externalIDsToConvert, diff --git a/Sources/DocumentationLanguageService/DocCReferenceResolutionService.swift b/Sources/DocumentationLanguageService/DocCReferenceResolutionService.swift deleted file mode 100644 index 2b3d185d8..000000000 --- a/Sources/DocumentationLanguageService/DocCReferenceResolutionService.swift +++ /dev/null @@ -1,234 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 DocCCommon -import Foundation -import IndexStoreDB -@_spi(SourceKitLSP) import LanguageServerProtocol -@_spi(SourceKitLSP) import SKLogging -import SemanticIndex -@_spi(Linkcompletion) @preconcurrency import SwiftDocC -import SwiftExtensions - -final class DocCReferenceResolutionService: DocumentationService, Sendable { - /// The message type that this service accepts. - static let symbolResolutionMessageType: DocumentationServer.MessageType = "resolve-reference" - - /// The message type that this service responds with when the requested symbol resolution was successful. - static let symbolResolutionResponseMessageType: DocumentationServer.MessageType = "resolve-reference-response" - - static let handlingTypes = [symbolResolutionMessageType] - - private let contextMap = ThreadSafeBox<[String: DocCReferenceResolutionContext]>(initialValue: [:]) - - init() {} - - func addContext(_ context: DocCReferenceResolutionContext, withKey key: String) { - contextMap.value[key] = context - } - - @discardableResult func removeContext(forKey key: String) -> DocCReferenceResolutionContext? { - contextMap.value.removeValue(forKey: key) - } - - func context(forKey key: String) -> DocCReferenceResolutionContext? { - contextMap.value[key] - } - - func process( - _ message: DocumentationServer.Message, - completion: @escaping (DocumentationServer.Message) -> Void - ) { - do { - let response = try process(message) - completion(response) - } catch { - completion(createResponseWithErrorMessage(error.localizedDescription)) - } - } - - private func process( - _ message: DocumentationServer.Message - ) throws(ReferenceResolutionError) -> DocumentationServer.Message { - // Decode the message payload - guard let payload = message.payload else { - throw ReferenceResolutionError.nilMessagePayload - } - let request = try decode(ConvertRequestContextWrapper.self, from: payload) - // Attempt to resolve the reference in the request - let resolvedReference = try resolveReference(request: request); - // Encode the response payload - let encodedResolvedReference = try encode(resolvedReference) - return createResponse(payload: encodedResolvedReference) - } - - private func resolveReference( - request: ConvertRequestContextWrapper - ) throws(ReferenceResolutionError) -> OutOfProcessReferenceResolver.Response { - guard let convertRequestIdentifier = request.convertRequestIdentifier else { - throw .missingConvertRequestIdentifier - } - guard let context = context(forKey: convertRequestIdentifier) else { - throw .missingContext - } - switch request.payload { - case .symbol(let symbolUSR): - throw .symbolNotFound(symbolUSR) - case .asset(let assetReference): - guard let catalog = context.catalogIndex else { - throw .indexNotAvailable - } - guard let dataAsset = catalog.assets[assetReference.assetName] else { - throw .assetNotFound - } - return .asset(dataAsset) - case .topic(let topicURL): - // Check if this is a link to another documentation article - let relevantPathComponents = topicURL.pathComponents.filter { $0 != "/" } - let resolvedReference: TopicRenderReference? = - switch relevantPathComponents.first { - case NodeURLGenerator.Path.documentationFolderName: - context.catalogIndex?.articles[topicURL.lastPathComponent] - case NodeURLGenerator.Path.tutorialsFolderName: - context.catalogIndex?.tutorials[topicURL.lastPathComponent] - default: - nil - } - if let resolvedReference { - return .resolvedInformation(OutOfProcessReferenceResolver.ResolvedInformation(resolvedReference, url: topicURL)) - } - // Otherwise this must be a link to a symbol - let urlString = topicURL.absoluteString - guard let doccSymbolLink = DocCSymbolLink(linkString: urlString) else { - throw .invalidURLInRequest - } - // Don't bother checking to see if the symbol actually exists in the index. This can be time consuming and - // it would be better to report errors/warnings for unresolved symbols directly within the document, anyway. - return .resolvedInformation( - OutOfProcessReferenceResolver.ResolvedInformation( - symbolURL: topicURL, - symbolName: doccSymbolLink.symbolName - ) - ) - } - } - - private func decode(_ type: T.Type, from data: Data) throws(ReferenceResolutionError) -> T { - do { - return try JSONDecoder().decode(T.self, from: data) - } catch { - throw .decodingFailure(error.localizedDescription) - } - } - - private func encode(_ value: T) throws(ReferenceResolutionError) -> Data { - do { - return try JSONEncoder().encode(value) - } catch { - throw .decodingFailure(error.localizedDescription) - } - } - - private func createResponseWithErrorMessage(_ message: String) -> DocumentationServer.Message { - let errorMessage = OutOfProcessReferenceResolver.Response.errorMessage(message) - let encodedErrorMessage = orLog("Encoding error message for OutOfProcessReferenceResolver.Response") { - try JSONEncoder().encode(errorMessage) - } - return createResponse(payload: encodedErrorMessage) - } - - private func createResponse(payload: Data?) -> DocumentationServer.Message { - DocumentationServer.Message( - type: DocCReferenceResolutionService.symbolResolutionResponseMessageType, - payload: payload - ) - } -} - -struct DocCReferenceResolutionContext { - let catalogURL: URL? - let catalogIndex: DocCCatalogIndex? -} - -fileprivate extension OutOfProcessReferenceResolver.ResolvedInformation { - init(symbolURL: URL, symbolName: String) { - self = OutOfProcessReferenceResolver.ResolvedInformation( - kind: .unknownSymbol, - url: symbolURL, - title: symbolName, - abstract: "", - language: .swift, - availableLanguages: [.swift], - platforms: [], - declarationFragments: nil - ) - } - - init(_ renderReference: TopicRenderReference, url: URL) { - let kind: DocumentationNode.Kind - switch renderReference.kind { - case .article: - kind = .article - case .tutorial, .overview: - kind = .tutorial - case .symbol: - kind = .unknownSymbol - case .section: - kind = .unknown - } - - self.init( - kind: kind, - url: url, - title: renderReference.title, - abstract: renderReference.abstract.map(\.plainText).joined(), - language: .swift, - availableLanguages: [.swift, .objectiveC], - topicImages: renderReference.images - ) - } -} - -enum ReferenceResolutionError: LocalizedError { - case nilMessagePayload - case invalidURLInRequest - case decodingFailure(String) - case encodingFailure(String) - case missingConvertRequestIdentifier - case missingContext - case indexNotAvailable - case symbolNotFound(String) - case assetNotFound - - var errorDescription: String? { - switch self { - case .nilMessagePayload: - return "Nil message payload provided." - case .decodingFailure(let error): - return "The service was unable to decode the given symbol resolution request: '\(error)'." - case .encodingFailure(let error): - return "The service failed to encode the result after resolving the symbol: \(error)" - case .invalidURLInRequest: - return "Failed to initialize an 'AbsoluteSymbolLink' from the given URL." - case .missingConvertRequestIdentifier: - return "The given request was missing a convert request identifier." - case .missingContext: - return "The given convert request identifier is not associated with any symbol resolution context." - case .indexNotAvailable: - return "An index was not available to complete this request." - case .symbolNotFound(let symbol): - return "Unable to find symbol '\(symbol)' in the index." - case .assetNotFound: - return "The requested asset could not be found." - } - } -}