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
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2021 Apple Inc. and the Swift project authors
Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -40,25 +40,35 @@ enum GeneratedDocumentationTopics {
/// - reference: The parent type reference.
/// - originDisplayName: The origin display name as provided by the symbol graph.
/// - extendedModuleName: Extended module name.
mutating func add(_ childReference: ResolvedTopicReference, to reference: ResolvedTopicReference, originDisplayName: String, extendedModuleName: String) throws {
// Detect the path components of the providing the default implementation.
let typeComponents = originDisplayName.components(separatedBy: ".")

// Verify that the fully qualified name contains at least a type name and default implementation name.
guard typeComponents.count >= 2 else { return }

mutating func add(_ childReference: ResolvedTopicReference, to reference: ResolvedTopicReference, originDisplayName: String, originParentSymbol: ResolvedTopicReference?, extendedModuleName: String) throws {
let fromType: String
let typeSimpleName: String
if let originParentSymbol = originParentSymbol, !originParentSymbol.pathComponents.isEmpty {
// If we have a resolved symbol for the parent of `sourceOrigin`, use that for the names
fromType = originParentSymbol.pathComponents.joined(separator: ".")
typeSimpleName = originParentSymbol.pathComponents.last!
} else {
// If we don't have a resolved `sourceOrigin` parent, fall back to parsing its display name

// Detect the path components of the providing the default implementation.
let typeComponents = originDisplayName.split(separator: ".")

// Verify that the fully qualified name contains at least a type name and default implementation name.
guard typeComponents.count >= 2 else { return }

// Get the fully qualified type.
fromType = typeComponents.dropLast().joined(separator: ".")
// The name of the type is second to last.
typeSimpleName = String(typeComponents[typeComponents.count-2])
}

// Create a type with inherited symbols, if needed.
if !implementingTypes.keys.contains(reference) {
implementingTypes[reference] = Collections()
}

// Get the fully qualified type.
let fromType = typeComponents.dropLast().joined(separator: ".")

// Create a new default implementations provider, if needed.
if !implementingTypes[reference]!.inheritedFromTypeName.keys.contains(fromType) {
// The name of the type is second to last.
let typeSimpleName = typeComponents[typeComponents.count-2]
implementingTypes[reference]!.inheritedFromTypeName[fromType] = Collections.APICollection(title: "\(typeSimpleName) Implementations", parentReference: reference)
}

Expand Down Expand Up @@ -215,8 +225,12 @@ enum GeneratedDocumentationTopics {
let child = context.symbolIndex[relationship.source],
// Get the swift extension data
let extends = child.symbol?.mixins[SymbolGraph.Symbol.Swift.Extension.mixinKey] as? SymbolGraph.Symbol.Swift.Extension {
var originParentSymbol: ResolvedTopicReference? = nil
if let originSymbol = context.symbolIndex[origin.identifier] {
originParentSymbol = try? symbolsURLHierarchy.parent(of: originSymbol.reference)
}
// Add the inherited symbol to the index.
try inheritanceIndex.add(child.reference, to: parent.reference, originDisplayName: origin.displayName, extendedModuleName: extends.extendedModule)
try inheritanceIndex.add(child.reference, to: parent.reference, originDisplayName: origin.displayName, originParentSymbol: originParentSymbol, extendedModuleName: extends.extendedModule)
}
}

Expand Down
33 changes: 32 additions & 1 deletion Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2021 Apple Inc. and the Swift project authors
Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -623,6 +623,37 @@ class RenderNodeTranslatorTests: XCTestCase {
}

}

/// Verify that symbols with ellipsis operators don't get curated into an unnamed protocol implementation section.
func testAutomaticImplementationsWithExtraDots() throws {
let fancyProtocolSGFURL = Bundle.module.url(
forResource: "FancyProtocol.symbols", withExtension: "json", subdirectory: "Test Resources")!

// Create a test bundle copy with the symbol graph from above
let (_, bundle, context) = try testBundleAndContext(copying: "TestBundle", excludingPaths: [], codeListings: [:]) { url in
try? FileManager.default.copyItem(at: fancyProtocolSGFURL, to: url.appendingPathComponent("FancyProtocol.symbols.json"))
}

let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/FancyProtocol/SomeClass", sourceLanguage: .swift)
var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference, source: nil)
let node = try context.entity(with: reference)
let symbol = try XCTUnwrap(node.semantic as? Symbol)
let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode)

let defaultImplementationSection = try XCTUnwrap(renderNode.topicSections.first(where: { $0.title == "Default Implementations" }))
XCTAssertEqual(defaultImplementationSection.identifiers, [
"doc://org.swift.docc.example/documentation/FancyProtocol/SomeClass/Comparable-Implementations",
"doc://org.swift.docc.example/documentation/FancyProtocol/SomeClass/Equatable-Implementations",
"doc://org.swift.docc.example/documentation/FancyProtocol/SomeClass/FancyProtocol-Implementations",
])
let implReferences = defaultImplementationSection.identifiers.compactMap({ renderNode.references[$0] as? TopicRenderReference })
XCTAssertEqual(implReferences.map({ $0.title }), [
"Comparable Implementations",
"Equatable Implementations",
"FancyProtocol Implementations",
])

}

func testAutomaticTaskGroupTopicsAreSorted() throws {
let (bundle, context) = try testBundleAndContext(named: "DefaultImplementations")
Expand Down
Loading