Skip to content
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

Ensure that synthesized landing pages for combined archives display page images and links in abstracts from referenced types #1179

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

Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
Copyright (c) 2021-2025 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 All @@ -13,9 +13,9 @@ import SymbolKit
import Markdown

public struct RenderReferenceDependencies {
var topicReferences = [ResolvedTopicReference]()
var linkReferences = [LinkReference]()
var imageReferences = [ImageReference]()
public var topicReferences = [ResolvedTopicReference]()
public var linkReferences = [LinkReference]()
public var imageReferences = [ImageReference]()

public init(topicReferences: [ResolvedTopicReference] = [], linkReferences: [LinkReference] = [], imageReferences: [ImageReference] = []) {
self.topicReferences = topicReferences
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public struct TopicImage: Codable, Hashable {
public let type: TopicImageType

/// The reference identifier for the image.
public let identifier: RenderReferenceIdentifier
public var identifier: RenderReferenceIdentifier

/// Create a new topic image with the given type and reference identifier.
public init(
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftDocCTestUtilities/TestFileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ package class TestFileSystem: FileManagerProtocol {
files["/tmp"] = Self.folderFixtureData

for folder in folders {
try addFolder(folder)
try addFolder(folder, basePath: URL(fileURLWithPath: "/"))
}
}

Expand Down Expand Up @@ -127,13 +127,13 @@ package class TestFileSystem: FileManagerProtocol {
}

@discardableResult
func addFolder(_ folder: Folder) throws -> [String] {
package func addFolder(_ folder: Folder, basePath: URL) throws -> [String] {
guard !disableWriting else { return [] }

filesLock.lock()
defer { filesLock.unlock() }

let rootURL = URL(fileURLWithPath: "/\(folder.name)")
let rootURL = basePath.appendingPathComponent(folder.name)// URL(fileURLWithPath: "/\(folder.name)")
files[rootURL.path] = Self.folderFixtureData
let fileList = try filesIn(folder: folder, at: rootURL)
files.merge(fileList, uniquingKeysWith: +)
Expand Down
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) 2024 Apple Inc. and the Swift project authors
Copyright (c) 2024-2025 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 All @@ -13,9 +13,17 @@ import SwiftDocC

extension MergeAction {
struct RootRenderReferences {
var documentation, tutorials: [TopicRenderReference]
struct Information {
var reference: TopicRenderReference
var dependencies: [any RenderReference]

var rawIdentifier: String {
reference.identifier.identifier
}
}
var documentation, tutorials: [Information]

fileprivate var all: [TopicRenderReference] {
fileprivate var all: [Information] {
documentation + tutorials
}
var isEmpty: Bool {
Expand All @@ -27,19 +35,20 @@ extension MergeAction {
}

func readRootNodeRenderReferencesIn(dataDirectory: URL) throws -> RootRenderReferences {
func inner(url: URL) throws -> [TopicRenderReference] {
func inner(url: URL) throws -> [RootRenderReferences.Information] {
try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
.compactMap {
guard $0.pathExtension == "json" else {
return nil
}

let data = try fileManager.contents(of: $0)
return try JSONDecoder().decode(RootNodeRenderReference.self, from: data)
.renderReference
let decoded = try JSONDecoder().decode(RootNodeRenderReference.self, from: data)

return .init(reference: decoded.renderReference, dependencies: decoded.renderDependencies)
}
.sorted(by: { lhs, rhs in
lhs.title < rhs.title
lhs.reference.title < rhs.reference.title
})
}

Expand Down Expand Up @@ -72,21 +81,26 @@ extension MergeAction {
if rootRenderReferences.containsBothKinds {
// If the combined archive contains both documentation and tutorial content, create separate topic sections for each.
renderNode.topicSections = [
.init(title: "Modules", abstract: nil, discussion: nil, identifiers: rootRenderReferences.documentation.map(\.identifier.identifier)),
.init(title: "Tutorials", abstract: nil, discussion: nil, identifiers: rootRenderReferences.tutorials.map(\.identifier.identifier)),
.init(title: "Modules", abstract: nil, discussion: nil, identifiers: rootRenderReferences.documentation.map(\.rawIdentifier)),
.init(title: "Tutorials", abstract: nil, discussion: nil, identifiers: rootRenderReferences.tutorials.map(\.rawIdentifier)),
]
} else {
// Otherwise, create a single unnamed topic section
renderNode.topicSections = [
.init(title: nil, abstract: nil, discussion: nil, identifiers: (rootRenderReferences.all).map(\.identifier.identifier)),
.init(title: nil, abstract: nil, discussion: nil, identifiers: (rootRenderReferences.all).map(\.rawIdentifier)),
]
}

for renderReference in rootRenderReferences.documentation {
renderNode.references[renderReference.identifier.identifier] = renderReference
renderNode.references[renderReference.rawIdentifier] = renderReference.reference

for dependencyReference in renderReference.dependencies {
renderNode.references[dependencyReference.identifier.identifier] = dependencyReference
}
}
for renderReference in rootRenderReferences.tutorials {
renderNode.references[renderReference.identifier.identifier] = renderReference
renderNode.references[renderReference.rawIdentifier] = renderReference.reference
// Tutorial pages don't have page images.
}

return renderNode
Expand All @@ -97,6 +111,7 @@ extension MergeAction {
private struct RootNodeRenderReference: Decodable {
/// The decoded root node render reference
var renderReference: TopicRenderReference
var renderDependencies: [any RenderReference]

enum CodingKeys: CodingKey {
// The only render node keys that should be needed
Expand All @@ -107,7 +122,7 @@ private struct RootNodeRenderReference: Decodable {

struct StringCodingKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
init(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? = nil
Expand All @@ -122,6 +137,7 @@ private struct RootNodeRenderReference: Decodable {

let identifier = try container.decode(ResolvedTopicReference.self, forKey: .identifier)
let rawIdentifier = identifier.url.absoluteString
let imageReferencePrefix = identifier.bundleID.rawValue + "/"

// Every node should include a reference to the root page.
// For reference documentation, this is because the root appears as a link in the breadcrumbs on every page.
Expand All @@ -130,8 +146,21 @@ private struct RootNodeRenderReference: Decodable {
// If the root page has a reference to itself, then that the fastest and easiest way to access the correct topic render reference.
if container.contains(.references) {
let referencesContainer = try container.nestedContainer(keyedBy: StringCodingKey.self, forKey: .references)
if let selfReference = try referencesContainer.decodeIfPresent(TopicRenderReference.self, forKey: .init(stringValue: rawIdentifier)!) {
if var selfReference = try referencesContainer.decodeIfPresent(TopicRenderReference.self, forKey: .init(stringValue: rawIdentifier)) {
renderDependencies = try Self.decodeDependencyReferences(
container: referencesContainer,
images: selfReference.images,
imageReferencePrefix: imageReferencePrefix,
abstract: selfReference.abstract
)

// Image references don't include the bundle ID that they're part of and can collide with other images.
for index in selfReference.images.indices {
selfReference.images[index].identifier.addPrefix(imageReferencePrefix)
}

renderReference = selfReference

return
}
}
Expand All @@ -140,13 +169,62 @@ private struct RootNodeRenderReference: Decodable {
// we can create a new topic reference by decoding a little bit more information from the render node.
let metadata = try container.decode(RenderMetadata.self, forKey: .metadata)

var prefixedImages = metadata.images
// Image references don't include the bundle ID that they're part of and can collide with other images.
for index in prefixedImages.indices {
prefixedImages[index].identifier.addPrefix(imageReferencePrefix)
}

renderReference = TopicRenderReference(
identifier: RenderReferenceIdentifier(rawIdentifier),
title: metadata.title ?? identifier.lastPathComponent,
abstract: try container.decodeIfPresent([RenderInlineContent].self, forKey: .abstract) ?? [],
url: identifier.path.lowercased(),
kind: try container.decode(RenderNode.Kind.self, forKey: .kind),
images: metadata.images
images: prefixedImages
)

if container.contains(.references) {
renderDependencies = try Self.decodeDependencyReferences(
container: try container.nestedContainer(keyedBy: StringCodingKey.self, forKey: .references),
images: metadata.images,
imageReferencePrefix: imageReferencePrefix,
abstract: renderReference.abstract
)

} else {
renderDependencies = []
}
}

private static func decodeDependencyReferences(
container: KeyedDecodingContainer<RootNodeRenderReference.StringCodingKey>,
images: [TopicImage],
imageReferencePrefix: String,
abstract: [RenderInlineContent]
) throws -> [any RenderReference] {
var references: [any RenderReference] = []

for image in images {
// Image references don't include the bundle ID that they're part of and can collide with other images.
var imageRef = try container.decode(ImageReference.self, forKey: .init(stringValue: image.identifier.identifier))
imageRef.identifier.addPrefix(imageReferencePrefix)

references.append(imageRef)
}

for case .reference(identifier: let identifier, isActive: _, overridingTitle: _, overridingTitleInlineContent: _) in abstract {
references.append(
try container.decode(TopicRenderReference.self, forKey: .init(stringValue: identifier.identifier))
)
}

return references
}
}

private extension RenderReferenceIdentifier {
mutating func addPrefix(_ prefix: String) {
identifier = prefix + identifier
}
}
Loading