Skip to content

Commit 365ff4e

Browse files
committed
Consider parent aliases when resolving links
When resolving a link, consider the parent's alias references when resolving the link. rdar://91125617
1 parent 6134634 commit 365ff4e

File tree

6 files changed

+333
-7
lines changed

6 files changed

+333
-7
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,17 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
155155
var assetManagers = [BundleIdentifier: DataAssetManager]()
156156
/// A list of non-topic links that can be resolved.
157157
var nodeAnchorSections = [ResolvedTopicReference: AnchorSection]()
158-
/// A list of reference aliases.
158+
159+
/// The canonical references for each with alias reference.
159160
///
160-
/// When multiple references resolve to the same documentation, use this to find the main reference that is associated with the node in the topic graph.
161+
/// When multiple references resolve to the same documentation, use this to find the canonical reference that is associated with the node in the topic graph.
161162
///
162-
/// The key to lookup the main reference is the alias reference. A main reference can have many aliases but an alias can only have one main reference.
163-
var referenceAliases = [ResolvedTopicReference: ResolvedTopicReference]()
163+
/// The key is the alias reference and the value is the canonical reference..
164+
/// A main reference can have many aliases but an alias can only have one main reference.
165+
private var canonicalReferences = [ResolvedTopicReference: ResolvedTopicReference]()
166+
167+
/// The alias references for each canonical reference.
168+
private var referenceAliases = [ResolvedTopicReference: Set<ResolvedTopicReference>]()
164169

165170
/// A list of all the problems that was encountered while registering and processing the documentation bundles in this context.
166171
public var problems: [Problem] {
@@ -1156,7 +1161,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
11561161
symbolIndex[symbolData.preciseIdentifier] = symbolData.node
11571162

11581163
for alias in symbolData.referenceAliases where alias != symbolData.reference {
1159-
referenceAliases[alias] = symbolData.reference
1164+
registerAliasReference(alias, for: symbolData.reference)
11601165
}
11611166

11621167
for anchor in result.0.node.anchorSections {
@@ -2270,7 +2275,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
22702275
- Throws: ``ContextError/notFound(_:)`` if a documentation node with the given identifier was not found.
22712276
*/
22722277
public func entity(with reference: ResolvedTopicReference) throws -> DocumentationNode {
2273-
let reference = referenceAliases[reference] ?? reference
2278+
let reference = canonicalReference(for: reference)
22742279
if let cached = documentationCache[reference] {
22752280
return cached
22762281
}
@@ -2473,9 +2478,19 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
24732478
return .success(resolved)
24742479
} else if reference.fragment != nil, nodeAnchorSections.keys.contains(reference) {
24752480
return .success(reference)
2476-
} else if let alias = referenceAliases[reference] {
2481+
} else if let alias = canonicalReferences[reference] {
24772482
return .success(alias)
24782483
} else {
2484+
// Grab the aliases of the reference's parent (rather than the contextual parent) and check if the
2485+
// reference can be resolved as any of their child.
2486+
let referenceParent = reference.removingLastPathComponent()
2487+
for parentAlias in aliasReferences(for: referenceParent) {
2488+
let aliasReference = parentAlias.appendingPath(reference.lastPathComponent)
2489+
if let alias = canonicalReferences[aliasReference] {
2490+
return .success(alias)
2491+
}
2492+
}
2493+
24792494
allCandidateURLs.append(reference.url)
24802495
return nil
24812496
}
@@ -2607,6 +2622,27 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
26072622
}
26082623
}
26092624

2625+
/// Registers the given alias for the given canonical reference.
2626+
private func registerAliasReference(
2627+
_ aliasReference: ResolvedTopicReference,
2628+
for canonicalReference: ResolvedTopicReference
2629+
) {
2630+
canonicalReferences[aliasReference] = canonicalReference
2631+
referenceAliases[canonicalReference, default: Set()].insert(aliasReference)
2632+
}
2633+
2634+
/// Returns the canonical reference for the given reference.
2635+
///
2636+
/// If no canonical reference is registered for the given reference, then it's considered to be the canonical reference and it's returned.
2637+
private func canonicalReference(for reference: ResolvedTopicReference) -> ResolvedTopicReference {
2638+
canonicalReferences[reference] ?? reference
2639+
}
2640+
2641+
/// Returns the aliases registered for the given canonical reference, if any.
2642+
private func aliasReferences(for canonicalReference: ResolvedTopicReference) -> Set<ResolvedTopicReference> {
2643+
referenceAliases[canonicalReference] ?? []
2644+
}
2645+
26102646
/// Update the asset with a new value given the assets name and the topic it's referenced in.
26112647
///
26122648
/// - Parameters:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import XCTest
12+
@testable import SwiftDocC
13+
14+
class DocumentationContext_MixedLanguageLinkResolutionTests: XCTestCase {
15+
16+
func testResolvesLinksUsingParentReferenceAlias() throws {
17+
let (_, _, context) = try testBundleAndContext(copying: "MixedLanguageFrameworkComplexLinks")
18+
19+
func assertCanResolveSymbolLinks(
20+
symbolPaths: String...,
21+
parentPath: String,
22+
file: StaticString = #file,
23+
line: UInt = #line
24+
) {
25+
for symbolPath in symbolPaths {
26+
let resolutionResult = context.resolve(
27+
.unresolved(UnresolvedTopicReference(topicURL: ValidatedURL(symbolPath: symbolPath))),
28+
in: ResolvedTopicReference(
29+
bundleIdentifier: "org.swift.MixedLanguageFramework",
30+
path: "/documentation/MixedLanguageFramework/\(parentPath)",
31+
sourceLanguage: .swift
32+
),
33+
fromSymbolLink: true
34+
)
35+
36+
switch resolutionResult {
37+
case .success:
38+
continue
39+
case .failure(_, let errorMessage):
40+
XCTFail(
41+
"""
42+
Link resolution for '\(symbolPath)' in parent '\(parentPath)' unexpectedly failed: \
43+
\(errorMessage)
44+
""",
45+
file: file,
46+
line: line
47+
)
48+
}
49+
}
50+
}
51+
52+
// See MixedLanguageFrameworkComplexLinks.docc/OriginalSource.h for the class in which this test resolves links.
53+
54+
assertCanResolveSymbolLinks(
55+
symbolPaths: "first(_:one:)", "first:one:", "second:two:", "second(_:two:)",
56+
parentPath: "FooSwift"
57+
)
58+
59+
assertCanResolveSymbolLinks(
60+
symbolPaths: "FooSwift", "FooObjC", "second:two:", "second(_:two:)",
61+
parentPath: "FooSwift/first(_:one:)"
62+
)
63+
64+
assertCanResolveSymbolLinks(
65+
symbolPaths: "FooSwift", "FooObjC", "first:one:", "first(_:one:)",
66+
parentPath: "FooSwift/second(_:two:)"
67+
)
68+
}
69+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleName</key>
6+
<string>MixedLanguageFramework</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>MixedLanguageFramework</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>org.swift.MixedLanguageFramework</string>
11+
<key>CFBundleVersion</key>
12+
<string>0.1.0</string>
13+
</dict>
14+
</plist>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
// This is the header corresponding to the symbol graph files
12+
// generated in this catalog.
13+
14+
#import <Foundation/Foundation.h>
15+
16+
NS_SWIFT_NAME(FooSwift) @interface FooObjC : NSObject
17+
18+
+ (void)first:(NSString *)a one: (NSString *)b;
19+
+ (void)second:(NSString *)x two: (NSString *)y;
20+
21+
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"metadata": {
3+
"formatVersion": {
4+
"major": 0,
5+
"minor": 5,
6+
"patch": 3
7+
},
8+
"generator": "clang"
9+
},
10+
"module": {
11+
"name": "MixedLanguageFramework",
12+
"platform": {
13+
"architecture": "x86_64",
14+
"operatingSystem": {
15+
"minimumVersion": {
16+
"major": 11,
17+
"minor": 0,
18+
"patch": 0
19+
},
20+
"name": "macos"
21+
},
22+
"vendor": "apple"
23+
}
24+
},
25+
"relationships": [
26+
{
27+
"kind": "memberOf",
28+
"source": "c:objc(cs)FooObjC(cm)first:one:",
29+
"target": "c:objc(cs)FooObjC",
30+
"targetFallback": null
31+
},
32+
{
33+
"kind": "memberOf",
34+
"source": "c:objc(cs)FooObjC(cm)second:two:",
35+
"target": "c:objc(cs)FooObjC",
36+
"targetFallback": null
37+
}
38+
],
39+
"symbols": [
40+
{
41+
"accessLevel": "public",
42+
"identifier": {
43+
"interfaceLanguage": "occ",
44+
"precise": "c:objc(cs)FooObjC"
45+
},
46+
"kind": {
47+
"displayName": "Class",
48+
"identifier": "class"
49+
},
50+
"names": {
51+
"title": "FooObjC"
52+
},
53+
"pathComponents": [
54+
"FooObjC"
55+
]
56+
},
57+
{
58+
"accessLevel": "public",
59+
"identifier": {
60+
"interfaceLanguage": "occ",
61+
"precise": "c:objc(cs)FooObjC(cm)first:one:"
62+
},
63+
"kind": {
64+
"displayName": "Type Method",
65+
"identifier": "type.method"
66+
},
67+
"names": {
68+
"title": "first:one:"
69+
},
70+
"pathComponents": [
71+
"FooObjC",
72+
"first:one:"
73+
]
74+
},
75+
{
76+
"accessLevel": "public",
77+
"identifier": {
78+
"interfaceLanguage": "occ",
79+
"precise": "c:objc(cs)FooObjC(cm)second:two:"
80+
},
81+
"kind": {
82+
"displayName": "Type Method",
83+
"identifier": "type.method"
84+
},
85+
"names": {
86+
"title": "second:two:"
87+
},
88+
"pathComponents": [
89+
"FooObjC",
90+
"second:two:"
91+
]
92+
}
93+
]
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
{
2+
"metadata": {
3+
"formatVersion": {
4+
"major": 0,
5+
"minor": 5,
6+
"patch": 3
7+
},
8+
"generator": "Apple Swift version 5.7 (swiftlang-5.7.0.104.28 clang-1400.0.12.6.3)"
9+
},
10+
"module": {
11+
"name": "MixedLanguageFramework",
12+
"platform": {
13+
"architecture": "x86_64",
14+
"operatingSystem": {
15+
"minimumVersion": {
16+
"major": 11,
17+
"minor": 0,
18+
"patch": 0
19+
},
20+
"name": "macosx"
21+
},
22+
"vendor": "apple"
23+
}
24+
},
25+
"relationships": [
26+
{
27+
"kind": "memberof",
28+
"source": "c:objc(cs)fooobjc(cm)first:one:",
29+
"target": "c:objc(cs)fooobjc"
30+
},
31+
{
32+
"kind": "memberOf",
33+
"source": "c:objc(cs)FooObjC(cm)second:two:",
34+
"target": "c:objc(cs)FooObjC"
35+
}
36+
],
37+
"symbols": [
38+
{
39+
"accessLevel": "open",
40+
"identifier": {
41+
"interfaceLanguage": "swift",
42+
"precise": "c:objc(cs)FooObjC"
43+
},
44+
"kind": {
45+
"displayName": "Class",
46+
"identifier": "swift.class"
47+
},
48+
"names": {
49+
"title": "FooSwift"
50+
},
51+
"pathComponents": [
52+
"FooSwift"
53+
]
54+
},
55+
{
56+
"accessLevel": "open",
57+
"identifier": {
58+
"interfaceLanguage": "swift",
59+
"precise": "c:objc(cs)FooObjC(cm)first:one:"
60+
},
61+
"kind": {
62+
"displayName": "Type Method",
63+
"identifier": "swift.type.method"
64+
},
65+
"names": {
66+
"title": "first(_:one:)"
67+
},
68+
"pathComponents": [
69+
"FooSwift",
70+
"first(_:one:)"
71+
]
72+
},
73+
{
74+
"accessLevel": "open",
75+
"identifier": {
76+
"interfaceLanguage": "swift",
77+
"precise": "c:objc(cs)FooObjC(cm)second:two:"
78+
},
79+
"kind": {
80+
"displayName": "Type Method",
81+
"identifier": "swift.type.method"
82+
},
83+
"names": {
84+
"title": "second(_:two:)"
85+
},
86+
"pathComponents": [
87+
"FooSwift",
88+
"second(_:two:)"
89+
]
90+
}
91+
]
92+
}

0 commit comments

Comments
 (0)