-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix a few issues with infinite recursions if the content contains cyc…
…lic curation (#898) * Add functions to operate on directed graphs * Update topic graph to use sequences for graph traversal * Avoid computing full paths to determine if a symbol is manually curated * Fix an infinite recursion determining the paths to a node when there's cyclic curation rdar://126974763 * Avoid computing all paths when the caller only needs the shortest path * Avoid computing all paths when the caller only needs the roots/leaves * Avoid computing all paths when the caller only needs know if a certain node is encountered * Avoid computing all paths when the caller only needs to visit each reachable node once * Rename 'pathsTo(_:options:)' to 'finitePaths(to:options:) * Fix another infinite recursion when pages are curated in cycles rdar://126974763 * Fix correctness issue where symbol has two auto curated parents * Address code review feedback: - Rename neighbors parameter to edges for DirectedGraph initializer - Rename GraphPathIterator and move it to DirectedGraph+Paths file - Add convenience properties for topic graph directed graph "views - Elaborate on breadcrumbs path documentation and implementation comments - Elaborate on graph cycle documentation with concrete examples - Fix missing edge in directed graph test data - Use preconditionFailure for topic graph node that should always exist - Add additional graph cycle tests * Explicitly exit (trap) if trying to dump a topic graph with cyclic paths
- Loading branch information
1 parent
1357a2d
commit e6b8152
Showing
21 changed files
with
1,462 additions
and
227 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
Sources/SwiftDocC/Infrastructure/DocumentationContext+Breadcrumbs.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
This source file is part of the Swift.org open source project | ||
Copyright (c) 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 | ||
*/ | ||
|
||
extension DocumentationContext { | ||
/// Options that configure how the context produces node breadcrumbs. | ||
struct PathOptions: OptionSet { | ||
let rawValue: Int | ||
|
||
/// Prefer a technology as the canonical path over a shorter path. | ||
static let preferTechnologyRoot = PathOptions(rawValue: 1 << 0) | ||
} | ||
|
||
/// Finds all finite (acyclic) paths, also called "breadcrumbs", to the given reference in the topic graph. | ||
/// | ||
/// Each path is a list of references that describe a walk through the topic graph from a leaf node up to, but not including, the given `reference`. | ||
/// | ||
/// The first path is the canonical path to the node. The other paths are sorted by increasing length (number of components). | ||
/// | ||
/// > Note: | ||
/// If all paths from the given reference are infinite (cycle back on themselves) then this function will return an empty list, because there are no _finite_ paths in the topic graph from that reference. | ||
/// | ||
/// - Parameters: | ||
/// - reference: The reference to find paths to. | ||
/// - options: Options for how the context produces node breadcrumbs. | ||
/// - Returns: A list of finite paths to the given reference in the topic graph. | ||
func finitePaths(to reference: ResolvedTopicReference, options: PathOptions = []) -> [[ResolvedTopicReference]] { | ||
topicGraph.reverseEdgesGraph | ||
.allFinitePaths(from: reference) | ||
// Graph traversal typically happens from the starting point outwards, but the callers of `finitePaths(to:options:)` | ||
// expect paths going inwards from the leaves to the starting point, excluding the starting point itself. | ||
// To match the caller's expectations we remove the starting point and then flip the paths. | ||
.map { $0.dropFirst().reversed() } | ||
.sorted { (lhs, rhs) -> Bool in | ||
// Order a path rooted in a technology as the canonical one. | ||
if options.contains(.preferTechnologyRoot), let first = lhs.first { | ||
return try! entity(with: first).semantic is Technology | ||
} | ||
|
||
return breadcrumbsAreInIncreasingOrder(lhs, rhs) | ||
} | ||
} | ||
|
||
/// Finds the shortest finite (acyclic) path, also called "breadcrumb", to the given reference in the topic graph. | ||
/// | ||
/// The path is a list of references that describe a walk through the topic graph from a leaf node up to, but not including, the given `reference`. | ||
/// | ||
/// > Note: | ||
/// If all paths from the given reference are infinite (cycle back on themselves) then this function will return `nil`, because there are no _finite_ paths in the topic graph from that reference. | ||
/// | ||
/// - Parameter reference: The reference to find the shortest path to. | ||
/// - Returns: The shortest path to the given reference, or `nil` if all paths to the reference are infinite (contain cycles). | ||
func shortestFinitePath(to reference: ResolvedTopicReference) -> [ResolvedTopicReference]? { | ||
topicGraph.reverseEdgesGraph | ||
.shortestFinitePaths(from: reference) | ||
// Graph traversal typically happens from the starting point outwards, but the callers of `shortestFinitePaths(to:)` | ||
// expect paths going inwards from the leaves to the starting point, excluding the starting point itself. | ||
// To match the caller's expectations we remove the starting point and then flip the paths. | ||
.map { $0.dropFirst().reversed() } | ||
.min(by: breadcrumbsAreInIncreasingOrder) | ||
} | ||
|
||
/// Finds all the reachable root node references from the given reference. | ||
/// | ||
/// > Note: | ||
/// If all paths from the given reference are infinite (cycle back on themselves) then this function will return an empty set, because there are no reachable roots in the topic graph from that reference. | ||
/// | ||
/// - Parameter reference: The reference to find reachable root node references from. | ||
/// - Returns: The references of the root nodes that are reachable fro the given reference, or `[]` if all paths from the reference are infinite (contain cycles). | ||
func reachableRoots(from reference: ResolvedTopicReference) -> Set<ResolvedTopicReference> { | ||
topicGraph.reverseEdgesGraph.reachableLeafNodes(from: reference) | ||
} | ||
} | ||
|
||
/// Compares two breadcrumbs for sorting so that the breadcrumb with fewer components come first and breadcrumbs with the same number of components are sorted alphabetically. | ||
private func breadcrumbsAreInIncreasingOrder(_ lhs: [ResolvedTopicReference], _ rhs: [ResolvedTopicReference]) -> Bool { | ||
// If the breadcrumbs have the same number of components, sort alphabetically to produce stable results. | ||
guard lhs.count != rhs.count else { | ||
return lhs.map({ $0.path }).joined(separator: ",") < rhs.map({ $0.path }).joined(separator: ",") | ||
} | ||
// Otherwise, sort by the number of breadcrumb components. | ||
return lhs.count < rhs.count | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.