Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -125,6 +132,19 @@ public struct AutomaticCuration {

let parentReference = canonicalPath.last!

func filterReferences(_ references: [ResolvedTopicReference]) throws -> [ResolvedTopicReference] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Is there a more specific name we can use to make this more readable at the call-site?

Suggested change
func filterReferences(_ references: [ResolvedTopicReference]) throws -> [ResolvedTopicReference] {
func referencesAvailableInCurrentTrait(in references: [ResolvedTopicReference]) throws -> [ResolvedTopicReference] {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also filters out the current reference from the task group, though. We could also consider making this function only handle the trait-based filtering, but the logic to filter out the current reference would then be duplicated.

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)
Copy link
Contributor

@ethan-kusters ethan-kusters Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be adopting this logic in the topics(for:withTrait:context:) function above? It seems cleaner than only relying on the kind being available for a given trait but maybe I'm missing something. (I'm not entirely sure what availableVariantTraits is pulled from.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but we need to get the appropriate language-specific kind of the symbol anyway in that case. If we ported this logic to topics(for:withTrait:context:), we'd check the languages in DocumentationNode. availableVariantTraits but also need to verify that the symbol has kindVariants for each of these languages which seems redundant. Compilers are expected to produce the kind field for their symbols, so DocC shouldn't be in a case where a symbol is available in multiple languages, but the symbol only has 1 kind value.

DocumentationNode.availableVariantTraits is populated from this logic: https://github.com/apple/swift-docc/blob/main/Sources/SwiftDocC/Infrastructure/Symbol%20Graph/SymbolReference.swift#L174

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compilers are expected to produce the kind field for their symbols, so DocC shouldn't be in a case where a symbol is available in multiple languages, but the symbol only has 1 kind value.

What if we have the same kind in multiple languages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The symbol would still have multiple kind entries, with the same value.

.availableVariantTraits
.contains(variantsTrait)
}
}

// Look up the render context first
if let taskGroups = renderContext?.store.content(for: parentReference)?.taskGroups,
let linkingGroup = taskGroups
Expand All @@ -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 }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit since this is a little out-of-scope, but the existing logic seems a little hacky to me and would break if we ever stop including the current reference in the linking group.

Suggested change
guard linkingGroup.references.count > 1 else { return nil }
let references = try filterReferences(linkingGroup.references)
guard !references.isEmpty else { return nil }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The automatic curation logic here finds a task group that contains this reference, removing this reference from the task group. The count > 1 is essentially to not emit the task group at all if the only reference it contains is this reference.

Copy link
Contributor

@ethan-kusters ethan-kusters Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right I see that- I'm just suggesting we do the filtering ahead of time so we can gate on the already filtered group being empty or not.

let references = try filterReferences(linkingGroup.references)
guard !references.isEmpty else { return nil }
return (title: linkingGroup.title, references: references)

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
Expand All @@ -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))
}
}

Expand Down
23 changes: 14 additions & 9 deletions Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
123 changes: 71 additions & 52 deletions Tests/SwiftDocCTests/Indexing/RenderIndexTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -224,15 +238,6 @@ final class RenderIndexTests: XCTestCase {
}
]
},
{
"title": "Variables",
"type": "groupMarker"
},
{
"path": "\/documentation\/mixedlanguageframework\/_mixedlanguageframeworkversionstring",
"title": "_MixedLanguageFrameworkVersionString",
"type": "var"
},
{
"title": "Enumerations",
"type": "groupMarker"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
)
Expand All @@ -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",
Expand Down
Loading