Skip to content

Commit 9413c59

Browse files
authored
Add a new version of the communication protocol between DocC and external link resolver executables (#1292)
* Move old request and response to a separate file * Introduce new request and response versions * Allow external link executables to more information based on declared capabilities rdar://159610082 #802 * Support transient references in external content * Stop emitting no longer necessary warning about external references rdar://76656957 rdar://77906558 rdar://86334416 rdar://92254059 rdar://120424365 #341 * Remove trailing comma in parameters for Swift 5.9 compatibility * Address code review feedback * Add missing public initializers to new response diagnostic info type
1 parent 1d1ebbe commit 9413c59

26 files changed

+2361
-1022
lines changed

Sources/SwiftDocC/Checker/Checkers/AbstractContainsFormattedTextOnly.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2025 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -14,6 +14,7 @@ public import Markdown
1414
/**
1515
A document's abstract may only contain formatted text. Images and links are not allowed.
1616
*/
17+
@available(*, deprecated, message: "This check is no longer applicable. This deprecated API will be removed after 6.3 is released")
1718
public struct AbstractContainsFormattedTextOnly: Checker {
1819
public var problems: [Problem] = [Problem]()
1920
private var sourceFile: URL?

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,6 @@ public class DocumentationContext {
265265
/// - source: The location of the document.
266266
private func check(_ document: Document, at source: URL) {
267267
var checker = CompositeChecker([
268-
AbstractContainsFormattedTextOnly(sourceFile: source).any(),
269268
DuplicateTopicsSections(sourceFile: source).any(),
270269
InvalidAdditionalTitle(sourceFile: source).any(),
271270
MissingAbstract(sourceFile: source).any(),
@@ -2739,17 +2738,17 @@ public class DocumentationContext {
27392738
knownEntityValue(
27402739
reference: reference,
27412740
valueInLocalEntity: \.availableSourceLanguages,
2742-
valueInExternalEntity: \.sourceLanguages
2741+
valueInExternalEntity: \.availableLanguages
27432742
)
27442743
}
27452744

27462745
/// Returns whether the given reference corresponds to a symbol.
27472746
func isSymbol(reference: ResolvedTopicReference) -> Bool {
27482747
knownEntityValue(
27492748
reference: reference,
2750-
valueInLocalEntity: { node in node.kind.isSymbol },
2751-
valueInExternalEntity: { entity in entity.topicRenderReference.kind == .symbol }
2752-
)
2749+
valueInLocalEntity: \.kind,
2750+
valueInExternalEntity: \.kind
2751+
).isSymbol
27532752
}
27542753

27552754
// MARK: - Relationship queries
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
extension OutOfProcessReferenceResolver {
12+
// MARK: Capabilities
13+
14+
/// A set of optional capabilities that either DocC or your external link resolver declares that it supports.
15+
///
16+
/// ## Supported messages
17+
///
18+
/// If your external link resolver declares none of the optional capabilities, then DocC will only send it the following messages:
19+
/// - ``RequestV2/link(_:)``
20+
/// - ``RequestV2/symbol(_:)``
21+
public struct Capabilities: OptionSet, Codable {
22+
public let rawValue: Int
23+
public init(rawValue: Int) {
24+
self.rawValue = rawValue
25+
}
26+
27+
public func encode(to encoder: any Encoder) throws {
28+
var container = encoder.singleValueContainer()
29+
try container.encode(rawValue)
30+
}
31+
32+
public init(from decoder: any Decoder) throws {
33+
let container = try decoder.singleValueContainer()
34+
rawValue = try container.decode(Int.self)
35+
}
36+
}
37+
38+
// MARK: Request & Response
39+
40+
/// Request messages that DocC sends to the external link resolver.
41+
///
42+
/// ## Topics
43+
/// ### Base requests
44+
///
45+
/// Your external link resolver always needs to handle the following requests regardless of its declared capabilities:
46+
///
47+
/// - ``link(_:)``
48+
/// - ``symbol(_:)``
49+
public enum RequestV2: Codable {
50+
/// A request to resolve a link
51+
///
52+
/// Your external resolver
53+
case link(String)
54+
/// A request to resolve a symbol based on its precise identifier.
55+
case symbol(String)
56+
57+
// This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled,
58+
// which is not available to Swift Packages without unsafe flags (rdar://78773361).
59+
// This can be removed once that is available and applied to Swift-DocC (rdar://89033233).
60+
@available(*, deprecated, message: """
61+
This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one.
62+
Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for.
63+
""")
64+
case _nonFrozenEnum_useDefaultCase
65+
66+
private enum CodingKeys: CodingKey {
67+
case link, symbol // Default requests keys
68+
}
69+
70+
public func encode(to encoder: any Encoder) throws {
71+
var container = encoder.container(keyedBy: CodingKeys.self)
72+
switch self {
73+
case .link(let link): try container.encode(link, forKey: .link)
74+
case .symbol(let id): try container.encode(id, forKey: .symbol)
75+
76+
case ._nonFrozenEnum_useDefaultCase:
77+
fatalError("Never use '_nonFrozenEnum_useDefaultCase' as a real case.")
78+
}
79+
}
80+
81+
public init(from decoder: any Decoder) throws {
82+
let container = try decoder.container(keyedBy: CodingKeys.self)
83+
self = switch container.allKeys.first {
84+
case .link?: .link( try container.decode(String.self, forKey: .link))
85+
case .symbol?: .symbol(try container.decode(String.self, forKey: .symbol))
86+
case nil: throw OutOfProcessReferenceResolver.Error.unknownTypeOfRequest
87+
}
88+
}
89+
}
90+
91+
/// A response message from the external link resolver.
92+
///
93+
/// If your external resolver sends a response that's associated with a capability that DocC hasn't declared support for, then DocC will fail to handle the response.
94+
public enum ResponseV2: Codable {
95+
/// The initial identifier and capabilities message.
96+
///
97+
/// Your external link resolver should send this message, exactly once, after it has launched to signal that its ready to receive requests.
98+
///
99+
/// The capabilities that your external link resolver declares in this message determines which optional request messages that DocC will send.
100+
/// If your resolver doesn't declare _any_ capabilities it only needs to handle the 3 default requests. See <doc:RequestV2#Base-requests>.
101+
case identifierAndCapabilities(DocumentationBundle.Identifier, Capabilities)
102+
/// The error message of the problem that the external link resolver encountered while resolving the requested topic or symbol.
103+
case failure(DiagnosticInformation)
104+
/// A response with the resolved information about the requested topic or symbol.
105+
case resolved(LinkDestinationSummary)
106+
107+
// This empty-marker case is here because non-frozen enums are only available when Library Evolution is enabled,
108+
// which is not available to Swift Packages without unsafe flags (rdar://78773361).
109+
// This can be removed once that is available and applied to Swift-DocC (rdar://89033233).
110+
@available(*, deprecated, message: """
111+
This enum is non-frozen and may be expanded in the future; add a `default` case, and do nothing in it, instead of matching this one.
112+
Your external link resolver won't be passed new messages that it hasn't declared the corresponding capability for.
113+
""")
114+
case _nonFrozenEnum_useDefaultCase
115+
116+
private enum CodingKeys: String, CodingKey {
117+
// Default response keys
118+
case identifier, capabilities
119+
case failure
120+
case resolved
121+
}
122+
123+
public init(from decoder: any Decoder) throws {
124+
let container = try decoder.container(keyedBy: CodingKeys.self)
125+
self = switch container.allKeys.first {
126+
case .identifier?, .capabilities?:
127+
.identifierAndCapabilities(
128+
try container.decode(DocumentationBundle.Identifier.self, forKey: .identifier),
129+
try container.decode(Capabilities.self, forKey: .capabilities)
130+
)
131+
case .failure?:
132+
.failure(try container.decode(DiagnosticInformation.self, forKey: .failure))
133+
case .resolved?:
134+
.resolved(try container.decode(LinkDestinationSummary.self, forKey: .resolved))
135+
case nil:
136+
throw OutOfProcessReferenceResolver.Error.invalidResponseKindFromClient
137+
}
138+
}
139+
140+
public func encode(to encoder: any Encoder) throws {
141+
var container = encoder.container(keyedBy: CodingKeys.self)
142+
switch self {
143+
case .identifierAndCapabilities(let identifier, let capabilities):
144+
try container.encode(identifier, forKey: .identifier)
145+
try container.encode(capabilities, forKey: .capabilities)
146+
147+
case .failure(errorMessage: let diagnosticInfo):
148+
try container.encode(diagnosticInfo, forKey: .failure)
149+
150+
case .resolved(let summary):
151+
try container.encode(summary, forKey: .resolved)
152+
153+
case ._nonFrozenEnum_useDefaultCase:
154+
fatalError("Never use '_nonFrozenEnum_useDefaultCase' for anything.")
155+
}
156+
}
157+
}
158+
}
159+
160+
extension OutOfProcessReferenceResolver.ResponseV2 {
161+
/// Information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request.
162+
public struct DiagnosticInformation: Codable {
163+
/// A brief user-facing summary of the issue that caused the external resolver to fail.
164+
public var summary: String
165+
166+
/// A list of possible suggested solutions that can address the failure.
167+
public var solutions: [Solution]?
168+
169+
/// Creates a new value with information about why the external resolver failed to resolve the `link(_:)`, or `symbol(_:)` request.
170+
/// - Parameters:
171+
/// - summary: A brief user-facing summary of the issue that caused the external resolver to fail.
172+
/// - solutions: Possible possible suggested solutions that can address the failure.
173+
public init(
174+
summary: String,
175+
solutions: [Solution]?
176+
) {
177+
self.summary = summary
178+
self.solutions = solutions
179+
}
180+
181+
/// A possible solution to an external resolver issue.
182+
public struct Solution: Codable {
183+
/// A brief user-facing description of what the solution is.
184+
public var summary: String
185+
/// A full replacement of the link.
186+
public var replacement: String?
187+
188+
/// Creates a new solution to an external resolver issue
189+
/// - Parameters:
190+
/// - summary: A brief user-facing description of what the solution is.
191+
/// - replacement: A full replacement of the link.
192+
public init(summary: String, replacement: String?) {
193+
self.summary = summary
194+
self.replacement = replacement
195+
}
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)