File: GeneratedDocumentationTopics.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (286 lines) | stat: -rw-r--r-- 15,089 bytes parent folder | download
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021-2024 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 Swift project authors
*/

import Foundation
import SymbolKit
import Markdown

/// A collection of APIs to generate documentation topics.
enum GeneratedDocumentationTopics {
    
    /// An index of types and symbols they inherit.
    struct InheritedSymbols {
        var implementingTypes = [ResolvedTopicReference: Collections]()
        
        /// An index of the default implementation providers for a single type.
        struct Collections {
            var inheritedFromTypeName = [String: APICollection]()
            
            /// A collection of symbols a single type inherits from a single provider.
            struct APICollection {
                /// The title of the collection.
                var title: String
                /// A reference to the parent of the collection.
                let parentReference: ResolvedTopicReference
                /// A list of topic references for the collection.
                var identifiers = [ResolvedTopicReference]()
            }
        }
        
        /// Adds a given inherited symbol to the index.
        /// - Parameters:
        ///   - childReference: The inherited symbol reference.
        ///   - reference: The parent type reference.
        ///   - originDisplayName: The origin display name as provided by the symbol graph.
        ///   - extendedModuleName: Extended module name.
        mutating func add(_ childReference: ResolvedTopicReference, to reference: ResolvedTopicReference, childSymbol: SymbolGraph.Symbol, originDisplayName: String, originSymbol: SymbolGraph.Symbol?, extendedModuleName: String) throws {
            let fromType: String
            let typeSimpleName: String
            if let originSymbol, originSymbol.pathComponents.count > 1 {
                // If we have a resolved symbol for the source origin, use its path components to
                // find the name of the parent by dropping the last path component.
                let parentSymbolPathComponents = originSymbol.pathComponents.dropLast()
                fromType = parentSymbolPathComponents.joined(separator: ".")
                typeSimpleName = parentSymbolPathComponents.last!
            } else if let childSymbolName = childSymbol.pathComponents.last,
                originDisplayName.count > (childSymbolName.count + 1)
            {
                // In the case where we don't have a resolved symbol for the source origin,
                // this allows us to still accurately handle cases like this:
                //
                //     "displayName": "SuperFancyProtocol..<..(_:_:)"
                //
                // Where there's no way for us to determine which of the periods is the one
                // splitting the name of the parent type and the symbol name. Using the count
                // of the symbol name (+1 for the period splitting the names)
                // from the source is a reliable way to support this.
                
                let parentSymbolName = originDisplayName.dropLast(childSymbolName.count + 1)
                fromType = String(parentSymbolName)
                typeSimpleName = String(parentSymbolName.split(separator: ".").last ?? parentSymbolName)
            } else {
                // This should never happen but is a last safeguard for the case where
                // the child symbol is unexpectedly short. In this case, we can attempt to just parse
                // the parent symbol name out of the origin display name.

                // Detect the path components of the providing the default implementation.
                let typeComponents = originDisplayName.split(separator: ".")

                // Verify that the fully qualified name contains at least a type name and default implementation name.
                guard typeComponents.count >= 2 else { return }

                // Get the fully qualified type.
                fromType = typeComponents.dropLast().joined(separator: ".")
                // The name of the type is second to last.
                typeSimpleName = String(typeComponents[typeComponents.count-2])
            }

            // Create a type with inherited symbols, if needed.
            if !implementingTypes.keys.contains(reference) {
                implementingTypes[reference] = Collections()
            }
            
            // Create a new default implementations provider, if needed.
            if !implementingTypes[reference]!.inheritedFromTypeName.keys.contains(fromType) {
                implementingTypes[reference]!.inheritedFromTypeName[fromType] = Collections.APICollection(title: "\(typeSimpleName) Implementations", parentReference: reference)
            }
            
            // Add the default implementation.
            implementingTypes[reference]!.inheritedFromTypeName[fromType]!.identifiers.append(childReference)
        }
    }
    
    private static let defaultImplementationGroupTitle = "Default Implementations"
    
    private static func createCollectionNode(parent: ResolvedTopicReference, title: String, identifiers: [ResolvedTopicReference], context: DocumentationContext, bundle: DocumentationBundle) throws {
        let automaticCurationSourceLanguage: SourceLanguage
        let automaticCurationSourceLanguages: Set<SourceLanguage>
        automaticCurationSourceLanguage = identifiers.first?.sourceLanguage ?? .swift
        automaticCurationSourceLanguages = Set(identifiers.flatMap { identifier in context.sourceLanguages(for: identifier) })
        
        // Create the collection topic reference
        let collectionReference = ResolvedTopicReference(
            bundleIdentifier: bundle.identifier,
            path: NodeURLGenerator.Path.documentationCuration(
                parentPath: parent.path,
                articleName: title
            ).stringValue,
            sourceLanguages: automaticCurationSourceLanguages
        )
        
        // Add the topic graph node
        let collectionTopicGraphNode = TopicGraph.Node(reference: collectionReference, kind: .collection, source: .external, title: title, isResolvable: false)
        context.topicGraph.addNode(collectionTopicGraphNode)

        // Curate the collection task group under the collection parent type
        
        let node = try context.entity(with: parent)
        if let symbol = node.semantic as? Symbol {
            for trait in node.availableVariantTraits {
                guard let language = trait.interfaceLanguage,
                      automaticCurationSourceLanguages.lazy.map(\.id).contains(language)
                else {
                    // If the collection is not available in this trait, don't curate it in this symbol's variant.
                    continue
                }
                if let matchIndex = symbol.automaticTaskGroupsVariants[trait]?.firstIndex(where: { $0.title == defaultImplementationGroupTitle }) {
                    // Update the existing group
                    var inheritedSection = symbol.automaticTaskGroupsVariants[trait]![matchIndex]
                    inheritedSection.references.append(collectionReference)
                    inheritedSection.references.sort(by: \.lastPathComponent)
                    symbol.automaticTaskGroupsVariants[trait]?[matchIndex] = inheritedSection
                } else {
                    // Add a new group
                    let inheritedSection = AutomaticTaskGroupSection(title: defaultImplementationGroupTitle, references: [collectionReference], renderPositionPreference: .bottom)
                    symbol.automaticTaskGroupsVariants[trait]?.append(inheritedSection)
                }
                context.linkResolver.localResolver.addTaskGroup(named: title, reference: collectionReference, to: parent)
            }
        } else {
            fatalError("createCollectionNode() should be used only to add nodes under symbols.")
        }
        
        // Curate all inherited symbols under the collection node
        for childReference in identifiers {
            if let childTopicGraphNode = context.topicGraph.nodeWithReference(childReference) {
                context.topicGraph.addEdge(from: collectionTopicGraphNode, to: childTopicGraphNode)
            }
        }

        // Create an article to provide content for the node
        var collectionArticle: Article
        
        // Find matching doc extension or create an empty article.
        if let docExtensionMatch = context.uncuratedDocumentationExtensions[collectionReference]?.value {
            collectionArticle = docExtensionMatch
            collectionArticle.title = Heading(level: 1, Text(title))
            context.uncuratedDocumentationExtensions.removeValue(forKey: collectionReference)
        } else {
            collectionArticle = Article(
                title: Heading(level: 1, Text(title)),
                abstractSection: nil,
                discussion: nil,
                topics: nil,
                seeAlso: nil,
                deprecationSummary: nil,
                metadata: nil,
                redirects: nil
            )
        }

        // Create a temp node in order to generate the automatic curation
        let temporaryCollectionNode = DocumentationNode(
            reference: collectionReference,
            kind: .collectionGroup,
            sourceLanguage: automaticCurationSourceLanguage,
            availableSourceLanguages: automaticCurationSourceLanguages,
            name: DocumentationNode.Name.conceptual(title: title),
            markup: Document(parsing: ""),
            semantic: Article(markup: nil, metadata: nil, redirects: nil, options: [:])
        )
        
        let collectionTaskGroups = try AutomaticCuration.topics(for: temporaryCollectionNode, withTraits: [], context: context)
            .map { taskGroup in
                AutomaticTaskGroupSection(
                    // Force-unwrapping the title since automatically-generated task groups always have a title.
                    title: taskGroup.title!,
                    references: taskGroup.references,
                    renderPositionPreference: .bottom
                )
            }

        // Add the curation task groups to the article
        collectionArticle.automaticTaskGroups = collectionTaskGroups
        
        // Create the documentation node
        let collectionNode = DocumentationNode(
            reference: collectionReference,
            kind: .collectionGroup,
            sourceLanguage: automaticCurationSourceLanguage,
            availableSourceLanguages: automaticCurationSourceLanguages,
            name: DocumentationNode.Name.conceptual(title: title),
            markup: Document(parsing: ""),
            semantic: collectionArticle
        )

        // Curate the collection node
        context.topicGraph.addEdge(from: context.topicGraph.nodeWithReference(parent)!, to: collectionTopicGraphNode)
        context.documentationCache[collectionReference] = collectionNode

        // Add the node anchors to the context
        for anchor in collectionNode.anchorSections {
            context.nodeAnchorSections[anchor.reference] = anchor
        }
    }
    
    /// Creates an API collection in the given documentation context for all inherited symbols according to the symbol graph.
    ///
    /// Inspects the given symbol relationships and extracts all inherited symbols into a separate level in the documentation hierarchy -
    /// an API collection called "Inherited APIs" where all inherited symbols are listed unless they are manually curated in
    /// a documentation extension.
    ///
    /// ```
    /// MyKit
    /// ╰ MyView
    ///   ╰ View Implementations
    ///     ╰ accessibilityValue()
    /// ```
    ///
    /// > Warning: This method tracks internal state via an ``InheritedSymbols`` inheritance index.
    /// It must be called **once** per symbol by passing in _all_ of the relationships that apply
    /// to a symbol.
    ///
    /// - Parameters:
    ///   - relationships: A set of relationships to inspect.
    ///   - symbolsURLHierarchy: A symbol graph hierarchy as created during symbol registration.
    ///   - context: A documentation context to update.
    ///   - bundle: The current documentation bundle.
    static func createInheritedSymbolsAPICollections(relationships: [SymbolGraph.Relationship], context: DocumentationContext, bundle: DocumentationBundle) throws {
        var inheritanceIndex = InheritedSymbols()
        
        // Walk the symbol graph relationships and look for parent <-> child links that stem in a different module.
        for relationship in relationships {
            
            // Check the relationship type
            if relationship.kind == .memberOf,
               // Check that there is origin information (i.e. the symbol is inherited)
               let origin = relationship[mixin: SymbolGraph.Relationship.SourceOrigin.self],
               // Resolve the containing type
               let parent = context.documentationCache[relationship.target],
               // Resolve the child
               let child = context.documentationCache[relationship.source],
               // Get the child symbol
               let childSymbol = child.symbol,
               // Get the swift extension data
               let extends = childSymbol[mixin: SymbolGraph.Symbol.Swift.Extension.self]
            {
                let originSymbol = context.documentationCache[origin.identifier]?.symbol
                
                // Add the inherited symbol to the index.
                try inheritanceIndex.add(child.reference, to: parent.reference, childSymbol: childSymbol, originDisplayName: origin.displayName, originSymbol: originSymbol, extendedModuleName: extends.extendedModule)
            }
        }
        
        // Create the API Collection nodes and the necessary topic graph curation.
        for (typeReference, collections) in inheritanceIndex.implementingTypes where !collections.inheritedFromTypeName.isEmpty {
            for (_, collection) in collections.inheritedFromTypeName where !collection.identifiers.isEmpty {
                // Create a collection for the given provider type's inherited symbols
                try createCollectionNode(parent: typeReference, title: collection.title, identifiers: collection.identifiers, context: context, bundle: bundle)
            }
        }
    }
    
    static func isInheritedSymbolsAPICollectionNode(_ reference: ResolvedTopicReference, in topicGraph: TopicGraph) -> Bool {
        guard let node = topicGraph.nodeWithReference(reference) else { return false }
        return !node.isResolvable
            && node.kind == .collection
            && node.source == .external
    }
}