Skip to content

Commit

Permalink
Merge branch 'main' into readable-symbol-link-disambiguation
Browse files Browse the repository at this point in the history
  • Loading branch information
d-ronnqvist committed Dec 11, 2023
2 parents 7341760 + 7b5f149 commit 9605330
Show file tree
Hide file tree
Showing 26 changed files with 1,109 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"branch" : "main",
"revision" : "31ee554ce4bed5fa05eea36bc30296f7d4149097"
"revision" : "3889b9673fcf1f1e9651b9143289b6ede462c958"
}
},
{
Expand Down
35 changes: 28 additions & 7 deletions Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2547,25 +2547,46 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
throw ContextError.notFound(reference.url)
}

/// Returns the set of languages the entity corresponding to the given reference is available in.
///
/// - Precondition: The entity associated with the given reference must be registered in the context.
public func sourceLanguages(for reference: ResolvedTopicReference) -> Set<SourceLanguage> {
private func knownEntityValue<Result>(
reference: ResolvedTopicReference,
valueInLocalEntity: (DocumentationNode) -> Result,
valueInExternalEntity: (LinkResolver.ExternalEntity) -> Result
) -> Result {
do {
// Look up the entity without its fragment. The documentation context does not keep track of page sections
// as nodes, and page sections are considered to be available in the same languages as the page they're
// defined in.
let referenceWithoutFragment = reference.withFragment(nil)
return try entity(with: referenceWithoutFragment).availableSourceLanguages
return try valueInLocalEntity(entity(with: referenceWithoutFragment))
} catch ContextError.notFound {
if let externalEntity = externalCache[reference] {
return externalEntity.sourceLanguages
return valueInExternalEntity(externalEntity)
}
preconditionFailure("Reference does not have an associated documentation node.")
} catch {
fatalError("Unexpected error when retrieving source languages: \(error)")
fatalError("Unexpected error when retrieving entity: \(error)")
}
}

/// Returns the set of languages the entity corresponding to the given reference is available in.
///
/// - Precondition: The entity associated with the given reference must be registered in the context.
public func sourceLanguages(for reference: ResolvedTopicReference) -> Set<SourceLanguage> {
knownEntityValue(
reference: reference,
valueInLocalEntity: \.availableSourceLanguages,
valueInExternalEntity: \.sourceLanguages
)
}

/// Returns whether the given reference corresponds to a symbol.
func isSymbol(reference: ResolvedTopicReference) -> Bool {
knownEntityValue(
reference: reference,
valueInLocalEntity: { node in node.kind.isSymbol },
valueInExternalEntity: { entity in entity.topicRenderReference.kind == .symbol }
)
}

// MARK: - Relationship queries

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,18 @@ struct PathHierarchy {

var topLevelCandidates = nodes
for relationship in graph.relationships where relationship.kind.formsHierarchy {
guard let sourceNode = nodes[relationship.source] else {
guard let sourceNode = nodes[relationship.source], let expectedContainerName = sourceNode.symbol?.pathComponents.dropLast().last else {
continue
}
if let targetNode = nodes[relationship.target] {
// The relationship only specify the target symbol's USR but if the target symbol has different representations in different source languages the relationship
// alone doesn't specify which language representation the source symbol belongs to. We could check the source and target symbol's interface language but that
// would require that we redundantly create multiple nodes for the same symbol in many common cases and then merge them. To avoid doing that, we instead check
// the source symbol's path components to find the correct target symbol by matching its name.
if let targetNode = nodes[relationship.target], targetNode.name == expectedContainerName {
targetNode.add(symbolChild: sourceNode)
topLevelCandidates.removeValue(forKey: relationship.source)
} else if let targetNodes = allNodes[relationship.target] {
for targetNode in targetNodes {
for targetNode in targetNodes where targetNode.name == expectedContainerName {
targetNode.add(symbolChild: sourceNode)
}
topLevelCandidates.removeValue(forKey: relationship.source)
Expand Down Expand Up @@ -630,7 +634,7 @@ private extension SymbolGraph.Relationship.Kind {
/// Whether or not this relationship kind forms a hierarchical relationship between the source and the target.
var formsHierarchy: Bool {
switch self {
case .memberOf, .requirementOf, .optionalRequirementOf, .extensionTo, .declaredIn:
case .memberOf, .optionalMemberOf, .requirementOf, .optionalRequirementOf, .extensionTo, .declaredIn:
return true
default:
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ extension AutomaticCuration {
case .ivar: return "Instance Variables"
case .macro: return "Macros"
case .`method`: return "Instance Methods"
case .namespace: return "Namespaces"
case .`property`: return "Instance Properties"
case .`protocol`: return "Protocols"
case .`struct`: return "Structures"
Expand All @@ -230,6 +231,8 @@ extension AutomaticCuration {
/// Add a symbol kind to `KindIdentifier.noPageKinds` if it should not generate a page in the
/// documentation hierarchy.
static let groupKindOrder: [SymbolGraph.Symbol.KindIdentifier] = [
.namespace,

.`class`,
.`protocol`,
.`struct`,
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftDocC/Model/DocumentationNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ public struct DocumentationNode {
case .ivar: return .instanceVariable
case .macro: return .macro
case .`method`: return .instanceMethod
case .namespace: return .namespace
case .`property`: return .instanceProperty
case .`protocol`: return .protocol
case .snippet: return .snippet
Expand Down
6 changes: 4 additions & 2 deletions Sources/SwiftDocC/Model/Kind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ extension DocumentationNode.Kind {
public static let dictionary = DocumentationNode.Kind(name: "Dictionary", id: "org.swift.docc.kind.dictionary", isSymbol: true)
/// Documentation about an HTTP request.
public static let httpRequest = DocumentationNode.Kind(name: "HTTP Request", id: "org.swift.docc.kind.httpRequest", isSymbol: true)

/// Documentation about a namespace.
public static let namespace = DocumentationNode.Kind(name: "Namespace", id: "org.swift.docc.kind.namespace", isSymbol: true)

// Leaves

/// Documentation about a local variable.
Expand Down Expand Up @@ -192,7 +194,7 @@ extension DocumentationNode.Kind {
// Conceptual
.root, .module, .article, .sampleCode, .technologyOverview, .volume, .chapter, .tutorial, .tutorialArticle, .onPageLandmark,
// Containers
.class, .structure, .enumeration, .protocol, .technology, .extension, .dictionary, .httpRequest,
.class, .structure, .enumeration, .protocol, .technology, .extension, .dictionary, .httpRequest, .namespace,
// Leaves
.localVariable, .globalVariable, .typeAlias, .typeDef, .typeConstant, .associatedType, .function, .operator, .macro, .union,
// Member-only leaves
Expand Down
40 changes: 15 additions & 25 deletions Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -795,13 +795,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
renderer: contentRenderer
) {
contentCompiler.collectedTopicReferences.append(contentsOf: seeAlso.references)
seeAlsoSections.append(TaskGroupRenderSection(
title: seeAlso.title,
abstract: nil,
discussion: nil,
identifiers: seeAlso.references.map { $0.absoluteString },
generated: true
))
seeAlsoSections.append(TaskGroupRenderSection(taskGroup: seeAlso))
}

return seeAlsoSections
Expand Down Expand Up @@ -1022,12 +1016,12 @@ public struct RenderNodeTranslator: SemanticVisitor {
contentCompiler: inout RenderContentCompiler
) -> [TaskGroupRenderSection] {
return topics.taskGroups.compactMap { group in
let supportedLanguages = Set(group.directives.compactMap {
let supportedLanguages = group.directives[SupportedLanguage.directiveName]?.compactMap {
SupportedLanguage(from: $0, source: nil, for: bundle, in: context)?.language
})
}

// If the task group has a set of supported languages, see if it should render for the allowed traits.
guard supportedLanguages.isEmpty || supportedLanguages.matchesOneOf(traits: allowedTraits) else {
if supportedLanguages?.matchesOneOf(traits: allowedTraits) == false {
return nil
}

Expand All @@ -1048,6 +1042,13 @@ public struct RenderNodeTranslator: SemanticVisitor {
return true
}

guard context.isSymbol(reference: reference) else {
// If the reference corresponds to any kind except Symbol
// (e.g., Article, Tutorial, SampleCode...), allow the topic
// to appear independently of the source language it belongs to.
return true
}

let referenceSourceLanguageIDs = Set(context.sourceLanguages(for: reference).map(\.id))

let availableSourceLanguageTraits = Set(availableTraits.compactMap(\.interfaceLanguage))
Expand Down Expand Up @@ -1491,7 +1492,7 @@ public struct RenderNodeTranslator: SemanticVisitor {

var sections = [TaskGroupRenderSection]()
if let topics = topics, !topics.taskGroups.isEmpty {
// Allowed traits should be all traits except the reverse of the objc/swift pairing
// Allowed symbol traits should be all traits except the reverse of the objc/swift pairing
sections.append(
contentsOf: renderGroups(
topics,
Expand Down Expand Up @@ -1524,12 +1525,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
guard !newReferences.isEmpty else { return nil }

contentCompiler.collectedTopicReferences.append(contentsOf: newReferences)
return TaskGroupRenderSection(
title: group.title,
abstract: nil,
discussion: nil,
identifiers: newReferences.map { $0.absoluteString }
)
return TaskGroupRenderSection(taskGroup: (title: group.title, references: newReferences))
})

// Place "bottom" rendering preference automatic task groups
Expand Down Expand Up @@ -1621,13 +1617,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
), !seeAlso.references.isEmpty {
contentCompiler.collectedTopicReferences.append(contentsOf: seeAlso.references)
seeAlsoSections.append(
TaskGroupRenderSection(
title: seeAlso.title,
abstract: nil,
discussion: nil,
identifiers: seeAlso.references.map { $0.absoluteString },
generated: true
)
TaskGroupRenderSection(taskGroup: seeAlso)
)
}

Expand Down Expand Up @@ -1978,7 +1968,7 @@ extension ContentLayout: RenderTree {}

extension ContentRenderSection: RenderTree {}

private extension Set where Element == SourceLanguage {
private extension Sequence where Element == SourceLanguage {
func matchesOneOf(traits: Set<DocumentationDataVariantsTrait>) -> Bool {
traits.contains(where: {
guard let languageID = $0.interfaceLanguage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import Foundation
import SymbolKit

/// Translates a symbol's declaration into a render node's Declarations section.
struct DeclarationsSectionTranslator: RenderSectionTranslator {
Expand All @@ -23,27 +24,28 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
guard !declaration.isEmpty else {
return nil
}


func translateFragment(_ token: SymbolGraph.Symbol.DeclarationFragments.Fragment) -> DeclarationRenderSection.Token {
// Create a reference if one found
var reference: ResolvedTopicReference?
if let preciseIdentifier = token.preciseIdentifier,
let resolved = renderNodeTranslator.context.symbolIndex[preciseIdentifier] {
reference = resolved

// Add relationship to render references
renderNodeTranslator.collectedTopicReferences.append(resolved)
}

// Add the declaration token
return DeclarationRenderSection.Token(fragment: token, identifier: reference?.absoluteString)
}

var declarations = [DeclarationRenderSection]()
for pair in declaration {
let (platforms, declaration) = pair

let renderedTokens = declaration.declarationFragments.map { token -> DeclarationRenderSection.Token in

// Create a reference if one found
var reference: ResolvedTopicReference?
if let preciseIdentifier = token.preciseIdentifier,
let resolved = renderNodeTranslator.context.symbolIndex[preciseIdentifier] {
reference = resolved

// Add relationship to render references
renderNodeTranslator.collectedTopicReferences.append(resolved)
}

// Add the declaration token
return DeclarationRenderSection.Token(fragment: token, identifier: reference?.absoluteString)
}

let renderedTokens = declaration.declarationFragments.map(translateFragment)

let platformNames = platforms.sorted { (lhs, rhs) -> Bool in
guard let lhsValue = lhs, let rhsValue = rhs else {
return lhs == nil
Expand All @@ -59,7 +61,28 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
)
)
}


if let alternateDeclarations = symbol.alternateDeclarationVariants[trait] {
for pair in alternateDeclarations {
let (platforms, decls) = pair
for alternateDeclaration in decls {
let renderedTokens = alternateDeclaration.declarationFragments.map(translateFragment)

let platformNames = platforms
.compactMap { $0 }
.sorted(by: \.rawValue)

declarations.append(
DeclarationRenderSection(
languages: [trait.interfaceLanguage ?? renderNodeTranslator.identifier.sourceLanguage.id],
platforms: platformNames,
tokens: renderedTokens
)
)
}
}
}

return DeclarationsRenderSection(declarations: declarations)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ extension TaskGroupRenderSection {
self.title = group.title
self.abstract = nil
self.identifiers = group.references.map({ $0.absoluteString })
self.generated = false
self.generated = true
self.discussion = nil
}
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftDocC/Model/SourceLanguage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ public struct SourceLanguage: Hashable, Codable {
"objective-c",
"objc",
"c", // FIXME: DocC should display C as its own language (github.com/apple/swift-docc/issues/169).
"c++", // FIXME: DocC should display C++ and Objective-C++ as their own languages (https://github.com/apple/swift-docc/issues/767)
"objective-c++",
"objc++",
"occ++",
],
linkDisambiguationID: "c"
)
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftDocC/Model/TaskGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ public struct TaskGroup {
self.originalContent = content
}

var directives: [BlockDirective] {
originalContent.compactMap { $0 as? BlockDirective }
var directives: [String: [BlockDirective]] {
.init(grouping: originalContent.compactMap { $0 as? BlockDirective }, by: \.name)
}
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/SwiftDocC/Semantics/Symbol/Symbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,10 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
public var declarationVariants = DocumentationDataVariants<[[PlatformName?]: SymbolGraph.Symbol.DeclarationFragments]>(
defaultVariantValue: [:]
)


/// The symbols alternate declarations in each language variant the symbol is available in.
public var alternateDeclarationVariants = DocumentationDataVariants<[[PlatformName?]: [SymbolGraph.Symbol.DeclarationFragments]]>()

public var locationVariants = DocumentationDataVariants<SymbolGraph.Symbol.Location>()

/// The symbol's availability or conformance constraints, in each language variant the symbol is available in.
Expand Down Expand Up @@ -307,6 +310,8 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
self.locationVariants[trait] = location
case let spi as SymbolGraph.Symbol.SPI:
self.isSPIVariants[trait] = spi.isSPI
case let alternateDeclarations as SymbolGraph.Symbol.AlternateDeclarations:
self.alternateDeclarationVariants[trait] = [[platformNameVariants[trait]]: alternateDeclarations.declarations]
default: break;
}
}
Expand Down

0 comments on commit 9605330

Please sign in to comment.