Skip to content

Commit 9171bdc

Browse files
Fix occasional missing breadcrumbs for some multi-language symbols (#1135) (#1137)
* Fix occasional missing breadcrumbs for some multi-language symbols rdar://141365081 * Fix unrelated deprecation warning * Apply suggestions from code review --------- Co-authored-by: Maya Epps <[email protected]>
1 parent eaa1e4a commit 9171bdc

File tree

5 files changed

+92
-18
lines changed

5 files changed

+92
-18
lines changed

Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchyBasedLinkResolver+Breadcrumbs.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,7 @@ extension PathHierarchyBasedLinkResolver {
2525
var node = pathHierarchy.lookup[nodeID]! // Only the path hierarchy can create its IDs and a created ID always matches a node
2626

2727
func matchesRequestedLanguage(_ node: PathHierarchy.Node) -> Bool {
28-
guard let symbol = node.symbol,
29-
let language = SourceLanguage(knownLanguageIdentifier: symbol.identifier.interfaceLanguage)
30-
else {
31-
return false
32-
}
33-
return language == sourceLanguage
28+
node.languages.contains(sourceLanguage)
3429
}
3530

3631
if !matchesRequestedLanguage(node) {

Sources/SwiftDocC/Model/Rendering/Navigation Tree/RenderHierarchyTranslator.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,9 @@ struct RenderHierarchyTranslator {
207207
)
208208

209209
for language in symbolReference.sourceLanguages where language != symbolReference.sourceLanguage {
210-
guard let variantPathReferences = context.linkResolver.localResolver.breadcrumbs(of: symbolReference, in: language) else {
210+
guard let variantPathReferences = context.linkResolver.localResolver.breadcrumbs(of: symbolReference, in: language),
211+
variantPathReferences != mainPathReferences
212+
else {
211213
continue
212214
}
213215
hierarchyVariants.variants.append(.init(

Tests/SwiftDocCTests/Infrastructure/SymbolBreadcrumbTests.swift

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import XCTest
1313

1414
class SymbolBreadcrumbTests: XCTestCase {
1515
func testLanguageSpecificBreadcrumbs() throws {
16-
let (_, context) = try testBundleAndContext(named: "GeometricalShapes")
16+
let (bundle, context) = try testBundleAndContext(named: "GeometricalShapes")
1717
let resolver = try XCTUnwrap(context.linkResolver.localResolver)
1818
let moduleReference = try XCTUnwrap(context.soleRootModuleReference)
1919

@@ -22,7 +22,8 @@ class SymbolBreadcrumbTests: XCTestCase {
2222
// CGFloat radius;
2323
// } TLACircle NS_SWIFT_NAME(Circle);
2424
do {
25-
let reference = moduleReference.appendingPath("Circle/center")
25+
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.path == "\(moduleReference.path)/Circle/center" }))
26+
XCTAssertEqual(reference.sourceLanguages.count, 2, "Symbol has 2 language representations")
2627

2728
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .swift)?.map(\.path), [
2829
"/documentation/GeometricalShapes",
@@ -32,11 +33,14 @@ class SymbolBreadcrumbTests: XCTestCase {
3233
"/documentation/GeometricalShapes",
3334
"/documentation/GeometricalShapes/Circle", // named TLACircle in Objective-C
3435
])
36+
37+
assertNoVariantsForRenderHierarchy(reference, context, bundle) // Same breadcrumbs in both languages
3538
}
3639

3740
// extern const TLACircle TLACircleZero NS_SWIFT_NAME(Circle.zero);
3841
do {
39-
let reference = moduleReference.appendingPath("Circle/zero")
42+
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.path == "\(moduleReference.path)/Circle/zero" }))
43+
XCTAssertEqual(reference.sourceLanguages.count, 2, "Symbol has 2 language representations")
4044

4145
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .swift)?.map(\.path), [
4246
"/documentation/GeometricalShapes",
@@ -45,11 +49,14 @@ class SymbolBreadcrumbTests: XCTestCase {
4549
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .objectiveC)?.map(\.path), [
4650
"/documentation/GeometricalShapes", // The Objective-C representation is a top-level function
4751
])
52+
53+
assertHasSomeVariantsForRenderHierarchy(reference, context, bundle) // Different breadcrumbs in different languages
4854
}
4955

5056
// BOOL TLACircleIntersects(TLACircle circle, TLACircle otherCircle) NS_SWIFT_NAME(Circle.intersects(self:_:));
5157
do {
52-
let reference = moduleReference.appendingPath("Circle/intersects(_:)")
58+
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.path == "\(moduleReference.path)/Circle/intersects(_:)" }))
59+
XCTAssertEqual(reference.sourceLanguages.count, 2, "Symbol has 2 language representations")
5360

5461
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .swift)?.map(\.path), [
5562
"/documentation/GeometricalShapes",
@@ -58,26 +65,99 @@ class SymbolBreadcrumbTests: XCTestCase {
5865
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .objectiveC)?.map(\.path), [
5966
"/documentation/GeometricalShapes", // The Objective-C representation is a top-level function
6067
])
68+
69+
assertHasSomeVariantsForRenderHierarchy(reference, context, bundle) // Different breadcrumbs in different languages
6170
}
6271

6372
// TLACircle TLACircleMake(CGPoint center, CGFloat radius) NS_SWIFT_UNAVAILABLE("Use 'Circle.init(center:radius:)' instead.");
6473
do {
65-
let reference = moduleReference.appendingPath("TLACircleMake")
74+
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.path == "\(moduleReference.path)/TLACircleMake" }))
75+
XCTAssertEqual(reference.sourceLanguages.count, 1, "Symbol only has one language representation")
6676

6777
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .swift)?.map(\.path), nil) // There is no Swift representation
6878
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .objectiveC)?.map(\.path), [
6979
"/documentation/GeometricalShapes", // The Objective-C representation is a top-level function
7080
])
81+
82+
assertNoVariantsForRenderHierarchy(reference, context, bundle) // Only has one language representation
7183
}
7284

7385
do {
74-
let reference = moduleReference.appendingPath("Circle/init(center:radius:)")
86+
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.path == "\(moduleReference.path)/Circle/init(center:radius:)" }))
87+
XCTAssertEqual(reference.sourceLanguages.count, 1, "Symbol only has one language representation")
7588

7689
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .swift)?.map(\.path), [
7790
"/documentation/GeometricalShapes",
7891
"/documentation/GeometricalShapes/Circle", // The Swift representation is a member
7992
])
8093
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .objectiveC)?.map(\.path), nil) // There is no Objective-C representation
94+
95+
assertNoVariantsForRenderHierarchy(reference, context, bundle) // Only has one language representation
8196
}
8297
}
98+
99+
func testMixedLanguageSpecificBreadcrumbs() throws {
100+
let (bundle, context) = try testBundleAndContext(named: "MixedLanguageFramework")
101+
let resolver = try XCTUnwrap(context.linkResolver.localResolver)
102+
let moduleReference = try XCTUnwrap(context.soleRootModuleReference)
103+
104+
do {
105+
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.path == "\(moduleReference.path)/MixedLanguageProtocol/mixedLanguageMethod()" }))
106+
XCTAssertEqual(reference.sourceLanguages.count, 2, "Symbol has 2 language representations")
107+
108+
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .swift)?.map(\.path), [
109+
"/documentation/MixedLanguageFramework",
110+
"/documentation/MixedLanguageFramework/MixedLanguageProtocol",
111+
])
112+
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .objectiveC)?.map(\.path), [
113+
"/documentation/MixedLanguageFramework",
114+
"/documentation/MixedLanguageFramework/MixedLanguageProtocol",
115+
])
116+
117+
assertNoVariantsForRenderHierarchy(reference, context, bundle) // Same breadcrumbs in both languages
118+
}
119+
do {
120+
let reference = try XCTUnwrap(context.knownPages.first(where: { $0.path == "\(moduleReference.path)/MixedLanguageProtocol" }))
121+
XCTAssertEqual(reference.sourceLanguages.count, 2, "Symbol has 2 language representations")
122+
123+
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .swift)?.map(\.path), [
124+
"/documentation/MixedLanguageFramework",
125+
])
126+
XCTAssertEqual(resolver.breadcrumbs(of: reference, in: .objectiveC)?.map(\.path), [
127+
"/documentation/MixedLanguageFramework",
128+
])
129+
130+
assertNoVariantsForRenderHierarchy(reference, context, bundle) // Same breadcrumbs in both languages
131+
}
132+
}
133+
134+
// MARK: Test helpers
135+
136+
private func assertNoVariantsForRenderHierarchy(
137+
_ reference: ResolvedTopicReference,
138+
_ context: DocumentationContext,
139+
_ bundle: DocumentationBundle,
140+
file: StaticString = #file,
141+
line: UInt = #line
142+
) {
143+
var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle)
144+
let hierarchyVariants = hierarchyTranslator.visitSymbol(reference)
145+
146+
XCTAssertNotNil(hierarchyVariants.defaultValue, "Should always have default breadcrumbs", file: file, line: line)
147+
XCTAssert(hierarchyVariants.variants.isEmpty, "No need for variants when value is same in Swift and Objective-C", file: file, line: line)
148+
}
149+
150+
private func assertHasSomeVariantsForRenderHierarchy(
151+
_ reference: ResolvedTopicReference,
152+
_ context: DocumentationContext,
153+
_ bundle: DocumentationBundle,
154+
file: StaticString = #file,
155+
line: UInt = #line
156+
) {
157+
var hierarchyTranslator = RenderHierarchyTranslator(context: context, bundle: bundle)
158+
let hierarchyVariants = hierarchyTranslator.visitSymbol(reference)
159+
160+
XCTAssertNotNil(hierarchyVariants.defaultValue, "Should always have default breadcrumbs", file: file, line: line)
161+
XCTAssertFalse(hierarchyVariants.variants.isEmpty, "Either language needs a variant when value is different in Swift and Objective-C", file: file, line: line)
162+
}
83163
}

Tests/SwiftDocCTests/Model/ParametersAndReturnValidatorTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class ParametersAndReturnValidatorTests: XCTestCase {
157157

158158
XCTAssert(context.problems.isEmpty, "Unexpected problems: \(context.problems.map(\.diagnostic.summary))")
159159

160-
let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift)
160+
let reference = ResolvedTopicReference(bundleID: bundle.id, path: "/documentation/ModuleName/functionName(...)", sourceLanguage: .swift)
161161
let node = try context.entity(with: reference)
162162
let symbol = try XCTUnwrap(node.semantic as? Symbol)
163163

Tests/SwiftDocCTests/Model/RenderHierarchyTranslatorTests.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,7 @@ class RenderHierarchyTranslatorTests: XCTestCase {
179179
"doc://GeometricalShapes/documentation/GeometricalShapes",
180180
"doc://GeometricalShapes/documentation/GeometricalShapes/Circle",
181181
],
182-
expectedObjectiveCPaths: [
183-
"doc://GeometricalShapes/documentation/GeometricalShapes",
184-
"doc://GeometricalShapes/documentation/GeometricalShapes/Circle", // named TLACircle in Objective-C
185-
]
182+
expectedObjectiveCPaths: nil // Same in both languages. Only encoded once.
186183
)
187184

188185
// extern const TLACircle TLACircleZero NS_SWIFT_NAME(Circle.zero);

0 commit comments

Comments
 (0)