1
- //===----------------------------------------------------------------------===//
2
- //
3
- // This source file is part of the Swift.org open source project
4
- //
5
- // Copyright (c) 2024 Apple Inc. and the Swift project authors
6
- // Licensed under Apache License v2.0 with Runtime Library Exception
7
- //
8
- // See https://swift.org/LICENSE.txt for license information
9
- // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10
- //
11
- //===----------------------------------------------------------------------===//
12
-
1
+ import BuildServerProtocol
2
+ import BuildSystemIntegration
13
3
package import Foundation
14
- @ preconcurrency package import IndexStoreDB
4
+ package import IndexStoreDB
15
5
package import LanguageServerProtocol
16
6
package import SemanticIndex
7
+ import SwiftDocC
17
8
18
- /// An actor that can be used to interface with SwiftDocC
19
- package protocol DocCDocumentationManager : Actor {
20
- func getRenderingSupport ( ) -> DocCDocumentationManagerWithRendering ?
21
- }
9
+ package struct DocCDocumentationManager : Sendable {
10
+ private let doccServer : DocCServer
11
+ private let referenceResolutionService : DocCReferenceResolutionService
12
+ private let catalogIndexManager : DocCCatalogIndexManager
22
13
23
- /// An actor that can be used to render SwiftDocC documentation
24
- package protocol DocCDocumentationManagerWithRendering : DocCDocumentationManager {
25
- func filesDidChange( _: [ FileEvent ] ) async
14
+ package init ( ) {
15
+ let symbolResolutionServer = DocumentationServer ( qualityOfService: . unspecified)
16
+ doccServer = DocCServer (
17
+ peer: symbolResolutionServer,
18
+ qualityOfService: . default
19
+ )
20
+ catalogIndexManager = DocCCatalogIndexManager ( server: doccServer)
21
+ referenceResolutionService = DocCReferenceResolutionService ( )
22
+ symbolResolutionServer. register ( service: referenceResolutionService)
23
+ }
26
24
27
- func catalogIndex( for: URL ) async throws -> DocCCatalogIndex
25
+ package func filesDidChange( _ events: [ FileEvent ] ) async {
26
+ let affectedCatalogURLs = events. reduce ( into: Set < URL > ( ) ) { affectedCatalogURLs, event in
27
+ guard let catalogURL = event. uri. fileURL? . doccCatalogURL else {
28
+ return
29
+ }
30
+ affectedCatalogURLs. insert ( catalogURL)
31
+ }
32
+ await catalogIndexManager. invalidate ( catalogURLs: affectedCatalogURLs)
33
+ }
28
34
29
- func symbolLink( string: String ) -> DocCSymbolLink ?
35
+ package func catalogIndex( for catalogURL: URL ) async throws ( DocCIndexError) -> DocCCatalogIndex {
36
+ try await catalogIndexManager. index ( for: catalogURL)
37
+ }
30
38
31
- func symbolLink( forUSR: String , in: CheckedIndex ) -> DocCSymbolLink ?
39
+ package func symbolLink( string: String ) -> DocCSymbolLink ? {
40
+ DocCSymbolLink ( string: string)
41
+ }
32
42
33
- func primaryDefinitionOrDeclarationOccurrence(
34
- ofDocCSymbolLink: DocCSymbolLink ,
35
- in: CheckedIndex
36
- ) -> SymbolOccurrence ?
43
+ package func symbolLink( forUSR usr: String , in index: CheckedIndex ) -> DocCSymbolLink ? {
44
+ guard let topLevelSymbolOccurrence = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: usr) else {
45
+ return nil
46
+ }
47
+ let module = topLevelSymbolOccurrence. location. moduleName
48
+ var components = [ topLevelSymbolOccurrence. symbol. name]
49
+ // Find any child symbols
50
+ var symbolOccurrence : SymbolOccurrence ? = topLevelSymbolOccurrence
51
+ while let currentSymbolOccurrence = symbolOccurrence, components. count > 0 {
52
+ let parentRelation = currentSymbolOccurrence. relations. first { $0. roles. contains ( . childOf) }
53
+ guard let parentRelation else {
54
+ break
55
+ }
56
+ if parentRelation. symbol. kind == . extension {
57
+ symbolOccurrence = index. occurrences ( relatedToUSR: parentRelation. symbol. usr, roles: . extendedBy) . first
58
+ } else {
59
+ symbolOccurrence = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: parentRelation. symbol. usr)
60
+ }
61
+ if let symbolOccurrence {
62
+ components. insert ( symbolOccurrence. symbol. name, at: 0 )
63
+ }
64
+ }
65
+ return DocCSymbolLink ( string: module) ? . appending ( components: components)
66
+ }
37
67
38
- func renderDocCDocumentation(
39
- symbolUSR: String ? ,
40
- symbolGraph: String ? ,
41
- overrideDocComments: [ String ] ? ,
42
- markupFile: String ? ,
43
- tutorialFile: String ? ,
44
- moduleName: String ? ,
45
- catalogURL: URL ?
46
- ) async throws -> DoccDocumentationResponse
47
- }
68
+ /// Find a `SymbolOccurrence` that is considered the primary definition of the symbol with the given `DocCSymbolLink`.
69
+ ///
70
+ /// If the `DocCSymbolLink` has an ambiguous definition, the most important role of this function is to deterministically return
71
+ /// the same result every time.
72
+ package func primaryDefinitionOrDeclarationOccurrence(
73
+ ofDocCSymbolLink symbolLink: DocCSymbolLink ,
74
+ in index: CheckedIndex
75
+ ) -> SymbolOccurrence ? {
76
+ var components = symbolLink. components
77
+ guard components. count > 0 else {
78
+ return nil
79
+ }
80
+ // Do a lookup to find the top level symbol
81
+ let topLevelSymbolName = components. removeLast ( ) . name
82
+ var topLevelSymbolOccurrences = [ SymbolOccurrence] ( )
83
+ index. forEachCanonicalSymbolOccurrence ( byName: topLevelSymbolName) { symbolOccurrence in
84
+ guard symbolOccurrence. location. moduleName == symbolLink. moduleName else {
85
+ return true
86
+ }
87
+ topLevelSymbolOccurrences. append ( symbolOccurrence)
88
+ return true
89
+ }
90
+ guard let topLevelSymbolOccurrence = topLevelSymbolOccurrences. first else {
91
+ return nil
92
+ }
93
+ // Find any child symbols
94
+ var symbolOccurrence : SymbolOccurrence ? = topLevelSymbolOccurrence
95
+ while let currentSymbolOccurrence = symbolOccurrence, components. count > 0 {
96
+ let nextComponent = components. removeLast ( )
97
+ let parentRelation = currentSymbolOccurrence. relations. first {
98
+ $0. roles. contains ( . childOf) && $0. symbol. name == nextComponent. name
99
+ }
100
+ guard let parentRelation else {
101
+ break
102
+ }
103
+ if parentRelation. symbol. kind == . extension {
104
+ symbolOccurrence = index. occurrences ( relatedToUSR: parentRelation. symbol. usr, roles: . extendedBy) . first
105
+ } else {
106
+ symbolOccurrence = index. primaryDefinitionOrDeclarationOccurrence ( ofUSR: parentRelation. symbol. usr)
107
+ }
108
+ }
109
+ guard symbolOccurrence != nil else {
110
+ return nil
111
+ }
112
+ return topLevelSymbolOccurrence
113
+ }
48
114
49
- extension DocCDocumentationManagerWithRendering {
50
115
package func renderDocCDocumentation(
51
116
symbolUSR: String ? = nil ,
52
117
symbolGraph: String ? = nil ,
@@ -56,21 +121,61 @@ extension DocCDocumentationManagerWithRendering {
56
121
moduleName: String ? ,
57
122
catalogURL: URL ?
58
123
) async throws -> DoccDocumentationResponse {
59
- try await renderDocCDocumentation (
60
- symbolUSR: symbolUSR,
61
- symbolGraph: symbolGraph,
62
- overrideDocComments: overrideDocComments,
63
- markupFile: markupFile,
64
- tutorialFile: tutorialFile,
65
- moduleName: moduleName,
66
- catalogURL: catalogURL
124
+ // Make inputs consumable by DocC
125
+ var externalIDsToConvert : [ String ] ? = nil
126
+ var overridingDocumentationComments = [ String: [ String] ] ( )
127
+ if let symbolUSR {
128
+ externalIDsToConvert = [ symbolUSR]
129
+ if let overrideDocComments {
130
+ overridingDocumentationComments [ symbolUSR] = overrideDocComments
131
+ }
132
+ }
133
+ var symbolGraphs = [ Data] ( )
134
+ if let symbolGraphData = symbolGraph? . data ( using: . utf8) {
135
+ symbolGraphs. append ( symbolGraphData)
136
+ }
137
+ var markupFiles = [ Data] ( )
138
+ if let markupFile = markupFile? . data ( using: . utf8) {
139
+ markupFiles. append ( markupFile)
140
+ }
141
+ var tutorialFiles = [ Data] ( )
142
+ if let tutorialFile = tutorialFile? . data ( using: . utf8) {
143
+ tutorialFiles. append ( tutorialFile)
144
+ }
145
+ // Store the convert request identifier in order to fulfill index requests from SwiftDocC
146
+ let convertRequestIdentifier = UUID ( ) . uuidString
147
+ var catalogIndex : DocCCatalogIndex ? = nil
148
+ if let catalogURL {
149
+ catalogIndex = try await catalogIndexManager. index ( for: catalogURL)
150
+ }
151
+ referenceResolutionService. addContext (
152
+ DocCReferenceResolutionContext (
153
+ catalogURL: catalogURL,
154
+ catalogIndex: catalogIndex
155
+ ) ,
156
+ withKey: convertRequestIdentifier
67
157
)
158
+ // Send the convert request to SwiftDocC and wait for the response
159
+ let convertResponse = try await doccServer. convert (
160
+ externalIDsToConvert: externalIDsToConvert,
161
+ documentPathsToConvert: nil ,
162
+ includeRenderReferenceStore: false ,
163
+ documentationBundleLocation: nil ,
164
+ documentationBundleDisplayName: moduleName ?? " Unknown " ,
165
+ documentationBundleIdentifier: " unknown " ,
166
+ symbolGraphs: symbolGraphs,
167
+ overridingDocumentationComments: overridingDocumentationComments,
168
+ emitSymbolSourceFileURIs: false ,
169
+ markupFiles: markupFiles,
170
+ tutorialFiles: tutorialFiles,
171
+ convertRequestIdentifier: convertRequestIdentifier
172
+ )
173
+ guard let renderNodeData = convertResponse. renderNodes. first else {
174
+ throw ResponseError . internalError ( " SwiftDocC did not return any render nodes " )
175
+ }
176
+ guard let renderNode = String ( data: renderNodeData, encoding: . utf8) else {
177
+ throw ResponseError . internalError ( " Failed to encode render node from SwiftDocC " )
178
+ }
179
+ return DoccDocumentationResponse ( renderNode: renderNode)
68
180
}
69
181
}
70
-
71
- /// Creates a new ``DocCDocumentationManager`` that can be used to interface with SwiftDocC.
72
- ///
73
- /// - Returns: An instance of ``DocCDocumentationManager``
74
- package func createDocCDocumentationManager( ) -> any DocCDocumentationManager {
75
- DocCDocumentationManagerImpl ( )
76
- }
0 commit comments