From f26b7abe000f16295478dc2b40c611a260a8ea8f Mon Sep 17 00:00:00 2001 From: Franklin Schrans Date: Mon, 14 Mar 2022 15:21:11 +0000 Subject: [PATCH] Filter automatic See Alsos based on language For automatically-generated See Also sections, filter out topics that are not available in the parent's current language. rdar://90168171 --- .../Topic Graph/AutomaticCuration.swift | 26 +++- .../Rendering/RenderNodeTranslator.swift | 23 +-- .../Indexing/RenderIndexTests.swift | 123 +++++++++------- .../AutomaticCurationTests.swift | 7 +- .../SemaToRenderNodeMultiLanguageTests.swift | 89 +++++++++++- ...derNodeTranslatorSymbolVariantsTests.swift | 135 ++++++++++++++---- .../MixedLanguageFramework.md | 7 + .../swift/MixedLanguageFramework.symbols.json | 75 ++++++++++ 8 files changed, 389 insertions(+), 96 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift b/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift index c8a60a7189..64bd97c971 100644 --- a/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift +++ b/Sources/SwiftDocC/Infrastructure/Topic Graph/AutomaticCuration.swift @@ -115,7 +115,14 @@ public struct AutomaticCuration { /// - bundle: A documentation bundle. /// - Returns: A group title and the group's references or links. /// `nil` if the method can't find any relevant links to automatically generate a See Also content. - static func seeAlso(for node: DocumentationNode, context: DocumentationContext, bundle: DocumentationBundle, renderContext: RenderContext?, renderer: DocumentationContentRenderer) throws -> TaskGroup? { + static func seeAlso( + for node: DocumentationNode, + withTrait variantsTrait: DocumentationDataVariantsTrait, + context: DocumentationContext, + bundle: DocumentationBundle, + renderContext: RenderContext?, + renderer: DocumentationContentRenderer + ) throws -> TaskGroup? { // First try getting the canonical path from a render context, default to the documentation context guard let canonicalPath = renderContext?.store.content(for: node.reference)?.canonicalPath ?? context.pathsTo(node.reference).first, !canonicalPath.isEmpty else { @@ -125,6 +132,19 @@ public struct AutomaticCuration { let parentReference = canonicalPath.last! + func filterReferences(_ references: [ResolvedTopicReference]) throws -> [ResolvedTopicReference] { + try references + // Don't include the current node. + .filter { $0 != node.reference } + + // Filter out nodes that aren't available in the given trait. + .filter { reference in + try context.entity(with: reference) + .availableVariantTraits + .contains(variantsTrait) + } + } + // Look up the render context first if let taskGroups = renderContext?.store.content(for: parentReference)?.taskGroups, let linkingGroup = taskGroups @@ -134,7 +154,7 @@ public struct AutomaticCuration { { // Group match in render context, verify if there are any other references besides the current one. guard linkingGroup.references.count > 1 else { return nil } - return (title: linkingGroup.title, references: linkingGroup.references.filter { $0 != node.reference }) + return (title: linkingGroup.title, references: try filterReferences(linkingGroup.references)) } // Get the parent's task groups @@ -152,7 +172,7 @@ public struct AutomaticCuration { return nil } - return (title: group.title, references: group.references.filter { $0 != node.reference }) + return (title: group.title, references: try filterReferences(group.references)) } } diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index 13bc48bee6..75cc9d7adc 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -661,15 +661,18 @@ public struct RenderNodeTranslator: SemanticVisitor { // Automatic groups are named after the child's kind, e.g. // "Methods", "Variables", etc. let alreadyCurated = Set(node.topicSections.flatMap { $0.identifiers }) - let groups = try! AutomaticCuration.topics(for: documentationNode, withTrait: nil, context: context) - .compactMap({ group -> AutomaticCuration.TaskGroup? in - // Remove references that have been already curated. - let newReferences = group.references.filter { !alreadyCurated.contains($0.absoluteString) } - // Remove groups that have no uncurated references - guard !newReferences.isEmpty else { return nil } - - return (title: group.title, references: newReferences) - }) + let groups = try! AutomaticCuration.topics( + for: documentationNode, + withTrait: trait, + context: context + ).compactMap { group -> AutomaticCuration.TaskGroup? in + // Remove references that have been already curated. + let newReferences = group.references.filter { !alreadyCurated.contains($0.absoluteString) } + // Remove groups that have no uncurated references + guard !newReferences.isEmpty else { return nil } + + return (title: group.title, references: newReferences) + } // Collect all child topic references. contentCompiler.collectedTopicReferences.append(contentsOf: groups.flatMap(\.references)) @@ -719,6 +722,7 @@ public struct RenderNodeTranslator: SemanticVisitor { // Automatic See Also section if let seeAlso = try! AutomaticCuration.seeAlso( for: documentationNode, + withTrait: trait, context: context, bundle: bundle, renderContext: renderContext, @@ -1309,6 +1313,7 @@ public struct RenderNodeTranslator: SemanticVisitor { // Curate the current node's siblings as further See Also groups. if let seeAlso = try! AutomaticCuration.seeAlso( for: documentationNode, + withTrait: trait, context: context, bundle: bundle, renderContext: renderContext, diff --git a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift index 3021cac3d7..0899a8beb4 100644 --- a/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift @@ -77,6 +77,47 @@ final class RenderIndexTests: XCTestCase { "title": "_MixedLanguageFrameworkVersionNumber", "type": "var" }, + { + "title": "Some Swift-only APIs, some Objective-C–only APIs, some mixed", + "type": "groupMarker" + }, + { + "path": "\/documentation\/mixedlanguageframework\/_mixedlanguageframeworkversionstring", + "title": "_MixedLanguageFrameworkVersionString", + "type": "var" + }, + { + "path": "/documentation/mixedlanguageframework/bar", + "title": "Bar", + "type": "class", + "children": [ + { + "title": "Type Methods", + "type": "groupMarker" + }, + { + "path": "/documentation/mixedlanguageframework/bar/mystringfunction(_:)", + "title": "myStringFunction:error: (navigator title)", + "type": "method", + "children": [ + { + "title": "Custom", + "type": "groupMarker" + }, + { + "title": "Foo", + "path": "/documentation/mixedlanguageframework/foo-occ.typealias", + "type": "typealias" + } + ] + } + ] + }, + { + "title": "Article", + "path": "/documentation/mixedlanguageframework/article", + "type": "article" + }, { "title": "Tutorials", "type": "groupMarker" @@ -141,33 +182,6 @@ final class RenderIndexTests: XCTestCase { "title": "Classes", "type": "groupMarker" }, - { - "children": [ - { - "title": "Type Methods", - "type": "groupMarker" - }, - { - "children": [ - { - "title": "Custom", - "type": "groupMarker" - }, - { - "path": "\/documentation\/mixedlanguageframework\/foo-occ.typealias", - "title": "Foo", - "type": "typealias" - } - ], - "path": "\/documentation\/mixedlanguageframework\/bar\/mystringfunction(_:)", - "title": "myStringFunction:error: (navigator title)", - "type": "method" - } - ], - "path": "\/documentation\/mixedlanguageframework\/bar", - "title": "Bar", - "type": "class" - }, { "path": "/documentation/mixedlanguageframework/mixedlanguageclassconformingtoprotocol", "title": "MixedLanguageClassConformingToProtocol", @@ -224,15 +238,6 @@ final class RenderIndexTests: XCTestCase { } ] }, - { - "title": "Variables", - "type": "groupMarker" - }, - { - "path": "\/documentation\/mixedlanguageframework\/_mixedlanguageframeworkversionstring", - "title": "_MixedLanguageFrameworkVersionString", - "type": "var" - }, { "title": "Enumerations", "type": "groupMarker" @@ -290,6 +295,36 @@ final class RenderIndexTests: XCTestCase { "title": "SwiftOnlyStruct", "type": "struct" }, + { + "title": "Some Swift-only APIs, some Objective-C–only APIs, some mixed", + "type": "groupMarker" + }, + { + "title": "SwiftOnlyClass", + "path": "/documentation/mixedlanguageframework/swiftonlyclass", + "type": "class" + }, + { + "path": "/documentation/mixedlanguageframework/bar", + "title": "Bar", + "type": "class", + "children": [ + { + "title": "Type Methods", + "type": "groupMarker" + }, + { + "title": "class func myStringFunction(String) throws -> String", + "path": "/documentation/mixedlanguageframework/bar/mystringfunction(_:)", + "type": "method" + } + ] + }, + { + "title": "Article", + "path": "/documentation/mixedlanguageframework/article", + "type": "article" + }, { "title": "Tutorials", "type": "groupMarker" @@ -365,22 +400,6 @@ final class RenderIndexTests: XCTestCase { "title": "Classes", "type": "groupMarker" }, - { - "children": [ - { - "title": "Type Methods", - "type": "groupMarker" - }, - { - "path": "\/documentation\/mixedlanguageframework\/bar\/mystringfunction(_:)", - "title": "class func myStringFunction(String) throws -> String", - "type": "method" - } - ], - "path": "\/documentation\/mixedlanguageframework\/bar", - "title": "Bar", - "type": "class" - }, { "path": "/documentation/mixedlanguageframework/mixedlanguageclassconformingtoprotocol", "title": "MixedLanguageClassConformingToProtocol", diff --git a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift index 6b00787731..88ce2ce49e 100644 --- a/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/AutomaticCurationTests.swift @@ -377,14 +377,15 @@ class AutomaticCurationTests: XCTestCase { "Classes", "/documentation/MixedLanguageFramework/Bar", "/documentation/MixedLanguageFramework/MixedLanguageClassConformingToProtocol", - + "/documentation/MixedLanguageFramework/SwiftOnlyClass", + "Protocols", "/documentation/MixedLanguageFramework/MixedLanguageProtocol", "Structures", "/documentation/MixedLanguageFramework/Foo-swift.struct", - // SwiftOnlyStruct is manually curated. + // SwiftOnlyStruct is manually curated in APICollection.md. // "/documentation/MixedLanguageFramework/SwiftOnlyStruct", ] ) @@ -409,7 +410,7 @@ class AutomaticCurationTests: XCTestCase { "Variables", - // _MixedLanguageFrameworkVersionNumber is manually curated. + // _MixedLanguageFrameworkVersionNumber is manually curated in APICollection.md. // "/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionNumber", "/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionString", diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift index 9edf7f10f9..4d16815799 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeMultiLanguageTests.swift @@ -24,6 +24,9 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { // Swift-only struct - ``SwiftOnlyStruct``: "s:22MixedLanguageFramework15SwiftOnlyStructV", + // Swift-only class - ``SwiftOnlyClass``: + "s:22MixedLanguageFramework15SwiftOnlyClassV", + // Member of Swift-only struct - ``SwiftOnlyStruct/tada()``: "s:22MixedLanguageFramework15SwiftOnlyStructV4tadayyF", @@ -86,6 +89,9 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { // Swift-only struct - ``SwiftOnlyStruct``: "s:22MixedLanguageFramework15SwiftOnlyStructV", + // Swift-only class - ``SwiftOnlyClass``: + "s:22MixedLanguageFramework15SwiftOnlyClassV", + // Member of Swift-only struct - ``SwiftOnlyStruct/tada()``: "s:22MixedLanguageFramework15SwiftOnlyStructV4tadayyF", @@ -158,12 +164,14 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { discussionSection: nil, topicSectionIdentifiers: [ "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftOnlyStruct", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftOnlyClass", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", "doc://org.swift.MixedLanguageFramework/tutorials/TutorialOverview", "doc://org.swift.MixedLanguageFramework/tutorials/MixedLanguageFramework/TutorialArticle", "doc://org.swift.MixedLanguageFramework/tutorials/MixedLanguageFramework/Tutorial", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/APICollection", - "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/MixedLanguageClassConformingToProtocol", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/MixedLanguageProtocol", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Foo-swift.struct", @@ -177,6 +185,7 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { "MixedLanguageFramework", "MixedLanguageFramework Tutorials", "MixedLanguageProtocol", + "SwiftOnlyClass", "SwiftOnlyStruct", "Tutorial", "Tutorial Article", @@ -186,6 +195,7 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { referenceFragments: [ "class Bar", "class MixedLanguageClassConformingToProtocol", + "class SwiftOnlyClass", "protocol MixedLanguageProtocol", "struct Foo", "struct SwiftOnlyStruct", @@ -210,15 +220,16 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { discussionSection: nil, topicSectionIdentifiers: [ "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionNumber", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionString", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", "doc://org.swift.MixedLanguageFramework/tutorials/TutorialOverview", "doc://org.swift.MixedLanguageFramework/tutorials/MixedLanguageFramework/TutorialArticle", "doc://org.swift.MixedLanguageFramework/tutorials/MixedLanguageFramework/Tutorial", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/APICollection", - "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/MixedLanguageClassConformingToProtocol", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/MixedLanguageProtocol", - "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionString", "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Foo-swift.struct", ], referenceTitles: [ @@ -230,6 +241,7 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { "MixedLanguageFramework", "MixedLanguageFramework Tutorials", "MixedLanguageProtocol", + "SwiftOnlyClass", "SwiftOnlyStruct", "Tutorial", "Tutorial Article", @@ -240,6 +252,7 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { "@interface Bar : NSObject", "MixedLanguageClassConformingToProtocol", "MixedLanguageProtocol", + "class SwiftOnlyClass", "struct Foo", "struct SwiftOnlyStruct", ], @@ -600,6 +613,76 @@ class SemaToRenderNodeMixedLanguageTests: ExperimentalObjectiveCTestCase { ] ) } + + func testAutomaticSeeAlsoOnlyShowsAPIsAvailableInParentsLanguageForSymbol() throws { + let outputConsumer = try mixedLanguageFrameworkConsumer() + + // Swift-only symbol. + XCTAssertEqual( + try outputConsumer.renderNode(withTitle: "SwiftOnlyClass") + .seeAlsoSections + .flatMap(\.identifiers), + [ + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", + ] + ) + + // Objective-C–only symbol. + XCTAssertEqual( + try outputConsumer.renderNode(withTitle: "_MixedLanguageFrameworkVersionString") + .seeAlsoSections + .flatMap(\.identifiers), + [ + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", + ] + ) + + // Swift variant of mixed-language symbol. + XCTAssertEqual( + try outputConsumer.renderNode(withTitle: "Bar") + .seeAlsoSections + .flatMap(\.identifiers), + [ + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftOnlyClass", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", + ] + ) + + // Objective-C variant of mixed-language symbol. + XCTAssertEqual( + try renderNodeApplyingObjectiveCVariantOverrides(to: outputConsumer.renderNode(withTitle: "Bar")) + .seeAlsoSections + .flatMap(\.identifiers), + [ + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionString", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Article", + ] + ) + + // Swift variant of mixed-language article. + XCTAssertEqual( + try outputConsumer.renderNode(withTitle: "Article") + .seeAlsoSections + .flatMap(\.identifiers), + [ + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/SwiftOnlyClass", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", + ] + ) + + // Objective-C variant of mixed-language article. + XCTAssertEqual( + try renderNodeApplyingObjectiveCVariantOverrides(to: outputConsumer.renderNode(withTitle: "Article")) + .seeAlsoSections + .flatMap(\.identifiers), + [ + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/_MixedLanguageFrameworkVersionString", + "doc://org.swift.MixedLanguageFramework/documentation/MixedLanguageFramework/Bar", + ] + ) + } func assertExpectedContent( _ renderNode: RenderNode, diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift index e29937f953..a650ab0fc0 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift @@ -618,31 +618,11 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { func testTopicsSectionVariants() throws { try assertMultiVariantSymbol( configureContext: { context, reference in - // Make MyProtocol available in Swift and Objective-C. - - let myProtocolReference = ResolvedTopicReference( - bundleIdentifier: reference.bundleIdentifier, - path: "/documentation/MyKit/MyProtocol", - sourceLanguage: .swift - ) - - let newMyProtocolReference = ResolvedTopicReference( + try makeSymbolAvailableInSwiftAndObjectiveC( + symbolPath: "/documentation/MyKit/MyProtocol", bundleIdentifier: reference.bundleIdentifier, - path: "/documentation/MyKit/MyProtocol", - sourceLanguages: [.swift, .objectiveC] - ) - - let myProtocolNode = try XCTUnwrap(context.topicGraph.nodes[myProtocolReference]) - - let newMyProtocolNode = TopicGraph.Node( - reference: newMyProtocolReference, - kind: myProtocolNode.kind, - source: myProtocolNode.source, - title: myProtocolNode.title + context: context ) - - context.topicGraph.nodes[myProtocolReference] = nil - context.topicGraph.nodes[newMyProtocolReference] = newMyProtocolNode }, configureSymbol: { symbol in symbol.automaticTaskGroupsVariants[.swift] = [] @@ -717,6 +697,100 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) } + func testArticleAutomaticTaskGroupsForArticleOnlyIncludeTopicsAvailableInTheArticleLanguage() throws { + func referenceWithPath(_ path: String) -> ResolvedTopicReference { + ResolvedTopicReference( + bundleIdentifier: "org.swift.docc.example", + path: path, + fragment: nil, + sourceLanguage: .swift + ) + } + + try assertMultiVariantArticle( + configureContext: { context, reference in + let articleTopicGraphNode = TopicGraph.Node( + reference: reference, + kind: .article, + source: .external, + title: "Article" + ) + + let myProtocolReference = referenceWithPath("/documentation/MyKit/MyProtocol") + let myClassReference = referenceWithPath("/documentation/MyKit/MyClass") + + let myProtocolTopicGraphNode = TopicGraph.Node( + reference: myProtocolReference, + kind: .protocol, + source: .external, + title: "MyProtocol" + ) + + let myClassTopicGraphNode = TopicGraph.Node( + reference: myClassReference, + kind: .protocol, + source: .external, + title: "MyProtocol" + ) + + // Remove MyProtocol and MyClass's parents and make them children of the article instead. + context.topicGraph.reverseEdges[myProtocolReference] = nil + context.topicGraph.reverseEdges[myClassReference] = nil + + context.topicGraph.addEdge( + from: articleTopicGraphNode, + to: myProtocolTopicGraphNode + ) + + context.topicGraph.addEdge( + from: articleTopicGraphNode, + to: myClassTopicGraphNode + ) + + try makeSymbolAvailableInSwiftAndObjectiveC( + symbolPath: "/documentation/MyKit/MyProtocol", + bundleIdentifier: reference.bundleIdentifier, + context: context + ) + + // Add an Objective-C kind to MyProtocol to make it a multi-language symbol. + try XCTUnwrap(context.documentationCache[myProtocolReference]?.semantic as? Symbol) + .kindVariants[.objectiveC] = SymbolGraph.Symbol.Kind( + parsedIdentifier: .protocol, + displayName: "Protocol" + ) + }, + configureArticle: { article in + article.automaticTaskGroups = [] + article.topics = nil + }, + assertOriginalRenderNode: { renderNode in + XCTAssertEqual( + renderNode.topicSections.flatMap { topicSection in + [topicSection.title] + topicSection.identifiers + }, + [ + "Classes", + "doc://org.swift.docc.example/documentation/MyKit/MyClass", + "Protocols", + "doc://org.swift.docc.example/documentation/MyKit/MyProtocol", + ] + ) + }, + assertAfterApplyingVariant: { renderNode in + XCTAssertEqual( + renderNode.topicSections.flatMap { topicSection in + [topicSection.title] + topicSection.identifiers + }, + [ + "Protocols", + "doc://org.swift.docc.example/documentation/MyKit/MyProtocol", + ] + ) + } + ) + } + func testTopicsSectionVariantsNoUserProvidedTopics() throws { try assertMultiVariantSymbol( configureSymbol: { symbol in @@ -810,6 +884,13 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { } try assertMultiVariantSymbol( + configureContext: { context, reference in + try makeSymbolAvailableInSwiftAndObjectiveC( + symbolPath: "/documentation/MyKit/MyProtocol", + bundleIdentifier: reference.bundleIdentifier, + context: context + ) + }, configureSymbol: { symbol in symbol.seeAlsoVariants[.swift] = makeSeeAlsoSection( destination: "doc://org.swift.docc.example/documentation/MyKit/MyProtocol" @@ -830,9 +911,9 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ) }, assertAfterApplyingVariant: { renderNode in - XCTAssertEqual(renderNode.seeAlsoSections.count, 1) + XCTAssertEqual(renderNode.seeAlsoSections.count, 2) let taskGroup = try XCTUnwrap(renderNode.seeAlsoSections.first) - XCTAssertEqual(taskGroup.title, "Basics") + XCTAssertEqual(taskGroup.title, "Related Documentation") XCTAssertEqual( taskGroup.identifiers, @@ -949,6 +1030,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { } private func assertMultiVariantArticle( + configureContext: (DocumentationContext, ResolvedTopicReference) throws -> () = { _, _ in }, configureArticle: (Article) throws -> () = { _ in }, configureRenderNodeTranslator: (inout RenderNodeTranslator) -> () = { _ in }, assertOriginalRenderNode: (RenderNode) throws -> (), @@ -963,6 +1045,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { sourceLanguage: .swift ) + try configureContext(context, identifier) context.documentationCache[identifier]?.availableSourceLanguages = [.swift, .objectiveC] let node = try context.entity(with: identifier) @@ -1059,7 +1142,7 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase { ]) } - private func makeSymbolAvailableOnSwiftAndObjectiveC( + private func makeSymbolAvailableInSwiftAndObjectiveC( symbolPath: String, bundleIdentifier: String, context: DocumentationContext diff --git a/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/MixedLanguageFramework.md b/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/MixedLanguageFramework.md index b3e124d367..84d84a75c8 100644 --- a/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/MixedLanguageFramework.md +++ b/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/MixedLanguageFramework.md @@ -12,6 +12,13 @@ This framework is available to both Swift and Objective-C clients. - +### Some Swift-only APIs, some Objective-C–only APIs, some mixed + +- ``SwiftOnlyClass`` +- +- ``Bar`` +- + ### Tutorials - diff --git a/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json b/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json index e8542ef31b..278ccd743b 100644 --- a/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json +++ b/Tests/SwiftDocCTests/Test Bundles/MixedLanguageFramework.docc/symbol-graphs/swift/MixedLanguageFramework.symbols.json @@ -364,6 +364,81 @@ } } }, + { + "kind": { + "identifier": "swift.class", + "displayName": "Class" + }, + "identifier": { + "precise": "s:22MixedLanguageFramework15SwiftOnlyClassV", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "SwiftOnlyClass" + ], + "names": { + "title": "SwiftOnlyClass", + "navigator": [ + { + "kind": "identifier", + "spelling": "SwiftOnlyClass" + } + ], + "subHeading": [ + { + "kind": "keyword", + "spelling": "class" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "SwiftOnlyClass" + } + ] + }, + "docComment": { + "lines": [ + { + "range": { + "start": { + "line": 20, + "character": 4 + }, + "end": { + "line": 20, + "character": 51 + } + }, + "text": "This is a class that is only exposed to Swift." + } + ] + }, + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "class" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "SwiftOnlyClass" + } + ], + "accessLevel": "public", + "location": { + "uri": "file://path/to/MixedLanguageFramework/MixedLanguageFramework/SwiftFile.swift", + "position": { + "line": 20, + "character": 14 + } + } + }, { "kind": { "identifier": "swift.method",