Skip to content

Commit dccf1a0

Browse files
authored
Merge branch 'main' into github-issue
2 parents fd27a07 + cd15ec3 commit dccf1a0

32 files changed

+1057
-189
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- **Explanation**: _A description of the issue being fixed or enhancement being made. This can be brief, but it should be clear._
2+
- **Scope**: _An assessment of the impact/importance of the change. For example, is the change a source-breaking language change, etc._
3+
- **GitHub Issue**: _The issue number if the change fixes/implements an issue/enhancement on [GitHub Issues](https://github.com/apple/swift-docc/issues)._
4+
- **Risk**: _What is the (specific) risk to the release for taking this change?_
5+
- **Testing**: _What specific testing has been done or needs to be done to further validate any impact of this change?_
6+
- **Reviewer**: _One or more code owners for the impacted components should review the change. Technical review can be delegated by a code owner or otherwise requested as deemed appropriate or useful._

.github/pull_request_template.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
<!--
2+
If you're opening a PR to cherry-pick a change for a release branch, use this template instead:
3+
https://github.com/apple/swift-docc/blob/main/.github/PULL_REQUEST_TEMPLATE/CHERRY_PICK.md
4+
-->
5+
16
Bug/issue #, if applicable:
27

38
## Summary

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ DerivedData
2222
docc-dist-build
2323
.docc-build
2424
.swiftpm
25+
.vscode

Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -910,25 +910,32 @@ extension NavigatorIndex {
910910
// Assign the children to the parents, starting with multi curated nodes
911911
var nodesMultiCurated = multiCurated.map { ($0, $1) }
912912

913-
for index in 0..<nodesMultiCurated.count {
914-
let (nodeID, parent) = nodesMultiCurated[index]
915-
let placeholders = identifierToChildren[nodeID]!
916-
for reference in placeholders {
917-
if let child = identifierToNode[reference] {
918-
parent.add(child: child)
919-
pendingUncuratedReferences.remove(reference)
920-
if !multiCurated.keys.contains(reference) && reference.fragment == nil {
921-
// As the children of a multi-curated node is itself curated multiple times
922-
// we need to process it as well, ignoring items with fragments as those are sections.
923-
nodesMultiCurated.append((reference, child))
924-
multiCurated[reference] = child
913+
while !nodesMultiCurated.isEmpty {
914+
// The children of the multicurated nodes. These need to be tracked so we can multicurate them as well.
915+
var nodesMultiCuratedChildren: [(Identifier, NavigatorTree.Node)] = []
916+
917+
for index in 0..<nodesMultiCurated.count {
918+
let (nodeID, parent) = nodesMultiCurated[index]
919+
let placeholders = identifierToChildren[nodeID]!
920+
for reference in placeholders {
921+
if let child = identifierToNode[reference] {
922+
parent.add(child: child)
923+
pendingUncuratedReferences.remove(reference)
924+
if !multiCurated.keys.contains(reference) && reference.fragment == nil {
925+
// As the children of a multi-curated node is itself curated multiple times
926+
// we need to process it as well, ignoring items with fragments as those are sections.
927+
nodesMultiCuratedChildren.append((reference, child))
928+
multiCurated[reference] = child
929+
}
925930
}
926931
}
932+
// Once assigned, placeholders can be removed as we use copy later.
933+
identifierToChildren[nodeID]!.removeAll()
927934
}
928-
// Once assigned, placeholders can be removed as we use copy later.
929-
identifierToChildren[nodeID]!.removeAll()
935+
936+
nodesMultiCurated = nodesMultiCuratedChildren
930937
}
931-
938+
932939
for (nodeIdentifier, placeholders) in identifierToChildren {
933940
for reference in placeholders {
934941
let parent = identifierToNode[nodeIdentifier]!

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
312312
/// - bundle: The bundle that was added.
313313
public func dataProvider(_ dataProvider: DocumentationContextDataProvider, didAddBundle bundle: DocumentationBundle) throws {
314314
try benchmark(wrap: Benchmark.Duration(id: "bundle-registration")) {
315+
// Enable reference caching for this documentation bundle.
316+
ResolvedTopicReference.enableReferenceCaching(for: bundle.identifier)
317+
315318
try self.register(bundle)
316319
}
317320
}
@@ -323,7 +326,11 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
323326
/// - bundle: The bundle that was removed.
324327
public func dataProvider(_ dataProvider: DocumentationContextDataProvider, didRemoveBundle bundle: DocumentationBundle) throws {
325328
referenceCache.sync { $0.removeAll() }
329+
330+
// Purge the reference cache for this bundle and disable reference caching for
331+
// this bundle moving forward.
326332
ResolvedTopicReference.purgePool(for: bundle.identifier)
333+
327334
unregister(bundle)
328335
}
329336

@@ -1561,28 +1568,24 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
15611568
/// - ``SymbolGraphRelationshipsBuilder``
15621569
func buildRelationships(_ relationships: Set<SymbolGraph.Relationship>, bundle: DocumentationBundle, engine: DiagnosticEngine) {
15631570
for edge in relationships {
1564-
// Build conformant type <-> protocol relationships
1565-
if case .conformsTo = edge.kind {
1571+
switch edge.kind {
1572+
case .conformsTo:
1573+
// Build conformant type <-> protocol relationships
15661574
SymbolGraphRelationshipsBuilder.addConformanceRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
1567-
continue
1568-
}
1569-
1570-
// Build implementation <-> protocol requirement relationships.
1571-
if case .defaultImplementationOf = edge.kind {
1575+
case .defaultImplementationOf:
1576+
// Build implementation <-> protocol requirement relationships.
15721577
SymbolGraphRelationshipsBuilder.addImplementationRelationship(edge: edge, in: bundle, context: self, symbolIndex: &symbolIndex, engine: diagnosticEngine)
1573-
continue
1574-
}
1575-
1576-
// Build ancestor <-> offspring relationships.
1577-
if case .inheritsFrom = edge.kind {
1578+
case .inheritsFrom:
1579+
// Build ancestor <-> offspring relationships.
15781580
SymbolGraphRelationshipsBuilder.addInheritanceRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
1579-
continue
1580-
}
1581-
1582-
// Build required member -> protocol relationships.
1583-
if case .requirementOf = edge.kind {
1581+
case .requirementOf:
1582+
// Build required member -> protocol relationships.
15841583
SymbolGraphRelationshipsBuilder.addRequirementRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
1585-
continue
1584+
case .optionalRequirementOf:
1585+
// Build optional required member -> protocol relationships.
1586+
SymbolGraphRelationshipsBuilder.addOptionalRequirementRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: diagnosticEngine)
1587+
default:
1588+
break
15861589
}
15871590
}
15881591
}
@@ -1881,7 +1884,10 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
18811884
let reference = ResolvedTopicReference(
18821885
bundleIdentifier: bundle.identifier,
18831886
path: path,
1884-
sourceLanguages: availableSourceLanguages ?? [.swift]
1887+
sourceLanguages: availableSourceLanguages
1888+
// FIXME: Pages in article-only catalogs should not be inferred as "Swift" as a fallback
1889+
// (github.com/apple/swift-docc/issues/240).
1890+
?? [.swift]
18851891
)
18861892

18871893
let title = article.topicGraphNode.title

Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
311311
}
312312
}
313313
} catch {
314-
results.append(.init(description: error.localizedDescription, source: source))
315-
diagnosticEngine.emit(.init(description: error.localizedDescription, source: source))
314+
recordProblem(from: error, in: &results, withIdentifier: "render-node")
316315
}
317316
}
318317
}
@@ -327,8 +326,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
327326
do {
328327
try outputConsumer.consume(assetsInBundle: bundle)
329328
} catch {
330-
conversionProblems.append(.init(description: error.localizedDescription, source: nil))
331-
diagnosticEngine.emit(.init(description: error.localizedDescription, source: nil))
329+
recordProblem(from: error, in: &conversionProblems, withIdentifier: "assets")
332330
}
333331

334332
// Write various metadata
@@ -338,17 +336,15 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
338336
try outputConsumer.consume(indexingRecords: indexingRecords)
339337
try outputConsumer.consume(assets: assets)
340338
} catch {
341-
conversionProblems.append(.init(description: error.localizedDescription, source: nil))
342-
diagnosticEngine.emit(.init(description: error.localizedDescription, source: nil))
339+
recordProblem(from: error, in: &conversionProblems, withIdentifier: "metadata")
343340
}
344341
}
345342

346343
if emitDigest {
347344
do {
348345
try outputConsumer.consume(problems: context.problems + conversionProblems)
349346
} catch {
350-
conversionProblems.append(.init(description: error.localizedDescription, source: nil))
351-
diagnosticEngine.emit(.init(description: error.localizedDescription, source: nil))
347+
recordProblem(from: error, in: &conversionProblems, withIdentifier: "problems")
352348
}
353349
}
354350

@@ -357,9 +353,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
357353
do {
358354
try outputConsumer.consume(documentationCoverageInfo: coverageInfo)
359355
} catch {
360-
let problem = Problem(description: error.localizedDescription, source: nil)
361-
conversionProblems.append(problem)
362-
diagnosticEngine.emit(problem)
356+
recordProblem(from: error, in: &conversionProblems, withIdentifier: "coverage")
363357
}
364358
case .none:
365359
break
@@ -412,6 +406,34 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
412406
return isDocumentPathToConvert || isExternalIDToConvert
413407
}
414408

409+
/// Record a problem from the given error in the given problem array.
410+
///
411+
/// Creates a ``Problem`` from the given `Error` and identifier, emits it to the
412+
/// ``DocumentationConverter``'s ``DiagnosticEngine``, and appends it to the given
413+
/// problem array.
414+
///
415+
/// - Parameters:
416+
/// - error: The error that describes the problem.
417+
/// - problems: The array that the created problem should be appended to.
418+
/// - identifier: A unique identifier the problem.
419+
private func recordProblem(
420+
from error: Swift.Error,
421+
in problems: inout [Problem],
422+
withIdentifier identifier: String
423+
) {
424+
let singleDiagnostic = Diagnostic(
425+
source: nil,
426+
severity: .error,
427+
range: nil,
428+
identifier: "org.swift.docc.documentation-converter.\(identifier)",
429+
summary: error.localizedDescription
430+
)
431+
let problem = Problem(diagnostic: singleDiagnostic, possibleSolutions: [])
432+
433+
diagnosticEngine.emit(problem)
434+
problems.append(problem)
435+
}
436+
415437
enum Error: DescribedError {
416438
case doesNotContainBundle(url: URL)
417439

@@ -427,21 +449,3 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
427449
}
428450
}
429451
}
430-
431-
private extension Problem {
432-
/// Creates a new problem with the given description and documentation source location.
433-
///
434-
/// Use this to provide a user-friendly description of an error,
435-
/// along with a direct reference to the source file and line number where you call this initializer.
436-
///
437-
/// - Parameters:
438-
/// - description: A brief description of the problem.
439-
/// - source: The URL for the documentation file that caused this problem, if any.
440-
/// - file: The source file where you call this initializer.
441-
init(description: String, source: URL?, file: String = #file) {
442-
let fileName = URL(fileURLWithPath: file).deletingPathExtension().lastPathComponent
443-
444-
let singleDiagnostic = Diagnostic(source: source, severity: .error, range: nil, identifier: "org.swift.docc.\(fileName)", summary: description)
445-
self.init(diagnostic: singleDiagnostic, possibleSolutions: [])
446-
}
447-
}

Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphRelationshipsBuilder.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -232,22 +232,42 @@ struct SymbolGraphRelationshipsBuilder {
232232
}
233233
}
234234

235-
/// Adds a relationship from a type member to a protocol requirement.
235+
/// Adds a required relationship from a type member to a protocol requirement.
236236
/// - Parameters:
237237
/// - edge: A symbol graph relationship with a source and a target.
238238
/// - bundle: A documentation bundle.
239239
/// - symbolIndex: A symbol lookup map by precise identifier.
240240
/// - engine: A diagnostic collecting engine.
241241
static func addRequirementRelationship(edge: SymbolGraph.Relationship, in bundle: DocumentationBundle, symbolIndex: inout [String: DocumentationNode], engine: DiagnosticEngine) {
242+
addProtocolRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: engine, required: true)
243+
}
244+
245+
/// Adds an optional relationship from a type member to a protocol requirement.
246+
/// - Parameters:
247+
/// - edge: A symbol graph relationship with a source and a target.
248+
/// - bundle: A documentation bundle.
249+
/// - symbolIndex: A symbol lookup map by precise identifier.
250+
/// - engine: A diagnostic collecting engine.
251+
static func addOptionalRequirementRelationship(edge: SymbolGraph.Relationship, in bundle: DocumentationBundle, symbolIndex: inout [String: DocumentationNode], engine: DiagnosticEngine) {
252+
addProtocolRelationship(edge: edge, in: bundle, symbolIndex: &symbolIndex, engine: engine, required: false)
253+
}
254+
255+
/// Adds a relationship from a type member to a protocol requirement.
256+
/// - Parameters:
257+
/// - edge: A symbol graph relationship with a source and a target.
258+
/// - bundle: A documentation bundle.
259+
/// - symbolIndex: A symbol lookup map by precise identifier.
260+
/// - engine: A diagnostic collecting engine.
261+
/// - required: A bool value indicating whether the protocol requirement is required or optional
262+
private static func addProtocolRelationship(edge: SymbolGraph.Relationship, in bundle: DocumentationBundle, symbolIndex: inout [String: DocumentationNode], engine: DiagnosticEngine, required: Bool) {
242263
// Resolve source symbol
243264
guard let requiredNode = symbolIndex[edge.source],
244265
let requiredSymbol = requiredNode.semantic as? Symbol else {
245266
// The source node for requirement relationship not found.
246267
engine.emit(NodeProblem.notFound(edge.source))
247268
return
248269
}
249-
250-
requiredSymbol.isRequired = true
270+
requiredSymbol.isRequired = required
251271
}
252272

253273
/// Sets a node in the context as an inherited symbol if the origin symbol is provided in the given relationship.

Sources/SwiftDocC/Model/Identifier.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString
9696
static func purgePool(for bundleIdentifier: String) {
9797
sharedPool.sync { $0.removeValue(forKey: bundleIdentifier) }
9898
}
99+
100+
/// Enables reference caching for any identifiers created with the given bundle identifier.
101+
static func enableReferenceCaching(for bundleIdentifier: ReferenceBundleIdentifier) {
102+
sharedPool.sync { sharedPool in
103+
if !sharedPool.keys.contains(bundleIdentifier) {
104+
sharedPool[bundleIdentifier] = [:]
105+
}
106+
}
107+
}
99108

100109
/// The URL scheme for `doc://` links.
101110
public static let urlScheme = "doc"
@@ -174,7 +183,10 @@ public struct ResolvedTopicReference: Hashable, Codable, Equatable, CustomString
174183
)
175184

176185
// Cache the reference
177-
Self.sharedPool.sync { $0[bundleIdentifier, default: [:]][key] = self }
186+
Self.sharedPool.sync { sharedPool in
187+
// If we have a shared pool for this bundle identifier, cache the reference
188+
sharedPool[bundleIdentifier]?[key] = self
189+
}
178190
}
179191

180192
private static func cacheKey(

0 commit comments

Comments
 (0)