-
Notifications
You must be signed in to change notification settings - Fork 37
/
Symbol.swift
393 lines (338 loc) · 19.4 KB
/
Symbol.swift
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/*
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
extension SymbolGraph {
/**
A symbol from a module.
A `Symbol` corresponds to some named declaration in a module.
For example, a class is a `Symbol` in the graph.
A `Symbol` should never contain another `Symbol` as a field or part of a field.
If a symbol is related to another symbol, it should be formalized
as a `Relationship` in an `Edge` if possible (it usually is).
Symbols may have information that is specific to its kind, but all symbols
must contain at least the following information in the `Symbol` interface.
In addition, various attributes of the symbol should be mixed in with
symbol *mix-ins*, some of which are defined below.
The consumer of a symbol graph should be able to dynamically handle or ignore
additional attributes in a `Symbol`.
*/
public struct Symbol: Codable {
/// The unique identifier for the symbol.
public var identifier: Identifier
/// The kind of symbol.
public var kind: Kind
/**
A short convenience path that uniquely identifies a symbol when there are no ambiguities using only URL-compatible characters. Do not include the module name here.
For example, in a Swift module `MyModule`, there might exist a function `bar` in `struct Foo`.
The `simpleComponents` for `bar` would be `["Foo", "bar"]`, corresponding to `Foo.bar`.
> Note: When writing relative links, an author may choose to remove leading components, so disambiguating path components should only be appended to the end, not prepended to the beginning.
*/
public var pathComponents: [String]
/// If the static type of a symbol is known, the precise identifier of
/// the symbol that declares the type.
public var type: String?
/// The context-specific names of a symbol.
public var names: Names
/// The in-source documentation comment attached to a symbol.
public var docComment: LineList?
/// If true, the symbol was created implicitly and not from source.
public var isVirtual: Bool
/// If the symbol has a documentation comment, whether the documentation comment is from
/// the same module as the symbol or not.
///
/// An inherited documentation comment is from the same module when the symbol that the documentation is inherited from is in the same module as this symbol.
@available(*, deprecated, message: "Use 'isDocCommentFromSameModule(symbolModuleName:)' instead.")
public var isDocCommentFromSameModule: Bool? {
_isDocCommentFromSameModule
}
// To avoid deprecation warnings in SymbolKit test until the deprecated property is removed.
internal var _isDocCommentFromSameModule: Bool? {
guard let docComment = docComment, !docComment.lines.isEmpty else {
return nil
}
// As a current implementation detail, documentation comments from within the current module has range information but
// documentation comments that are inherited from other modules don't have any range information.
//
// This isn't always correct and is only used as a fallback logic for symbol information before the source module was
// included in the symbol graph file.
return docComment.lines.contains(where: { $0.range != nil })
}
/// If the symbol has a documentation comment, checks whether the documentation comment is from the same module as the symbol.
///
/// A documentation comment is from the same module as the symbol when the source of the documentation comment is the symbol itself or another symbol in the same module.
///
/// - Parameter symbolModuleName: The name of the module where the symbol is defined.
/// - Returns: `true`if the source of the documentation comment is from the same module as this symbol.
public func isDocCommentFromSameModule(symbolModuleName: String) -> Bool? {
guard let docComment = docComment, !docComment.lines.isEmpty else {
return nil
}
if let moduleName = docComment.moduleName {
// If the new source module information is available, rely on that.
return moduleName == symbolModuleName
} else {
// Otherwise, fallback to the previous implementation.
return _isDocCommentFromSameModule
}
}
/// The access level of the symbol.
public var accessLevel: AccessControl
/// Information about a symbol that is not necessarily common to all symbols.
///
/// Mixins are keyed based on the ``Mixin/mixinKey`` of the mixin type itself. The decoding
/// method matches unknown JSON keys against registered mixin keys during the decoding
/// process, and decodes any matching type into this dictionary for later use.
///
/// - Note: If you intend to encode/decode this symbol, make sure to register
/// any added ``Mixin``s that do not appear on symbols in the standard format
/// on your coder using ``register(mixins:to:onEncodingError:onDecodingError:)``.
public var mixins: [String: Mixin] = [:]
/// Information about a symbol that is not necessarily common to all symbols.
///
/// - Note: If you intend to encode/decode this symbol, make sure to register
/// any added ``Mixin``s that do not appear on symbols in the standard format
/// on your coder using ``register(mixins:to:onEncodingError:onDecodingError:)``.
public subscript<M: Mixin>(mixin mixin: M.Type = M.self) -> M? {
get {
mixins[mixin.mixinKey] as? M
}
set {
mixins[mixin.mixinKey] = newValue
}
}
public init(identifier: Identifier, names: Names, pathComponents: [String], docComment: LineList?, accessLevel: AccessControl, kind: Kind, mixins: [String: Mixin], isVirtual: Bool = false) {
self.identifier = identifier
self.names = names
self.pathComponents = pathComponents
self.docComment = docComment
self.accessLevel = accessLevel
self.kind = kind
self.isVirtual = isVirtual
self.mixins = mixins
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
identifier = try container.decode(Identifier.self, forKey: .identifier)
kind = try container.decode(Kind.self, forKey: .kind)
pathComponents = try container.decode([String].self, forKey: .pathComponents)
type = try container.decodeIfPresent(String.self, forKey: .type)
names = try container.decode(Names.self, forKey: .names)
docComment = try container.decodeIfPresent(LineList.self, forKey: .docComment)
accessLevel = try container.decode(AccessControl.self, forKey: .accessLevel)
isVirtual = try container.decodeIfPresent(Bool.self, forKey: .isVirtual) ?? false
// Special-case `alternateDeclarations` and decode it into `alternateSymbols`.
// Do this before the next loop so that if both `alternateDeclarations` and
// `alternateSymbols` are present, the latter takes priority.
if container.contains(AlternateDeclarations.symbolCodingInfo.codingKey) {
let alternateDeclarations = try container.decode(AlternateDeclarations.self, forKey: AlternateDeclarations.symbolCodingInfo.codingKey)
mixins[AlternateSymbols.mixinKey] = AlternateSymbols(alternateDeclarations: alternateDeclarations)
}
for key in container.allKeys {
guard let info = CodingKeys.mixinCodingInfo[key.stringValue] ?? decoder.registeredSymbolMixins?[key.stringValue] else {
continue
}
mixins[key.stringValue] = try info.decode(container)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// Base
try container.encode(identifier, forKey: .identifier)
try container.encode(kind, forKey: .kind)
try container.encode(pathComponents, forKey: .pathComponents)
try container.encode(names, forKey: .names)
try container.encodeIfPresent(docComment, forKey: .docComment)
try container.encode(accessLevel, forKey: .accessLevel)
if isVirtual {
try container.encode(isVirtual, forKey: .isVirtual)
}
// Mixins
for (key, mixin) in mixins {
guard let info = CodingKeys.mixinCodingInfo[key] ?? encoder.registeredSymbolMixins?[key] else {
continue
}
try info.encode(mixin, &container)
}
}
/**
The absolute path from the module or framework to the symbol itself.
*/
public var absolutePath: String {
return pathComponents.joined(separator: "/")
}
}
}
extension SymbolGraph.Symbol {
struct CodingKeys: CodingKey, Hashable {
let stringValue: String
init?(stringValue: String) {
self = CodingKeys(rawValue: stringValue)
}
init(rawValue: String) {
self.stringValue = rawValue
}
// Base
static let identifier = CodingKeys(rawValue: "identifier")
static let kind = CodingKeys(rawValue: "kind")
static let pathComponents = CodingKeys(rawValue: "pathComponents")
static let type = CodingKeys(rawValue: "type")
static let names = CodingKeys(rawValue: "names")
static let docComment = CodingKeys(rawValue: "docComment")
static let accessLevel = CodingKeys(rawValue: "accessLevel")
static let isVirtual = CodingKeys(rawValue: "isVirtual")
// Mixins
static let availability = Availability.symbolCodingInfo
static let declarationFragments = DeclarationFragments.symbolCodingInfo
static let isReadOnly = Mutability.symbolCodingInfo
static let swiftExtension = Swift.Extension.symbolCodingInfo
static let swiftGenerics = Swift.Generics.symbolCodingInfo
static let location = Location.symbolCodingInfo
static let functionSignature = FunctionSignature.symbolCodingInfo
static let spi = SPI.symbolCodingInfo
static let snippet = Snippet.symbolCodingInfo
static let minimum = Minimum.symbolCodingInfo
static let maximum = Maximum.symbolCodingInfo
static let minimumExclusive = MinimumExclusive.symbolCodingInfo
static let maximumExclusive = MaximumExclusive.symbolCodingInfo
static let minimumLength = MinimumLength.symbolCodingInfo
static let maximumLength = MaximumLength.symbolCodingInfo
static let allowedValues = AllowedValues.symbolCodingInfo
static let defaultValue = DefaultValue.symbolCodingInfo
static let typeDetails = TypeDetails.symbolCodingInfo
static let httpEndpoint = HTTP.Endpoint.symbolCodingInfo
static let httpParameterSource = HTTP.ParameterSource.symbolCodingInfo
static let httpMediaType = HTTP.MediaType.symbolCodingInfo
static let alternateSymbols = AlternateSymbols.symbolCodingInfo
static let plistDetails = PlistDetails.symbolCodingInfo
static let mixinCodingInfo: [String: SymbolMixinCodingInfo] = [
CodingKeys.availability.codingKey.stringValue: Self.availability,
CodingKeys.declarationFragments.codingKey.stringValue: Self.declarationFragments,
CodingKeys.isReadOnly.codingKey.stringValue: Self.isReadOnly,
CodingKeys.swiftExtension.codingKey.stringValue: Self.swiftExtension,
CodingKeys.swiftGenerics.codingKey.stringValue: Self.swiftGenerics,
// malformed location data is discarded silently
CodingKeys.location.codingKey.stringValue: Self.location.with(decodingErrorHandler: { _ in nil }),
CodingKeys.functionSignature.codingKey.stringValue: Self.functionSignature,
CodingKeys.spi.codingKey.stringValue: Self.spi,
CodingKeys.snippet.codingKey.stringValue: Self.snippet,
CodingKeys.minimum.codingKey.stringValue: Self.minimum,
CodingKeys.maximum.codingKey.stringValue: Self.maximum,
CodingKeys.minimumExclusive.codingKey.stringValue: Self.minimumExclusive,
CodingKeys.maximumExclusive.codingKey.stringValue: Self.maximumExclusive,
CodingKeys.minimumLength.codingKey.stringValue: Self.minimumLength,
CodingKeys.maximumLength.codingKey.stringValue: Self.maximumLength,
CodingKeys.allowedValues.codingKey.stringValue: Self.allowedValues,
CodingKeys.defaultValue.codingKey.stringValue: Self.defaultValue,
CodingKeys.typeDetails.codingKey.stringValue: Self.typeDetails,
CodingKeys.httpEndpoint.codingKey.stringValue: Self.httpEndpoint,
CodingKeys.httpParameterSource.codingKey.stringValue: Self.httpParameterSource,
CodingKeys.httpMediaType.codingKey.stringValue: Self.httpMediaType,
CodingKeys.alternateSymbols.codingKey.stringValue: Self.alternateSymbols,
CodingKeys.plistDetails.codingKey.stringValue: Self.plistDetails
]
static func == (lhs: SymbolGraph.Symbol.CodingKeys, rhs: SymbolGraph.Symbol.CodingKeys) -> Bool {
lhs.stringValue == rhs.stringValue
}
func hash(into hasher: inout Hasher) {
stringValue.hash(into: &hasher)
}
var intValue: Int? { nil }
init?(intValue: Int) {
nil
}
}
}
extension SymbolGraph.Symbol {
/// Register types conforming to ``Mixin`` so they can be included when encoding or
/// decoding symbols.
///
/// If ``SymbolGraph/Symbol`` does not know the concrete type of a ``Mixin``, it cannot encode
/// or decode that type and thus skips such entries. Note that ``Mixin``s that occur on symbols
/// in the default symbol graph format do not have to be registered!
///
/// - Parameter userInfo: A property which allows editing the `userInfo` member of the
/// `Encoder`/`Decoder` protocol.
/// - Parameter onEncodingError: Defines the behavior when an error occurs while encoding these types of ``Mixin``s.
/// You can log warnings and either re-throw or consume the error.
/// - Parameter onDecodingError: Defines the behavior when an error occurs while decoding these types of ``Mixin``s.
/// Next to logging warnings, the function allows for either re-throwing the error,
/// skipping the erroneous entry, or providing a default value.
public static func register<M: Sequence>(mixins mixinTypes: M,
to userInfo: inout [CodingUserInfoKey: Any],
onEncodingError: ((_ error: Error, _ mixin: Mixin) throws -> Void)?,
onDecodingError: ((_ error: Error) throws -> Mixin?)?)
where M.Element == Mixin.Type {
var registeredMixins = userInfo[.symbolMixinKey] as? [String: SymbolMixinCodingInfo] ?? [:]
for type in mixinTypes {
var info = type.symbolCodingInfo
if let encodingErrorHandler = onEncodingError {
info = info.with(encodingErrorHandler: encodingErrorHandler)
}
if let decodingErrorHandler = onDecodingError {
info = info.with(decodingErrorHandler: decodingErrorHandler)
}
registeredMixins[type.mixinKey] = info
}
userInfo[.symbolMixinKey] = registeredMixins
}
}
public extension JSONEncoder {
/// Register types conforming to ``Mixin`` so they can be included when encoding symbols.
///
/// If ``SymbolGraph/Symbol`` does not know the concrete type of a ``Mixin``, it cannot encode
/// that type and thus skips such entries. Note that ``Mixin``s that occur on symbols
/// in the default symbol graph format do not have to be registered!
///
/// - Parameter onEncodingError: Defines the behavior when an error occurs while encoding these types of ``Mixin``s.
/// You can log warnings and either re-throw or consume the error.
/// - Parameter onDecodingError: Defines the behavior when an error occurs while decoding these types of ``Mixin``s.
/// Next to logging warnings, the function allows for either re-throwing the error,
/// skipping the erroneous entry, or providing a default value.
func register(symbolMixins mixinTypes: Mixin.Type...,
onEncodingError: ((_ error: Error, _ mixin: Mixin) throws -> Void)? = nil,
onDecodingError: ((_ error: Error) throws -> Mixin?)? = nil) {
SymbolGraph.Symbol.register(mixins: mixinTypes,
to: &self.userInfo,
onEncodingError: onEncodingError,
onDecodingError: onDecodingError)
}
}
public extension JSONDecoder {
/// Register types conforming to ``Mixin`` so they can be included when decoding symbols.
///
/// If ``SymbolGraph/Symbol`` does not know the concrete type of a ``Mixin``, it cannot decode
/// that type and thus skips such entries. Note that ``Mixin``s that occur on symbols
/// in the default symbol graph format do not have to be registered!
///
/// - Parameter onEncodingError: Defines the behavior when an error occurs while encoding these types of ``Mixin``s.
/// You can log warnings and either re-throw or consume the error.
/// - Parameter onDecodingError: Defines the behavior when an error occurs while decoding these types of ``Mixin``s.
/// Next to logging warnings, the function allows for either re-throwing the error,
/// skipping the erroneous entry, or providing a default value.
func register(symbolMixins mixinTypes: Mixin.Type...,
onEncodingError: ((_ error: Error, _ mixin: Mixin) throws -> Void)? = nil,
onDecodingError: ((_ error: Error) throws -> Mixin?)? = nil) {
SymbolGraph.Symbol.register(mixins: mixinTypes,
to: &self.userInfo,
onEncodingError: onEncodingError,
onDecodingError: onDecodingError)
}
}
extension Encoder {
var registeredSymbolMixins: [String: SymbolMixinCodingInfo]? {
self.userInfo[.symbolMixinKey] as? [String: SymbolMixinCodingInfo]
}
}
extension Decoder {
var registeredSymbolMixins: [String: SymbolMixinCodingInfo]? {
self.userInfo[.symbolMixinKey] as? [String: SymbolMixinCodingInfo]
}
}
extension CodingUserInfoKey {
static let symbolMixinKey = CodingUserInfoKey(rawValue: "org.swift.symbolkit.symbolMixinKey")!
}