Skip to content
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
6 changes: 6 additions & 0 deletions Sources/SwiftDocC/Model/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@ struct Availability {
///
/// A page as a whole is considered to be deprecated if the API is deprecated on all the platforms that the API is available for.
var isDeprecated: Bool {
// A page without any availability information (for example a module without default availability)
// isn't deprecated. Without this check, `allSatisfy` returns true, which reflects the content as deprecated.
let hasAnyAvailabilityInformation = (knownPlatforms + customPlatformsByName.values).contains { $0.source != .initialValue }
guard hasAnyAvailabilityInformation else {
return false
}
return knownPlatforms.allSatisfy {
switch $0.state {
case .available(_, .some, _):
Expand Down
84 changes: 84 additions & 0 deletions Tests/SwiftDocCTests/Model/AvailabilityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,90 @@ struct AvailabilityTests {
let renderReference = try #require(converter.renderContext.store.content(for: node.reference)?.renderReference as? TopicRenderReference)
#expect(renderReference.isDeprecated == false)
}

// MARK: Module availability

private func makeRenderNode(for reference: ResolvedTopicReference, in context: DocumentationContext, sourceLocation: SourceLocation = #_sourceLocation) throws -> RenderNode {
try makeRenderNodeAndReference(for: reference, in: context, sourceLocation: sourceLocation).renderNode
}

private func makeRenderNodeAndReference(for reference: ResolvedTopicReference, in context: DocumentationContext, sourceLocation: SourceLocation = #_sourceLocation) throws -> (renderNode: RenderNode, topicReference: TopicRenderReference) {
let converter = DocumentationContextConverter(context: context, renderContext: .init(documentationContext: context))
let node = try #require(context.documentationCache[reference], sourceLocation: sourceLocation)
let renderNode = try #require(converter.renderNode(for: node), sourceLocation: sourceLocation)
let topicReference = try #require(converter.renderContext.store.content(for: reference)?.renderReference as? TopicRenderReference, sourceLocation: sourceLocation)
return (renderNode, topicReference)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: This test helper might be a bit too specialized with it only being called from a single test. You could see if it makes sense to move it into It could move into that test as an inner function.

Alternatively, if there are more tests that need both the RenderNode and the TopicRenderReference; maybe it would make sense for a helper function to return both. A caller could restructure the return value as let (renderNode, _) = try ... if they only need one of the values. That way the assertIsNotDeprecated(...) helper could likely go away completely.


@Test(arguments: Self.allMainPlatforms)
func moduleWithoutAvailabilityInformationIsNotConsideredDeprecated(_ platform: SymbolGraph.Platform) async throws {
let catalog = Folder(name: "unit-test.docc") {
JSONFile(symbolGraph: makeSymbolGraph(moduleName: "ModuleName", platform: platform, symbols: [
makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"]),
]))
}
let context = try await load(catalog: catalog)
#expect(context.diagnostics.isEmpty, "Unexpected problems: \(context.diagnostics.map(\.summary))")

let moduleReference = try #require(context.soleRootModuleReference)
let symbolReference = try #require(context.documentationCache.reference(symbolID: "some-symbol-id"))
for reference in [moduleReference, symbolReference] {
let (renderNode, topicReference) = try makeRenderNodeAndReference(for: reference, in: context)
#expect(renderNode.isDeprecated == false)
#expect(topicReference.isDeprecated == false)
#expect(renderNode.metadata.platforms == nil)
}
}

@Test
func moduleFillsInFallbackAvailabilityFromVersionedDefaultAvailability() async throws {
let catalog = Folder(name: "unit-test.docc") {
JSONFile(symbolGraph: makeSymbolGraph(moduleName: "ModuleName", platform: .init(operatingSystem: .init(name: "ios")), symbols: [
makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"]),
]))
InfoPlist(defaultAvailability: ["ModuleName": [
.init(platformName: .iOS, platformVersion: "8.0")
]])
}
let context = try await load(catalog: catalog)
#expect(context.diagnostics.isEmpty, "Unexpected problems: \(context.diagnostics.map(\.summary))")

let moduleReference = try #require(context.soleRootModuleReference)
let symbolReference = try #require(context.documentationCache.reference(symbolID: "some-symbol-id"))
for reference in [moduleReference, symbolReference] {
let renderNode = try makeRenderNode(for: reference, in: context)
let renderPlatforms = try #require(renderNode.metadata.platforms)
#expect(renderPlatforms.compactMap(\.name) == ["iOS", "iPadOS", "Mac Catalyst"])
for platform in renderPlatforms {
#expect(platform.introduced == "8.0", "\(platform.name ?? "")")
}
#expect(renderNode.isDeprecated == false)
}
}

@Test
func moduleDisplaysVersionlessDefaultAvailabilityButItsSymbolsDoNot() async throws {
let catalog = Folder(name: "unit-test.docc") {
JSONFile(symbolGraph: makeSymbolGraph(moduleName: "ModuleName", platform: .init(operatingSystem: .init(name: "ios")), symbols: [
makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"]),
]))
InfoPlist(defaultAvailability: ["ModuleName": [
.init(platformName: .iOS, platformVersion: nil)
]])
}
let context = try await load(catalog: catalog)
#expect(context.diagnostics.isEmpty, "Unexpected problems: \(context.diagnostics.map(\.summary))")

// Known inconsistency (rdar://190715484): when the Info.plist default availability has no version,
// the module displays that platform but the symbols in the module don't.
let moduleReference = try #require(context.soleRootModuleReference)
let moduleRenderNode = try makeRenderNode(for: moduleReference, in: context)
#expect(moduleRenderNode.metadata.platforms?.compactMap(\.name) == ["iOS"])
#expect(moduleRenderNode.isDeprecated == false)

let symbolReference = try #require(context.documentationCache.reference(symbolID: "some-symbol-id"))
#expect(try makeRenderNode(for: symbolReference, in: context).metadata.platforms == nil)
}

@Test(arguments: ["9.2", nil])
func symbolAvailableOnOnePlatformIsNotConsideredDeprecatedWhenDefaultAvailable(defaultIntroducedVersion: String?) async throws {
Expand Down
Loading