Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,19 @@ public extension Collection where Element: DocCSymbolRepresentable {
} else {
// Disambiguate by kind
return map { currentSymbol in
(
shouldAddIdHash: filter {
$0.kindIdentifier == currentSymbol.kindIdentifier
}.count > 1,
shouldAddKind: true
)
let kindCount = filter { $0.kindIdentifier == currentSymbol.kindIdentifier }.count

if LinkResolutionMigrationConfiguration.shouldUseHierarchyBasedLinkResolver {
return (
shouldAddIdHash: kindCount > 1,
shouldAddKind: kindCount == 1
)
} else {
return (
shouldAddIdHash: kindCount > 1,
shouldAddKind: true
)
}
}
}
}
Expand Down
298 changes: 233 additions & 65 deletions Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
/// - currentPlatforms: The current version and beta information for platforms that may be encountered while processing symbol graph files.
/// that may be encountered while processing symbol graph files.
/// - workspace: A provided documentation workspace. Creates a new empty workspace if value is `nil`.
/// - context: A provided documentation context. Creates a new empty context in the workspace if value is `nil`.
/// - context: A provided documentation context.
/// - dataProvider: A data provider to use when registering bundles.
/// - Parameter fileManager: A file persistence manager
/// - Parameter externalIDsToConvert: The external IDs of the documentation nodes to convert.
Expand Down Expand Up @@ -377,6 +377,8 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
// Log the peak memory.
benchmark(add: Benchmark.PeakMemory())

context.linkResolutionMismatches.reportGatheredMismatchesIfEnabled()

return (analysisProblems: context.problems, conversionProblems: conversionProblems)
}

Expand Down
12 changes: 8 additions & 4 deletions Sources/SwiftDocC/Infrastructure/DocumentationCurator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ struct DocumentationCurator {
return cached
}

let unresolved = UnresolvedTopicReference(topicURL: ValidatedURL(symbolPath: destination))
let maybeResolved = context.resolve(.unresolved(unresolved), in: resolved, fromSymbolLink: true)

if case let .success(resolved) = maybeResolved {
// The symbol link may be written with a scheme and bundle identifier.
let url = ValidatedURL(parsingExact: destination)?.requiring(scheme: ResolvedTopicReference.urlScheme) ?? ValidatedURL(symbolPath: destination)
if case let .success(resolved) = context.resolve(.unresolved(.init(topicURL: url)), in: resolved, fromSymbolLink: true) {
return resolved
}
return nil
Expand Down Expand Up @@ -116,6 +115,11 @@ struct DocumentationCurator {
context.topicGraph.addNode(curatedNode)

// Move the article from the article cache to the documentation

if let hierarchyBasedLinkResolver = context.hierarchyBasedLinkResolver {
hierarchyBasedLinkResolver.addArticle(filename: articleFilename, reference: reference, anchorSections: documentationNode.anchorSections)
}

context.documentationCache[reference] = documentationNode
for anchor in documentationNode.anchorSections {
context.nodeAnchorSections[anchor.reference] = anchor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
This source file is part of the Swift.org open source project

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

/// A scope of configurations for how the documentation context should resolve links while migrating from one implementation to another.
///
/// If any reporting is enabled, the documentation context will setup both link resolver to compare them.
///
///> Note: This is a temporary configuration that will go away along with the ``DocumentationCacheBasedLinkResolver`` at some point in the future.
enum LinkResolutionMigrationConfiguration {

// MARK: Configuration

/// Whether or not the context should the a ``PathHierarchyBasedLinkResolver`` to resolve links.
static var shouldUseHierarchyBasedLinkResolver: Bool = {
return UserDefaults.standard.bool(forKey: "DocCUseHierarchyBasedLinkResolver")
|| ProcessInfo.processInfo.environment["DOCC_USE_HIERARCHY_BASED_LINK_RESOLVER"] == "YES"
}()

/// Whether or not the context should report differences between the disambiguated paths created by ``PathHierarchyBasedLinkResolver`` and ``DocumentationCacheBasedLinkResolver``.
///
/// What mismatches can be reported depend on the value of ``shouldUseHierarchyBasedLinkResolver``:
/// - When the cache based resolved is used to resolve links both mismatched symbol paths and mismatched link resolution reports will be reported.
/// - When the path hierarchy based resolved is used to resolve links only mismatched symbol paths will be reported.
static var shouldReportLinkResolutionMismatches: Bool = {
return UserDefaults.standard.bool(forKey: "DocCReportLinkResolutionMismatches")
|| ProcessInfo.processInfo.environment["DOCC_REPORT_LINK_RESOLUTION_MISMATCHES"] == "YES"
}()

// MARK: Derived conditions

/// Whether or not the context should set up a ``PathHierarchyBasedLinkResolver``.
///
/// > Node: Check ``shouldUseHierarchyBasedLinkResolver`` to determine which implementation to use to resolve links.
static var shouldSetUpHierarchyBasedLinkResolver: Bool {
return shouldUseHierarchyBasedLinkResolver || shouldReportLinkResolutionMismatches
}

/// Whether or not to report mismatches in link resolution results between the two implementations.
static var shouldReportLinkResolutionResultMismatches: Bool {
return shouldReportLinkResolutionMismatches && !shouldUseHierarchyBasedLinkResolver
}

/// Whether or not to report mismatches in symbol path disambiguation between the two implementations.
static var shouldReportLinkResolutionPathMismatches: Bool {
return shouldReportLinkResolutionMismatches
}
}

// MARK: Gathering mismatches

/// A type that gathers differences between the two link resolution implementations.
///
/// > Note: This is a temporary report that will go away along with the ``DocumentationCacheBasedLinkResolver`` at some point in the future.
final class LinkResolutionMismatches {
/// Gathered resolved reference paths that have different disambiguation in the two implementations.
var pathsWithMismatchedDisambiguation: [String: String] = [:]

/// Gathered resolved reference paths that are missing from the path hierarchy-based implementation.
var missingPathsInHierarchyBasedLinkResolver: [String] = []
/// Gathered resolved reference paths that are missing from the documentation cache-based implementation.
var missingPathsInCacheBasedLinkResolver: [String] = []

/// Information about the inputs for a link that resolved in one implementation but not the other.
struct FailedLinkInfo: Hashable {
/// The path, and optional fragment, of the unresolved reference.
let path: String
/// The path, and optional fragment, of the parent reference that the link was resolved relative to.
let parent: String
/// Whether or not the link was resolved as a symbol link.
let asSymbolLink: Bool
}

/// Links that resolved in the cache-based implementation but not the path hierarchy-based implementation
var mismatchedLinksThatHierarchyBasedLinkResolverFailedToResolve = Set<FailedLinkInfo>()

/// Links that resolved in the path hierarchy-based implementation but not the cache-based implementation.
var mismatchedLinksThatCacheBasedLinkResolverFailedToResolve = Set<FailedLinkInfo>()
}

// MARK: Reporting mismatches

extension LinkResolutionMismatches {
/// Prints the gathered mismatches
///
/// > Note: If ``LinkResolutionMigrationConfiguration/shouldReportLinkResolutionPathMismatches`` is ``false`` this won't print anything.
func reportGatheredMismatchesIfEnabled() {
guard LinkResolutionMigrationConfiguration.shouldReportLinkResolutionPathMismatches else { return }
let prefix = "[HierarchyBasedLinkResolutionDiff]"

if pathsWithMismatchedDisambiguation.isEmpty {
print("\(prefix) All symbol paths have the same disambiguation suffixes in both link resolver implementations.")
} else {
print("\(prefix) The following symbol paths have the different disambiguation across the two link resolver implementations:")
let columnWidth = max(40, (pathsWithMismatchedDisambiguation.keys.map { $0.count } + ["path hierarchy implementation".count]).max()!)
print("\("Path hierarchy implementation".padding(toLength: columnWidth, withPad: " ", startingAt: 0)) | Documentation cache implementation")
print("\(String(repeating: "-", count: columnWidth))-+-\(String(repeating: "-", count: columnWidth))")
for (hierarchyBasedPath, mainCacheBasedPath) in pathsWithMismatchedDisambiguation.sorted(by: \.key) {
print("\(hierarchyBasedPath.padding(toLength: columnWidth, withPad: " ", startingAt: 0)) | \(mainCacheBasedPath)")
}
}

if !missingPathsInHierarchyBasedLinkResolver.isEmpty {
let missingPaths = missingPathsInHierarchyBasedLinkResolver.sorted()
print("\(prefix) The following symbol paths exist in the cache-based link resolver but is missing in the path hierarchy-based link resolver:\n\(missingPaths.joined(separator: "\n"))")
}
if !missingPathsInCacheBasedLinkResolver.isEmpty {
let missingPaths = missingPathsInCacheBasedLinkResolver.sorted()
print("\(prefix) The following symbol paths exist in the path hierarchy-based link resolver but is missing in the cache-based link resolver:\n\(missingPaths.joined(separator: "\n"))")
}

guard !LinkResolutionMigrationConfiguration.shouldUseHierarchyBasedLinkResolver else {
// Results can only be reported when the documentation cache based implementation is used to resolve links
return
}

let mismatchedFailedCacheResults = mismatchedLinksThatCacheBasedLinkResolverFailedToResolve
let mismatchedFailedHierarchyResults = mismatchedLinksThatHierarchyBasedLinkResolverFailedToResolve

if mismatchedFailedCacheResults.isEmpty && mismatchedFailedHierarchyResults.isEmpty {
print("\(prefix) Both link resolver implementations succeeded and failed to resolve the same links.")
} else {
if !mismatchedFailedCacheResults.isEmpty {
print("\(prefix) The following links failed to resolve in the documentation cache implementation but succeeded in the path hierarchy implementation:")

let firstColumnWidth = mismatchedFailedCacheResults.map { $0.path.count }.max()! + 2 // 2 extra for the quotes
for result in mismatchedFailedCacheResults {
print("\(result.path.singleQuoted.padding(toLength: firstColumnWidth, withPad: " ", startingAt: 0)) relative to \(result.parent.singleQuoted) \(result.asSymbolLink ? "(symbol link)" : "")")
}
}

if !mismatchedFailedHierarchyResults.isEmpty {
print("\(prefix) The following links failed to resolve in the path hierarchy implementation but succeeded in the documentation cache implementation:")

let firstColumnWidth = mismatchedFailedHierarchyResults.map { $0.path.count }.max()! + 2 // 2 extra for the quotes
for result in mismatchedFailedHierarchyResults {
print("\(result.path.singleQuoted.padding(toLength: firstColumnWidth, withPad: " ", startingAt: 0)) relative to \(result.parent.singleQuoted) \(result.asSymbolLink ? "(symbol link)" : "")")
}
}
}
}
}
Loading