Skip to content

Commit 29f830f

Browse files
authored
Treat external content as pre-rendered (#806)
* Treat all externally resolved content as "pre-rendered" rdar://78718811 * Collect external links grouped by bundle ID to avoid need to assert * Use new cache struct for local content as well * Remove configuration that's never used * Remove test resolver that's never used * Remove private types that are never used * Remove unused argument to problem factory method * Fix a performance regression The cause of the regression was accidentally copying the local cache while mutating it. * Fix a bug where external resolved references with updated paths wasn't found * Update test to expect relative URL * Add more documentation for ContentCache struct * Remove reserveSymbolIDCapacity parameters that was always passed true * Remove argument label for `value` parameter * Rename references to allReferences to make it explicit * Document when the resolved reference doesn't match the authored link * More documentation for how the convert service fallback resolver works * More documentation for how the local and external content caches are built up * Update new test to lookup nodes by symbol ID directly in the cache * Consistently use `---` instead of em dashes in documentation comments * Properly test automatic curation for extended symbols
1 parent feff3f7 commit 29f830f

File tree

77 files changed

+1597
-2331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1597
-2331
lines changed

Sources/SwiftDocC/Benchmark/Metrics/ExternalTopicsHash.swift

Lines changed: 6 additions & 8 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-2024 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
@@ -11,19 +11,18 @@
1111
import Foundation
1212

1313
extension Benchmark {
14-
/// A hash metric produced off the externally resolved links and symbols.
14+
/// A hash metric produced off the externally resolved links.
1515
///
16-
/// Use this metric to verify that your code changes
17-
/// did not affect external resolving.
16+
/// Use this metric to verify that your code changes did not affect external link resolution.
1817
public class ExternalTopicsHash: BenchmarkMetric {
1918
public static let identifier = "external-topics-hash"
2019
public static let displayName = "External Topics Checksum"
2120

22-
/// Creates a new metric and stores the checksum of the given documentation context external topics.
23-
/// - Parameter context: A documentation context.
21+
/// Creates a new metric that stores the checksum of the successfully externally resolved links.
22+
/// - Parameter context: A documentation context that the external links were resolved in.
2423
public init(context: DocumentationContext) {
2524
// If there are no externally resolved topics return quickly.
26-
guard !context.externallyResolvedLinks.isEmpty || !context.externallyResolvedSymbols.isEmpty else {
25+
guard !context.externallyResolvedLinks.isEmpty else {
2726
return
2827
}
2928

@@ -37,7 +36,6 @@ extension Benchmark {
3736
return nil
3837
}
3938
}).sorted().joined()
40-
+ context.externallyResolvedSymbols.map({ $0.absoluteString }).sorted().joined()
4139

4240
result = .checksum(Checksum.md5(of: Data(sourceString.utf8)))
4341
}

Sources/SwiftDocC/Catalog Processing/GeneratedCurationWriter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public struct GeneratedCurationWriter {
137137
}
138138

139139
var contentsToWrite = [URL: String]()
140-
for (usr, reference) in context.symbolIndex {
140+
for (usr, reference) in context.documentationCache.referencesBySymbolID {
141141
// Filter out symbols that aren't in the specified sub hierarchy.
142142
if symbolLink != nil || depthLimit != nil {
143143
guard reference == curationCrawlRoot || context.pathsTo(reference).contains(where: { path in path.suffix(depthLimit ?? .max).contains(curationCrawlRoot)}) else {

Sources/SwiftDocC/DocumentationService/Convert/ConvertService.swift

Lines changed: 3 additions & 5 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-2023 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 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
@@ -155,7 +155,6 @@ public struct ConvertService: DocumentationService {
155155

156156
// Enable support for generating documentation for standalone articles and tutorials.
157157
context.allowsRegisteringArticlesWithoutTechnologyRoot = true
158-
context.allowsRegisteringUncuratedTutorials = true
159158
context.considerDocumentationExtensionsThatDoNotMatchSymbolsAsResolved = true
160159

161160
context.configureSymbolGraph = { symbolGraph in
@@ -173,9 +172,8 @@ public struct ConvertService: DocumentationService {
173172
convertRequestIdentifier: messageIdentifier
174173
)
175174

176-
context.fallbackReferenceResolvers[request.bundleInfo.identifier] = resolver
177-
context.fallbackAssetResolvers[request.bundleInfo.identifier] = resolver
178-
context.externalSymbolResolver = resolver
175+
context.convertServiceFallbackResolver = resolver
176+
context.globalExternalSymbolResolver = resolver
179177
}
180178

181179
var converter = try self.converter ?? DocumentationConverter(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Foundation
12+
13+
/// A resolver that attempts to resolve local references to content that wasn't included in the catalog or symbol input.
14+
///
15+
/// The ``ConvertService`` builds documentation for a single page, which doesn't have to be a top-level page. If this page's content contains references to other local
16+
/// symbols, pages, or assets that aren't included in the ``ConvertRequest``, this fallback resolver resolves those references to on-demand fill in the missing local content.
17+
///
18+
/// For example, when building documentation for `someFunction()` that's a member of `SomeClass` in `SomeModule`, the ``ConvertService`` can pass a
19+
/// "partial" symbol graph file that only contains `someFunction()` and its relationships but not `SomeClass` or any other symbols. If `someFunction()` has a local
20+
/// documentation link or symbol link to another symbol or page, DocC won't be able to find the page that the link refers to and will ask the fallback resolver to attempt to resolve it.
21+
///
22+
/// > Note: The ``ConvertService`` only renders the one page that it provided inputs for. Because of this, the content that this fallback resolver returns is considered
23+
/// "external" content, even if it represents pages that would be "local" if the full project was built together.
24+
protocol ConvertServiceFallbackResolver {
25+
26+
// MARK: References
27+
28+
/// Attempts to resolve an unresolved reference for a page that couldn't be resolved locally.
29+
///
30+
/// - Parameter reference: The unresolved local reference.
31+
/// - Returns: The resolved reference, or information about why the resolver failed to resolve the reference.
32+
func resolve(_ reference: TopicReference) -> TopicReferenceResolutionResult
33+
34+
/// Returns an external entity with the documentation content for a local resolved reference if the reference was previously resolved by this resolver.
35+
///
36+
/// - Parameter reference: The local reference that this resolver may have previously resolved.
37+
/// - Returns: An entity with the documentation content for the referenced page or landmark, or `nil` if the reference wasn't previously resolved by this resolver.
38+
func entityIfPreviouslyResolved(with reference: ResolvedTopicReference) -> LinkResolver.ExternalEntity?
39+
40+
// MARK: Assets
41+
42+
/// Attempts to resolve an asset that couldn't be resolved locally.
43+
///
44+
/// - Parameter assetName: The name of the local asset to resolve.
45+
/// - Returns: The local asset with the given name if found; otherwise `nil`.
46+
func resolve(assetNamed assetName: String) -> DataAsset?
47+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
extension DocumentationContext {
12+
/// A cache for symbol and page content.
13+
///
14+
/// The context uses this cache type with different values for both local content (``DocumentationContext/LocalCache``) and external content (``DocumentationContext/ExternalCache``).
15+
///
16+
/// > Note:
17+
/// > The cache is not thread-safe. It's safe to read from the cache concurrently but writing needs to happen with exclusive access. It is the callers responsibility to synchronize write access.
18+
struct ContentCache<Value> {
19+
/// The main storage of cached values.
20+
private(set) var valuesByReference = [ResolvedTopicReference: Value]()
21+
/// A supplementary lookup of references by their symbol ID.
22+
///
23+
/// If a reference is found, ``valuesByReference`` will also have a value for that reference because ``add(_:reference:symbolID:)`` is the only place that writes to this lookup and it always adds the reference-value pair to ``valuesByReference``.
24+
private(set) var referencesBySymbolID = [String: ResolvedTopicReference]()
25+
26+
/// Accesses the value for a given reference.
27+
/// - Parameter reference: The reference to find in the cache.
28+
subscript(reference: ResolvedTopicReference) -> Value? {
29+
// Avoid copying the values if possible
30+
_read { yield valuesByReference[reference] }
31+
_modify { yield &valuesByReference[reference] }
32+
}
33+
34+
/// Adds a value to the cache for a given reference _and_ symbol ID.
35+
/// - Parameters:
36+
/// - value: The value to add to the cache.
37+
/// - reference: The reference associated with that value.
38+
/// - symbolID: The symbol ID associated with that value.
39+
mutating func add(_ value: Value, reference: ResolvedTopicReference, symbolID: String) {
40+
referencesBySymbolID[symbolID] = reference
41+
valuesByReference[reference] = value
42+
}
43+
44+
/// Accesses the reference for a given symbol ID.
45+
/// - Parameter symbolID: The symbol ID to find in the cache.
46+
func reference(symbolID: String) -> ResolvedTopicReference? {
47+
referencesBySymbolID[symbolID]
48+
}
49+
50+
/// Accesses the value for a given symbol ID.
51+
/// - Parameter symbolID: The symbol ID to find in the cache.
52+
subscript(symbolID: String) -> Value? {
53+
// Avoid copying the values if possible
54+
_read { yield referencesBySymbolID[symbolID].map { valuesByReference[$0]! } }
55+
}
56+
57+
/// Reserves enough space to store the specified number of values and symbol IDs.
58+
///
59+
/// If you are adding a known number of values pairs to a cache, use this method to avoid multiple reallocations.
60+
///
61+
/// > Note: The cache reserves the specified capacity for both values and symbol IDs.
62+
///
63+
/// - Parameter minimumCapacity: The requested number of key-value pairs to store.
64+
mutating func reserveCapacity(_ minimumCapacity: Int) {
65+
valuesByReference.reserveCapacity(minimumCapacity)
66+
// The only place that currently calls expects reserve the same capacity for both stored properties.
67+
// This is because symbols are
68+
referencesBySymbolID.reserveCapacity(minimumCapacity)
69+
}
70+
71+
/// Returns a list of all the references in the cache.
72+
var allReferences: [ResolvedTopicReference] {
73+
return Array(valuesByReference.keys)
74+
}
75+
76+
/// Returns a list of all the references in the cache.
77+
var symbolReferences: [ResolvedTopicReference] {
78+
return Array(referencesBySymbolID.values)
79+
}
80+
}
81+
}
82+
83+
// Support iterating over the cached values, checking the number of cached values, and other collection operations.
84+
extension DocumentationContext.ContentCache: Collection {
85+
typealias Wrapped = [ResolvedTopicReference: Value]
86+
typealias Index = Wrapped.Index
87+
typealias Element = Wrapped.Element
88+
89+
func makeIterator() -> Wrapped.Iterator {
90+
valuesByReference.makeIterator()
91+
}
92+
93+
var startIndex: Wrapped.Index {
94+
valuesByReference.startIndex
95+
}
96+
97+
var endIndex: Wrapped.Index {
98+
valuesByReference.endIndex
99+
}
100+
101+
func index(after i: Wrapped.Index) -> Wrapped.Index {
102+
valuesByReference.index(after: i)
103+
}
104+
105+
subscript(position: Wrapped.Index) -> Wrapped.Element {
106+
_read { yield valuesByReference[position] }
107+
}
108+
}

Sources/SwiftDocC/Infrastructure/CoverageDataEntry.swift

Lines changed: 2 additions & 2 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-2023 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 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
@@ -371,7 +371,7 @@ extension CoverageDataEntry {
371371
)
372372
let total = children.count
373373
let documented = children.filter {
374-
(context.nodeWithSymbolIdentifier($0.reference.description)?.semantic as? Symbol)?.abstractSection != nil
374+
(context.documentationCache[$0.reference.description]?.semantic as? Symbol)?.abstractSection != nil
375375
}.count
376376

377377
if total == 0 {

0 commit comments

Comments
 (0)