diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift index a3981a1dac..bb4b51cda4 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift @@ -59,24 +59,6 @@ struct SymbolGraphLoader { let bundle = self.bundle let dataProvider = self.dataProvider - /// Computes the default availbiality based on the `inheritDefaultAvailability` option. - let defaultAvailabilities: ([DefaultAvailability.ModuleAvailability]?) -> [DefaultAvailability.ModuleAvailability]? = { defautAvailabilities in - guard let defautAvailabilities else { return nil } - // Check the selected behaviour for inheritance of the default availability and remove the avaialbity - // version if it's set to `platformOnly`. - if !bundle.info.defaultAvailabilityOptions.options.contains(.inheritVersionNumber) { - return defautAvailabilities.map { defaultAvailability in - var defaultAvailability = defaultAvailability - switch defaultAvailability.versionInformation { - case .available(_): defaultAvailability.versionInformation = .available(version: nil) - case .unavailable: () - } - return defaultAvailability - } - } - return defautAvailabilities - } - let loadGraphAtURL: (URL) -> Void = { symbolGraphURL in // Bail out in case a symbol graph has already errored guard loadError == nil else { return } @@ -97,9 +79,8 @@ struct SymbolGraphLoader { configureSymbolGraph?(&symbolGraph) let (moduleName, isMainSymbolGraph) = Self.moduleNameFor(symbolGraph, at: symbolGraphURL) - let defaultAvailabilities = defaultAvailabilities(bundle.info.defaultAvailability?.modules[moduleName]) // If the bundle provides availability defaults add symbol availability data. - self.addDefaultAvailability(to: &symbolGraph, moduleName: moduleName, defaultAvailabilities: defaultAvailabilities) + self.addDefaultAvailability(to: &symbolGraph, moduleName: moduleName) // main symbol graphs are ambiguous var usesExtensionSymbolFormat: Bool? = nil @@ -172,7 +153,7 @@ struct SymbolGraphLoader { var defaultUnavailablePlatforms = [PlatformName]() var defaultAvailableInformation = [DefaultAvailability.ModuleAvailability]() - if let defaultAvailabilities = defaultAvailabilities(bundle.info.defaultAvailability?.modules[unifiedGraph.moduleName]) { + if let defaultAvailabilities = bundle.info.defaultAvailability?.modules[unifiedGraph.moduleName] { let (unavailablePlatforms, availablePlatforms) = defaultAvailabilities.categorize(where: { $0.versionInformation == .unavailable }) defaultUnavailablePlatforms = unavailablePlatforms.map(\.platformName) defaultAvailableInformation = availablePlatforms @@ -298,11 +279,11 @@ struct SymbolGraphLoader { /// If the bundle defines default availability for the symbols in the given symbol graph /// this method adds them to each of the symbols in the graph. - private func addDefaultAvailability(to symbolGraph: inout SymbolGraph, moduleName: String, defaultAvailabilities: [DefaultAvailability.ModuleAvailability]?) { + private func addDefaultAvailability(to symbolGraph: inout SymbolGraph, moduleName: String) { let selector = UnifiedSymbolGraph.Selector(forSymbolGraph: symbolGraph) // Check if there are defined default availabilities for the current module - if let defaultAvailabilities = defaultAvailabilities, - let platformName = symbolGraph.module.platform.name.map(PlatformName.init) { + if let defaultAvailabilities = bundle.info.defaultAvailability?.modules[moduleName], + let platformName = symbolGraph.module.platform.name.map(PlatformName.init) { // Prepare a default availability versions lookup for this module. let defaultAvailabilityVersionByPlatform = defaultAvailabilities diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DefaultAvailailabilityOptions.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DefaultAvailailabilityOptions.swift index bba999fb06..781d829430 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/DefaultAvailailabilityOptions.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/DefaultAvailailabilityOptions.swift @@ -10,60 +10,66 @@ import Foundation -extension DocumentationBundle.Info { +/// A collection of options that customaise the default availability behaviour. +/// +/// Default availability options are applied to all the modules contained in the documentation bundle. +/// +/// This information can be authored in the bundle's Info.plist file, as a dictionary of option name and boolean pairs. +/// +/// ``` +/// CDDefaultAvailabilityOptions +/// +/// OptionName +/// +/// +/// ``` +public struct DefaultAvailabilityOptions: Codable, Equatable { - /// A collection of options that customaise the default availability behaviour. - /// - /// Default availability options are applied to all the modules contained in the documentation bundle. - /// - /// This information can be authored in the bundle's Info.plist file, as a dictionary of option name and boolean pairs. - /// - /// ``` - /// CDDefaultAvailabilityOptions - /// - /// OptionName - /// - /// - /// ``` - public struct DefaultAvailabilityOptions: Codable, Equatable { - - /// A set of non-standard behaviors that apply to this node. - fileprivate(set) var options: Options - - /// Options that specify behaviors of the default availability logic. - struct Options: OptionSet { + /// A set of non-standard behaviors that apply to this node. + fileprivate(set) var options: Options + + /// Options that specify behaviors of the default availability logic. + struct Options: OptionSet { - let rawValue: Int - - /// Enable or disable symbol availability version inference from the module default availability. - static let inheritVersionNumber = Options(rawValue: 1 << 0) - } + let rawValue: Int - /// String representation of the default availability options. - private enum CodingKeys: String, CodingKey { - case inheritVersionNumber = "InheritVersionNumber" - } + /// Enable or disable symbol availability version inference from the module default availability. + static let inheritVersionNumber = Options(rawValue: 1 << 0) + } + + /// String representation of the default availability options. + private enum CodingKeys: String, CodingKey { + case inheritVersionNumber = "InheritVersionNumber" + } + + public init() { + self.options = .inheritVersionNumber + } - public init(from decoder: any Decoder) throws { - self.init() - let values = try decoder.container(keyedBy: CodingKeys.self) - if try values.decodeIfPresent(Bool.self, forKey: .inheritVersionNumber) == false { - options.remove(.inheritVersionNumber) - } + public init(from decoder: any Decoder) throws { + self.init() + let values = try decoder.container(keyedBy: CodingKeys.self) + if try values.decodeIfPresent(Bool.self, forKey: .inheritVersionNumber) == false { + options.remove(.inheritVersionNumber) } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - if !options.contains(.inheritVersionNumber) { - try container.encode(false, forKey: .inheritVersionNumber) - } - + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if !options.contains(.inheritVersionNumber) { + try container.encode(false, forKey: .inheritVersionNumber) } - - public init() { - self.options = .inheritVersionNumber + } + + /// Convenient method to determine if an option has to be applied depending + /// on it's exsistence inside the options set. + func shouldApplyOption(_ option: Options) -> Bool { + switch option { + case .inheritVersionNumber: + return options.contains(.inheritVersionNumber) + default: + return false } - } } diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift index b4a001e204..7a9083ade6 100644 --- a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift +++ b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift @@ -50,7 +50,7 @@ extension DocumentationBundle { static let requiredKeys: Set = [.displayName, .identifier] /// The default availability behaviour options. - public var defaultAvailabilityOptions = DefaultAvailabilityOptions() + public var defaultAvailabilityOptions: DefaultAvailabilityOptions? enum CodingKeys: String, CodingKey, CaseIterable { @@ -114,7 +114,7 @@ extension DocumentationBundle { defaultCodeListingLanguage: String?, defaultAvailability: DefaultAvailability?, defaultModuleKind: String?, - defaultAvailabilityOptions: DefaultAvailabilityOptions + defaultAvailabilityOptions: DefaultAvailabilityOptions? ) { self.displayName = displayName self.identifier = identifier @@ -252,9 +252,31 @@ extension DocumentationBundle { self.defaultCodeListingLanguage = try decodeOrFallbackIfPresent(String.self, with: .defaultCodeListingLanguage) self.defaultModuleKind = try decodeOrFallbackIfPresent(String.self, with: .defaultModuleKind) - self.defaultAvailability = try decodeOrFallbackIfPresent(DefaultAvailability.self, with: .defaultAvailability) + self.defaultAvailabilityOptions = try decodeOrFallbackIfPresent(DefaultAvailabilityOptions.self, with: .defaultAvailabilityOptions) + var defaultAvailability = try decodeOrFallbackIfPresent(DefaultAvailability.self, with: .defaultAvailability) + + // Apply default availability options mutations. + if let defaultAvailabilityOptions { + // Remove the availability version if `inheritVersionNumber` is not part + // of the default availability options. + if !defaultAvailabilityOptions.shouldApplyOption(.inheritVersionNumber) { + for (key, var moduleAvailability) in defaultAvailability?.modules ?? [:] { + moduleAvailability = moduleAvailability.map { moduleAvailability in + var moduleAvailability = moduleAvailability + moduleAvailability.versionInformation = { + switch moduleAvailability.versionInformation { + case .available(_): return .available(version: nil) + default: return moduleAvailability.versionInformation + } + }() + return moduleAvailability + } + defaultAvailability?.modules[key] = moduleAvailability + } + } + } + self.defaultAvailability = defaultAvailability self.featureFlags = try decodeOrFallbackIfPresent(BundleFeatureFlags.self, with: .featureFlags) - self.defaultAvailabilityOptions = try decodeOrFallbackIfPresent(DefaultAvailabilityOptions.self, with: .defaultAvailabilityOptions) ?? DefaultAvailabilityOptions() } init( @@ -266,7 +288,7 @@ extension DocumentationBundle { defaultAvailability: DefaultAvailability? = nil, featureFlags: BundleFeatureFlags? = nil, inheritDefaultAvailability: InheritDefaultAvailabilityOptions? = nil, - defaultAvailabilityOptions: DefaultAvailabilityOptions = DefaultAvailabilityOptions() + defaultAvailabilityOptions: DefaultAvailabilityOptions? = nil ) { self.displayName = displayName self.identifier = identifier @@ -289,7 +311,7 @@ extension DocumentationBundle { try container.encodeIfPresent(self.defaultModuleKind, forKey: DocumentationBundle.Info.CodingKeys.defaultModuleKind) try container.encodeIfPresent(self.featureFlags, forKey: DocumentationBundle.Info.CodingKeys.featureFlags) if defaultAvailabilityOptions != .init() { - try container.encode(self.defaultAvailabilityOptions, forKey: DocumentationBundle.Info.CodingKeys.defaultAvailabilityOptions) + try container.encodeIfPresent(self.defaultAvailabilityOptions, forKey: DocumentationBundle.Info.CodingKeys.defaultAvailabilityOptions) } } } @@ -318,7 +340,7 @@ extension BundleDiscoveryOptions { fallbackDefaultModuleKind: String? = nil, fallbackDefaultAvailability: DefaultAvailability? = nil, additionalSymbolGraphFiles: [URL] = [], - defaultAvailabilityOptions: DocumentationBundle.Info.DefaultAvailabilityOptions? = nil + defaultAvailabilityOptions: DefaultAvailabilityOptions? = nil ) { // Iterate over all possible coding keys with a switch // to build up the dictionary of fallback options. diff --git a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift index ce2c002a11..3ddc28bb6c 100644 --- a/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift +++ b/Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift @@ -1243,8 +1243,8 @@ public struct RenderNodeTranslator: SemanticVisitor { .compactMap { availability -> AvailabilityRenderItem? in // Filter items with insufficient availability data unless the default availability behaviour // allows availability withound version information. - let applyDefaultAvailabilityVersionToSymbols = !bundle.info.defaultAvailabilityOptions.options.contains(.inheritVersionNumber) - guard availability.introducedVersion != nil || applyDefaultAvailabilityVersionToSymbols else { + let omitDefaultAvailabilityVersionFromSymbols = bundle.info.defaultAvailabilityOptions?.shouldApplyOption(.inheritVersionNumber) == false + guard availability.introducedVersion != nil || omitDefaultAvailabilityVersionFromSymbols else { return nil } guard let name = availability.domain.map({ PlatformName(operatingSystemName: $0.rawValue) }),