Skip to content

Commit 37a2d03

Browse files
Fix Duplicate Links in Auto Curated API Collections (#1021)
* Remove extra commas from MixedLanguageFramework.symbols.json fixtures * DocumentationContext#registerSymbols passes a unique set of relationships to GeneratedDocumentationTopics.createInheritedSymbolsAPICollections. * Rewrite unit test, avoiding manual changes to symbol graph fixture files rdar://133964293
1 parent e4f1bc5 commit 37a2d03

File tree

3 files changed

+65
-11
lines changed

3 files changed

+65
-11
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,9 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
10921092
// Making sure that we correctly let decoding memory get released, do not remove the autorelease pool.
10931093
try autoreleasepool {
10941094
/// We need only unique relationships so we'll collect them in a set.
1095-
var combinedRelationships = [UnifiedSymbolGraph.Selector: Set<SymbolGraph.Relationship>]()
1095+
var combinedRelationshipsBySelector = [UnifiedSymbolGraph.Selector: Set<SymbolGraph.Relationship>]()
1096+
/// Also track the unique relationships across all languages and platforms
1097+
var uniqueRelationships = Set<SymbolGraph.Relationship>()
10961098
/// Collect symbols from all symbol graphs.
10971099
var combinedSymbols = [String: UnifiedSymbolGraph.Symbol]()
10981100

@@ -1106,7 +1108,8 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
11061108
documentLocationMap.reserveCapacity(symbolReferences.count)
11071109
topicGraph.nodes.reserveCapacity(symbolReferences.count)
11081110
topicGraph.edges.reserveCapacity(symbolReferences.count)
1109-
combinedRelationships.reserveCapacity(symbolReferences.count)
1111+
combinedRelationshipsBySelector.reserveCapacity(symbolReferences.count)
1112+
uniqueRelationships.reserveCapacity(symbolReferences.count)
11101113
combinedSymbols.reserveCapacity(symbolReferences.count)
11111114

11121115
// Iterate over batches of symbol graphs, each batch describing one module.
@@ -1207,15 +1210,17 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
12071210
combinedSymbols.merge(unifiedSymbolGraph.symbols, uniquingKeysWith: { $1 })
12081211

12091212
for (selector, relationships) in unifiedSymbolGraph.relationshipsByLanguage {
1210-
combinedRelationships[selector, default: []].formUnion(relationships)
1213+
combinedRelationshipsBySelector[selector, default: []].formUnion(relationships)
1214+
uniqueRelationships.formUnion(relationships)
12111215
}
12121216

12131217
// Keep track of relationships that refer to symbols that are absent from the symbol graph, so that
12141218
// we can diagnose them.
1215-
combinedRelationships[
1219+
combinedRelationshipsBySelector[
12161220
.init(interfaceLanguage: "unknown", platform: nil),
12171221
default: []
12181222
].formUnion(unifiedSymbolGraph.orphanRelationships)
1223+
uniqueRelationships.formUnion(unifiedSymbolGraph.orphanRelationships)
12191224
}
12201225

12211226
try shouldContinueRegistration()
@@ -1305,10 +1310,10 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
13051310
}
13061311
emitWarningsForSymbolsMatchedInMultipleDocumentationExtensions(with: symbolsWithMultipleDocumentationExtensionMatches)
13071312
symbolsWithMultipleDocumentationExtensionMatches.removeAll()
1308-
1313+
13091314
// Create inherited API collections
13101315
try GeneratedDocumentationTopics.createInheritedSymbolsAPICollections(
1311-
relationships: combinedRelationships.flatMap(\.value),
1316+
relationships: uniqueRelationships,
13121317
context: self,
13131318
bundle: bundle
13141319
)
@@ -1350,9 +1355,9 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
13501355
preResolveExternalLinks(references: Array(moduleReferences.values) + combinedSymbols.keys.compactMap({ documentationCache.reference(symbolID: $0) }), localBundleID: bundle.identifier)
13511356

13521357
// Look up and add symbols that are _referenced_ in the symbol graph but don't exist in the symbol graph.
1353-
try resolveExternalSymbols(in: combinedSymbols, relationships: combinedRelationships)
1358+
try resolveExternalSymbols(in: combinedSymbols, relationships: combinedRelationshipsBySelector)
13541359

1355-
for (selector, relationships) in combinedRelationships {
1360+
for (selector, relationships) in combinedRelationshipsBySelector {
13561361
// Build relationships in the completed graph
13571362
buildRelationships(relationships, selector: selector, bundle: bundle)
13581363
// Merge into target symbols the member symbols that get rendered on the same page as target.

Sources/SwiftDocC/Infrastructure/Symbol Graph/GeneratedDocumentationTopics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ enum GeneratedDocumentationTopics {
231231
/// - symbolsURLHierarchy: A symbol graph hierarchy as created during symbol registration.
232232
/// - context: A documentation context to update.
233233
/// - bundle: The current documentation bundle.
234-
static func createInheritedSymbolsAPICollections(relationships: [SymbolGraph.Relationship], context: DocumentationContext, bundle: DocumentationBundle) throws {
234+
static func createInheritedSymbolsAPICollections(relationships: Set<SymbolGraph.Relationship>, context: DocumentationContext, bundle: DocumentationBundle) throws {
235235
var inheritanceIndex = InheritedSymbols()
236236

237237
// Walk the symbol graph relationships and look for parent <-> child links that stem in a different module.

Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,56 @@ class AutomaticCurationTests: XCTestCase {
448448
)
449449
}
450450
}
451-
451+
452+
func testNoAutoCuratedMixedLanguageDuplicates() throws {
453+
let (_, bundle, context) = try testBundleAndContext(copying: "MixedLanguageFramework") { url in
454+
455+
// Load the existing Obj-C symbol graph from this fixture.
456+
let path = "symbol-graphs/clang/MixedLanguageFramework.symbols.json"
457+
var graph = try JSONDecoder().decode(SymbolGraph.self, from: Data(contentsOf: url.appendingPathComponent(path)))
458+
459+
// Add an Objective-C relationship between MixedLanguageClassConformingToProtocol.mixedLanguageMethod
460+
// and the protocol requirement: MixedLanguageProtocol.mixedLanguageMethod. This matches an existing
461+
// Swift relationship, causing duplicate memberOf relationships.
462+
var relationship = SymbolGraph.Relationship(
463+
source: "c:@CM@TestFramework@objc(cs)MixedLanguageClassConformingToProtocol(im)mixedLanguageMethod",
464+
target: "c:@M@TestFramework@objc(cs)MixedLanguageClassConformingToProtocol",
465+
kind: .memberOf,
466+
targetFallback: nil
467+
)
468+
relationship.mixins["sourceOrigin"] = SymbolKit.SymbolGraph.Relationship.SourceOrigin(
469+
identifier: "c:@M@TestFramework@objc(pl)MixedLanguageProtocol(im)mixedLanguageMethod",
470+
displayName: "MixedLanguageProtocol.mixedLanguageMethod()"
471+
)
472+
graph.relationships.append(relationship)
473+
let newGraphData = try JSONEncoder().encode(graph)
474+
try newGraphData.write(to: url.appendingPathComponent("symbol-graphs/clang/MixedLanguageFramework.symbols.json"))
475+
}
476+
477+
// Load the "MixedLanguageProtocol Implementations" API COllection
478+
let protocolImplementationsNode = try context.entity(
479+
with: ResolvedTopicReference(
480+
bundleIdentifier: bundle.identifier,
481+
path: "/documentation/MixedLanguageFramework/MixedLanguageClassConformingToProtocol/MixedLanguageProtocol-Implementations",
482+
sourceLanguages: [.swift, .objectiveC]
483+
)
484+
)
485+
486+
// This page should contain an auto-curated "Instance Methods" task group.
487+
let protocolImplementationsArticle = try XCTUnwrap(protocolImplementationsNode.semantic as? Article)
488+
XCTAssertEqual(1, protocolImplementationsArticle.automaticTaskGroups.count)
489+
let instanceMethodsTaskGroup = protocolImplementationsArticle.automaticTaskGroups.first!
490+
XCTAssertEqual("Instance Methods", instanceMethodsTaskGroup.title)
491+
492+
// And this task group should contain only one reference, to a combined Swift/Obj-C child node.
493+
XCTAssertEqual(1, instanceMethodsTaskGroup.references.count)
494+
let ref = instanceMethodsTaskGroup.references.first!
495+
XCTAssertEqual(
496+
"doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/MixedLanguageClassConformingToProtocol/mixedLanguageMethod()",
497+
ref.absoluteString
498+
)
499+
}
500+
452501
func testRelevantLanguagesAreAutoCuratedInMixedLanguageFramework() throws {
453502
let (bundle, context) = try testBundleAndContext(named: "MixedLanguageFramework")
454503

@@ -465,7 +514,7 @@ class AutomaticCurationTests: XCTestCase {
465514
withTraits: [.swift],
466515
context: context
467516
)
468-
517+
469518
XCTAssertEqual(
470519
swiftTopics.flatMap { taskGroup in
471520
[taskGroup.title] + taskGroup.references.map(\.path)

0 commit comments

Comments
 (0)