1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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 BuildServerProtocol
package import BuildSystemIntegration
package import Foundation
package import LanguageServerProtocol
import SKLogging
import SwiftDocC
package struct DocCDocumentationManager: Sendable {
private let doccServer: DocCServer
private let referenceResolutionService: DocCReferenceResolutionService
private let catalogIndexManager: DocCCatalogIndexManager
private let buildSystemManager: BuildSystemManager
package init(buildSystemManager: BuildSystemManager) {
let symbolResolutionServer = DocumentationServer(qualityOfService: .unspecified)
doccServer = DocCServer(
peer: symbolResolutionServer,
qualityOfService: .default
)
catalogIndexManager = DocCCatalogIndexManager(server: doccServer)
referenceResolutionService = DocCReferenceResolutionService()
symbolResolutionServer.register(service: referenceResolutionService)
self.buildSystemManager = buildSystemManager
}
package func filesDidChange(_ events: [FileEvent]) async {
for event in events {
for target in await buildSystemManager.targets(for: event.uri) {
guard let catalogURL = await buildSystemManager.doccCatalog(for: target) else {
continue
}
await catalogIndexManager.invalidate(catalogURL)
}
}
}
package func catalogIndex(for catalogURL: URL) async throws(DocCIndexError) -> DocCCatalogIndex {
try await catalogIndexManager.index(for: catalogURL)
}
/// Generates the SwiftDocC RenderNode for a given symbol, tutorial, or markdown file.
///
/// - Parameters:
/// - symbolUSR: The USR of the symbol to render
/// - symbolGraph: The symbol graph that includes the given symbol USR
/// - overrideDocComments: An array of documentation comment lines that will override the comments in the symbol graph
/// - markupFile: The markdown article or symbol extension to render
/// - tutorialFile: The tutorial file to render
/// - moduleName: The name of the Swift module that will be rendered
/// - catalogURL: The URL pointing to the docc catalog that this symbol, tutorial, or markdown file is a part of
/// - Throws: A ResponseError if something went wrong
/// - Returns: The DoccDocumentationResponse containing the RenderNode if successful
package func renderDocCDocumentation(
symbolUSR: String? = nil,
symbolGraph: String? = nil,
overrideDocComments: [String]? = nil,
markupFile: String? = nil,
tutorialFile: String? = nil,
moduleName: String?,
catalogURL: URL?
) async throws -> DoccDocumentationResponse {
// Make inputs consumable by DocC
var externalIDsToConvert: [String]? = nil
var overridingDocumentationComments: [String: [String]] = [:]
if let symbolUSR {
externalIDsToConvert = [symbolUSR]
if let overrideDocComments {
overridingDocumentationComments[symbolUSR] = overrideDocComments
}
}
var symbolGraphs: [Data] = []
if let symbolGraphData = symbolGraph?.data(using: .utf8) {
symbolGraphs.append(symbolGraphData)
}
var markupFiles: [Data] = []
if let markupFile = markupFile?.data(using: .utf8) {
markupFiles.append(markupFile)
}
var tutorialFiles: [Data] = []
if let tutorialFile = tutorialFile?.data(using: .utf8) {
tutorialFiles.append(tutorialFile)
}
// 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,
documentPathsToConvert: nil,
includeRenderReferenceStore: false,
documentationBundleLocation: nil,
documentationBundleDisplayName: moduleName ?? "Unknown",
documentationBundleIdentifier: "unknown",
symbolGraphs: symbolGraphs,
overridingDocumentationComments: overridingDocumentationComments,
emitSymbolSourceFileURIs: false,
markupFiles: markupFiles,
tutorialFiles: tutorialFiles,
convertRequestIdentifier: convertRequestIdentifier
)
guard let renderNodeData = convertResponse.renderNodes.first else {
throw ResponseError.internalError("SwiftDocC did not return any render nodes")
}
guard let renderNode = String(data: renderNodeData, encoding: .utf8) else {
throw ResponseError.internalError("Failed to encode render node from SwiftDocC")
}
return DoccDocumentationResponse(renderNode: renderNode)
}
}
|