Skip to content

Commit 930bc62

Browse files
committed
Add @AlternateRepresentation directive
Adds a new child directive to `@Metadata` which can be used in a symbol extension file by specifying the link to another symbol: ```swift @metadata { @AlternateRepresentation(``MyClass/property``) } ``` External links are also supported, as long as they're quoted: ```swift @metadata { @AlternateRepresentation("doc://com.example/documentation/MyClass/property") } ``` The intent of this directive is to define an alternate language representation for the current symbol, such that both symbols are considered to be alternate representations of the same symbol. Ideally two symbols which are equivalent would have the same USR and be resolved as the same symbol by the compiler, but this is not always the case. For the cases in which it is not feasible to change the source code to have them as one symbol, the `@AlternateRepresentation` directive can be used to manually link them as variants of each other. Discussion: ---------- A mutable topic reference type was chosen as the type for parsing&storing the link so that it can be parsed as an unresolved link at the directive parsing stage, and then later be updated to a resolved link at a later stage when the documentation context is resolving all links. A parsing failure diagnostic is returned if the link is invalid in any way: ``` AlternateRepresentation expects an argument for an unnamed parameter that's convertible to 'TopicReference' --> SynonymSample.docc/Symbol.md:4:31-4:37 2 | 3 | @metadata { 4 + @AlternateRepresentation("doc://") 5 | } 6 | ``` This commit adds the directive itself, but doesn't hook it up to other parts of SwiftDocC. Subsequent commits will add: - link resolution - rendering logic Alternatives considered: ----------------------- Considered other names such as `@Synonym`, `@Counterpart`, `@AlternativeDeclaration` and `@VariantOf`. In the end disqualified these as being confusing, and chose `@AlternateRepresentation` for being the one which strikes the best balance between readable and closeness to the technical term for this concept.
1 parent 4b12c49 commit 930bc62

File tree

7 files changed

+407
-2
lines changed

7 files changed

+407
-2
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024 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+
import Foundation
12+
import Markdown
13+
14+
15+
/// A directive that configures an alternate language representations of a symbol.
16+
///
17+
/// An API that can be called from more than one source language has more than one language representation.
18+
///
19+
/// Whenever possible, prefer to define alternative language representations for a symbol by using in-source annotations
20+
/// such as the `@objc` and `@_objcImplementation` attributes in Swift,
21+
/// or the `NS_SWIFT_NAME` macro in Objective C.
22+
///
23+
/// If your source language doesn’t have a mechanism for specifying alternate representations or if your intended alternate representation isn't compatible with those attributes,
24+
/// you can use the `@AlternateRepresentation` directive to specify another symbol that should be considered an alternate representation of the documented symbol.
25+
///
26+
/// ```md
27+
/// @Metadata {
28+
/// @AlternateRepresentation(MyApp/MyClass/property)
29+
/// }
30+
/// ```
31+
/// If you prefer, you can wrap the symbol link in a set of double backticks (\`\`), or use any other supported syntax for linking to symbols.
32+
/// For more information about linking to symbols, see <doc:linking-to-symbols-and-other-content>.
33+
///
34+
/// This provides a hint to the renderer as to the alternate language representations for the current symbol.
35+
/// The renderer may use this hint to provide a link to these alternate symbols.
36+
/// For example, Swift-DocC-Render shows a toggle between supported languages, where switching to a different language representation will redirect to the documentation for the configured alternate symbol.
37+
///
38+
/// ### Special considerations
39+
///
40+
/// Links containing a colon (`:`) must be wrapped in quotes:
41+
/// ```md
42+
/// @Metadata {
43+
/// @AlternateRepresentation("doc://com.example/documentation/MyClass/property")
44+
/// @AlternateRepresentation("MyClass/myFunc(_:_:)")
45+
/// }
46+
/// ```
47+
///
48+
/// The `@AlternateRepresentation` directive only specifies an alternate language representation in one direction.
49+
/// To define a two-way relationship, add an `@AlternateRepresentation` directive, linking to this symbol, to the other symbol as well.
50+
///
51+
/// You can only configure custom alternate language representations for languages that the documented symbol doesn't already have a language representation for,
52+
/// either from in-source annotations or from a previous `@AlternateRepresentation` directive.
53+
public final class AlternateRepresentation: Semantic, AutomaticDirectiveConvertible {
54+
public static let introducedVersion = "6.1"
55+
56+
// Directive parameter definition
57+
58+
/// A link to another symbol that should be considered an alternate language representation of the current symbol.
59+
///
60+
/// If you prefer, you can wrap the symbol link in a set of double backticks (\`\`).
61+
@DirectiveArgumentWrapped(
62+
name: .unnamed,
63+
parseArgument: { _, argumentValue in
64+
// Allow authoring of links with leading and trailing "``"s
65+
var argumentValue = argumentValue
66+
if argumentValue.hasPrefix("``"), argumentValue.hasSuffix("``") {
67+
argumentValue = String(argumentValue.dropFirst(2).dropLast(2))
68+
}
69+
guard let url = ValidatedURL(parsingAuthoredLink: argumentValue), !url.components.path.isEmpty else {
70+
return nil
71+
}
72+
return .unresolved(UnresolvedTopicReference(topicURL: url))
73+
}
74+
)
75+
public internal(set) var reference: TopicReference
76+
77+
static var keyPaths: [String : AnyKeyPath] = [
78+
"reference" : \AlternateRepresentation._reference
79+
]
80+
81+
// Boiler-plate required by conformance to AutomaticDirectiveConvertible
82+
83+
public var originalMarkup: Markdown.BlockDirective
84+
85+
@available(*, deprecated, message: "Do not call directly. Required for 'AutomaticDirectiveConvertible")
86+
init(originalMarkup: Markdown.BlockDirective) {
87+
self.originalMarkup = originalMarkup
88+
}
89+
}

Sources/SwiftDocC/Semantics/Metadata/Metadata.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Markdown
1919
///
2020
/// ### Child Directives
2121
///
22+
/// - ``AlternateRepresentation``
2223
/// - ``DocumentationExtension``
2324
/// - ``TechnologyRoot``
2425
/// - ``DisplayName``
@@ -77,6 +78,9 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
7778

7879
@ChildDirective
7980
var redirects: [Redirect]? = nil
81+
82+
@ChildDirective(requirements: .zeroOrMore)
83+
var alternateRepresentations: [AlternateRepresentation]
8084

8185
static var keyPaths: [String : AnyKeyPath] = [
8286
"documentationOptions" : \Metadata._documentationOptions,
@@ -91,6 +95,7 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
9195
"_pageColor" : \Metadata.__pageColor,
9296
"titleHeading" : \Metadata._titleHeading,
9397
"redirects" : \Metadata._redirects,
98+
"alternateRepresentations" : \Metadata._alternateRepresentations,
9499
]
95100

96101
@available(*, deprecated, message: "Do not call directly. Required for 'AutomaticDirectiveConvertible'.")
@@ -100,7 +105,7 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
100105

101106
func validate(source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) -> Bool {
102107
// Check that something is configured in the metadata block
103-
if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty && customMetadata.isEmpty && callToAction == nil && availability.isEmpty && pageKind == nil && pageColor == nil && titleHeading == nil && redirects == nil {
108+
if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty && customMetadata.isEmpty && callToAction == nil && availability.isEmpty && pageKind == nil && pageColor == nil && titleHeading == nil && redirects == nil && alternateRepresentations.isEmpty {
104109
let diagnostic = Diagnostic(
105110
source: source,
106111
severity: .information,

Sources/SwiftDocC/Utility/MarkupExtensions/BlockDirectiveExtensions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extension BlockDirective {
4141
Metadata.directiveName,
4242
Metadata.Availability.directiveName,
4343
Metadata.PageKind.directiveName,
44+
AlternateRepresentation.directiveName,
4445
MultipleChoice.directiveName,
4546
Options.directiveName,
4647
PageColor.directiveName,

Sources/docc/DocCDocumentation.docc/DocC.symbols.json

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,245 @@
2626

2727
],
2828
"symbols" : [
29+
{
30+
"accessLevel" : "public",
31+
"availability" : [
32+
{
33+
"domain" : "Swift-DocC",
34+
"introduced" : {
35+
"major" : 6,
36+
"minor" : 1,
37+
"patch" : 0
38+
}
39+
}
40+
],
41+
"declarationFragments" : [
42+
{
43+
"kind" : "typeIdentifier",
44+
"spelling" : "@"
45+
},
46+
{
47+
"kind" : "typeIdentifier",
48+
"spelling" : "AlternateRepresentation"
49+
},
50+
{
51+
"kind" : "text",
52+
"spelling" : "("
53+
},
54+
{
55+
"kind" : "text",
56+
"spelling" : "_ "
57+
},
58+
{
59+
"kind" : "identifier",
60+
"spelling" : "reference"
61+
},
62+
{
63+
"kind" : "text",
64+
"spelling" : ": "
65+
},
66+
{
67+
"kind" : "typeIdentifier",
68+
"spelling" : "TopicReference"
69+
},
70+
{
71+
"kind" : "text",
72+
"spelling" : ")"
73+
}
74+
],
75+
"docComment" : {
76+
"lines" : [
77+
{
78+
"text" : "A directive that configures an alternate language representations of a symbol."
79+
},
80+
{
81+
"text" : ""
82+
},
83+
{
84+
"text" : "An API that can be called from more than one source language has more than one language representation."
85+
},
86+
{
87+
"text" : ""
88+
},
89+
{
90+
"text" : "Whenever possible, prefer to define alternative language representations for a symbol by using in-source annotations"
91+
},
92+
{
93+
"text" : "such as the `@objc` and `@_objcImplementation` attributes in Swift,"
94+
},
95+
{
96+
"text" : "or the `NS_SWIFT_NAME` macro in Objective C."
97+
},
98+
{
99+
"text" : ""
100+
},
101+
{
102+
"text" : "If your source language doesn’t have a mechanism for specifying alternate representations or if your intended alternate representation isn't compatible with those attributes,"
103+
},
104+
{
105+
"text" : "you can use the `@AlternateRepresentation` directive to specify another symbol that should be considered an alternate representation of the documented symbol."
106+
},
107+
{
108+
"text" : ""
109+
},
110+
{
111+
"text" : "```md"
112+
},
113+
{
114+
"text" : "@Metadata {"
115+
},
116+
{
117+
"text" : " @AlternateRepresentation(MyApp\/MyClass\/property)"
118+
},
119+
{
120+
"text" : "}"
121+
},
122+
{
123+
"text" : "```"
124+
},
125+
{
126+
"text" : "If you prefer, you can wrap the symbol link in a set of double backticks (\\`\\`), or use any other supported syntax for linking to symbols."
127+
},
128+
{
129+
"text" : "For more information about linking to symbols, see <doc:linking-to-symbols-and-other-content>."
130+
},
131+
{
132+
"text" : ""
133+
},
134+
{
135+
"text" : "This provides a hint to the renderer as to the alternate language representations for the current symbol."
136+
},
137+
{
138+
"text" : "The renderer may use this hint to provide a link to these alternate symbols."
139+
},
140+
{
141+
"text" : "For example, Swift-DocC-Render shows a toggle between supported languages, where switching to a different language representation will redirect to the documentation for the configured alternate symbol."
142+
},
143+
{
144+
"text" : ""
145+
},
146+
{
147+
"text" : "### Special considerations"
148+
},
149+
{
150+
"text" : ""
151+
},
152+
{
153+
"text" : "Links containing a colon (`:`) must be wrapped in quotes:"
154+
},
155+
{
156+
"text" : "```md"
157+
},
158+
{
159+
"text" : "@Metadata {"
160+
},
161+
{
162+
"text" : " @AlternateRepresentation(\"doc:\/\/com.example\/documentation\/MyClass\/property\")"
163+
},
164+
{
165+
"text" : " @AlternateRepresentation(\"MyClass\/myFunc(_:_:)\")"
166+
},
167+
{
168+
"text" : "}"
169+
},
170+
{
171+
"text" : "```"
172+
},
173+
{
174+
"text" : ""
175+
},
176+
{
177+
"text" : "The `@AlternateRepresentation` directive only specifies an alternate language representation in one direction."
178+
},
179+
{
180+
"text" : "To define a two-way relationship, add an `@AlternateRepresentation` directive, linking to this symbol, to the other symbol as well."
181+
},
182+
{
183+
"text" : ""
184+
},
185+
{
186+
"text" : "You can only configure custom alternate language representations for languages that the documented symbol doesn't already have a language representation for,"
187+
},
188+
{
189+
"text" : "either from in-source annotations or from a previous `@AlternateRepresentation` directive."
190+
},
191+
{
192+
"text" : "- Parameters:"
193+
},
194+
{
195+
"text" : " - reference: A link to another symbol that should be considered an alternate language representation of the current symbol."
196+
},
197+
{
198+
"text" : " **(required)**"
199+
},
200+
{
201+
"text" : " "
202+
},
203+
{
204+
"text" : " If you prefer, you can wrap the symbol link in a set of double backticks (\\`\\`)."
205+
}
206+
]
207+
},
208+
"identifier" : {
209+
"interfaceLanguage" : "swift",
210+
"precise" : "__docc_universal_symbol_reference_$AlternateRepresentation"
211+
},
212+
"kind" : {
213+
"displayName" : "Directive",
214+
"identifier" : "class"
215+
},
216+
"names" : {
217+
"navigator" : [
218+
{
219+
"kind" : "attribute",
220+
"spelling" : "@"
221+
},
222+
{
223+
"kind" : "identifier",
224+
"preciseIdentifier" : "__docc_universal_symbol_reference_$AlternateRepresentation",
225+
"spelling" : "AlternateRepresentation"
226+
}
227+
],
228+
"subHeading" : [
229+
{
230+
"kind" : "identifier",
231+
"spelling" : "@"
232+
},
233+
{
234+
"kind" : "identifier",
235+
"spelling" : "AlternateRepresentation"
236+
},
237+
{
238+
"kind" : "text",
239+
"spelling" : "("
240+
},
241+
{
242+
"kind" : "text",
243+
"spelling" : "_ "
244+
},
245+
{
246+
"kind" : "identifier",
247+
"spelling" : "reference"
248+
},
249+
{
250+
"kind" : "text",
251+
"spelling" : ": "
252+
},
253+
{
254+
"kind" : "typeIdentifier",
255+
"spelling" : "TopicReference"
256+
},
257+
{
258+
"kind" : "text",
259+
"spelling" : ")"
260+
}
261+
],
262+
"title" : "AlternateRepresentation"
263+
},
264+
"pathComponents" : [
265+
"AlternateRepresentation"
266+
]
267+
},
29268
{
30269
"accessLevel" : "public",
31270
"availability" : [
@@ -3347,6 +3586,9 @@
33473586
{
33483587
"text" : ""
33493588
},
3589+
{
3590+
"text" : "- ``AlternateRepresentation``"
3591+
},
33503592
{
33513593
"text" : "- ``DocumentationExtension``"
33523594
},

Tests/SwiftDocCTests/Semantics/DirectiveInfrastructure/DirectiveIndexTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class DirectiveIndexTests: XCTestCase {
1717
XCTAssertEqual(
1818
DirectiveIndex.shared.indexedDirectives.keys.sorted(),
1919
[
20+
"AlternateRepresentation",
2021
"Assessments",
2122
"AutomaticArticleSubheading",
2223
"AutomaticSeeAlso",

0 commit comments

Comments
 (0)