Skip to content

Commit b46c7b5

Browse files
authored
[external-links] Display frameworks as beta only if all platforms are beta (#938)
* Display frameworks as beta only if all platforms are beta Changes the behaviour of external link resolution in `OutOfProcessReferenceResolver` to only display a framework to be in beta if all the platforms its supported in are in beta. This makes it match the behaviour for internal link resolution, so that the behaviour is consistent whether you're linking to a symbol internally within a framework or externally from another framework. * Refactor code to have same behaviour in both external link resolvers Makes changes to `OutOfProcessReferenceResolver .ResolvedInformation` so that it can be replaced by `LinkDestinationSummary` at some point in the future. Both `OutOfProcessReferenceResolver .ResolvedInformation` and `LinkDestinationSummary` now contain information about whether the resolved external link points to a symbol in beta or not, using equivalent API. This is an attempt at keeping both of the logics about whether an external symbol is in beta in sync with each other. Hopefully in the future we can unify and have the same code path for both, but right now the logic is duplicated (and equal). * Add a test for beta logic in ExternalPathHierarchyResolver Adds a unit test which verifies that the logic for determining whether a symbol is in beta when using `ExternalPathHierarchyResolver` is as expected. * Fix formatting Test file `OutOfProcessReferenceResolverTests` had inconsistent and confusing formatting, particularly with how the type of `makeResolver` was being defined across multiple lines in a way inconsistent with the rest of the codebase. `makeResolver` is now declared as a one-liner and fixes other minor formatting/spacing issues. * Remove noop Info.plist The file isn't needed, so removing it. * Remove changes to testBundleAndContext helpers We want to avoid spreading the configure context parameter to the other test helpers, as we're slowly working to make the context not be configured after creation. Instead, `ExternalPathHierarchyResolverTests` now calls the underlying `loadBundle(from:configureContext:)` directly. --- Resolves rdar://128997995.
1 parent 38700f2 commit b46c7b5

File tree

5 files changed

+462
-13
lines changed

5 files changed

+462
-13
lines changed

Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE
155155
kind: kind,
156156
role: role,
157157
fragments: resolvedInformation.declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) },
158-
isBeta: (resolvedInformation.platforms ?? []).contains(where: { $0.isBeta == true }),
158+
isBeta: resolvedInformation.isBeta,
159159
isDeprecated: (resolvedInformation.platforms ?? []).contains(where: { $0.deprecated != nil }),
160160
images: resolvedInformation.topicImages ?? []
161161
)
@@ -587,6 +587,15 @@ extension OutOfProcessReferenceResolver {
587587

588588
/// The variants of content (kind, url, title, abstract, language, declaration) for this resolver information.
589589
public var variants: [Variant]?
590+
591+
/// A value that indicates whether this symbol is under development and likely to change.
592+
var isBeta: Bool {
593+
guard let platforms, !platforms.isEmpty else {
594+
return false
595+
}
596+
597+
return platforms.allSatisfy { $0.isBeta == true }
598+
}
590599

591600
/// Creates a new resolved information value with all its values.
592601
///

Sources/SwiftDocC/Infrastructure/Link Resolution/ExternalPathHierarchyResolver.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ private extension Sequence<DeclarationRenderSection.Token> {
182182
// MARK: ExternalEntity
183183

184184
private extension LinkDestinationSummary {
185+
/// A value that indicates whether this symbol is under development and likely to change.
186+
var isBeta: Bool {
187+
guard let platforms, !platforms.isEmpty else {
188+
return false
189+
}
190+
191+
return platforms.allSatisfy { $0.isBeta == true }
192+
}
193+
185194
/// Create a topic render render reference for this link summary and its content variants.
186195
func topicRenderReference() -> TopicRenderReference {
187196
let (kind, role) = DocumentationContentRenderer.renderKindAndRole(kind, semantic: nil)
@@ -215,7 +224,7 @@ private extension LinkDestinationSummary {
215224
navigatorTitleVariants: .init(defaultValue: nil),
216225
estimatedTime: nil,
217226
conformance: nil,
218-
isBeta: platforms?.contains(where: { $0.isBeta == true }) ?? false,
227+
isBeta: isBeta,
219228
isDeprecated: platforms?.contains(where: { $0.unconditionallyDeprecated == true }) ?? false,
220229
defaultImplementationCount: nil,
221230
propertyListKeyNames: nil,

Tests/SwiftDocCTests/Infrastructure/ExternalPathHierarchyResolverTests.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,30 @@ class ExternalPathHierarchyResolverTests: XCTestCase {
820820
to: "doc://com.shapes.ShapeKit/documentation/ShapeKit/OverloadedProtocol/fourthTestMemberName(test:)-9b6be"
821821
)
822822
}
823+
824+
func testBetaInformationPreserved() throws {
825+
let platformMetadata = [
826+
"macOS": PlatformVersion(VersionTriplet(1, 0, 0), beta: true),
827+
"watchOS": PlatformVersion(VersionTriplet(2, 0, 0), beta: true),
828+
"tvOS": PlatformVersion(VersionTriplet(3, 0, 0), beta: true),
829+
"iOS": PlatformVersion(VersionTriplet(4, 0, 0), beta: true),
830+
"Mac Catalyst": PlatformVersion(VersionTriplet(4, 0, 0), beta: true),
831+
"iPadOS": PlatformVersion(VersionTriplet(4, 0, 0), beta: true),
832+
]
833+
let linkResolvers = try makeLinkResolversForTestBundle(named: "AvailabilityBetaBundle") { context in
834+
context.externalMetadata.currentPlatforms = platformMetadata
835+
}
836+
837+
// MyClass is only available on beta platforms (macos=1.0.0, watchos=2.0.0, tvos=3.0.0, ios=4.0.0)
838+
try linkResolvers.assertBetaStatus(authoredLink: "/MyKit/MyClass", isBeta: true)
839+
840+
// MyOtherClass is available on some beta platforms (macos=1.0.0, watchos=2.0.0, tvos=3.0.0, ios=3.0.0)
841+
try linkResolvers.assertBetaStatus(authoredLink: "/MyKit/MyOtherClass", isBeta: false)
842+
843+
// MyThirdClass has no platform availability information
844+
try linkResolvers.assertBetaStatus(authoredLink: "/MyKit/MyThirdClass", isBeta: false)
845+
846+
}
823847

824848
// MARK: Test helpers
825849

@@ -864,6 +888,23 @@ class ExternalPathHierarchyResolverTests: XCTestCase {
864888
}
865889
}
866890

891+
func assertBetaStatus(
892+
authoredLink: String,
893+
isBeta: Bool,
894+
file: StaticString = #file,
895+
line: UInt = #line
896+
) throws {
897+
try assertResults(authoredLink: authoredLink) { result, label in
898+
switch result {
899+
case .success(let resolved):
900+
let entity = externalResolver.entity(resolved)
901+
XCTAssertEqual(entity.topicRenderReference.isBeta, isBeta, file: file, line: line)
902+
case .failure(_, let errorInfo):
903+
XCTFail("Unexpectedly failed to resolve \(label) link: \(errorInfo.message) \(errorInfo.solutions.map(\.summary).joined(separator: ", "))", file: file, line: line)
904+
}
905+
}
906+
}
907+
867908
func assertFailsToResolve(
868909
authoredLink: String,
869910
errorMessage: String,
@@ -896,8 +937,10 @@ class ExternalPathHierarchyResolverTests: XCTestCase {
896937
}
897938
}
898939

899-
private func makeLinkResolversForTestBundle(named testBundleName: String) throws -> LinkResolvers {
900-
let (bundle, context) = try testBundleAndContext(named: testBundleName)
940+
private func makeLinkResolversForTestBundle(named testBundleName: String, configureContext: ((DocumentationContext) throws -> Void)? = nil) throws -> LinkResolvers {
941+
let bundleURL = try XCTUnwrap(Bundle.module.url(forResource: testBundleName, withExtension: "docc", subdirectory: "Test Bundles"))
942+
let (_, bundle, context) = try loadBundle(from: bundleURL, configureContext: configureContext)
943+
901944
let localResolver = try XCTUnwrap(context.linkResolver.localResolver)
902945

903946
let resolverInfo = try localResolver.prepareForSerialization(bundleID: bundle.identifier)
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
{
2+
"metadata": {
3+
"formatVersion" : {
4+
"major" : 1
5+
},
6+
"generator" : "app/1.0"
7+
},
8+
"module" : {
9+
"name" : "MyKit",
10+
"platform" : {
11+
"architecture" : "x86_64",
12+
"vendor" : "apple",
13+
"operatingSystem" : {
14+
"name" : "macosx",
15+
"minimumVersion" : {
16+
"major" : 1,
17+
"minor" : 0,
18+
"patch" : 0
19+
}
20+
}
21+
}
22+
},
23+
"symbols" : [
24+
{
25+
"accessLevel" : "public",
26+
"kind" : {
27+
"identifier" : "swift.class",
28+
"displayName" : "Class"
29+
},
30+
"names" : {
31+
"title" : "MyClass",
32+
"subHeading" : [
33+
{
34+
"kind" : "keyword",
35+
"spelling" : "class"
36+
},
37+
{
38+
"kind" : "text",
39+
"spelling" : " "
40+
},
41+
{
42+
"kind" : "identifier",
43+
"spelling" : "MyClass"
44+
}
45+
],
46+
"navigator" : [
47+
{
48+
"kind" : "identifier",
49+
"spelling" : "MyClassNavigator"
50+
}
51+
]
52+
},
53+
"availability" : [
54+
{
55+
"domain": "macOS",
56+
"introduced": {
57+
"major": 1,
58+
"minor": 0
59+
}
60+
},
61+
{
62+
"domain": "watchOS",
63+
"introduced": {
64+
"major": 2,
65+
"minor": 0
66+
}
67+
},
68+
{
69+
"domain": "tvOS",
70+
"introduced": {
71+
"major": 3,
72+
"minor": 0
73+
}
74+
},
75+
{
76+
"domain": "iOS",
77+
"introduced": {
78+
"major": 4,
79+
"minor": 0
80+
}
81+
}
82+
],
83+
"pathComponents" : [
84+
"MyClass"
85+
],
86+
"identifier" : {
87+
"precise" : "s:5MyKit0A5ClassC",
88+
"interfaceLanguage": "swift"
89+
},
90+
"declarationFragments" : [
91+
{
92+
"kind" : "keyword",
93+
"spelling" : "class"
94+
},
95+
{
96+
"kind" : "text",
97+
"spelling" : " "
98+
},
99+
{
100+
"kind" : "identifier",
101+
"spelling" : "MyClass"
102+
}
103+
]
104+
},
105+
{
106+
"accessLevel" : "public",
107+
"kind" : {
108+
"identifier" : "swift.class",
109+
"displayName" : "Class"
110+
},
111+
"names" : {
112+
"title" : "MyOtherClass",
113+
"subHeading" : [
114+
{
115+
"kind" : "keyword",
116+
"spelling" : "class"
117+
},
118+
{
119+
"kind" : "text",
120+
"spelling" : " "
121+
},
122+
{
123+
"kind" : "identifier",
124+
"spelling" : "MyOtherClass"
125+
}
126+
],
127+
"navigator" : [
128+
{
129+
"kind" : "identifier",
130+
"spelling" : "MyOtherClassNavigator"
131+
}
132+
]
133+
},
134+
"availability" : [
135+
{
136+
"domain": "macOS",
137+
"introduced": {
138+
"major": 1,
139+
"minor": 0
140+
}
141+
},
142+
{
143+
"domain": "watchOS",
144+
"introduced": {
145+
"major": 2,
146+
"minor": 0
147+
}
148+
},
149+
{
150+
"domain": "tvOS",
151+
"introduced": {
152+
"major": 3,
153+
"minor": 0
154+
}
155+
},
156+
{
157+
"domain": "iOS",
158+
"introduced": {
159+
"major": 3,
160+
"minor": 0
161+
}
162+
}
163+
],
164+
"pathComponents" : [
165+
"MyOtherClass"
166+
],
167+
"identifier" : {
168+
"precise" : "s:5MyKit0A5MyOtherClassC",
169+
"interfaceLanguage": "swift"
170+
},
171+
"declarationFragments" : [
172+
{
173+
"kind" : "keyword",
174+
"spelling" : "class"
175+
},
176+
{
177+
"kind" : "text",
178+
"spelling" : " "
179+
},
180+
{
181+
"kind" : "identifier",
182+
"spelling" : "MyOtherClass"
183+
}
184+
]
185+
},
186+
{
187+
"accessLevel" : "public",
188+
"kind" : {
189+
"identifier" : "swift.class",
190+
"displayName" : "Class"
191+
},
192+
"names" : {
193+
"title" : "MyThirdClass",
194+
"subHeading" : [
195+
{
196+
"kind" : "keyword",
197+
"spelling" : "class"
198+
},
199+
{
200+
"kind" : "text",
201+
"spelling" : " "
202+
},
203+
{
204+
"kind" : "identifier",
205+
"spelling" : "MyThirdClass"
206+
}
207+
],
208+
"navigator" : [
209+
{
210+
"kind" : "identifier",
211+
"spelling" : "MyThirdClassNavigator"
212+
}
213+
]
214+
},
215+
"availability" : [],
216+
"pathComponents" : [
217+
"MyThirdClass"
218+
],
219+
"identifier" : {
220+
"precise" : "s:5MyKit0A5MyThirdClassC",
221+
"interfaceLanguage": "swift"
222+
},
223+
"declarationFragments" : [
224+
{
225+
"kind" : "keyword",
226+
"spelling" : "class"
227+
},
228+
{
229+
"kind" : "text",
230+
"spelling" : " "
231+
},
232+
{
233+
"kind" : "identifier",
234+
"spelling" : "MyThirdClass"
235+
}
236+
]
237+
},
238+
239+
],
240+
"relationships" : []
241+
}

0 commit comments

Comments
 (0)