From 64066976fef4d67e8ad7a330e7de1f3633dd5d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 14 May 2024 11:29:35 +0200 Subject: [PATCH 1/7] Fix a navigator index performance for mixed Swift/Objective-C projects rdar://127759734 --- .../Navigator/NavigatorIndex+Ext.swift | 59 +---- .../Indexing/Navigator/NavigatorIndex.swift | 83 ++++--- .../Navigator/RenderNode+NavigatorIndex.swift | 214 ++++++++++++++++++ .../Rendering/References/TopicImage.swift | 8 - .../Variants/VariantPatchOperation.swift | 82 +++++++ .../Indexing/NavigatorIndexTests.swift | 73 ++++++ 6 files changed, 427 insertions(+), 92 deletions(-) create mode 100644 Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift index f4ed35223e..659f960c41 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift @@ -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-2024 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 @@ -75,61 +75,10 @@ public class FileSystemRenderNodeProvider: RenderNodeProvider { } extension RenderNode { - private static let typesThatShouldNotUseNavigatorTitle: Set = [ - .framework, .class, .structure, .enumeration, .protocol, .typeAlias, .associatedType, .extension - ] - - /// Returns a navigator title preferring the fragments inside the metadata, if applicable. - func navigatorTitle() -> String? { - let fragments: [DeclarationRenderSection.Token]? - - // FIXME: Use `metadata.navigatorTitle` for all Swift symbols (github.com/apple/swift-docc/issues/176). - if identifier.sourceLanguage == .swift || (metadata.navigatorTitle ?? []).isEmpty { - let pageType = navigatorPageType() - guard !Self.typesThatShouldNotUseNavigatorTitle.contains(pageType) else { - return metadata.title - } - fragments = metadata.fragments - } else { - fragments = metadata.navigatorTitle - } - - return fragments?.map(\.text).joined() ?? metadata.title - } - /// Returns the NavigatorIndex.PageType indicating the type of the page. + @_disfavoredOverload + @available(*, deprecated, message: "This deprecated API will be removed after 6.1 is released") public func navigatorPageType() -> NavigatorIndex.PageType { - - // This is a workaround to support plist keys. - if let roleHeading = metadata.roleHeading?.lowercased() { - if roleHeading == "property list key" { - return .propertyListKey - } else if roleHeading == "property list key reference" { - return .propertyListKeyReference - } - } - - switch self.kind { - case .article: - if let role = metadata.role { - return NavigatorIndex.PageType(role: role) - } - return NavigatorIndex.PageType.article - case .tutorial: - return NavigatorIndex.PageType.tutorial - case .section: - return NavigatorIndex.PageType.section - case .overview: - return NavigatorIndex.PageType.overview - case .symbol: - if let symbolKind = metadata.symbolKind { - return NavigatorIndex.PageType(symbolKind: symbolKind) - } - if let role = metadata.role { - return NavigatorIndex.PageType(role: role) - } - return NavigatorIndex.PageType.symbol - } + return (self as any NavigatorIndex.Builder.IndexableRenderNodeRepresentation).navigatorPageType() } - } diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift index 7aa26b6161..78cdfe7ee3 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift @@ -618,7 +618,57 @@ extension NavigatorIndex { /// Index a single render `RenderNode`. /// - Parameter renderNode: The render node to be indexed. public func index(renderNode: RenderNode) throws { + // Always index the main render node representation + let language = try index(renderNode, traits: nil) + // Additionally, for Swift want to also index the Objective-C variant, if there is any. + guard language == .swift else { + return + } + + // Check if the render node has an Objective-C representation + guard let objCVariantTrait = renderNode.variants?.flatMap(\.traits).first(where: { trait in + switch trait { + case .interfaceLanguage(let language): + return InterfaceLanguage.from(string: language) == .objc + } + }) else { + return + } + + // A render node holds is structured differently depending on if it was created by "rendering" a documentation node + // or if it was deserialized from a documentation archive. + // + // If it was created by rendering a documentation node, all variant information is stored in each individual variant collection and the variant overrides are nil. + // If it was deserialized from a documentation archive, all variant information is stored in the variant overrides and the variant collections are empty. + + // Operating on the variant override is _significantly_ slower, so we only take that code path if we have to. + // The only reason why this code path still exists is to support the `docc process-archive index` command, which creates an navigation index from an already build documentation archive. + if let overrides = renderNode.variantOverrides, !overrides.isEmpty { + // This code looks peculiar and very inefficient because it is. + // I didn't write it and I really wanted to remove it, but it's the only way to support the `docc process-archive index` command for now. + // rdar://128050800 Tracks fixing the inefficiencies with this code, to make `docc process-archive index` command as fast as indexing during a `docc convert` command. + // + // First, it encodes the render node, which was read from a file, back to data; because that's what the overrides applier operates on + let encodedRenderNode = try renderNode.encodeToJSON() + // Second, the overrides applier will decode that data into an abstract JSON representation of arrays, dictionaries, string, numbers, etc. + // After that the overrides applier loops over all the JSON patches and applies them to the abstract JSON representation. + // With all the patches applies, the overrides applier encodes the abstract JSON representation into data again and returns it. + let transformedData = try RenderNodeVariantOverridesApplier().applyVariantOverrides(in: encodedRenderNode, for: [objCVariantTrait]) + // Third, this code decodes the render node from the transformed data. If you count reading the render node from the documentation archive, + // this is the fifth time that the same node is either encoded or decoded. + let variantRenderNode = try RenderNode.decode(fromJSON: transformedData) + // Finally, the decoded node is in a way flattened, so that it only contains its Objective-C content. That's why we pass `nil` instead of `[objCVariantTrait]` to this call. + _ = try index(variantRenderNode, traits: nil) + } + + // If this render node was created by rendering a documentation node, we create a "view" into its Objective-C specific data and index that. + let objVariantView = RenderNodeVariantView(wrapped: renderNode, traits: [objCVariantTrait]) + _ = try index(objVariantView, traits: [objCVariantTrait]) + } + + // The private index implementation which indexes a given render node representation + private func index(_ renderNode: any IndexableRenderNodeRepresentation, traits: [RenderNode.Variant.Trait]?) throws -> InterfaceLanguage? { guard let navigatorIndex else { throw Error.navigatorIndexIsNil } @@ -643,10 +693,10 @@ extension NavigatorIndex { .normalizedNavigatorIndexIdentifier(forLanguage: language.mask) guard identifierToNode[normalizedIdentifier] == nil else { - return // skip as item exists already. + return nil // skip as item exists already. } - guard let title = (usePageTitle) ? renderNode.metadata.title : renderNode.navigatorTitle() else { + guard let title = usePageTitle ? renderNode.metadata.title : renderNode.navigatorTitle() else { throw Error.missingTitle(description: "\(renderNode.identifier.absoluteString.singleQuoted) has an empty title and so can't have a usable entry in the index.") } @@ -724,13 +774,11 @@ extension NavigatorIndex { navigationItem.usrIdentifier = language.name + "-" + ExternalIdentifier.usr(usr).hash // We pair the hash and the language name } - let childrenRelationship = renderNode.childrenRelationship() - let navigatorNode = NavigatorTree.Node(item: navigationItem, bundleIdentifier: bundleIdentifier) // Process the children var children = [Identifier]() - for (index, child) in childrenRelationship.enumerated() { + for (index, child) in renderNode.navigatorChildren(for: traits).enumerated() { let groupIdentifier: Identifier? if let title = child.name { @@ -807,30 +855,7 @@ extension NavigatorIndex { // Bump the nodes counter. counter += 1 - // We only want to check for an objective-c variant - // if we're currently indexing a swift variant. - guard language == .swift else { - return - } - - // Check if the render node has a variant for Objective-C - // - // Note that we need to check the `variants` property here, not the `variantsOverride` - // property because `variantsOverride` is only populated when the RenderNode is encoded. - let objCVariantTrait = renderNode.variants?.flatMap(\.traits).first { trait in - switch trait { - case .interfaceLanguage(let language): - return InterfaceLanguage.from(string: language) == .objc - } - } - - // In case we have a variant for Objective-C, apply the variant and re-index the render node. - if let variantToApply = objCVariantTrait { - let encodedRenderNode = try renderNode.encodeToJSON() - let transformedData = try RenderNodeVariantOverridesApplier().applyVariantOverrides(in: encodedRenderNode, for: [variantToApply]) - let variantRenderNode = try RenderNode.decode(fromJSON: transformedData) - try index(renderNode: variantRenderNode) - } + return language } /// An internal struct to store data about a single navigator entry. diff --git a/Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift new file mode 100644 index 0000000000..e337155e11 --- /dev/null +++ b/Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift @@ -0,0 +1,214 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 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 + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Foundation + +extension NavigatorIndex.Builder { + /// A language specific representation of a render node value for indexing. + protocol IndexableRenderNodeRepresentation { + associatedtype Metadata: IndexableRenderMetadataRepresentation + + // Information that's the same for all language variants + var identifier: ResolvedTopicReference { get } + var references: [String: RenderReference] { get } + var kind: RenderNode.Kind { get } + var sections: [RenderSection] { get } + + // Information that's different for each language variant + var metadata: Metadata { get } + var topicSections: [TaskGroupRenderSection] { get } + var defaultImplementationsSections: [TaskGroupRenderSection] { get } + } + + /// A language specific representation of a render metadata value for indexing. + protocol IndexableRenderMetadataRepresentation { + // Information that's the same for all language variants + var role: String? { get } + var images: [TopicImage] { get } + + // Information that's different for each language variant + var title: String? { get } + var navigatorTitle: [DeclarationRenderSection.Token]? { get } + var fragments: [DeclarationRenderSection.Token]? { get } + var externalID: String? { get } + var roleHeading: String? { get } + var symbolKind: String? { get } + var platforms: [AvailabilityRenderItem]? { get } + } +} + +extension NavigatorIndex.Builder.IndexableRenderNodeRepresentation { + var icon: RenderReferenceIdentifier? { + metadata.images.first { $0.type == .icon }?.identifier + } +} + +extension RenderNode: NavigatorIndex.Builder.IndexableRenderNodeRepresentation {} +extension RenderMetadata: NavigatorIndex.Builder.IndexableRenderMetadataRepresentation {} + +struct RenderMetadataVariantView: NavigatorIndex.Builder.IndexableRenderMetadataRepresentation { + var wrapped: RenderMetadata + var traits: [RenderNode.Variant.Trait] + + // The same for all language variants + var role: String? { + wrapped.role + } + var images: [TopicImage] { + wrapped.images + } + + // Different for each language variant + var title: String? { + wrapped.titleVariants.value(for: traits) + } + var navigatorTitle: [DeclarationRenderSection.Token]? { + wrapped.navigatorTitleVariants.value(for: traits) + } + var fragments: [DeclarationRenderSection.Token]? { + wrapped.fragmentsVariants.value(for: traits) + } + var externalID: String? { + wrapped.externalIDVariants.value(for: traits) + } + var roleHeading: String? { + wrapped.roleHeadingVariants.value(for: traits) + } + var symbolKind: String? { + wrapped.symbolKindVariants.value(for: traits) + } + var platforms: [AvailabilityRenderItem]? { + wrapped.platformsVariants.value(for: traits) + } +} + +struct RenderNodeVariantView: NavigatorIndex.Builder.IndexableRenderNodeRepresentation { + var wrapped: RenderNode + var traits: [RenderNode.Variant.Trait] + + init(wrapped: RenderNode, traits: [RenderNode.Variant.Trait]) { + self.wrapped = wrapped + self.traits = traits + let traitLanguages = traits.map { + switch $0 { + case .interfaceLanguage(let id): + return SourceLanguage(id: id) + } + } + self.identifier = wrapped.identifier.withSourceLanguages(Set(traitLanguages)) + self.metadata = RenderMetadataVariantView(wrapped: wrapped.metadata, traits: traits) + } + + // Computed during initialization + var identifier: ResolvedTopicReference + var metadata: RenderMetadataVariantView + + // The same for all language variants + var references: [String: any RenderReference] { wrapped.references } + var kind: RenderNode.Kind { wrapped.kind } + var sections: [any RenderSection] { wrapped.sections } + + // Different for each language variant + var topicSections: [TaskGroupRenderSection] { + wrapped.topicSectionsVariants.value(for: traits) + } + var defaultImplementationsSections: [TaskGroupRenderSection] { + wrapped.defaultImplementationsSectionsVariants.value(for: traits) + } +} + +private let typesThatShouldNotUseNavigatorTitle: Set = [ + .framework, .class, .structure, .enumeration, .protocol, .typeAlias, .associatedType, .extension +] + +extension NavigatorIndex.Builder.IndexableRenderNodeRepresentation { + /// Returns a navigator title preferring the fragments inside the metadata, if applicable. + func navigatorTitle() -> String? { + let tokens: [DeclarationRenderSection.Token]? + + // FIXME: Use `metadata.navigatorTitle` for all Swift symbols (github.com/apple/swift-docc/issues/176). + if identifier.sourceLanguage == .swift || (metadata.navigatorTitle ?? []).isEmpty { + let pageType = navigatorPageType() + guard !typesThatShouldNotUseNavigatorTitle.contains(pageType) else { + return metadata.title + } + tokens = metadata.fragments + } else { + tokens = metadata.navigatorTitle + } + + return tokens?.map(\.text).joined() ?? metadata.title + } + + /// Returns the type of page for the render node. + func navigatorPageType() -> NavigatorIndex.PageType { + // This is a workaround to support plist keys. + switch metadata.roleHeading?.lowercased() { + case "property list key": return .propertyListKey + case "property list key reference": return .propertyListKeyReference + default: break + } + + switch kind { + case .article: return metadata.role.map { .init(role: $0) } + ?? .article + case .tutorial: return .tutorial + case .section: return .section + case .overview: return .overview + case .symbol: return metadata.symbolKind.map { .init(symbolKind: $0) } + ?? metadata.role.map { .init(role: $0) } + ?? .symbol + } + } +} + +extension NavigatorIndex.Builder.IndexableRenderNodeRepresentation { + func navigatorChildren(for traits: [RenderNode.Variant.Trait]?) -> [RenderRelationshipsGroup] { + switch kind { + case .overview: + var groups = [RenderRelationshipsGroup]() + for case let section as VolumeRenderSection in sections { + groups.append(contentsOf: section.chapters.map { chapter in + RenderRelationshipsGroup( + name: chapter.name, + abstract: nil, + references: chapter.tutorials.compactMap { self.references[$0.identifier] as? TopicRenderReference } + ) + }) + } + return groups + default: + // Gather all topic references, transformed based on the traits, organizer by their identifier + let references: [String: TopicRenderReference] = references.values.reduce(into: [:]) { acc, renderReference in + guard var renderReference = renderReference as? TopicRenderReference else { return } + // Transform the topic reference to hold the variant title + if let traits { + renderReference.title = renderReference.titleVariants.applied(to: renderReference.title, for: traits) + } + acc[renderReference.identifier.identifier] = renderReference + } + + func makeGroup(topicSection: TaskGroupRenderSection, isNestingReferences: Bool) -> RenderRelationshipsGroup { + RenderRelationshipsGroup( + name: topicSection.title, + abstract: nil, // The navigator index only needs the title and the references. + references: topicSection.identifiers.map { references[$0]! }, + referencesAreNested: isNestingReferences + ) + } + + return topicSections.map { + makeGroup(topicSection: $0, isNestingReferences: false) + } + defaultImplementationsSections.map { + makeGroup(topicSection: $0, isNestingReferences: true) + } + } + } +} diff --git a/Sources/SwiftDocC/Model/Rendering/References/TopicImage.swift b/Sources/SwiftDocC/Model/Rendering/References/TopicImage.swift index df381f6a19..3082a29972 100644 --- a/Sources/SwiftDocC/Model/Rendering/References/TopicImage.swift +++ b/Sources/SwiftDocC/Model/Rendering/References/TopicImage.swift @@ -78,11 +78,3 @@ extension TopicImage { } } - -extension RenderNode { - var icon: RenderReferenceIdentifier? { - return metadata.images.first { image in - image.type == .icon - }?.identifier - } -} diff --git a/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift b/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift index 4cf0ff5b95..35c1f6b089 100644 --- a/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift +++ b/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift @@ -79,3 +79,85 @@ extension VariantCollection.Variant where Value: RangeReplaceableCollection { // The synthesized implementation is sufficient for this conformance. extension VariantPatchOperation: Equatable where Value: Equatable {} + +// MARK: Applying patches + +/// A type that can be transformed by incrementally applying variant patch operations. +protocol VariantCollectionPatchable { + /// Apply an "add" patch operation to the value + mutating func add(_ other: Self) + /// Apply a "remove" patch operation to the value + mutating func remove() +} + +extension Optional: VariantCollectionPatchable where Wrapped: VariantCollectionPatchable { + mutating func add(_ other: Wrapped?) { + guard var wrapped, let other else { return } + wrapped.add(other) + self = wrapped + } + + mutating func remove() { + self = nil + } +} + +extension Array: VariantCollectionPatchable { + mutating func add(_ other: [Element]) { + append(contentsOf: other) + } + + mutating func remove() { + self.removeAll() + } +} + +extension String: VariantCollectionPatchable { + mutating func add(_ other: String) { + append(contentsOf: other) + } + + mutating func remove() { + self.removeAll() + } +} + +extension VariantCollection where Value: VariantCollectionPatchable { + /// Returns the transformed value after applying the patch operations for all variants that match the given source language to the default value. + /// - Parameters: + /// - language: The source language that determine what variant's patches to apply to the default value. + /// - Returns: The transformed value, or the default value if no variants match the given source language. + func value(for language: SourceLanguage) -> Value { + applied(to: defaultValue, for: [.interfaceLanguage(language.id)]) + } + + /// Returns the transformed value after applying the patch operations for all variants that match the given traits to the default value. + /// - Parameters: + /// - traits: The traits that determine what variant's patches to apply to the default value. + /// - Returns: The transformed value, or the default value if no variants match the given traits. + func value(for traits: [RenderNode.Variant.Trait]) -> Value { + applied(to: defaultValue, for: traits) + } + + /// Returns the transformed value after applying the patch operations for all variants that match the given traits to the original value. + /// - Parameters: + /// - originalValue: The original value to transform. + /// - traits: The traits that determine what variant's patches to apply to the original value. + /// - Returns: The transformed value, or the original value if no variants match the given traits. + func applied(to originalValue: Value, for traits: [RenderNode.Variant.Trait]) -> Value { + var patchedValue = originalValue + for variant in variants where variant.traits == traits { + for patch in variant.patch { + switch patch { + case .replace(let value): + patchedValue = value + case .add(let value): + patchedValue.add(value) + case .remove: + patchedValue.remove() + } + } + } + return patchedValue + } +} diff --git a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift index f868ae72af..101f43e7f7 100644 --- a/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift +++ b/Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift @@ -637,6 +637,79 @@ Root """) } + func testNavigatorWithDifferentSwiftAndObjectiveCHierarchies() throws { + let (_, bundle, context) = try testBundleAndContext(named: "GeometricalShapes") + let renderContext = RenderContext(documentationContext: context, bundle: bundle) + let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext) + + let fromMemoryBuilder = NavigatorIndex.Builder(outputURL: try createTemporaryDirectory(), bundleIdentifier: bundle.identifier, sortRootChildrenByName: true, groupByLanguage: true) + let fromDecodedBuilder = NavigatorIndex.Builder(outputURL: try createTemporaryDirectory(), bundleIdentifier: bundle.identifier, sortRootChildrenByName: true, groupByLanguage: true) + fromMemoryBuilder.setup() + fromDecodedBuilder.setup() + + for identifier in context.knownPages { + let source = context.documentURL(for: identifier) + let entity = try context.entity(with: identifier) + + let renderNode = try XCTUnwrap(converter.renderNode(for: entity, at: source)) + XCTAssertNil(renderNode.variantOverrides) + try fromMemoryBuilder.index(renderNode: renderNode) + + let encoded = try RenderJSONEncoder.makeEncoder(emitVariantOverrides: true).encode(renderNode) + let decoded = try RenderJSONDecoder.makeDecoder().decode(RenderNode.self, from: encoded) + XCTAssertNotNil(decoded.variantOverrides) + try fromDecodedBuilder.index(renderNode: decoded) + } + + fromMemoryBuilder.finalize() + fromDecodedBuilder.finalize() + let fromMemoryNavigatorTree = try XCTUnwrap(fromMemoryBuilder.navigatorIndex).navigatorTree.root + let fromDecodedNavigatorTree = try XCTUnwrap(fromDecodedBuilder.navigatorIndex).navigatorTree.root + + XCTAssertEqual(fromMemoryNavigatorTree.dumpTree(), fromDecodedNavigatorTree.dumpTree()) + XCTAssertEqual(fromMemoryNavigatorTree.dumpTree(), """ + [Root] + ┣╸Objective-C + ┃ ┗╸GeometricalShapes + ┃ ┣╸Structures + ┃ ┣╸TLACircle + ┃ ┃ ┣╸Instance Properties + ┃ ┃ ┣╸center + ┃ ┃ ┗╸radius + ┃ ┣╸Variables + ┃ ┣╸TLACircleDefaultRadius + ┃ ┣╸TLACircleNull + ┃ ┣╸TLACircleZero + ┃ ┣╸Functions + ┃ ┣╸TLACircleToString + ┃ ┣╸TLACircleFromString + ┃ ┣╸TLACircleIntersects + ┃ ┣╸TLACircleIsEmpty + ┃ ┣╸TLACircleIsNull + ┃ ┗╸TLACircleMake + ┗╸Swift + ┗╸GeometricalShapes + ┣╸Structures + ┗╸Circle + ┣╸Initializers + ┣╸init() + ┣╸init(center: CGPoint, radius: CGFloat) + ┣╸init(string: String) + ┣╸Instance Properties + ┣╸var center: CGPoint + ┣╸var debugDescription: String + ┣╸var isEmpty: Bool + ┣╸var isNull: Bool + ┣╸var radius: CGFloat + ┣╸Instance Methods + ┣╸func intersects(Circle) -> Bool + ┣╸Type Properties + ┣╸static let defaultRadius: CGFloat + ┣╸static let null: Circle + ┗╸static let zero: Circle + """) + } + func testNavigatorIndexGenerationVariantsPayload() throws { let jsonFile = Bundle.module.url(forResource: "Variant-render-node", withExtension: "json", subdirectory: "Test Resources")! let jsonData = try Data(contentsOf: jsonFile) From 3fb5d815e41876e36d5e8ccd0f4d9d0b9e74575f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 14 May 2024 11:41:16 +0200 Subject: [PATCH 2/7] Use new apply-patch function in other places --- .../LinkTargets/LinkDestinationSummary.swift | 2 +- .../Variants/VariantPatchOperation.swift | 21 ------------- .../Model/SemaToRenderNodeTests.swift | 9 ++---- .../Variants/VariantPatchOperationTests.swift | 30 ++++++++++++------- .../OutOfProcessReferenceResolverTests.swift | 16 +++------- 5 files changed, 27 insertions(+), 51 deletions(-) diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 0e79f2ce12..909f273c01 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -341,7 +341,7 @@ public extension DocumentationNode { taskGroups = topicSectionGroups for variant in renderNode.topicSectionsVariants.variants { - taskGroupVariants[variant.traits] = variant.applyingPatchTo(renderNode.topicSections).map { group in .init(title: group.title, identifiers: group.identifiers) } + taskGroupVariants[variant.traits] = renderNode.topicSectionsVariants.value(for: variant.traits).map { group in .init(title: group.title, identifiers: group.identifiers) } } } } else { diff --git a/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift b/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift index 35c1f6b089..848915709f 100644 --- a/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift +++ b/Sources/SwiftDocC/Model/Rendering/Variants/VariantPatchOperation.swift @@ -56,27 +56,6 @@ public enum VariantPatchOperation { } } -extension VariantCollection.Variant where Value: RangeReplaceableCollection { - /// Applies the variant's patch operations to a given value and returns the patched value. - /// - /// - Parameter originalValue: The value that the variant will apply the patch operations to. - /// - Returns: The value after applying all patch operations. - func applyingPatchTo(_ originalValue: Value) -> Value { - var result = originalValue - for operation in patch { - switch operation { - case .replace(let newValue): - result = newValue - case .add(let newValue): - result.append(contentsOf: newValue) - case .remove: - result.removeAll() - } - } - return result - } -} - // The synthesized implementation is sufficient for this conformance. extension VariantPatchOperation: Equatable where Value: Equatable {} diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 6f9f2f4a91..4f8ae9680f 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -3314,8 +3314,7 @@ Document "doc://org.swift.MixedFramework/documentation/MixedFramework/MyObjectiveCClassSwiftName/myMethodSwiftName()" ]) - let objcTopicSectionVariant = try XCTUnwrap(topicSectionsVariants.variants.first { $0.traits[0] == .interfaceLanguage("occ") }) - let objcTopicSection = objcTopicSectionVariant.applyingPatchTo(swiftTopicSection) + let objcTopicSection = topicSectionsVariants.value(for: [.interfaceLanguage("occ")]) XCTAssertEqual(objcTopicSection.first?.title, "Something Objective-C only") XCTAssertEqual(objcTopicSection.first?.abstract?.plainText, "This link is only for Objective-C") @@ -3392,8 +3391,7 @@ Document "doc://GeometricalShapes/documentation/GeometricalShapes/Circle", ]) - let objcTopicSectionsVariant = try XCTUnwrap(renderNode.topicSectionsVariants.variants.first(where: { $0.traits == [.interfaceLanguage(SourceLanguage.objectiveC.id) ]})) - let objcTopicSections = objcTopicSectionsVariant.applyingPatchTo(swiftTopicSections) + let objcTopicSections = renderNode.topicSectionsVariants.value(for: .objectiveC) XCTAssertEqual(objcTopicSections.flatMap { [$0.title!] + $0.identifiers }, [ "Structures", "doc://GeometricalShapes/documentation/GeometricalShapes/Circle", @@ -3443,8 +3441,7 @@ Document "doc://GeometricalShapes/documentation/GeometricalShapes/Circle/zero" ]) - let objcTopicSectionsVariant = try XCTUnwrap(renderNode.topicSectionsVariants.variants.first(where: { $0.traits == [.interfaceLanguage(SourceLanguage.objectiveC.id) ]})) - let objcTopicSections = objcTopicSectionsVariant.applyingPatchTo(swiftTopicSections) + let objcTopicSections = renderNode.topicSectionsVariants.value(for: .objectiveC) XCTAssertEqual(objcTopicSections.flatMap { [$0.title!] + $0.identifiers }, [ "Instance Properties", "doc://GeometricalShapes/documentation/GeometricalShapes/Circle/center", diff --git a/Tests/SwiftDocCTests/Rendering/Variants/VariantPatchOperationTests.swift b/Tests/SwiftDocCTests/Rendering/Variants/VariantPatchOperationTests.swift index 0301d3eb83..15a318e087 100644 --- a/Tests/SwiftDocCTests/Rendering/Variants/VariantPatchOperationTests.swift +++ b/Tests/SwiftDocCTests/Rendering/Variants/VariantPatchOperationTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2021-2023 Apple Inc. and the Swift project authors + Copyright (c) 2021-2024 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 @@ -15,28 +15,28 @@ import XCTest class VariantPatchOperationTests: XCTestCase { func testApplyingPatch() { let original = [1, 2, 3] - let addVariant = VariantCollection<[Int]>.Variant(traits: [], patch: [ + let addVariant = makeVariantCollection(original, patch: [ .add(value: [4, 5, 6]) ]) - XCTAssertEqual(addVariant.applyingPatchTo(original), [1, 2, 3, 4, 5, 6]) + XCTAssertEqual(addVariant.value(for: testTraits), [1, 2, 3, 4, 5, 6]) - let removeVariant = VariantCollection<[Int]>.Variant(traits: [], patch: [ + let removeVariant = makeVariantCollection(original, patch: [ .remove ]) - XCTAssertEqual(removeVariant.applyingPatchTo(original), []) + XCTAssertEqual(removeVariant.value(for: testTraits), []) - let replaceVariant = VariantCollection<[Int]>.Variant(traits: [], patch: [ + let replaceVariant = makeVariantCollection(original, patch: [ .replace(value: [4, 5, 6]) ]) - XCTAssertEqual(replaceVariant.applyingPatchTo(original), [4, 5, 6]) + XCTAssertEqual(replaceVariant.value(for: testTraits), [4, 5, 6]) - let mixVariant = VariantCollection<[Int]>.Variant(traits: [], patch: [ + let mixVariant = makeVariantCollection(original, patch: [ .replace(value: [4, 5, 6]), .remove, .add(value: [6, 7]), .add(value: [8, 9]), ]) - XCTAssertEqual(mixVariant.applyingPatchTo(original), [6, 7, 8, 9]) + XCTAssertEqual(mixVariant.value(for: testTraits), [6, 7, 8, 9]) } func testApplyingSeriesOfPatchOperations() { @@ -62,8 +62,8 @@ class VariantPatchOperationTests: XCTestCase { "MNOPQR", ] for (index, expectedValue) in expectedValues.enumerated() { - let stringVariant = VariantCollection.Variant(traits: [], patch: Array(stringPatches.prefix(index))) - XCTAssertEqual(stringVariant.applyingPatchTo("A"), expectedValue) + let stringVariant = makeVariantCollection("A", patch: Array(stringPatches.prefix(index))) + XCTAssertEqual(stringVariant.value(for: testTraits), expectedValue) } } @@ -91,4 +91,12 @@ class VariantPatchOperationTests: XCTestCase { return } } + + private let testTraits = [RenderNode.Variant.Trait.interfaceLanguage("unit-test")] + + private func makeVariantCollection(_ original: Value, patch: [VariantPatchOperation]) -> VariantCollection { + VariantCollection(defaultValue: original, variants: [ + .init(traits: testTraits, patch: patch) + ]) + } } diff --git a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift index 74dba81f8c..8e74f42e34 100644 --- a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift @@ -124,12 +124,8 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - - let titleVariant = try XCTUnwrap(entity.topicRenderReference.titleVariants.variants.first(where: { $0.traits == variantTraits })) - XCTAssertEqual(titleVariant.applyingPatchTo(entity.topicRenderReference.title), "Resolved Variant Title") - - let abstractVariant = try XCTUnwrap(entity.topicRenderReference.abstractVariants.variants.first(where: { $0.traits == variantTraits })) - XCTAssertEqual(abstractVariant.applyingPatchTo(entity.topicRenderReference.abstract), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) @@ -299,12 +295,8 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] - - let titleVariant = try XCTUnwrap(entity.topicRenderReference.titleVariants.variants.first(where: { $0.traits == variantTraits })) - XCTAssertEqual(titleVariant.applyingPatchTo(entity.topicRenderReference.title), "Resolved Variant Title") - - let abstractVariant = try XCTUnwrap(entity.topicRenderReference.abstractVariants.variants.first(where: { $0.traits == variantTraits })) - XCTAssertEqual(abstractVariant.applyingPatchTo(entity.topicRenderReference.abstract), [.text("Resolved variant abstract for this topic.")]) + XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") + XCTAssertEqual(entity.topicRenderReference.abstractVariants.value(for: variantTraits), [.text("Resolved variant abstract for this topic.")]) let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) From 9e7805afd58918d4acaf11d18fe210a75b7e1c5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 14 May 2024 11:42:52 +0200 Subject: [PATCH 3/7] Deprecate unused `RenderNode/childrenRelationship(for:)` --- .../Indexing/RenderNode+Relationships.swift | 44 ++----------------- .../Model/RenderNodeSerializationTests.swift | 2 +- .../Model/SemaToRenderNodeTests.swift | 8 ++-- 3 files changed, 8 insertions(+), 46 deletions(-) diff --git a/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift b/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift index 2cb7a8519b..80f456bf3e 100644 --- a/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift +++ b/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift @@ -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-2024 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 @@ -17,47 +17,9 @@ extension RenderNode { - Returns: A list of `RenderRelationshipsGroup`. */ + @available(*, deprecated, message: "This deprecated API will be removed after 6.1 is released") public func childrenRelationship(for language: String? = nil) -> [RenderRelationshipsGroup] { - var groups = [RenderRelationshipsGroup]() - - switch kind { - case .overview: - for case let section as VolumeRenderSection in sections { - let chapters = section.chapters - for chapter in chapters { - let name = chapter.name - - // Extract the identifiers of linked chapters. - let tutorials = chapter.tutorials.map { $0.identifier } - // Get the references preserving the order. - let references = tutorials.compactMap { self.references[$0] } as! [TopicRenderReference] - - groups.append(RenderRelationshipsGroup(name: name, abstract: nil, references: references)) - } - } - default: - let topicReferences: [TopicRenderReference] = self.references.values.filter { $0 is TopicRenderReference } as! [TopicRenderReference] - let references: [String : TopicRenderReference] = Dictionary(uniqueKeysWithValues: topicReferences.map{ ($0.identifier.identifier, $0) }) - - func processBlock(nestingReferences: Bool) -> ((TaskGroupRenderSection) -> ()) { - return { topicSection in - // Get all the related identifiers - let identifiers = topicSection.identifiers - // Map the references preserving the order - let relationships = identifiers.map { references[$0]! } - // Get the abstract - let abstract = topicSection.abstract?.map { $0.rawIndexableTextContent(references: self.references) }.joined(separator: " ") - - // Append the group to the result - groups.append(RenderRelationshipsGroup(name: topicSection.title, abstract: abstract, references: relationships, referencesAreNested: nestingReferences)) - } - } - - topicSections.forEach(processBlock(nestingReferences: false)) - defaultImplementationsSections.forEach(processBlock(nestingReferences: true)) - } - - return groups + return (self as any NavigatorIndex.Builder.IndexableRenderNodeRepresentation).navigatorChildren(for: nil) } /** diff --git a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift index f65aaf6df9..df2c3aa0e5 100644 --- a/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift +++ b/Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift @@ -165,7 +165,7 @@ class RenderNodeSerializationTests: XCTestCase { XCTAssertNotNil(renderNode.projectFiles()) XCTAssertEqual(renderNode.projectFiles()?.url.lastPathComponent, "project.zip") - XCTAssertEqual(renderNode.childrenRelationship().count, 0) + XCTAssertEqual(renderNode.navigatorChildren(for: nil).count, 0) XCTAssertEqual(renderNode.downloadReferences().count, 1) // Check the output of the dictionary diff --git a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift index 4f8ae9680f..82be2a0a18 100644 --- a/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Model/SemaToRenderNodeTests.swift @@ -467,7 +467,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(renderNode.sections[2].kind, .callToAction) - XCTAssertEqual(renderNode.childrenRelationship().count, 0) + XCTAssertEqual(renderNode.navigatorChildren(for: nil).count, 0) XCTAssertNil(renderNode.metadata.roleHeading) XCTAssertEqual(renderNode.metadata.title, "Making an Augmented Reality App") @@ -622,7 +622,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(renderNode.references.count, 13) - XCTAssertEqual(renderNode.childrenRelationship().count, 1) + XCTAssertEqual(renderNode.navigatorChildren(for: nil).count, 1) guard let introImageReference = renderNode.references["intro.png"] as? ImageReference else { XCTFail("Missing intro.png image reference") @@ -878,7 +878,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(renderNode.references.count, 13) - XCTAssertEqual(renderNode.childrenRelationship().count, 3, "Expected three chapters as children.") + XCTAssertEqual(renderNode.navigatorChildren(for: nil).count, 3, "Expected three chapters as children.") guard let introImageReference = renderNode.references["intro.png"] as? ImageReference else { XCTFail("Missing intro.png image reference") @@ -1060,7 +1060,7 @@ class SemaToRenderNodeTests: XCTestCase { XCTAssertEqual(discussion.kind, RenderSectionKind.content) // Test childrenRelationships are handled correctly - let children = renderNode.childrenRelationship() + let children = renderNode.navigatorChildren(for: nil) XCTAssertEqual(children.count, renderNode.topicSections.count) XCTAssertEqual(children.first?.name, "Task Group Exercising Symbol Links") XCTAssertEqual(children.first?.references.count, 3) From 2541b152a439fdfbf7ad1d6429dd553ed1c2353b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 14 May 2024 11:46:19 +0200 Subject: [PATCH 4/7] Move inner function in test to avoid warning about captured state --- .../Rendering/RenderNodeTranslatorTests.swift | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift index f7f010d1f6..e2acf842b6 100644 --- a/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift +++ b/Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift @@ -1303,24 +1303,6 @@ class RenderNodeTranslatorTests: XCTestCase { } func testExpectedRoleHeadingIsAssigned() throws { - func renderNodeArticleFromReferencePath( - referencePath: String - ) throws -> RenderNode { - let reference = ResolvedTopicReference( - bundleIdentifier: bundle.identifier, - path: referencePath, - sourceLanguage: .swift - ) - let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Article) - var translator = RenderNodeTranslator( - context: context, - bundle: bundle, - identifier: reference, - source: nil - ) - return try XCTUnwrap(translator.visitArticle(symbol) as? RenderNode) - } - let exampleDocumentation = Folder( name: "unit-test.docc", content: [ @@ -1385,6 +1367,15 @@ class RenderNodeTranslatorTests: XCTestCase { let tempURL = try createTempFolder(content: [exampleDocumentation]) let (_, bundle, context) = try loadBundle(from: tempURL) + func renderNodeArticleFromReferencePath( + referencePath: String + ) throws -> RenderNode { + let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: referencePath, sourceLanguage: .swift) + let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Article) + var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference, source: nil) + return try XCTUnwrap(translator.visitArticle(symbol) as? RenderNode) + } + // Assert that articles that curates any symbol gets 'API Collection' assigned as the eyebrow title. var renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/APICollection") XCTAssertEqual(renderNode.metadata.roleHeading, "API Collection") From c6ab711b1f888c529feff0b02899727f32dc696c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 14 May 2024 15:49:52 +0200 Subject: [PATCH 5/7] Fix unrelated issue where the language in disambiguated references wasn't stable --- .../Link Resolution/PathHierarchy+DisambiguatedPaths.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+DisambiguatedPaths.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+DisambiguatedPaths.swift index 10151e3ea3..e5b9c23b32 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+DisambiguatedPaths.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+DisambiguatedPaths.swift @@ -192,8 +192,8 @@ extension PathHierarchy.DisambiguationContainer { let groupedByKind = [String?: [Element]](grouping: elements, by: \.kind) for (kind, elements) in groupedByKind where elements.count == 1 && kind != nil { let element = elements.first! - if includeLanguage, let symbol = element.node.symbol { - collisions.append((value: element.node, disambiguation: .kind("\(SourceLanguage(id: symbol.identifier.interfaceLanguage).linkDisambiguationID).\(kind!)"))) + if includeLanguage, let language = element.node.languages.min() { + collisions.append((value: element.node, disambiguation: .kind("\(language.linkDisambiguationID).\(kind!)"))) } else { collisions.append((value: element.node, disambiguation: .kind(kind!))) } From 1b6ebe01e74fc320d4e418bdbe902cc8a5f38c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Thu, 16 May 2024 16:51:14 +0200 Subject: [PATCH 6/7] Move new indexable-render-node-representation protocol top-level --- .../Navigator/NavigatorIndex+Ext.swift | 2 +- .../Indexing/Navigator/NavigatorIndex.swift | 2 +- .../Navigator/RenderNode+NavigatorIndex.swift | 74 +++++++++---------- .../Indexing/RenderNode+Relationships.swift | 2 +- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift index 659f960c41..9ca01bf78d 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex+Ext.swift @@ -79,6 +79,6 @@ extension RenderNode { @_disfavoredOverload @available(*, deprecated, message: "This deprecated API will be removed after 6.1 is released") public func navigatorPageType() -> NavigatorIndex.PageType { - return (self as any NavigatorIndex.Builder.IndexableRenderNodeRepresentation).navigatorPageType() + return (self as any NavigatorIndexableRenderNodeRepresentation).navigatorPageType() } } diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift index 78cdfe7ee3..f2b1345db3 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift @@ -668,7 +668,7 @@ extension NavigatorIndex { } // The private index implementation which indexes a given render node representation - private func index(_ renderNode: any IndexableRenderNodeRepresentation, traits: [RenderNode.Variant.Trait]?) throws -> InterfaceLanguage? { + private func index(_ renderNode: any NavigatorIndexableRenderNodeRepresentation, traits: [RenderNode.Variant.Trait]?) throws -> InterfaceLanguage? { guard let navigatorIndex else { throw Error.navigatorIndexIsNil } diff --git a/Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift index e337155e11..b3b8915232 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/RenderNode+NavigatorIndex.swift @@ -10,50 +10,48 @@ import Foundation -extension NavigatorIndex.Builder { - /// A language specific representation of a render node value for indexing. - protocol IndexableRenderNodeRepresentation { - associatedtype Metadata: IndexableRenderMetadataRepresentation - - // Information that's the same for all language variants - var identifier: ResolvedTopicReference { get } - var references: [String: RenderReference] { get } - var kind: RenderNode.Kind { get } - var sections: [RenderSection] { get } - - // Information that's different for each language variant - var metadata: Metadata { get } - var topicSections: [TaskGroupRenderSection] { get } - var defaultImplementationsSections: [TaskGroupRenderSection] { get } - } +/// A language specific representation of a render node value for building a navigator index. +protocol NavigatorIndexableRenderNodeRepresentation { + associatedtype Metadata: NavigatorIndexableRenderMetadataRepresentation - /// A language specific representation of a render metadata value for indexing. - protocol IndexableRenderMetadataRepresentation { - // Information that's the same for all language variants - var role: String? { get } - var images: [TopicImage] { get } - - // Information that's different for each language variant - var title: String? { get } - var navigatorTitle: [DeclarationRenderSection.Token]? { get } - var fragments: [DeclarationRenderSection.Token]? { get } - var externalID: String? { get } - var roleHeading: String? { get } - var symbolKind: String? { get } - var platforms: [AvailabilityRenderItem]? { get } - } + // Information that's the same for all language variants + var identifier: ResolvedTopicReference { get } + var references: [String: RenderReference] { get } + var kind: RenderNode.Kind { get } + var sections: [RenderSection] { get } + + // Information that's different for each language variant + var metadata: Metadata { get } + var topicSections: [TaskGroupRenderSection] { get } + var defaultImplementationsSections: [TaskGroupRenderSection] { get } +} + +/// A language specific representation of a render metadata value for building a navigator index. +protocol NavigatorIndexableRenderMetadataRepresentation { + // Information that's the same for all language variants + var role: String? { get } + var images: [TopicImage] { get } + + // Information that's different for each language variant + var title: String? { get } + var navigatorTitle: [DeclarationRenderSection.Token]? { get } + var fragments: [DeclarationRenderSection.Token]? { get } + var externalID: String? { get } + var roleHeading: String? { get } + var symbolKind: String? { get } + var platforms: [AvailabilityRenderItem]? { get } } -extension NavigatorIndex.Builder.IndexableRenderNodeRepresentation { +extension NavigatorIndexableRenderNodeRepresentation { var icon: RenderReferenceIdentifier? { metadata.images.first { $0.type == .icon }?.identifier } } -extension RenderNode: NavigatorIndex.Builder.IndexableRenderNodeRepresentation {} -extension RenderMetadata: NavigatorIndex.Builder.IndexableRenderMetadataRepresentation {} +extension RenderNode: NavigatorIndexableRenderNodeRepresentation {} +extension RenderMetadata: NavigatorIndexableRenderMetadataRepresentation {} -struct RenderMetadataVariantView: NavigatorIndex.Builder.IndexableRenderMetadataRepresentation { +struct RenderMetadataVariantView: NavigatorIndexableRenderMetadataRepresentation { var wrapped: RenderMetadata var traits: [RenderNode.Variant.Trait] @@ -89,7 +87,7 @@ struct RenderMetadataVariantView: NavigatorIndex.Builder.IndexableRenderMetadata } } -struct RenderNodeVariantView: NavigatorIndex.Builder.IndexableRenderNodeRepresentation { +struct RenderNodeVariantView: NavigatorIndexableRenderNodeRepresentation { var wrapped: RenderNode var traits: [RenderNode.Variant.Trait] @@ -128,7 +126,7 @@ private let typesThatShouldNotUseNavigatorTitle: Set = .framework, .class, .structure, .enumeration, .protocol, .typeAlias, .associatedType, .extension ] -extension NavigatorIndex.Builder.IndexableRenderNodeRepresentation { +extension NavigatorIndexableRenderNodeRepresentation { /// Returns a navigator title preferring the fragments inside the metadata, if applicable. func navigatorTitle() -> String? { let tokens: [DeclarationRenderSection.Token]? @@ -169,7 +167,7 @@ extension NavigatorIndex.Builder.IndexableRenderNodeRepresentation { } } -extension NavigatorIndex.Builder.IndexableRenderNodeRepresentation { +extension NavigatorIndexableRenderNodeRepresentation { func navigatorChildren(for traits: [RenderNode.Variant.Trait]?) -> [RenderRelationshipsGroup] { switch kind { case .overview: diff --git a/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift b/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift index 80f456bf3e..6c8413468f 100644 --- a/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift +++ b/Sources/SwiftDocC/Indexing/RenderNode+Relationships.swift @@ -19,7 +19,7 @@ extension RenderNode { */ @available(*, deprecated, message: "This deprecated API will be removed after 6.1 is released") public func childrenRelationship(for language: String? = nil) -> [RenderRelationshipsGroup] { - return (self as any NavigatorIndex.Builder.IndexableRenderNodeRepresentation).navigatorChildren(for: nil) + return (self as any NavigatorIndexableRenderNodeRepresentation).navigatorChildren(for: nil) } /** From 0f2931385faade80abe5454feae61d7459c92fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Thu, 23 May 2024 09:04:11 +0200 Subject: [PATCH 7/7] Remove extra word in code comment Co-authored-by: Maya Epps <53411851+mayaepps@users.noreply.github.com> --- Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift index f2b1345db3..93d503a970 100644 --- a/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift +++ b/Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift @@ -636,7 +636,7 @@ extension NavigatorIndex { return } - // A render node holds is structured differently depending on if it was created by "rendering" a documentation node + // A render node is structured differently depending on if it was created by "rendering" a documentation node // or if it was deserialized from a documentation archive. // // If it was created by rendering a documentation node, all variant information is stored in each individual variant collection and the variant overrides are nil.