File: ExternalReferenceWalker.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 (232 lines) | stat: -rw-r--r-- 8,631 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
/*
 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 Markdown

fileprivate extension Optional {
    /// If self is not `nil`, run the given block.
    func unwrap(_ block: (Wrapped) -> Void) {
        if let self {
            block(self)
        }
    }
}

/**
 Walks a `Semantic` tree and collects any and all links external to the given bundle.
 
 Visits semantic nodes and descends into all their children that do have (indirectly or directly) content.
 When visiting a node that directly contains markup content visits the markup with an instance of ``ExternalMarkupReferenceWalker``
 which in turn walks the markup tree and collects external links.
 
 Once the visitor has finished visiting the semantic node and the relevant children all
 encountered external links are collected in ``collectedExternalReferences``.
 
 > Warning: This type needs to keep up to date with the semantic objects it walks. When changing the API design
   for types like ``Symbol`` or ``Article``, if the changes include new pieces of content that might contain external links,
   this type needs to be updated to walk those new pieces of content as well.
 */
struct ExternalReferenceWalker: SemanticVisitor {
    typealias Result = Void

    /// A markup walker to use for collecting links from markup elements.
    private var markupResolver: ExternalMarkupReferenceWalker
    
    /// Collected unresolved external references, grouped by the bundle ID.
    var collectedExternalReferences: [BundleIdentifier: [UnresolvedTopicReference]] {
        return markupResolver.collectedExternalLinks.mapValues { links in
            links.map(UnresolvedTopicReference.init(topicURL:))
        }
    }
    
    /// Creates a new semantic walker that collects links to other documentation sources.
    /// - Parameter localBundleID: The local bundle ID, used to identify and skip absolute fully qualified local links.
    init(localBundleID: BundleIdentifier) {
        self.markupResolver = ExternalMarkupReferenceWalker(localBundleID: localBundleID)
    }
    
    mutating func visitCode(_ code: Code) { }
    
    mutating func visitSteps(_ steps: Steps) {
        steps.content.forEach { visit($0) }
    }
    
    mutating func visitStep(_ step: Step) {
        visit(step.content)
        visit(step.caption)
    }
        
    mutating func visitTutorialSection(_ tutorialSection: TutorialSection) {
        visitMarkupLayouts(tutorialSection.introduction)
        tutorialSection.stepsContent.unwrap { visitSteps($0) }
    }
    
    mutating func visitTutorial(_ tutorial: Tutorial) {
        visit(tutorial.intro)
        tutorial.sections.forEach { visit($0) }
        if let assessments = tutorial.assessments {
            visit(assessments)
        }
    }
    
    mutating func visitIntro(_ intro: Intro) {
        visit(intro.content)
    }
    
    mutating func visitXcodeRequirement(_ xcodeRequirement: XcodeRequirement) { }
    
    mutating func visitAssessments(_ assessments: Assessments) {
        assessments.questions.forEach { visit($0) }
    }
    
    mutating func visitMultipleChoice(_ multipleChoice: MultipleChoice) {
        visit(multipleChoice.questionPhrasing)
        visit(multipleChoice.content)
        multipleChoice.choices.forEach { visit($0) }
    }
    
    mutating func visitJustification(_ justification: Justification) {
        visit(justification.content)
    }
    
    mutating func visitChoice(_ choice: Choice) {
        visit(choice.content)
        visit(choice.justification)
    }
    
    mutating func visitMarkupContainer(_ markupContainer: MarkupContainer) {
        markupContainer.elements.forEach { markupResolver.visit($0) }
    }
    
    mutating func visitMarkup(_ markup: Markup) {
        visitMarkupContainer(MarkupContainer(markup))
    }
    
    mutating func visitTechnology(_ technology: Technology) {
        visit(technology.intro)
        technology.volumes.forEach { visit($0) }
        technology.resources.unwrap { visit($0) }
    }
    
    mutating func visitImageMedia(_ imageMedia: ImageMedia) { }
    
    mutating func visitVideoMedia(_ videoMedia: VideoMedia) { }
    
    mutating func visitContentAndMedia(_ contentAndMedia: ContentAndMedia) {
        visit(contentAndMedia.content)
    }
    
    mutating func visitVolume(_ volume: Volume) {
        volume.content.unwrap { visit($0) }
        volume.chapters.forEach { visit($0) }
    }
    
    mutating func visitChapter(_ chapter: Chapter) {
        visit(chapter.content)
        chapter.topicReferences.forEach { visit($0) }
    }
    
    mutating func visitTutorialReference(_ tutorialReference: TutorialReference) { }

    mutating func visitResources(_ resources: Resources) {
        visitMarkupContainer(resources.content)
        resources.tiles.forEach { visitTile($0) }
    }
    
    mutating func visitTile(_ tile: Tile) {
        visitMarkupContainer(tile.content)
    }
    
    mutating func visitTutorialArticle(_ article: TutorialArticle) {
        article.intro.unwrap { visitIntro($0) }
        visitMarkupLayouts(article.content)
        article.assessments.unwrap { visit($0) }
    }
    
    mutating func visitArticle(_ article: Article) {
        article.abstractSection.unwrap { visitMarkup($0.paragraph) }
        article.discussion.unwrap { $0.content.forEach { visitMarkup($0) }}
        article.topics.unwrap { $0.content.forEach { visitMarkup($0) }}
        article.seeAlso.unwrap { $0.content.forEach { visitMarkup($0) }}
        article.deprecationSummary.unwrap { visitMarkupContainer($0) }
    }

    private mutating func visitMarkupLayouts(_ markupLayouts: some Sequence<MarkupLayout>) {
        markupLayouts.forEach { content in
            switch content {
            case .markup(let markup): visitMarkupContainer(markup)
            case .contentAndMedia(let contentAndMedia): visitContentAndMedia(contentAndMedia)
            case .stack(let stack): visitStack(stack)
            }
        }
    }
    
    mutating func visitStack(_ stack: Stack) {
        stack.contentAndMedia.forEach { visitContentAndMedia($0) }
    }

    mutating func visitComment(_ comment: Comment) { }

    mutating func visitSection(_ section: Section) {
        for markup in section.content { visitMarkup(markup) }
    }

    mutating func visitSectionVariants(_ variants: DocumentationDataVariants<some Section>) {
        for variant in variants.allValues.map(\.variant) {
            visitSection(variant)
        }
    }

    mutating func visitSymbol(_ symbol: Symbol) {

        visitSectionVariants(symbol.abstractSectionVariants)
        visitSectionVariants(symbol.discussionVariants)
        visitSectionVariants(symbol.topicsVariants)
        visitSectionVariants(symbol.seeAlsoVariants)
        visitSectionVariants(symbol.returnsSectionVariants)
        visitSectionVariants(symbol.deprecatedSummaryVariants)

        if let parametersSection = symbol.parametersSection {
            for parameter in parametersSection.parameters {
                for markup in parameter.contents { visitMarkup(markup) }
            }
        }

        for dictionaryKeysSection in symbol.dictionaryKeysSectionVariants.allValues.map(\.variant) {
            for dictionaryKeys in dictionaryKeysSection.dictionaryKeys {
                for markup in dictionaryKeys.contents { visitMarkup(markup) }
            }
        }

        for httpParametersSection in symbol.httpParametersSectionVariants.allValues.map(\.variant) {
            for param in httpParametersSection.parameters {
                for markup in param.contents { visitMarkup(markup) }
            }
        }

        for httpResponsesSection in symbol.httpResponsesSectionVariants.allValues.map(\.variant) {
            for param in httpResponsesSection.responses {
                for markup in param.contents { visitMarkup(markup) }
            }
        }

        for httpBodySection in symbol.httpBodySectionVariants.allValues.map(\.variant) {
            for markup in httpBodySection.body.contents { visitMarkup(markup) }
            for parameter in httpBodySection.body.parameters {
                for markup in parameter.contents { visitMarkup(markup) }
            }
        }
    }

    mutating func visitDeprecationSummary(_ summary: DeprecationSummary) {
        visit(summary.content)
    }
}