Skip to content

Commit 812c56a

Browse files
committed
Include symbols without common lang in navigator
The Topics section has a filtering logic to ensure that it displays the correct variant of a referenced symbol (e.g. the Objective-C variant of the symbol is shown only for the Objective-C version of a page). If the page and symbol have no languages in common, the symbol will be shown anyway in order to not drop the reference completely. For example, if a Swift-only symbol is curated under an Objective-C only page, the symbol will be shown in the Topics section even though their languages don't match. The navigator also has a similar filtering logic to display all references present in the Topics section. However, it drops a reference if the pages don't have any source languages in common. This results in cases where the navigator is incorrectly missing references that are present in the Topics section. This issue can be reproduced with the `MixedLanguageFrameworkSingleLanguageCuration.docc` test bundle by running `xcrun docc preview Tests/SwiftDocCTests/Test\ Bundles/MixedLanguageFrameworkSingleLanguageCuration.docc` and viewing the navigator in the preview. This patch updates the navigator's filtering logic to include references if they do not share a common language with the relevant page. rdar://155522179
1 parent 1e69ac1 commit 812c56a

File tree

3 files changed

+77
-16
lines changed

3 files changed

+77
-16
lines changed

Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,12 @@ public class NavigatorIndex {
230230
}
231231

232232
/**
233-
Initialize an `NavigatorIndex` from a given path with an empty tree.
233+
Initialize a `NavigatorIndex` from a given path with an empty tree.
234234

235235
- Parameter url: The URL pointing to the path from which the index should be read.
236236
- Parameter bundleIdentifier: The name of the bundle the index is referring to.
237237

238-
- Note: Don't exposed this initializer as it's used **ONLY** for building an index.
238+
- Note: Don't expose this initializer as it's used **ONLY** for building an index.
239239
*/
240240
fileprivate init(withEmptyTree url: URL, bundleIdentifier: String) throws {
241241
self.url = url
@@ -466,6 +466,17 @@ extension NavigatorIndex {
466466
self.fragment = fragment
467467
self.languageIdentifier = languageIdentifier
468468
}
469+
470+
/// Compare an identifier with another one, ignoring the identifier language.
471+
///
472+
/// Used when curating cross-language references in multi-language frameworks.
473+
///
474+
/// - Parameter other: The other identifier to compare with.
475+
func isEquivalentIgnoringLanguage(to other: Identifier) -> Bool {
476+
return self.bundleIdentifier == other.bundleIdentifier &&
477+
self.path == other.path &&
478+
self.fragment == other.fragment
479+
}
469480
}
470481

471482
/**
@@ -916,7 +927,7 @@ extension NavigatorIndex {
916927
/// - emitJSONRepresentation: Whether or not a JSON representation of the index should
917928
/// be written to disk.
918929
///
919-
/// Defaults to `false`.
930+
/// Defaults to `true`.
920931
///
921932
/// - emitLMDBRepresentation: Whether or not an LMDB representation of the index should
922933
/// written to disk.
@@ -949,14 +960,28 @@ extension NavigatorIndex {
949960
let (nodeID, parent) = nodesMultiCurated[index]
950961
let placeholders = identifierToChildren[nodeID]!
951962
for reference in placeholders {
952-
if let child = identifierToNode[reference] {
963+
var child = identifierToNode[reference]
964+
var childReference = reference
965+
966+
// If no child node exists in this language, try to find one across all available languages.
967+
if child == nil {
968+
if let match = identifierToNode.first(where: { (identifier, node) in
969+
identifier.isEquivalentIgnoringLanguage(to: reference) &&
970+
PageType(rawValue: node.item.pageType)?.isSymbolKind == true
971+
}) {
972+
childReference = match.key
973+
child = match.value
974+
}
975+
}
976+
977+
if let child = child {
953978
parent.add(child: child)
954-
pendingUncuratedReferences.remove(reference)
955-
if !multiCurated.keys.contains(reference) && reference.fragment == nil {
979+
pendingUncuratedReferences.remove(childReference)
980+
if !multiCurated.keys.contains(childReference) && childReference.fragment == nil {
956981
// As the children of a multi-curated node is itself curated multiple times
957982
// we need to process it as well, ignoring items with fragments as those are sections.
958-
nodesMultiCuratedChildren.append((reference, child))
959-
multiCurated[reference] = child
983+
nodesMultiCuratedChildren.append((childReference, child))
984+
multiCurated[childReference] = child
960985
}
961986
}
962987
}
@@ -970,10 +995,24 @@ extension NavigatorIndex {
970995
for (nodeIdentifier, placeholders) in identifierToChildren {
971996
for reference in placeholders {
972997
let parent = identifierToNode[nodeIdentifier]!
973-
if let child = identifierToNode[reference] {
974-
let needsCopy = multiCurated[reference] != nil
998+
var child = identifierToNode[reference]
999+
var childReference = reference
1000+
1001+
// If no child node exists in this language, try to find one across all available languages.
1002+
if child == nil {
1003+
if let match = identifierToNode.first(where: { (identifier, node) in
1004+
identifier.isEquivalentIgnoringLanguage(to: reference) &&
1005+
PageType(rawValue: node.item.pageType)?.isSymbolKind == true
1006+
}) {
1007+
childReference = match.key
1008+
child = match.value
1009+
}
1010+
}
1011+
1012+
if let child = child {
1013+
let needsCopy = multiCurated[childReference] != nil
9751014
parent.add(child: (needsCopy) ? child.copy() : child)
976-
pendingUncuratedReferences.remove(reference)
1015+
pendingUncuratedReferences.remove(childReference)
9771016
}
9781017
}
9791018
}
@@ -1005,10 +1044,7 @@ extension NavigatorIndex {
10051044

10061045
// If an uncurated page has been curated in another language, don't add it to the top-level.
10071046
if curatedReferences.contains(where: { curatedNodeID in
1008-
// Compare all the identifier's properties for equality, except for its language.
1009-
curatedNodeID.bundleIdentifier == nodeID.bundleIdentifier
1010-
&& curatedNodeID.path == nodeID.path
1011-
&& curatedNodeID.fragment == nodeID.fragment
1047+
curatedNodeID.isEquivalentIgnoringLanguage(to: nodeID)
10121048
}) {
10131049
continue
10141050
}

Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,32 @@ Root
894894
)
895895
}
896896

897+
/// Test the curation of a symbol in a page where the page language is different from the symbol language.
898+
/// This occurs in multi-language frameworks when a symbol in one language is referenced in another, e.g.
899+
/// a Swift-only symbol includes a link to an Objective-C only symbol.
900+
func testCurateSymbolsInPageWithNoCommonLanguage() async throws {
901+
let navigatorIndex = try await generatedNavigatorIndex(
902+
for: "MixedLanguageFrameworkSingleLanguageCuration",
903+
bundleIdentifier: "org.swift.mixedlanguageframework"
904+
)
905+
906+
XCTAssertEqual(
907+
navigatorIndex.navigatorTree.root.children
908+
.first { $0.item.title == "Swift" }?
909+
.children
910+
.first { $0.item.title == "MixedLanguageFramework" }?
911+
.children
912+
.first { $0.item.title == "SwiftOnlyStruct2" }?
913+
.children
914+
.contains { $0.item.title == "ObjectiveCOnlyClass" },
915+
true,
916+
"""
917+
Expected the Objective-C-only node with title "ObjectiveCOnlyClass" to be curated in the Swift \
918+
navigator tree.
919+
"""
920+
)
921+
}
922+
897923
func testNavigatorIndexUsingPageTitleGeneration() async throws {
898924
let (bundle, context) = try await testBundleAndContext(named: "LegacyBundle_DoNotUseInNewTests")
899925
let renderContext = RenderContext(documentationContext: context, bundle: bundle)

Tests/SwiftDocCTests/Test Bundles/MixedLanguageFrameworkSingleLanguageCuration.docc/MixedLanguageFramework.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
These symbols are Objective-C only and curated in multiple places in the catalog.
88

9-
- ``MultiCuratedObjectiveCOnlyClass1``
109
- ``MultiCuratedObjectiveCOnlyClass1``
1110
- ``MultiCuratedObjectiveCOnlyClass2``
1211

0 commit comments

Comments
 (0)