Skip to content

Commit 0f9187a

Browse files
authored
Populate the "Attributes" section based on OAS data (#841)
rdar://121475839&121224338
1 parent c141bd3 commit 0f9187a

File tree

6 files changed

+144
-2
lines changed

6 files changed

+144
-2
lines changed

Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
13791379
HTTPBodySectionTranslator(),
13801380
HTTPResponsesSectionTranslator(),
13811381
DictionaryKeysSectionTranslator(),
1382+
AttributesSectionTranslator(),
13821383
ReturnsSectionTranslator(),
13831384
MentionsSectionTranslator(referencingSymbol: identifier),
13841385
DiscussionSectionTranslator(),
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 SymbolKit
13+
14+
/// Translates a symbol's constraints and details into a render node's Attributes section.
15+
struct AttributesSectionTranslator: RenderSectionTranslator {
16+
func translateSection(
17+
for symbol: Symbol,
18+
renderNode: inout RenderNode,
19+
renderNodeTranslator: inout RenderNodeTranslator
20+
) -> VariantCollection<CodableContentSection?>? {
21+
translateSectionToVariantCollection(
22+
documentationDataVariants: symbol.attributesVariants
23+
) { _, attributes in
24+
guard !attributes.isEmpty else { return nil }
25+
26+
func translateFragments(_ fragments: [SymbolGraph.Symbol.DeclarationFragments.Fragment]) -> [DeclarationRenderSection.Token] {
27+
return fragments.map { fragment in
28+
let reference: ResolvedTopicReference?
29+
if let preciseIdentifier = fragment.preciseIdentifier,
30+
let resolved = renderNodeTranslator.context.localOrExternalReference(symbolID: preciseIdentifier)
31+
{
32+
reference = resolved
33+
renderNodeTranslator.collectedTopicReferences.append(resolved)
34+
} else {
35+
reference = nil
36+
}
37+
38+
// Add the declaration token
39+
return DeclarationRenderSection.Token(fragment: fragment, identifier: reference?.absoluteString)
40+
}
41+
}
42+
43+
return AttributesRenderSection(
44+
title: "Attributes",
45+
attributes: attributes.compactMap { kind, attribute in
46+
47+
switch (kind, attribute) {
48+
case (.minimum, let value as SymbolGraph.AnyNumber):
49+
return RenderAttribute.minimum(String(value))
50+
case (.maximum, let value as SymbolGraph.AnyNumber):
51+
return RenderAttribute.maximum(String(value))
52+
case (.minimumExclusive, let value as SymbolGraph.AnyNumber):
53+
return RenderAttribute.minimumExclusive(String(value))
54+
case (.maximumExclusive, let value as SymbolGraph.AnyNumber):
55+
return RenderAttribute.maximumExclusive(String(value))
56+
case (.minimumLength, let value as Int):
57+
return RenderAttribute.minimumLength(String(value))
58+
case (.maximumLength, let value as Int):
59+
return RenderAttribute.maximumLength(String(value))
60+
case (.default, let value as SymbolGraph.AnyScalar):
61+
return RenderAttribute.default(String(value))
62+
case (.allowedTypes, let types as [SymbolGraph.Symbol.TypeDetail]):
63+
let tokens = types.compactMap { $0.fragments.map(translateFragments) }
64+
return RenderAttribute.allowedTypes(tokens)
65+
case (.allowedValues, let values as [SymbolGraph.AnyScalar]):
66+
let stringValues = values.map { String($0) }
67+
return RenderAttribute.allowedValues(stringValues)
68+
default:
69+
return nil
70+
}
71+
72+
}.sorted { $0.title < $1.title }
73+
)
74+
}
75+
}
76+
77+
78+
}

Sources/SwiftDocC/Semantics/Symbol/Symbol.swift

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import SymbolKit
3636
/// - ``accessLevelVariants``
3737
/// - ``deprecatedSummaryVariants``
3838
/// - ``declarationVariants``
39+
/// - ``attributesVariants``
3940
/// - ``locationVariants``
4041
/// - ``constraintsVariants``
4142
/// - ``originVariants``
@@ -145,9 +146,12 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
145146
defaultVariantValue: [:]
146147
)
147148

148-
/// The symbols alternate declarations in each language variant the symbol is available in.
149+
/// The symbol's alternate declarations in each language variant the symbol is available in.
149150
public var alternateDeclarationVariants = DocumentationDataVariants<[[PlatformName?]: [SymbolGraph.Symbol.DeclarationFragments]]>()
150151

152+
/// The symbol's possible values in each language variant the symbol is available in.
153+
public var attributesVariants = DocumentationDataVariants<[RenderAttribute.Kind: Any]>()
154+
151155
public var locationVariants = DocumentationDataVariants<SymbolGraph.Symbol.Location>()
152156

153157
/// The symbol's availability or conformance constraints, in each language variant the symbol is available in.
@@ -304,6 +308,7 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
304308
self.mixinsVariants = mixinsVariants
305309

306310
for (trait, variant) in mixinsVariants.allValues {
311+
var attributes: [RenderAttribute.Kind: Any] = [:]
307312
for item in variant.values {
308313
switch item {
309314
case let declaration as SymbolGraph.Symbol.DeclarationFragments:
@@ -317,9 +322,33 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
317322
self.isSPIVariants[trait] = spi.isSPI
318323
case let alternateDeclarations as SymbolGraph.Symbol.AlternateDeclarations:
319324
self.alternateDeclarationVariants[trait] = [[platformNameVariants[trait]]: alternateDeclarations.declarations]
325+
326+
case let attribute as SymbolGraph.Symbol.Minimum:
327+
attributes[.minimum] = attribute.value
328+
case let attribute as SymbolGraph.Symbol.Maximum:
329+
attributes[.maximum] = attribute.value
330+
case let attribute as SymbolGraph.Symbol.MinimumExclusive:
331+
attributes[.minimumExclusive] = attribute.value
332+
case let attribute as SymbolGraph.Symbol.MaximumExclusive:
333+
attributes[.maximumExclusive] = attribute.value
334+
case let attribute as SymbolGraph.Symbol.MinimumLength:
335+
attributes[.minimumLength] = attribute.value
336+
case let attribute as SymbolGraph.Symbol.MaximumLength:
337+
attributes[.maximumLength] = attribute.value
338+
case let attribute as SymbolGraph.Symbol.DefaultValue:
339+
attributes[.default] = attribute.value
340+
341+
case let attribute as SymbolGraph.Symbol.TypeDetails:
342+
attributes[.allowedTypes] = attribute.value
343+
case let attribute as SymbolGraph.Symbol.AllowedValues:
344+
attributes[.allowedValues] = attribute.value
320345
default: break;
321346
}
322347
}
348+
if !attributes.isEmpty {
349+
self.attributesVariants[trait] = attributes
350+
}
351+
323352
}
324353

325354
if !relationshipsVariants.isEmpty {

Tests/SwiftDocCTests/Model/SemaToRenderNodeDictionaryDataTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,17 @@ class SemaToRenderNodeDictionaryDataTests: XCTestCase {
224224
let outputConsumer = try renderNodeConsumer(for: "DictionaryData")
225225
let genreRenderNode = try outputConsumer.renderNode(withIdentifier: "data:test:Genre")
226226

227-
print(genreRenderNode)
227+
let type1 = DeclarationRenderSection.Token(fragment: SymbolGraph.Symbol.DeclarationFragments.Fragment(kind: .text, spelling: "string", preciseIdentifier: nil), identifier: nil)
228+
let type2 = DeclarationRenderSection.Token(fragment: SymbolGraph.Symbol.DeclarationFragments.Fragment(kind: .text, spelling: "GENCODE", preciseIdentifier: nil), identifier: nil)
229+
228230
assertExpectedContent(
229231
genreRenderNode,
230232
sourceLanguage: "data",
231233
symbolKind: "typealias",
232234
title: "Genre",
233235
navigatorTitle: nil,
234236
abstract: nil,
237+
attributes: [.maximumLength("40"), .allowedTypes([[type1], [type2]]), .allowedValues(["Classic Rock", "Folk", "null"])],
235238
declarationTokens: [
236239
"string ",
237240
"Genre"

Tests/SwiftDocCTests/Test Bundles/DictionaryData.docc/dictionary.symbols.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,12 @@
239239
},
240240
{
241241
"accessLevel": "public",
242+
"allowedValues": [
243+
"Classic Rock",
244+
"Folk",
245+
null
246+
],
247+
"maximumLength": 40,
242248
"declarationFragments": [
243249
{
244250
"kind": "text",
@@ -276,6 +282,21 @@
276282
"typeDetails": [
277283
{
278284
"baseType": "string",
285+
"fragments": [
286+
{
287+
"kind": "text",
288+
"spelling": "string"
289+
}
290+
]
291+
},
292+
{
293+
"baseType": "string",
294+
"fragments": [
295+
{
296+
"kind": "text",
297+
"spelling": "GENCODE"
298+
}
299+
]
279300
}
280301
]
281302
},

Tests/SwiftDocCTests/XCTestCase+AssertingTestData.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ extension XCTestCase {
2323
title expectedTitle: String,
2424
navigatorTitle expectedNavigatorTitle: String?,
2525
abstract expectedAbstract: String?,
26+
attributes expectedAttributes: [RenderAttribute]? = nil,
2627
declarationTokens expectedDeclarationTokens: [String]?,
2728
endpointTokens expectedEndpointTokens: [String]? = nil,
2829
httpParameters expectedHTTPParameters: [String]? = nil,
@@ -62,6 +63,15 @@ extension XCTestCase {
6263
line: line
6364
)
6465

66+
let attributesSection = renderNode.primaryContentSections.compactMap { $0 as? AttributesRenderSection }.first
67+
XCTAssertEqual(
68+
attributesSection?.attributes,
69+
expectedAttributes,
70+
failureMessageForField("attributes"),
71+
file: file,
72+
line: line
73+
)
74+
6575
XCTAssertEqual(
6676
(renderNode.primaryContentSections.first as? DeclarationsRenderSection)?
6777
.declarations

0 commit comments

Comments
 (0)