Skip to content

Commit 1ca1441

Browse files
fix some review comments
1 parent 0adaf25 commit 1ca1441

File tree

8 files changed

+181
-202
lines changed

8 files changed

+181
-202
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ var targets: [Target] = [
223223
"BuildSystemIntegration",
224224
"LanguageServerProtocol",
225225
"SemanticIndex",
226+
"SKLogging",
226227
"SwiftExtensions",
227228
.product(name: "IndexStoreDB", package: "indexstore-db"),
228229
.product(name: "SwiftDocC", package: "swift-docc"),

Sources/DocCDocumentation/BuildSystemIntegrationExtensions.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import BuildServerProtocol
13+
package import BuildServerProtocol
1414
package import BuildSystemIntegration
1515
package import Foundation
16-
package import LanguageServerProtocol
16+
import LanguageServerProtocol
1717

1818
package extension BuildSystemManager {
19-
func moduleName(for document: DocumentURI) async -> String? {
20-
guard let target = await canonicalTarget(for: document) else {
21-
return nil
22-
}
19+
/// Retrieves the name of the Swift module for a given target.
20+
///
21+
/// - Parameter target: The build target identifier
22+
/// - Returns: The name of the Swift module or nil if it could not be determined
23+
func moduleName(for target: BuildTargetIdentifier) async -> String? {
2324
let sourceFiles = (try? await sourceFiles(in: [target]).flatMap(\.sources)) ?? []
2425
for sourceFile in sourceFiles {
2526
let language = await defaultLanguage(for: sourceFile.uri, in: target)
@@ -33,10 +34,11 @@ package extension BuildSystemManager {
3334
return nil
3435
}
3536

36-
func doccCatalog(for document: DocumentURI) async -> URL? {
37-
guard let target = await canonicalTarget(for: document) else {
38-
return nil
39-
}
37+
/// Finds the SwiftDocC documentation catalog associated with a target, if any.
38+
///
39+
/// - Parameter target: The build target identifier
40+
/// - Returns: The URL of the documentation catalog or nil if one could not be found
41+
func doccCatalog(for target: BuildTargetIdentifier) async -> URL? {
4042
let sourceFiles = (try? await sourceFiles(in: [target]).flatMap(\.sources)) ?? []
4143
let catalogURLs = sourceFiles.compactMap { sourceItem -> URL? in
4244
guard sourceItem.dataKind == .sourceKit,

Sources/DocCDocumentation/DocCCatalogIndexManager.swift

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,20 @@ package import Foundation
1515

1616
final actor DocCCatalogIndexManager {
1717
private let server: DocCServer
18-
private var catalogToIndexMap = [URL: Result<DocCCatalogIndex, DocCIndexError>]()
18+
private var catalogToIndexMap: [URL: Result<DocCCatalogIndex, DocCIndexError>] = [:]
1919

2020
init(server: DocCServer) {
2121
self.server = server
2222
}
2323

24-
func invalidate(catalogURLs: some Collection<URL>) {
25-
guard catalogURLs.count > 0 else {
26-
return
27-
}
28-
for catalogURL in catalogURLs {
29-
catalogToIndexMap.removeValue(forKey: catalogURL)
30-
}
24+
func invalidate(_ url: URL) {
25+
catalogToIndexMap.removeValue(forKey: url)
3126
}
3227

3328
func index(for catalogURL: URL) async throws(DocCIndexError) -> DocCCatalogIndex {
3429
if let existingCatalog = catalogToIndexMap[catalogURL] {
3530
return try existingCatalog.get()
3631
}
37-
let catalogIndexResult: Result<DocCCatalogIndex, DocCIndexError>
3832
do {
3933
let convertResponse = try await server.convert(
4034
externalIDsToConvert: [],
@@ -49,36 +43,28 @@ final actor DocCCatalogIndexManager {
4943
tutorialFiles: [],
5044
convertRequestIdentifier: UUID().uuidString
5145
)
52-
catalogIndexResult = Result { convertResponse }
53-
.flatMap { convertResponse in
54-
guard let renderReferenceStoreData = convertResponse.renderReferenceStore else {
55-
return .failure(.unexpectedlyNilRenderReferenceStore)
56-
}
57-
return .success(renderReferenceStoreData)
58-
}
59-
.flatMap { renderReferenceStoreData in
60-
Result { try JSONDecoder().decode(RenderReferenceStore.self, from: renderReferenceStoreData) }
61-
.flatMapError { .failure(.decodingFailure($0)) }
62-
}
63-
.map { DocCCatalogIndex(from: $0) }
46+
guard let renderReferenceStoreData = convertResponse.renderReferenceStore else {
47+
throw DocCIndexError.unexpectedlyNilRenderReferenceStore
48+
}
49+
let renderReferenceStore = try JSONDecoder().decode(RenderReferenceStore.self, from: renderReferenceStoreData)
50+
let catalogIndex = DocCCatalogIndex(from: renderReferenceStore)
51+
catalogToIndexMap[catalogURL] = .success(catalogIndex)
52+
return catalogIndex
6453
} catch {
65-
catalogIndexResult = .failure(.internalError(error))
54+
let internalError = error as? DocCIndexError ?? DocCIndexError.internalError(error)
55+
catalogToIndexMap[catalogURL] = .failure(internalError)
56+
throw internalError
6657
}
67-
catalogToIndexMap[catalogURL] = catalogIndexResult
68-
return try catalogIndexResult.get()
6958
}
7059
}
7160

7261
/// Represents a potential error that the ``DocCCatalogIndexManager`` could encounter while indexing
7362
package enum DocCIndexError: LocalizedError {
74-
case decodingFailure(Error)
75-
case internalError(LocalizedError)
63+
case internalError(any Error)
7664
case unexpectedlyNilRenderReferenceStore
7765

7866
package var errorDescription: String? {
7967
switch self {
80-
case .decodingFailure(let decodingError):
81-
return "Failed to decode a received message: \(decodingError.localizedDescription)"
8268
case .internalError(let internalError):
8369
return "An internal error occurred: \(internalError.localizedDescription)"
8470
case .unexpectedlyNilRenderReferenceStore:
@@ -89,14 +75,13 @@ package enum DocCIndexError: LocalizedError {
8975

9076
package struct DocCCatalogIndex: Sendable {
9177
private let assetReferenceToDataAsset: [String: DataAsset]
92-
private let fuzzyAssetReferenceToDataAsset: [String: DataAsset]
9378
private let documentationExtensionToSourceURL: [DocCSymbolLink: URL]
9479
let articlePathToSourceURLAndReference: [String: (URL, TopicRenderReference)]
9580
let tutorialPathToSourceURLAndReference: [String: (URL, TopicRenderReference)]
9681
let tutorialOverviewPathToSourceURLAndReference: [String: (URL, TopicRenderReference)]
9782

9883
func asset(for assetReference: AssetReference) -> DataAsset? {
99-
assetReferenceToDataAsset[assetReference.assetName] ?? fuzzyAssetReferenceToDataAsset[assetReference.assetName]
84+
assetReferenceToDataAsset[assetReference.assetName]
10085
}
10186

10287
package func documentationExtension(for symbolLink: DocCSymbolLink) -> URL? {
@@ -105,21 +90,15 @@ package struct DocCCatalogIndex: Sendable {
10590

10691
init(from renderReferenceStore: RenderReferenceStore) {
10792
// Assets
108-
var assetReferenceToDataAsset = [String: DataAsset]()
109-
var fuzzyAssetReferenceToDataAsset = [String: DataAsset]()
93+
var assetReferenceToDataAsset: [String: DataAsset] = [:]
11094
for (reference, asset) in renderReferenceStore.assets {
11195
var asset = asset
11296
asset.variants = asset.variants.compactMapValues { $0.withScheme("doc-asset") }
11397
assetReferenceToDataAsset[reference.assetName] = asset
114-
if let indexOfExtensionDelimiter = reference.assetName.lastIndex(of: ".") {
115-
let assetNameWithoutExtension = reference.assetName.prefix(upTo: indexOfExtensionDelimiter)
116-
fuzzyAssetReferenceToDataAsset[String(assetNameWithoutExtension)] = asset
117-
}
11898
}
11999
self.assetReferenceToDataAsset = assetReferenceToDataAsset
120-
self.fuzzyAssetReferenceToDataAsset = fuzzyAssetReferenceToDataAsset
121100
// Markdown and Tutorial content
122-
var documentationExtensionToSourceURL = [DocCSymbolLink: URL]()
101+
var documentationExtensionToSourceURL: [DocCSymbolLink: URL] = [:]
123102
var articlePathToSourceURLAndReference = [String: (URL, TopicRenderReference)]()
124103
var tutorialPathToSourceURLAndReference = [String: (URL, TopicRenderReference)]()
125104
var tutorialOverviewPathToSourceURLAndReference = [String: (URL, TopicRenderReference)]()
@@ -129,27 +108,23 @@ package struct DocCCatalogIndex: Sendable {
129108
else {
130109
continue
131110
}
111+
let lastPathComponent = renderReferenceKey.url.lastPathComponent
132112

133-
if topicContentValue.isDocumentationExtensionContent {
134-
guard
113+
switch topicRenderReference.kind {
114+
case .article:
115+
articlePathToSourceURLAndReference[lastPathComponent] = (topicContentSource, topicRenderReference)
116+
case .tutorial:
117+
tutorialPathToSourceURLAndReference[lastPathComponent] = (topicContentSource, topicRenderReference)
118+
case .overview:
119+
tutorialOverviewPathToSourceURLAndReference[lastPathComponent] = (topicContentSource, topicRenderReference)
120+
default:
121+
guard topicContentValue.isDocumentationExtensionContent,
135122
let absoluteSymbolLink = AbsoluteSymbolLink(string: topicContentValue.renderReference.identifier.identifier)
136123
else {
137124
continue
138125
}
139126
let doccSymbolLink = DocCSymbolLink(absoluteSymbolLink: absoluteSymbolLink)
140127
documentationExtensionToSourceURL[doccSymbolLink] = topicContentValue.source
141-
} else if topicRenderReference.kind == .article {
142-
articlePathToSourceURLAndReference[renderReferenceKey.url.lastPathComponent] = (
143-
topicContentSource, topicRenderReference
144-
)
145-
} else if topicRenderReference.kind == .tutorial {
146-
tutorialPathToSourceURLAndReference[renderReferenceKey.url.lastPathComponent] = (
147-
topicContentSource, topicRenderReference
148-
)
149-
} else if topicRenderReference.kind == .overview {
150-
tutorialOverviewPathToSourceURLAndReference[renderReferenceKey.url.lastPathComponent] = (
151-
topicContentSource, topicRenderReference
152-
)
153128
}
154129
}
155130
self.documentationExtensionToSourceURL = documentationExtensionToSourceURL

Sources/DocCDocumentation/DocCDocumentationManager.swift

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package import BuildSystemIntegration
33
package import Foundation
44
package import IndexStoreDB
55
package import LanguageServerProtocol
6+
import SKLogging
67
package import SemanticIndex
78
import SwiftDocC
89

@@ -27,7 +28,9 @@ package struct DocCDocumentationManager: Sendable {
2728

2829
package func filesDidChange(_ events: [FileEvent]) async {
2930
for event in events {
30-
guard let catalogURL = await buildSystemManager.doccCatalog(for: event.uri) else {
31+
guard let target = await buildSystemManager.canonicalTarget(for: event.uri),
32+
let catalogURL = await buildSystemManager.doccCatalog(for: target)
33+
else {
3134
continue
3235
}
3336
await catalogIndexManager.invalidate(catalogURL)
@@ -42,27 +45,38 @@ package struct DocCDocumentationManager: Sendable {
4245
DocCSymbolLink(string: string)
4346
}
4447

48+
private func parentSymbol(of symbol: SymbolOccurrence, in index: CheckedIndex) -> SymbolOccurrence? {
49+
let allParentRelations = symbol.relations
50+
.filter { $0.roles.contains(.childOf) }
51+
.sorted()
52+
if allParentRelations.count > 1 {
53+
logger.debug("Symbol \(symbol.symbol.usr) has multiple parent symbols")
54+
}
55+
guard let parentRelation = allParentRelations.first else {
56+
return nil
57+
}
58+
if parentRelation.symbol.kind == .extension {
59+
let allSymbolOccurrences = index.occurrences(relatedToUSR: parentRelation.symbol.usr, roles: .extendedBy)
60+
.sorted()
61+
if allSymbolOccurrences.count > 1 {
62+
logger.debug("Extension \(parentRelation.symbol.usr) extends multiple symbols")
63+
}
64+
return allSymbolOccurrences.first
65+
}
66+
return index.primaryDefinitionOrDeclarationOccurrence(ofUSR: parentRelation.symbol.usr)
67+
}
68+
4569
package func symbolLink(forUSR usr: String, in index: CheckedIndex) -> DocCSymbolLink? {
4670
guard let topLevelSymbolOccurrence = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
4771
return nil
4872
}
4973
let module = topLevelSymbolOccurrence.location.moduleName
5074
var components = [topLevelSymbolOccurrence.symbol.name]
51-
// Find any child symbols
52-
var symbolOccurrence: SymbolOccurrence? = topLevelSymbolOccurrence
53-
while let currentSymbolOccurrence = symbolOccurrence, components.count > 0 {
54-
let parentRelation = currentSymbolOccurrence.relations.first { $0.roles.contains(.childOf) }
55-
guard let parentRelation else {
56-
break
57-
}
58-
if parentRelation.symbol.kind == .extension {
59-
symbolOccurrence = index.occurrences(relatedToUSR: parentRelation.symbol.usr, roles: .extendedBy).first
60-
} else {
61-
symbolOccurrence = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: parentRelation.symbol.usr)
62-
}
63-
if let symbolOccurrence {
64-
components.insert(symbolOccurrence.symbol.name, at: 0)
65-
}
75+
// Find any parent symbols
76+
var symbolOccurrence: SymbolOccurrence = topLevelSymbolOccurrence
77+
while let parentSymbolOccurrence = parentSymbol(of: symbolOccurrence, in: index) {
78+
components.insert(parentSymbolOccurrence.symbol.name, at: 0)
79+
symbolOccurrence = parentSymbolOccurrence
6680
}
6781
return DocCSymbolLink(string: module)?.appending(components: components)
6882
}
@@ -81,39 +95,47 @@ package struct DocCDocumentationManager: Sendable {
8195
}
8296
// Do a lookup to find the top level symbol
8397
let topLevelSymbolName = components.removeLast().name
84-
var topLevelSymbolOccurrences = [SymbolOccurrence]()
98+
var topLevelSymbolOccurrences: [SymbolOccurrence] = []
8599
index.forEachCanonicalSymbolOccurrence(byName: topLevelSymbolName) { symbolOccurrence in
86100
guard symbolOccurrence.location.moduleName == symbolLink.moduleName else {
87-
return true
101+
return true // continue
88102
}
89103
topLevelSymbolOccurrences.append(symbolOccurrence)
90-
return true
104+
return true // continue
91105
}
92-
guard let topLevelSymbolOccurrence = topLevelSymbolOccurrences.first else {
93-
return nil
94-
}
95-
// Find any child symbols
96-
var symbolOccurrence: SymbolOccurrence? = topLevelSymbolOccurrence
97-
while let currentSymbolOccurrence = symbolOccurrence, components.count > 0 {
98-
let nextComponent = components.removeLast()
99-
let parentRelation = currentSymbolOccurrence.relations.first {
100-
$0.roles.contains(.childOf) && $0.symbol.name == nextComponent.name
101-
}
102-
guard let parentRelation else {
103-
break
106+
// Search each potential symbol's parents to find an exact match
107+
let symbolOccurences = topLevelSymbolOccurrences.filter { topLevelSymbolOccurrence in
108+
var components = components
109+
var symbolOccurrence = topLevelSymbolOccurrence
110+
while let parentSymbolOccurrence = parentSymbol(of: symbolOccurrence, in: index), !components.isEmpty {
111+
let nextComponent = components.removeLast()
112+
guard parentSymbolOccurrence.symbol.name == nextComponent.name else {
113+
return false
114+
}
115+
symbolOccurrence = parentSymbolOccurrence
104116
}
105-
if parentRelation.symbol.kind == .extension {
106-
symbolOccurrence = index.occurrences(relatedToUSR: parentRelation.symbol.usr, roles: .extendedBy).first
107-
} else {
108-
symbolOccurrence = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: parentRelation.symbol.usr)
117+
guard components.isEmpty else {
118+
return false
109119
}
120+
return true
121+
}.sorted()
122+
if symbolOccurences.count > 1 {
123+
logger.debug("Multiple symbols found for DocC symbol link '\(symbolLink.absoluteString)'")
110124
}
111-
guard symbolOccurrence != nil else {
112-
return nil
113-
}
114-
return topLevelSymbolOccurrence
125+
return symbolOccurences.first
115126
}
116127

128+
/// Generates the SwiftDocC RenderNode for a given symbol, tutorial, or markdown file.
129+
///
130+
/// - Parameters:
131+
/// - symbolUSR: The USR of the symbol to render
132+
/// - symbolGraph: The symbol graph that includes the given symbol USR
133+
/// - markupFile: The markdown article or symbol extension to render
134+
/// - tutorialFile: The tutorial file to render
135+
/// - moduleName: The name of the Swift module that will be rendered
136+
/// - catalogURL: The URL pointing to the docc catalog that this symbol, tutorial, or markdown file is a part of
137+
/// - Throws: A ResponseError if something went wrong
138+
/// - Returns: The DoccDocumentationResponse containing the RenderNode if successful
117139
package func renderDocCDocumentation(
118140
symbolUSR: String? = nil,
119141
symbolGraph: String? = nil,
@@ -132,15 +154,15 @@ package struct DocCDocumentationManager: Sendable {
132154
overridingDocumentationComments[symbolUSR] = overrideDocComments
133155
}
134156
}
135-
var symbolGraphs = [Data]()
157+
var symbolGraphs: [Data] = []
136158
if let symbolGraphData = symbolGraph?.data(using: .utf8) {
137159
symbolGraphs.append(symbolGraphData)
138160
}
139-
var markupFiles = [Data]()
161+
var markupFiles: [Data] = []
140162
if let markupFile = markupFile?.data(using: .utf8) {
141163
markupFiles.append(markupFile)
142164
}
143-
var tutorialFiles = [Data]()
165+
var tutorialFiles: [Data] = []
144166
if let tutorialFile = tutorialFile?.data(using: .utf8) {
145167
tutorialFiles.append(tutorialFile)
146168
}

0 commit comments

Comments
 (0)