File: DocCSymbolRepresentable.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 (217 lines) | stat: -rw-r--r-- 8,261 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
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021 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

/// A type that can be converted to a DocC symbol.
public protocol DocCSymbolRepresentable: Equatable {
    /// A namespaced, unique identifier for the kind of symbol.
    ///
    /// For example, a Swift class might use `swift.class`.
    var kindIdentifier: String? { get }
    
    /// A unique identifier for this symbol.
    ///
    /// For Swift, this is the USR.
    var preciseIdentifier: String? { get }
    
    /// The case-sensitive title of this symbol as would be used in documentation.
    ///
    /// > Note: DocC embeds function parameter information directly in the title.
    /// > For example: `functionName(parameterName:secondParameter)`
    /// > or `functionName(_:firstNamedParameter)`.
    var title: String { get }
}

public extension DocCSymbolRepresentable {
    /// The given symbol information as a symbol link component.
    ///
    /// The component will include a disambiguation suffix
    /// based on the included information in the symbol. For example, if the symbol
    /// includes a kind identifier and a precise identifier, both
    /// will be represented in the link component.
    var asLinkComponent: AbsoluteSymbolLink.LinkComponent {
        AbsoluteSymbolLink.LinkComponent(
            name: title,
            disambiguationSuffix: .init(
                kindIdentifier: kindIdentifier,
                preciseIdentifier: preciseIdentifier
            )
        )
    }
}

extension AbsoluteSymbolLink.LinkComponent {
    /// Given an array of symbols that are overloads for the symbol represented
    /// by this link component, returns those that are precisely identified by the component.
    ///
    /// If the link is not specific enough to disambiguate between the given symbols,
    /// this function will return an empty array.
    public func disambiguateBetweenOverloadedSymbols<SymbolType: DocCSymbolRepresentable>(
        _ overloadedSymbols: [SymbolType]
    ) -> [SymbolType] {
        // First confirm that we were given symbols to disambiguate
        guard !overloadedSymbols.isEmpty else {
            return []
        }
        
        // Pair each overloaded symbol with its required disambiguation
        // suffix. This will tell us what kind of disambiguation suffix the
        // link should have.
        let overloadedSymbolsWithSuffixes = zip(
            overloadedSymbols, overloadedSymbols.requiredDisambiguationSuffixes
        )
        
        // Next we filter the given symbols for those that are precise matches
        // for the component.
        let matchingSymbols = overloadedSymbolsWithSuffixes.filter { (symbol, _) in
            // Filter the results by those that are fully represented by the element.
            // This includes checking case sensitivity and disambiguation suffix.
            // This _should_ always return a single element but we can't be entirely sure.
            return fullyRepresentsSymbol(symbol)
        }
        
        // We now check all the returned matching symbols to confirm that
        // the current link has the correct disambiguation suffix
        for (_, (shouldAddIdHash, shouldAddKind)) in matchingSymbols {
            if shouldAddIdHash && shouldAddKind {
                guard case .kindAndPreciseIdentifier = disambiguationSuffix else {
                    return []
                }
            } else if shouldAddIdHash {
                guard case .preciseIdentifierHash = disambiguationSuffix else {
                    return []
                }
            } else if shouldAddKind {
                guard case .kindIdentifier = disambiguationSuffix else {
                    return []
                }
            } else {
                guard case .none = disambiguationSuffix else {
                    return []
                }
            }
        }
        
        // Since we've validated that the link has the correct
        // disambiguation suffix, we now return all matching symbols.
        return matchingSymbols.map(\.0)
    }
    
    /// Returns true if the given symbol is fully represented by the
    /// symbol link.
    ///
    /// This means that the element has the same name (case-sensitive)
    /// and, if the symbol link has a disambiguation suffix, the given element has the same
    /// type or usr.
    private func fullyRepresentsSymbol(
        _ symbol: some DocCSymbolRepresentable
    ) -> Bool {
        guard name == symbol.title else {
            return false
        }
        
        switch self.disambiguationSuffix {
        case .none:
            return true
        case .kindIdentifier(let kindIdentifier):
            return symbol.kindIdentifier == kindIdentifier
        case .preciseIdentifierHash(let preciseIdentifierHash):
            return symbol.preciseIdentifier?.stableHashString == preciseIdentifierHash
        case .kindAndPreciseIdentifier(
            kindIdentifier: let kindIdentifier,
            preciseIdentifierHash: let preciseIdentifierHash):
            return symbol.preciseIdentifier?.stableHashString == preciseIdentifierHash
                && symbol.kindIdentifier == kindIdentifier
        }
    }
}

public extension Collection where Element: DocCSymbolRepresentable {
    /// Given a collection of colliding symbols, returns the disambiguation suffix required
    /// for each symbol to disambiguate it from the others in the collection.
    var requiredDisambiguationSuffixes: [(shouldAddIdHash: Bool, shouldAddKind: Bool)] {
        guard let first else {
            return []
        }
        
        guard count > 1 else {
            // There are no path collisions
            return Array(repeating: (shouldAddIdHash: false, shouldAddKind: false), count: count)
        }
        
        if allSatisfy({ symbol in symbol.kindIdentifier == first.kindIdentifier }) {
            // All collisions are the same symbol kind.
            return Array(repeating: (shouldAddIdHash: true, shouldAddKind: false), count: count)
        } else {
            // Disambiguate by kind
            return map { currentSymbol in
                let kindCount = filter { $0.kindIdentifier == currentSymbol.kindIdentifier }.count
                return (
                    shouldAddIdHash: kindCount > 1,
                    shouldAddKind: kindCount == 1
                )
            }
        }
    }
}

extension SymbolGraph.Symbol: DocCSymbolRepresentable {
    public var preciseIdentifier: String? {
        self.identifier.precise
    }
    
    public var title: String {
        self.names.title
    }
    
    public var kindIdentifier: String? {
        "\(self.identifier.interfaceLanguage).\(self.kind.identifier.identifier)"
    }
    
    public static func == (lhs: SymbolGraph.Symbol, rhs: SymbolGraph.Symbol) -> Bool {
        lhs.identifier.precise == rhs.identifier.precise
    }
}

extension UnifiedSymbolGraph.Symbol: DocCSymbolRepresentable {
    public var preciseIdentifier: String? {
        self.uniqueIdentifier
    }

    public var title: String {
        guard let selector = self.defaultSelector else {
            fatalError("""
                Failed to find a supported default selector. \
                Language unsupported or corrupt symbol graph provided.
                """
            )
        }

        return self.names[selector]!.title
    }

    public var kindIdentifier: String? {
        guard let selector = self.defaultSelector else {
            fatalError("""
                Failed to find a supported default selector. \
                Language unsupported or corrupt symbol graph provided.
                """
            )
        }

        return "\(selector.interfaceLanguage).\(self.kind[selector]!.identifier.identifier)"
    }

    public static func == (lhs: UnifiedSymbolGraph.Symbol, rhs: UnifiedSymbolGraph.Symbol) -> Bool {
        lhs.uniqueIdentifier == rhs.uniqueIdentifier
    }
}