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
24 changes: 23 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,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 Down Expand Up @@ -122,6 +122,7 @@ let package = Package(
// This target shouldn't have any local dependencies so that all other targets can depend on it.
// We can add dependencies on SymbolKit and Markdown here but they're not needed yet.
],
exclude: ["CMakeLists.txt"],
swiftSettings: [.swiftLanguageMode(.v6)]
),

Expand All @@ -134,6 +135,27 @@ let package = Package(
swiftSettings: [.swiftLanguageMode(.v6)]
),

.target(
name: "DocCHTML",
dependencies: [
.target(name: "DocCCommon"),
.product(name: "Markdown", package: "swift-markdown"),
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
],
exclude: ["CMakeLists.txt"],
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "DocCHTMLTests",
dependencies: [
.target(name: "DocCHTML"),
.target(name: "SwiftDocC"),
.product(name: "Markdown", package: "swift-markdown"),
.target(name: "SwiftDocCTestUtilities"),
],
swiftSettings: [.swiftLanguageMode(.v6)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need language mode v6 to support Swift Testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we just need to specify swift-tools-version:6.0 in the package manifest which we already do. Because this code doesn't load any symbols or documentation catalogs, it can start using Swift Testing right away. Most other tests in DocC probably need the updated test helpers in #1362.

My reason for using the Swift 6 language mode here is that it enabled a number of features by default and is strict about concurrency warnings. That way, any new code has to follow the stricter concurrency checks from the start, rather than adapting it afterwards.

),

// Test app for SwiftDocCUtilities
.executableTarget(
name: "signal-test-app",
Expand Down
23 changes: 23 additions & 0 deletions Sources/DocCHTML/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[[
This source file is part of the Swift open source project

Copyright © 2014 - 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
#]]

add_library(DocCHTML STATIC
LinkProvider.swift
MarkdownRenderer.swift
WordBreak.swift
XMLNode+element.swift)
target_link_libraries(DocCHTML PRIVATE
DocCCommon)
target_link_libraries(DocCHTML PUBLIC
SwiftMarkdown::Markdown
DocC::SymbolKit)
# FIXME(compnerd) workaround leaking dependencies
target_link_libraries(DocCHTML PUBLIC
libcmark-gfm
libcmark-gfm-extensions)
116 changes: 116 additions & 0 deletions Sources/DocCHTML/LinkProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 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
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

package import Foundation
package import Markdown
package import DocCCommon

/// A type that provides information about other pages, and on-page elements, that the rendered page references.
package protocol LinkProvider {
/// Provide information about another page or on-page element, or `nil` if the other page can't be found.
func element(for path: URL) -> LinkedElement?

/// Provide the path for a symbol based on its unique identifier, or `nil` if the other symbol with that identifier can't be found.
func pathForSymbolID(_ usr: String) -> URL?

/// Provide information about an asset (for example an image or video), or `nil` if the asset can't be found.
func assetNamed(_ assetName: String) -> LinkedAsset?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are assets? Images? Videos? Both? Other objects? Maybe a bit of explanation here or where you define LinkedAsset below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I briefly documented this in d89d3e5


/// Fallback link text for a link string that the provider couldn't provide any information for.
func fallbackLinkText(linkString: String) -> String
}

package struct LinkedElement {
/// The path within the output archive to the linked element.
package var path: URL
/// The names of the linked element, for display when the element is referenced in inline content.
///
/// Articles, headings, tutorials, and similar pages have a ``Names/single/conceptual(_:)`` name.
/// Symbols can either have a ``Names/single/symbol(_:)`` name or have different names for each language representation (``Names/languageSpecificSymbol``).
package var names: Names
/// The subheadings of the linked element, for display when the element is referenced in either a Topics section, See Also section, or in a `@Links` directive.
///
/// Articles, headings, tutorials, and similar pages have a ``Names/single/conceptual(_:)`` name.
/// Symbols can either have a ``Names/single/symbol(_:)`` name or have different names for each language representation (``Names/languageSpecificSymbol``).
package var subheadings: Subheadings
/// The abstract of the page—to be displayed in either a Topics section, See Also section, or in a `@Links` directive—or `nil` if the linked element doesn't have an abstract.
package var abstract: Paragraph?

package init(path: URL, names: Names, subheadings: Subheadings, abstract: Paragraph?) {
self.path = path
self.names = names
self.subheadings = subheadings
self.abstract = abstract
}

/// The single name or language-specific names to use when referring to a linked element in inline content.
package enum Names {
/// This element has the same name in all language representations
case single(Name)
/// This element is a symbol with different names in different languages.
///
/// Because `@DisplayName` applies to all language representations, these language specific names are always the symbol's subheading declaration and should display in a monospaced font.
case languageSpecificSymbol([SourceLanguage: String])
}
package enum Name {
/// The name refers to an article, heading, or custom `@DisplayName` and should display as regular text.
case conceptual(String)
/// The name refers to a symbol's subheading declaration and should display in a monospaced font.
case symbol(String)
}

/// The single subheading or language-specific subheadings to use when referring to a linked element in either a Topics section, See Also section, or in a `@Links` directive.
package enum Subheadings {
/// This element has the same name in all language representations
case single(Subheading)
/// This element is a symbol with different names in different languages.
///
/// Because `@DisplayName` applies to all language representations, these language specific names are always the symbol's subheading declaration and should display in a monospaced font.
case languageSpecificSymbol([SourceLanguage: [SymbolNameFragment]])
}
package enum Subheading {
/// The name refers to an article, heading, or custom `@DisplayName` and should display as regular text.
case conceptual(String)
/// The name refers to a symbol's subheading declaration and should display in a monospaced font.
case symbol([SymbolNameFragment])
}

/// A fragment in a symbol's name
package struct SymbolNameFragment {
/// The textual spelling of this fragment
package var text: String
/// The kind of fragment
package var kind: Kind

/// The display kind of a single symbol name fragment
package enum Kind: String {
case identifier, decorator
}

package init(text: String, kind: Kind) {
self.text = text
self.kind = kind
}
}
}

/// Information about a referenced image, video, or download asset that may be represented by more than one file for different color styles and display scales.
package struct LinkedAsset {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the previous comment. Should this be named LinkedImages ? Or, if we expect there to be videos or other types of media is having a single images attribute incorrect?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Videos and I think downloadable files would also be considered assets. I'll update this comment and add a TODO for a future PR to verify that videos and downloads work, but that probably requires support for the @Video directive and @CallToAction directive first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I briefly documented this in d89d3e5

/// The path within the output archive to each file for this asset, grouped by their light/dark style and display scale.
package var files: [ColorStyle: [Int /* display scale*/: URL]]

package init(files: [ColorStyle : [Int /* display scale*/: URL]]) {
self.files = files
}

package enum ColorStyle: String {
case light, dark
}
}
Loading