Skip to content

Commit

Permalink
Provide ConvertService with mapping of USRs to minimal access level r…
Browse files Browse the repository at this point in the history
…equired for extended documentation to be available

rdar://105460209
  • Loading branch information
daniel-grumberg committed Apr 27, 2023
1 parent 88d0fd6 commit 85341fa
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class DocumentationContextConverter {
/// Whether the documentation converter should include access level information for symbols.
let shouldEmitSymbolAccessLevels: Bool

let symbolIdentifiersWithExpandedDocumentation: [String]?

/// The remote source control repository where the documented module's source is hosted.
let sourceRepository: SourceRepository?

Expand All @@ -61,14 +63,16 @@ public class DocumentationContextConverter {
renderContext: RenderContext,
emitSymbolSourceFileURIs: Bool = false,
emitSymbolAccessLevels: Bool = false,
sourceRepository: SourceRepository? = nil
sourceRepository: SourceRepository? = nil,
symbolIdentifiersWithExpandedDocumentation: [String]? = nil
) {
self.bundle = bundle
self.context = context
self.renderContext = renderContext
self.shouldEmitSymbolSourceFileURIs = emitSymbolSourceFileURIs
self.shouldEmitSymbolAccessLevels = emitSymbolAccessLevels
self.sourceRepository = sourceRepository
self.symbolIdentifiersWithExpandedDocumentation = symbolIdentifiersWithExpandedDocumentation
}

/// Converts a documentation node to a render node.
Expand All @@ -91,7 +95,8 @@ public class DocumentationContextConverter {
renderContext: renderContext,
emitSymbolSourceFileURIs: shouldEmitSymbolSourceFileURIs,
emitSymbolAccessLevels: shouldEmitSymbolAccessLevels,
sourceRepository: sourceRepository
sourceRepository: sourceRepository,
symbolIdentifiersWithExpandedDocumentation: symbolIdentifiersWithExpandedDocumentation
)
return translator.visit(node.semantic) as? RenderNode
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ public struct ConvertService: DocumentationService {
additionalSymbolGraphFiles: []
),
emitSymbolSourceFileURIs: request.emitSymbolSourceFileURIs,
emitSymbolAccessLevels: true
emitSymbolAccessLevels: true,
symbolIdentifiersWithExpandedDocumentation: request.symbolIdentifiersWithExpandedDocumentation
)

// Run the conversion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ public struct ConvertRequest: Codable {
/// - ``DocumentationBundle/miscResourceURLs``
public var miscResourceURLs: [URL]

/// The symbol identifiers that have an expanded documentation page available if they meet the associated access level requirement.
///
/// DocC sets the ``RenderMetadata/hasExpandedDocumentationForSymbols`` property to `true`
/// for these symbols if they meet the provided requirements, so that renderers can display a "View More" link
/// that navigates the user to the full version of the documentation page.
public var symbolIdentifiersWithExpandedDocumentation: [String: ExpandedDocumentationRequirements]?

/// The default code listing language for the documentation bundle to convert.
///
/// ## See Also
Expand Down Expand Up @@ -177,6 +184,8 @@ public struct ConvertRequest: Codable {
version: version,
defaultCodeListingLanguage: defaultCodeListingLanguage
)

self.symbolIdentifiersWithExpandedDocumentation = nil
}

/// Creates a request to convert in-memory documentation.
Expand All @@ -195,6 +204,8 @@ public struct ConvertRequest: Codable {
/// - markupFiles: The article and documentation extension file data included in the documentation bundle to convert.
/// - tutorialFiles: The tutorial file data included in the documentation bundle to convert.
/// - miscResourceURLs: The on-disk resources in the documentation bundle to convert.
/// - symbolIdentifiersWithExpandedDocumentation: A dictionary of identifiers to requirements for these symbols to have expanded
/// documentation available.
public init(
bundleInfo: DocumentationBundle.Info,
featureFlags: FeatureFlags = FeatureFlags(),
Expand All @@ -208,7 +219,8 @@ public struct ConvertRequest: Codable {
emitSymbolSourceFileURIs: Bool = true,
markupFiles: [Data],
tutorialFiles: [Data] = [],
miscResourceURLs: [URL]
miscResourceURLs: [URL],
symbolIdentifiersWithExpandedDocumentation: [String: ExpandedDocumentationRequirements]? = nil
) {
self.externalIDsToConvert = externalIDsToConvert
self.documentPathsToConvert = documentPathsToConvert
Expand All @@ -229,6 +241,7 @@ public struct ConvertRequest: Codable {
self.miscResourceURLs = miscResourceURLs
self.bundleInfo = bundleInfo
self.featureFlags = featureFlags
self.symbolIdentifiersWithExpandedDocumentation = symbolIdentifiersWithExpandedDocumentation
}
}

Expand Down Expand Up @@ -288,4 +301,17 @@ extension ConvertRequest {
self.character = character
}
}

/// Represents any requirements needed for a symbol to have additional documentation available in the client.
public struct ExpandedDocumentationRequirements: Codable {
/// Access control levels required for the symbol to have additional documentation available.
public let accessControlLevels: [String]
/// Whether the client provides additional documentation for the symbol despite it being prefixed with an underscore.
public let canBeUnderscored: Bool

public init(accessControlLevels: [String], canBeUnderscored: Bool = false) {
self.accessControlLevels = accessControlLevels
self.canBeUnderscored = canBeUnderscored
}
}
}
28 changes: 26 additions & 2 deletions Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
/// The source repository where the documentation's sources are hosted.
var sourceRepository: SourceRepository?

/// The identifiers and access level requirements for symbols that have an expanded version of their documentation page if the requirements are met
var symbolIdentifiersWithExpandedDocumentation: [String: ConvertRequest.ExpandedDocumentationRequirements]? = nil

/// `true` if the conversion is cancelled.
private var isCancelled: Synchronized<Bool>? = nil

Expand All @@ -118,6 +121,8 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
/// Before passing `true` please confirm that your use case doesn't include public
/// distribution of any created render nodes as there are filesystem privacy and security
/// concerns with distributing this data.
/// - Parameter symbolIdentifiersWithExpandedDocumentation: Identifiers and access level requirements for symbols
/// that have an expanded version of their documentation page if the access level requirement is met.
public init(
documentationBundleURL: URL?,
emitDigest: Bool,
Expand All @@ -133,7 +138,8 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
emitSymbolAccessLevels: Bool = false,
sourceRepository: SourceRepository? = nil,
isCancelled: Synchronized<Bool>? = nil,
diagnosticEngine: DiagnosticEngine = .init()
diagnosticEngine: DiagnosticEngine = .init(),
symbolIdentifiersWithExpandedDocumentation: [String: ConvertRequest.ExpandedDocumentationRequirements]? = nil
) {
self.rootURL = documentationBundleURL
self.emitDigest = emitDigest
Expand All @@ -149,6 +155,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
self.sourceRepository = sourceRepository
self.isCancelled = isCancelled
self.diagnosticEngine = diagnosticEngine
self.symbolIdentifiersWithExpandedDocumentation = symbolIdentifiersWithExpandedDocumentation

// Inject current platform versions if provided
if let currentPlatforms = currentPlatforms {
Expand Down Expand Up @@ -250,13 +257,22 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
// Copy images, sample files, and other static assets.
try outputConsumer.consume(assetsInBundle: bundle)

let symbolIdentifiersMeetingRequirementsForExpandedDocumentation: [String]? = symbolIdentifiersWithExpandedDocumentation?.compactMap { (identifier, expandedDocsRequirement) -> String? in
guard let documentationNode = context.nodeWithSymbolIdentifier(identifier) else {
return nil
}

return documentationNode.meetsExpandedDocumentationRequirements(expandedDocsRequirement) ? identifier : nil
}

let converter = DocumentationContextConverter(
bundle: bundle,
context: context,
renderContext: renderContext,
emitSymbolSourceFileURIs: shouldEmitSymbolSourceFileURIs,
emitSymbolAccessLevels: shouldEmitSymbolAccessLevels,
sourceRepository: sourceRepository
sourceRepository: sourceRepository,
symbolIdentifiersWithExpandedDocumentation: symbolIdentifiersMeetingRequirementsForExpandedDocumentation
)

var indexingRecords = [IndexingRecord]()
Expand Down Expand Up @@ -455,3 +471,11 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
}
}
}

extension DocumentationNode {
func meetsExpandedDocumentationRequirements(_ requirements: ConvertRequest.ExpandedDocumentationRequirements) -> Bool {
guard let symbol = symbol else { return false }

return requirements.accessControlLevels.contains(symbol.accessLevel.rawValue) && (!symbol.names.title.starts(with: "_") || requirements.canBeUnderscored)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,11 @@ import SymbolKit
extension SymbolGraph.Symbol.AccessControl: Comparable {
private var level: Int? {
switch self {
case .private:
return 0
case .filePrivate:
return 1
case .internal:
return 2
case .public:
return 3
case .open:
return 4
case .private : return 1
case .filePrivate: return 2
case .internal: return 3
case .public: return 4
case .open: return 5
default:
assertionFailure("Unknown AccessControl case was used in comparison.")
return nil
Expand All @@ -34,7 +29,6 @@ extension SymbolGraph.Symbol.AccessControl: Comparable {
let rhs = rhs.level else {
return false
}

return lhs < rhs
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ public struct RenderMetadata: VariantContainer {

/// Any tags assigned to the node.
public var tags: [RenderNode.Tag]?

/// Whether there isn't a version of the page with more content that a renderer can link to.
///
/// This property indicates to renderers that an expanded version of the page does not exist for this render node,
/// which, for example, controls whether a 'View More' link should be displayed or not.
///
/// It's the renderer's responsibility to fetch the full version of the page, for example using
/// the ``RenderNode/variants`` property.
public var hasNoExpandedDocumentation: Bool = false
}

extension RenderMetadata: Codable {
Expand Down Expand Up @@ -238,6 +247,7 @@ extension RenderMetadata: Codable {
public static let images = CodingKeys(stringValue: "images")
public static let color = CodingKeys(stringValue: "color")
public static let customMetadata = CodingKeys(stringValue: "customMetadata")
public static let hasNoExpandedDocumentation = CodingKeys(stringValue: "hasNoExpandedDocumentation")
}

public init(from decoder: Decoder) throws {
Expand Down Expand Up @@ -267,6 +277,7 @@ extension RenderMetadata: Codable {
sourceFileURIVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .sourceFileURI)
remoteSourceVariants = try container.decodeVariantCollectionIfPresent(ofValueType: RemoteSource?.self, forKey: .remoteSource)
tags = try container.decodeIfPresent([RenderNode.Tag].self, forKey: .tags)
hasNoExpandedDocumentation = try container.decodeIfPresent(Bool.self, forKey: .hasNoExpandedDocumentation) ?? false

let extraKeys = Set(container.allKeys).subtracting(
[
Expand All @@ -288,7 +299,8 @@ extension RenderMetadata: Codable {
.navigatorTitle,
.sourceFileURI,
.remoteSource,
.tags
.tags,
.hasNoExpandedDocumentation,
]
)
for extraKey in extraKeys {
Expand Down Expand Up @@ -330,5 +342,6 @@ extension RenderMetadata: Codable {
try container.encodeIfNotEmpty(images, forKey: .images)
try container.encodeIfPresent(color, forKey: .color)
try container.encodeIfNotEmpty(customMetadata, forKey: .customMetadata)
try container.encodeIfTrue(hasNoExpandedDocumentation, forKey: .hasNoExpandedDocumentation)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,11 @@ extension KeyedEncodingContainer {
try encode(value, forKey: key)
}
}

/// Encodes the given boolean if its value is true.
mutating func encodeIfTrue(_ value: Bool, forKey key: Key) throws {
if value {
try encode(value, forKey: key)
}
}
}
15 changes: 12 additions & 3 deletions Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public struct RenderNodeTranslator: SemanticVisitor {
/// The source repository where the documentation's sources are hosted.
var sourceRepository: SourceRepository?

var symbolIdentifiersWithExpandedDocumentation: [String]? = nil

public mutating func visitCode(_ code: Code) -> RenderTree? {
let fileType = NSString(string: code.fileName).pathExtension
let fileReference = code.fileReference
Expand Down Expand Up @@ -1179,8 +1181,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
} else if let extendedModule = symbol.extendedModule, extendedModule != moduleName.displayName {
node.metadata.modulesVariants = VariantCollection(defaultValue: [RenderMetadata.Module(name: moduleName.displayName, relatedModules: [extendedModule])])
} else {
node.metadata.modulesVariants = VariantCollection(defaultValue: [RenderMetadata.Module(name: moduleName.displayName, relatedModules: nil)]
)
node.metadata.modulesVariants = VariantCollection(defaultValue: [RenderMetadata.Module(name: moduleName.displayName, relatedModules: nil)])
}

node.metadata.extendedModuleVariants = VariantCollection<String?>(defaultValue: symbol.extendedModule)
Expand Down Expand Up @@ -1355,6 +1356,12 @@ public struct RenderNodeTranslator: SemanticVisitor {
node.metadata.symbolAccessLevelVariants = VariantCollection<String?>(from: symbol.accessLevelVariants)
}

if let externalID = symbol.externalID,
let symbolIdentifiersWithExpandedDocumentation = symbolIdentifiersWithExpandedDocumentation
{
node.metadata.hasNoExpandedDocumentation = !symbolIdentifiersWithExpandedDocumentation.contains(externalID)
}

node.relationshipSectionsVariants = VariantCollection<[RelationshipsRenderSection]>(
from: documentationNode.availableVariantTraits,
fallbackDefaultValue: []
Expand Down Expand Up @@ -1878,7 +1885,8 @@ public struct RenderNodeTranslator: SemanticVisitor {
renderContext: RenderContext? = nil,
emitSymbolSourceFileURIs: Bool = false,
emitSymbolAccessLevels: Bool = false,
sourceRepository: SourceRepository? = nil
sourceRepository: SourceRepository? = nil,
symbolIdentifiersWithExpandedDocumentation: [String]? = nil
) {
self.context = context
self.bundle = bundle
Expand All @@ -1889,6 +1897,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
self.shouldEmitSymbolSourceFileURIs = emitSymbolSourceFileURIs
self.shouldEmitSymbolAccessLevels = emitSymbolAccessLevels
self.sourceRepository = sourceRepository
self.symbolIdentifiersWithExpandedDocumentation = symbolIdentifiersWithExpandedDocumentation
}
}

Expand Down
Loading

0 comments on commit 85341fa

Please sign in to comment.