-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extensions to External Types #335
Changes from all commits
a1d716b
16d1ae2
3ae5f3b
c8428da
afe3c49
4b52aef
7d34951
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -274,6 +274,9 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { | |
public var externalMetadata = ExternalMetadata() | ||
|
||
|
||
/// The decoder used in the `SymbolGraphLoader` | ||
var decoder: JSONDecoder = JSONDecoder() | ||
|
||
/// Initializes a documentation context with a given `dataProvider` and registers all the documentation bundles that it provides. | ||
/// | ||
/// - Parameter dataProvider: The data provider to register bundles from. | ||
|
@@ -1022,7 +1025,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { | |
private func parentChildRelationship(from edge: SymbolGraph.Relationship) -> (ResolvedTopicReference, ResolvedTopicReference)? { | ||
// Filter only parent <-> child edges | ||
switch edge.kind { | ||
case .memberOf, .requirementOf: | ||
case .memberOf, .requirementOf, .declaredIn: | ||
guard let parentRef = symbolIndex[edge.target]?.reference, let childRef = symbolIndex[edge.source]?.reference else { | ||
return nil | ||
} | ||
|
@@ -1920,7 +1923,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate { | |
discoveryGroup.async(queue: discoveryQueue) { [unowned self] in | ||
symbolGraphLoader = SymbolGraphLoader(bundle: bundle, dataProvider: self.dataProvider) | ||
do { | ||
try symbolGraphLoader.loadAll() | ||
try symbolGraphLoader.loadAll(using: decoder) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly out of curiosity: why is a decoded passed as an argument? AFIACT the context's decoder is never modified. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be used (in tests) to decode symbol kinds not known to SymbolKit. See e.g. the changes in This registration process is part of swiftlang/swift-docc-symbolkit#39. For usage details please refer to the documentation here. |
||
if LinkResolutionMigrationConfiguration.shouldSetUpHierarchyBasedLinkResolver { | ||
let pathHierarchy = PathHierarchy(symbolGraphLoader: symbolGraphLoader, bundleName: urlReadablePath(bundle.displayName), knownDisambiguatedPathComponents: knownDisambiguatedSymbolPathComponents) | ||
hierarchyBasedResolver = PathHierarchyBasedLinkResolver(pathHierarchy: pathHierarchy) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -335,15 +335,65 @@ final class DocumentationCacheBasedLinkResolver { | |
let moduleName: String | ||
var languages: Set<SourceLanguage> | ||
} | ||
var pathCollisionInfo = [String: [PathCollisionInfo]]() | ||
var pathCollisionInfo = [[String]: [PathCollisionInfo]]() | ||
pathCollisionInfo.reserveCapacity(totalSymbolCount) | ||
|
||
// Group symbols by path from all of the available symbol graphs | ||
for (moduleName, symbolGraph) in unifiedGraphs { | ||
let symbols = Array(symbolGraph.symbols.values) | ||
let pathsAndLanguages: [[(String, SourceLanguage)]] = symbols.concurrentMap { referencesWithoutDisambiguationFor($0, moduleName: moduleName, bundle: bundle, context: context).map { | ||
($0.path.lowercased(), $0.sourceLanguage) | ||
} } | ||
|
||
let referenceMap = symbols.concurrentMap { symbol in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to take some more time to review this change in detail. Like the comment at the top of this function says:
Subtle changes, even bug fixes, can result in pages getting different paths or different disambiguation which results in broken links (since the documentation cache based link resolver requires the exact right paths and disambiguation) to resolve links. |
||
(symbol, referencesWithoutDisambiguationFor(symbol, moduleName: moduleName, bundle: bundle, context: context)) | ||
}.reduce(into: [String: [SourceLanguage: ResolvedTopicReference]](), { result, next in | ||
let (symbol, references) = next | ||
for reference in references { | ||
result[symbol.uniqueIdentifier, default: [:]][reference.sourceLanguage] = reference | ||
} | ||
}) | ||
|
||
let parentMap = symbolGraph.relationshipsByLanguage.reduce(into: [String: [SourceLanguage: String]](), { parentMap, next in | ||
let (selector, relationships) = next | ||
guard let language = SourceLanguage(knownLanguageIdentifier: selector.interfaceLanguage) else { | ||
return | ||
} | ||
|
||
for relationship in relationships { | ||
switch relationship.kind { | ||
case .memberOf, .requirementOf, .declaredIn: | ||
parentMap[relationship.source, default: [:]][language] = relationship.target | ||
default: | ||
break | ||
} | ||
} | ||
}) | ||
|
||
let pathsAndLanguages: [[([String], SourceLanguage)]] = symbols.concurrentMap { symbol in | ||
guard let references = referenceMap[symbol.uniqueIdentifier] else { | ||
return [] | ||
} | ||
|
||
return references.map { language, reference in | ||
var prefixLength: Int | ||
if let parentId = parentMap[symbol.uniqueIdentifier]?[language], | ||
let parentReference = referenceMap[parentId]?[language] ?? referenceMap[parentId]?.values.first { | ||
// This is a child of some other symbol | ||
prefixLength = parentReference.pathComponents.count | ||
} else { | ||
// This is a top-level symbol or another symbol without parent (e.g. default implementation) | ||
prefixLength = reference.pathComponents.count-1 | ||
} | ||
|
||
// PathComponents can have prefixes which are not known locally. In that case, | ||
// the "empty" segments will be cut out later on. We follow the same logic here, as otherwise | ||
// some collisions would not be detected. | ||
// E.g. consider an extension to an external nested type `SomeModule.SomeStruct.SomeStruct`. The | ||
// parent of this extended type symbol is `SomeModule`, however, the path for the extended type symbol | ||
// is `SomeModule/SomeStruct/SomeStruct`, later on, this will change to `SomeModule/SomeStruct`. Now, if | ||
// we also extend `SomeModule.SomeStruct`, the paths for both extensions could collide. To recognize (and resolve) | ||
// the collision here, we work with the same, shortened paths. | ||
return ((reference.pathComponents[0..<prefixLength] + [reference.pathComponents.last!]).map{ $0.lowercased() }, reference.sourceLanguage) | ||
} | ||
} | ||
|
||
for (symbol, symbolPathsAndLanguages) in zip(symbols, pathsAndLanguages) { | ||
for (path, language) in symbolPathsAndLanguages { | ||
|
@@ -495,10 +545,13 @@ final class DocumentationCacheBasedLinkResolver { | |
// therefore for the currently processed symbol to be a child of a re-written symbol it needs to have | ||
// at least 3 components. It's a fair optimization to make since graphs will include a lot of root level symbols. | ||
guard reference.pathComponents.count > 3, | ||
// Fetch the symbol's parent | ||
let parentReference = try symbolsURLHierarchy.parent(of: reference), | ||
// If the parent path matches the current reference path, bail out | ||
parentReference.pathComponents != reference.pathComponents.dropLast() | ||
// Fetch the symbol's parent | ||
let parentReference = try symbolsURLHierarchy.parent(of: reference), | ||
// If the parent path matches the current reference path, bail out | ||
parentReference.pathComponents != reference.pathComponents.dropLast(), | ||
// If the parent is not from the same module (because we're dealing with a | ||
// default implementation of an external protocol), bail out | ||
parentReference.pathComponents[..<3] == reference.pathComponents[..<3] | ||
else { return reference } | ||
|
||
// Build an up to date reference path for the current node based on the parent path | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
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 SymbolKit | ||
|
||
extension SymbolGraph.Symbol.AccessControl: Comparable { | ||
private var level: Int? { | ||
switch self { | ||
case .private: | ||
return 0 | ||
case .filePrivate: | ||
return 1 | ||
case .internal: | ||
return 2 | ||
case .public: | ||
return 3 | ||
case .open: | ||
return 4 | ||
default: | ||
assertionFailure("Unknown AccessControl case was used in comparison.") | ||
return nil | ||
} | ||
} | ||
|
||
public static func < (lhs: SymbolGraph.Symbol.AccessControl, rhs: SymbolGraph.Symbol.AccessControl) -> Bool { | ||
guard let lhs = lhs.level, | ||
let rhs = rhs.level else { | ||
return false | ||
} | ||
|
||
return lhs < rhs | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly out of curiosity: what functionality of the utilities target is dealing with SymbolKit directly? My understanding is that the utilities target is mainly meant to cover the command-line interface and its related functionality but not any core logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I factored out
makeSymbolGraph(moduleName:symbols:relationships:)
intoXCTestCase
as it was used in more than one test file. See Sources/SwiftDocCTestUtilities/SymbolGraphCreation.swift