Skip to content

Populate symbol kind in external render nodes #1251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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 @@ -194,7 +194,12 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE
imageReferences: (resolvedInformation.references ?? []).compactMap { $0 as? ImageReference }
)

return LinkResolver.ExternalEntity(topicRenderReference: renderReference, renderReferenceDependencies: dependencies, sourceLanguages: resolvedInformation.availableLanguages)
return LinkResolver.ExternalEntity(
topicRenderReference: renderReference,
renderReferenceDependencies: dependencies,
sourceLanguages: resolvedInformation.availableLanguages,
documentationKind: resolvedInformation.kind
)
}

// MARK: Implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ final class ExternalPathHierarchyResolver {
return .init(
topicRenderReference: resolvedInformation.topicRenderReference(),
renderReferenceDependencies: dependencies,
sourceLanguages: resolvedInformation.availableLanguages
sourceLanguages: resolvedInformation.availableLanguages,
documentationKind: resolvedInformation.kind
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ package struct ExternalRenderNode {
}

/// The symbol kind of this documentation node.
///
/// This value is `nil` if the referenced page is not a symbol.
var symbolKind: SymbolGraph.Symbol.KindIdentifier? {
// Symbol kind information is not available for external entities
return nil
DocumentationNode.symbolKind(for: externalEntity.documentationKind)
}

/// The additional "role" assigned to the symbol, if any
Expand Down Expand Up @@ -116,7 +117,7 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation {
navigatorTitle: renderNode.navigatorTitleVariants.value(for: traits),
externalID: renderNode.externalIdentifier.identifier,
role: renderNode.role,
symbolKind: renderNode.symbolKind?.identifier,
symbolKind: renderNode.symbolKind?.renderingIdentifier,
images: renderNode.images
)
}
Expand Down Expand Up @@ -145,4 +146,4 @@ struct ExternalRenderNodeMetadataRepresentation: NavigatorIndexableRenderMetadat
var fragments: [DeclarationRenderSection.Token]? = nil
var roleHeading: String? = nil
var platforms: [AvailabilityRenderItem]? = nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ public class LinkResolver {
/// - topicRenderReference: The render reference for this external topic.
/// - renderReferenceDependencies: Any dependencies for the render reference.
/// - sourceLanguages: The different source languages for which this page is available.
/// - documentationKind: The kind of external content that's being referenced.
@_spi(ExternalLinks)
public init(topicRenderReference: TopicRenderReference, renderReferenceDependencies: RenderReferenceDependencies, sourceLanguages: Set<SourceLanguage>) {
public init(topicRenderReference: TopicRenderReference, renderReferenceDependencies: RenderReferenceDependencies, sourceLanguages: Set<SourceLanguage>, documentationKind: DocumentationNode.Kind) {
self.topicRenderReference = topicRenderReference
self.renderReferenceDependencies = renderReferenceDependencies
self.sourceLanguages = sourceLanguages
self.documentationKind = documentationKind
}

/// The render reference for this external topic.
Expand All @@ -63,7 +65,11 @@ public class LinkResolver {
var renderReferenceDependencies: RenderReferenceDependencies
/// The different source languages for which this page is available.
var sourceLanguages: Set<SourceLanguage>

/// The kind of external content that's being referenced.
///
/// For example, the navigator requires specific knowledge about what type of external symbol is being linked to.
var documentationKind: DocumentationNode.Kind

/// Creates a pre-render new topic content value to be added to a render context's reference store.
func topicContent() -> RenderReferenceStore.TopicContent {
return .init(
Expand Down
50 changes: 50 additions & 0 deletions Sources/SwiftDocC/Model/DocumentationNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ public struct DocumentationNode {
case .union: return .union
case .`var`: return .globalVariable
case .module: return .module
case .extension: return .extension
case .extendedModule: return .extendedModule
case .extendedStructure: return .extendedStructure
case .extendedClass: return .extendedClass
Expand All @@ -684,6 +685,55 @@ public struct DocumentationNode {
}
}

/// Returns a symbol kind for the given documentation node.
/// - Parameter symbol: A documentation node kind.
/// - Returns: A symbol graph symbol.
static func symbolKind(for kind: Kind) -> SymbolGraph.Symbol.KindIdentifier? {
switch kind {
case .associatedType: return .`associatedtype`
case .class: return .`class`
case .deinitializer: return .`deinit`
case .dictionary, .object: return .dictionary
case .dictionaryKey: return .dictionaryKey
case .enumeration: return .`enum`
case .enumerationCase: return .`case`
case .function: return .`func`
case .httpRequest: return .httpRequest
case .httpParameter: return .httpParameter
case .httpBody: return .httpBody
case .httpResponse: return .httpResponse
case .operator: return .`operator`
case .initializer: return .`init`
case .instanceVariable: return .ivar
case .macro: return .macro
case .instanceMethod: return .`method`
case .namespace: return .namespace
case .instanceProperty: return .`property`
case .protocol: return .`protocol`
case .snippet: return .snippet
case .structure: return .`struct`
case .instanceSubscript: return .`subscript`
case .typeMethod: return .`typeMethod`
case .typeProperty, .typeConstant: return .`typeProperty`
case .typeSubscript: return .`typeSubscript`
case .typeAlias, .typeDef: return .`typealias`
case .union: return .union
case .globalVariable, .localVariable: return .`var`
case .module: return .module
case .extension: return .extension
case .extendedModule: return .extendedModule
case .extendedStructure: return .extendedStructure
case .extendedClass: return .extendedClass
case .extendedEnumeration: return .extendedEnumeration
case .extendedProtocol: return .extendedProtocol
case .unknownExtendedType: return .unknownExtendedType
default:
// For non-symbol kinds (like .article, .tutorial, etc.),
// return nil since these don't have corresponding SymbolGraph.Symbol.KindIdentifier values
return nil
}
}

/// Initializes a documentation node to represent a symbol from a symbol graph.
///
/// - Parameters:
Expand Down
3 changes: 2 additions & 1 deletion Tests/SwiftDocCTests/Benchmark/ExternalTopicsHashTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class ExternalTopicsGraphHashTests: XCTestCase {
estimatedTime: nil
),
renderReferenceDependencies: .init(),
sourceLanguages: [.swift]
sourceLanguages: [.swift],
documentationKind: .class
)
return (reference, entity)
}
Expand Down
11 changes: 8 additions & 3 deletions Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class ExternalRenderNodeTests: XCTestCase {

XCTAssertEqual(externalRenderNodes[1].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/objCSymbol")
XCTAssertEqual(externalRenderNodes[1].kind, .symbol)
XCTAssertEqual(externalRenderNodes[1].symbolKind, nil)
XCTAssertEqual(externalRenderNodes[1].symbolKind, .func)
XCTAssertEqual(externalRenderNodes[1].role, "symbol")
XCTAssertEqual(externalRenderNodes[1].externalIdentifier.identifier, "doc://com.test.external/path/to/external/objCSymbol")

Expand All @@ -104,7 +104,7 @@ class ExternalRenderNodeTests: XCTestCase {

XCTAssertEqual(externalRenderNodes[3].identifier.absoluteString, "doc://org.swift.MixedLanguageFramework/example/path/to/external/swiftSymbol")
XCTAssertEqual(externalRenderNodes[3].kind, .symbol)
XCTAssertEqual(externalRenderNodes[3].symbolKind, nil)
XCTAssertEqual(externalRenderNodes[3].symbolKind, .class)
XCTAssertEqual(externalRenderNodes[3].role, "symbol")
XCTAssertEqual(externalRenderNodes[3].externalIdentifier.identifier, "doc://com.test.external/path/to/external/swiftSymbol")
}
Expand Down Expand Up @@ -135,7 +135,8 @@ class ExternalRenderNodeTests: XCTestCase {
navigatorTitleVariants: .init(defaultValue: navigatorTitle, objectiveCValue: occNavigatorTitle)
),
renderReferenceDependencies: .init(),
sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")])
sourceLanguages: [SourceLanguage(name: "swift"), SourceLanguage(name: "objc")],
documentationKind: .function)
let externalRenderNode = ExternalRenderNode(
externalEntity: externalEntity,
bundleIdentifier: "com.test.external"
Expand Down Expand Up @@ -208,6 +209,8 @@ class ExternalRenderNodeTests: XCTestCase {
XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"])
XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal))
XCTAssert(occExternalNodes.allSatisfy(\.isExternal))
XCTAssertEqual(swiftExternalNodes.map(\.type), ["article", "class"])
XCTAssertEqual(occExternalNodes.map(\.type), ["article", "func"])
}

func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() throws {
Expand Down Expand Up @@ -268,5 +271,7 @@ class ExternalRenderNodeTests: XCTestCase {
XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCSymbol"])
XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal))
XCTAssert(occExternalNodes.allSatisfy(\.isExternal))
XCTAssertEqual(swiftExternalNodes.map(\.type), ["article"])
XCTAssertEqual(occExternalNodes.map(\.type), ["func"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ class ExternalReferenceResolverTests: XCTestCase {
}
),
renderReferenceDependencies: RenderReferenceDependencies(),
sourceLanguages: [resolvedEntityLanguage]
sourceLanguages: [resolvedEntityLanguage],
documentationKind: resolvedEntityKind
)
}
}
Expand Down Expand Up @@ -707,7 +708,8 @@ class ExternalReferenceResolverTests: XCTestCase {
estimatedTime: nil
),
renderReferenceDependencies: RenderReferenceDependencies(),
sourceLanguages: [.swift]
sourceLanguages: [.swift],
documentationKind: .instanceProperty
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ class TestMultiResultExternalReferenceResolver: ExternalDocumentationSource {
images: entityInfo.topicImages?.map(\.0) ?? []
),
renderReferenceDependencies: dependencies,
sourceLanguages: [entityInfo.language]
sourceLanguages: [entityInfo.language],
documentationKind: entityInfo.kind
)
}
}
35 changes: 35 additions & 0 deletions Tests/SwiftDocCTests/Model/DocumentationNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import Foundation
import Markdown
@testable import SwiftDocC
import SymbolKit
import XCTest

class DocumentationNodeTests: XCTestCase {
Expand Down Expand Up @@ -41,4 +42,38 @@ class DocumentationNodeTests: XCTestCase {
XCTAssertEqual(anchorSection.reference, node.reference.withFragment(expectedTitle))
}
}

func testDocumentationKindToSymbolKindMapping() throws {
// Testing all symbol kinds map to a documentation kind
for symbolKind in SymbolGraph.Symbol.KindIdentifier.allCases {
let documentationKind = DocumentationNode.kind(forKind: symbolKind)
guard documentationKind != .unknown else {
continue
}

let roundtrippedSymbolKind = DocumentationNode.symbolKind(for: documentationKind)
XCTAssertEqual(symbolKind, roundtrippedSymbolKind)
}

// Testing that documentation kinds correctly map to a symbol kind
// Sometimes there are multiple mappings from DocumentationKind -> SymbolKind, exclude those here and test them separately
let documentationKinds = DocumentationNode.Kind.allKnownValues
.filter({ ![.localVariable, .typeDef, .typeConstant, .`keyword`, .tag, .object].contains($0) })
for documentationKind in documentationKinds {
let symbolKind = DocumentationNode.symbolKind(for: documentationKind)
if documentationKind.isSymbol {
let symbolKind = try XCTUnwrap(DocumentationNode.symbolKind(for: documentationKind), "Expected a symbol kind equivalent for \(documentationKind)")
let rountrippedDocumentationKind = DocumentationNode.kind(forKind: symbolKind)
XCTAssertEqual(documentationKind, rountrippedDocumentationKind)
} else {
XCTAssertNil(symbolKind)
}
}

// Test the exception documentation kinds
XCTAssertEqual(DocumentationNode.symbolKind(for: .localVariable), .var)
XCTAssertEqual(DocumentationNode.symbolKind(for: .typeDef), .typealias)
XCTAssertEqual(DocumentationNode.symbolKind(for: .typeConstant), .typeProperty)
XCTAssertEqual(DocumentationNode.symbolKind(for: .object), .dictionary)
}
}
6 changes: 4 additions & 2 deletions Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,8 @@ class SemaToRenderNodeTests: XCTestCase {
estimatedTime: nil
),
renderReferenceDependencies: .init(),
sourceLanguages: [.objectiveC]
sourceLanguages: [.objectiveC],
documentationKind: .class
)
return (reference, entity)
}
Expand Down Expand Up @@ -1218,7 +1219,8 @@ class SemaToRenderNodeTests: XCTestCase {
estimatedTime: nil
),
renderReferenceDependencies: .init(),
sourceLanguages: [.swift]
sourceLanguages: [.swift],
documentationKind: .collection
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class OutOfProcessReferenceResolverTests: XCTestCase {
} else {
XCTFail("Unexpected fragments variant patch")
}

XCTAssertEqual(entity.documentationKind, .init(name: "Kind Name", id: "com.test.kind.id", isSymbol: true))
}

func testResolvingTopicLinkProcess() throws {
Expand Down