Skip to content

Commit b22d790

Browse files
authored
Merge branch 'main' into input-discovery-contributor-docs
2 parents 8e0527f + 784ba58 commit b22d790

File tree

7 files changed

+448
-68
lines changed

7 files changed

+448
-68
lines changed

Sources/SwiftDocC/Indexing/Navigator/NavigatorIndex.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,8 @@ extension NavigatorIndex {
473473
A `Builder` is a utility class to build a navigator index.
474474

475475
The builder generates an index for content navigation, but also maps important information to filter content based on availability, symbol type, platform and some others.
476-
477-
- Note: The builder is not thread safe and therefore, calling `index(renderNode:)` requires external synchronization in case the process is performed on different threads.
476+
477+
- Note: The builder is not thread safe and therefore, calling `index(renderNode:)` requires external synchronization in case the process is performed on different threads.
478478
*/
479479
open class Builder {
480480

@@ -613,12 +613,13 @@ extension NavigatorIndex {
613613

614614
/// Index a single render `RenderNode`.
615615
/// - Parameter renderNode: The render node to be indexed.
616-
public func index(renderNode: RenderNode) throws {
616+
/// - Parameter ignoringLanguage: Whether language variants should be ignored when indexing this render node.
617+
public func index(renderNode: RenderNode, ignoringLanguage: Bool = false) throws {
617618
// Always index the main render node representation
618619
let language = try index(renderNode, traits: nil)
619620

620621
// Additionally, for Swift want to also index the Objective-C variant, if there is any.
621-
guard language == .swift else {
622+
guard !ignoringLanguage && language == .swift else {
622623
return
623624
}
624625

Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import SymbolKit
1313

1414
typealias OverloadDeclaration = (
1515
declaration: [SymbolGraph.Symbol.DeclarationFragments.Fragment],
16-
reference: ResolvedTopicReference
16+
reference: ResolvedTopicReference,
17+
conformance: ConformanceSection?
1718
)
1819

1920
/// Translates a symbol's declaration into a render node's Declarations section.
@@ -141,7 +142,9 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
141142
commonFragments: commonFragments)
142143
otherDeclarations.append(.init(
143144
tokens: translatedDeclaration,
144-
identifier: overloadDeclaration.reference.absoluteString))
145+
identifier: overloadDeclaration.reference.absoluteString,
146+
conformance: overloadDeclaration.conformance
147+
))
145148

146149
// Add a topic reference to the overload
147150
renderNodeTranslator.collectedTopicReferences.append(
@@ -160,6 +163,8 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
160163
return nil
161164
}
162165

166+
let conformance = renderNodeTranslator.contentRenderer.conformanceSectionFor(overloadReference, collectedConstraints: [:])
167+
163168
let declarationFragments = overload.declarationVariants[trait]?.values
164169
.first?
165170
.declarationFragments
@@ -168,7 +173,7 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
168173
"Overloaded symbols must have declaration fragments."
169174
)
170175
return declarationFragments.map({
171-
(declaration: $0, reference: overloadReference)
176+
(declaration: $0, reference: overloadReference, conformance: conformance)
172177
})
173178
}
174179

@@ -204,13 +209,13 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
204209
// Pre-process the declarations by splitting text fragments apart to increase legibility
205210
let mainDeclaration = declaration.declarationFragments.flatMap(preProcessFragment(_:))
206211
let processedOverloadDeclarations = overloadDeclarations.map({
207-
OverloadDeclaration($0.declaration.flatMap(preProcessFragment(_:)), $0.reference)
212+
OverloadDeclaration($0.declaration.flatMap(preProcessFragment(_:)), $0.reference, $0.conformance)
208213
})
209214

210215
// Collect the "common fragments" so we can highlight the ones that are different
211216
// in each declaration
212217
let commonFragments = commonFragments(
213-
for: (mainDeclaration, renderNode.identifier),
218+
for: (mainDeclaration, renderNode.identifier, nil),
214219
overloadDeclarations: processedOverloadDeclarations)
215220

216221
renderedTokens = translateDeclaration(

Sources/SwiftDocC/Model/Rendering/Symbol/DeclarationsRenderSection.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,14 @@ public struct DeclarationRenderSection: Codable, Equatable {
184184

185185
/// The symbol's identifier.
186186
public let identifier: String
187-
187+
188+
public let conformance: ConformanceSection?
189+
188190
/// Creates a new other declaration for a symbol that is connected to this one, e.g. an overload.
189-
public init(tokens: [Token], identifier: String) {
191+
public init(tokens: [Token], identifier: String, conformance: ConformanceSection? = nil) {
190192
self.tokens = tokens
191193
self.identifier = identifier
194+
self.conformance = conformance
192195
}
193196
}
194197
/// The displayable declarations for this symbol's overloads.

Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,6 +2591,9 @@
25912591
},
25922592
"identifier": {
25932593
"type": "string"
2594+
},
2595+
"conformance": {
2596+
"$ref" : "#/components/schemas/ConformanceSection"
25942597
}
25952598
}
25962599
},

Tests/SwiftDocCTests/Indexing/NavigatorIndexTests.swift

Lines changed: 89 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,14 @@ Root
703703
}
704704

705705
func testNavigatorIndexGenerationVariantsPayload() throws {
706+
try testNavigatorIndexGenerationVariantsPayload(ignoringLanguage: false)
707+
}
708+
709+
func testNavigatorIndexGenerationVariantsPayloadIgnoringLanguage() throws {
710+
try testNavigatorIndexGenerationVariantsPayload(ignoringLanguage: true)
711+
}
712+
713+
private func testNavigatorIndexGenerationVariantsPayload(ignoringLanguage: Bool) throws {
706714
let jsonFile = Bundle.module.url(forResource: "Variant-render-node", withExtension: "json", subdirectory: "Test Resources")!
707715
let jsonData = try Data(contentsOf: jsonFile)
708716

@@ -711,88 +719,112 @@ Root
711719
builder.setup()
712720

713721
let renderNode = try XCTUnwrap(RenderJSONDecoder.makeDecoder().decode(RenderNode.self, from: jsonData))
714-
try builder.index(renderNode: renderNode)
722+
try builder.index(renderNode: renderNode, ignoringLanguage: ignoringLanguage)
715723

716724
builder.finalize()
717725

718726
let navigatorIndex = builder.navigatorIndex!
719727

720728
assertUniqueIDs(node: navigatorIndex.navigatorTree.root)
721-
assertEqualDumps(navigatorIndex.navigatorTree.root.dumpTree(), """
729+
var expectedDump = """
722730
[Root]
731+
732+
"""
733+
734+
if !ignoringLanguage {
735+
expectedDump += """
723736
┣╸Objective-C
724737
┃ ┗╸My Article in Objective-C
725738
┃ ┣╸Task Group 1
726739
┃ ┣╸Task Group 2
727740
┃ ┗╸Task Group 3
741+
742+
"""
743+
}
744+
745+
expectedDump += """
728746
┗╸Swift
729747
┗╸My Article
730748
┣╸Task Group 1
731749
┣╸Task Group 2
732750
┗╸Task Group 3
733-
""")
734-
735-
try XCTAssertEqual(
736-
RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")),
737-
RenderIndex.fromString(#"""
738-
{
739-
"interfaceLanguages": {
740-
"occ": [
751+
"""
752+
753+
assertEqualDumps(navigatorIndex.navigatorTree.root.dumpTree(), expectedDump)
754+
755+
var expectedRenderIndexString = """
756+
{
757+
"interfaceLanguages": {
758+
"""
759+
760+
if !ignoringLanguage {
761+
expectedRenderIndexString += #"""
762+
"occ": [
763+
{
764+
"children": [
765+
{
766+
"title": "Task Group 1",
767+
"type": "groupMarker"
768+
},
769+
{
770+
"title": "Task Group 2",
771+
"type": "groupMarker"
772+
},
741773
{
742-
"children": [
743-
{
744-
"title": "Task Group 1",
745-
"type": "groupMarker"
746-
},
747-
{
748-
"title": "Task Group 2",
749-
"type": "groupMarker"
750-
},
751-
{
752-
"title": "Task Group 3",
753-
"type": "groupMarker"
754-
}
755-
],
756-
"path": "\/documentation\/mykit\/my-article",
757-
"title": "My Article in Objective-C",
758-
"type": "article"
774+
"title": "Task Group 3",
775+
"type": "groupMarker"
759776
}
760777
],
761-
"swift": [
778+
"path": "\/documentation\/mykit\/my-article",
779+
"title": "My Article in Objective-C",
780+
"type": "article"
781+
}
782+
],
783+
"""#
784+
}
785+
786+
expectedRenderIndexString += #"""
787+
"swift": [
788+
{
789+
"children": [
762790
{
763-
"children": [
764-
{
765-
"title": "Task Group 1",
766-
"type": "groupMarker"
767-
},
768-
{
769-
"title": "Task Group 2",
770-
"type": "groupMarker"
771-
},
772-
{
773-
"title": "Task Group 3",
774-
"type": "groupMarker"
775-
}
776-
],
777-
"path": "\/documentation\/mykit\/my-article",
778-
"title": "My Article",
779-
"type": "article"
791+
"title": "Task Group 1",
792+
"type": "groupMarker"
793+
},
794+
{
795+
"title": "Task Group 2",
796+
"type": "groupMarker"
797+
},
798+
{
799+
"title": "Task Group 3",
800+
"type": "groupMarker"
780801
}
781-
]
782-
},
783-
"includedArchiveIdentifiers": [
784-
"org.swift.docc.example"
785-
],
786-
"schemaVersion": {
787-
"major": 0,
788-
"minor": 1,
789-
"patch": 2
802+
],
803+
"path": "\/documentation\/mykit\/my-article",
804+
"title": "My Article",
805+
"type": "article"
790806
}
791-
}
807+
]
792808
"""#
793-
)
809+
810+
expectedRenderIndexString += #"""
811+
},
812+
"includedArchiveIdentifiers": [
813+
"org.swift.docc.example"
814+
],
815+
"schemaVersion": {
816+
"major": 0,
817+
"minor": 1,
818+
"patch": 2
819+
}
820+
}
821+
"""#
822+
823+
try XCTAssertEqual(
824+
RenderIndex.fromURL(targetURL.appendingPathComponent("index.json")),
825+
RenderIndex.fromString(expectedRenderIndexString)
794826
)
795-
827+
796828
try FileManager.default.removeItem(at: targetURL)
797829
}
798830

Tests/SwiftDocCTests/Rendering/DeclarationsRenderSectionTests.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,50 @@ class DeclarationsRenderSectionTests: XCTestCase {
356356
XCTAssert(declarations.tokens.allSatisfy({ $0.highlight == nil }))
357357
}
358358
}
359+
360+
func testOverloadConformanceDataIsSavedWithDeclarations() throws {
361+
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)
362+
363+
let symbolGraphFile = Bundle.module.url(
364+
forResource: "ConformanceOverloads",
365+
withExtension: "symbols.json",
366+
subdirectory: "Test Resources"
367+
)!
368+
369+
let tempURL = try createTempFolder(content: [
370+
Folder(name: "unit-test.docc", content: [
371+
InfoPlist(displayName: "ConformanceOverloads", identifier: "com.test.example"),
372+
CopyOfFile(original: symbolGraphFile),
373+
])
374+
])
375+
376+
let (_, bundle, context) = try loadBundle(from: tempURL)
377+
378+
// MyClass<T>
379+
// - myFunc() where T: Equatable
380+
// - myFunc() where T: Hashable // <- overload group
381+
let reference = ResolvedTopicReference(
382+
bundleIdentifier: bundle.identifier,
383+
path: "/documentation/ConformanceOverloads/MyClass/myFunc()",
384+
sourceLanguage: .swift
385+
)
386+
let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Symbol)
387+
var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: reference)
388+
let renderNode = try XCTUnwrap(translator.visitSymbol(symbol) as? RenderNode)
389+
let declarationsSection = try XCTUnwrap(renderNode.primaryContentSections.compactMap({ $0 as? DeclarationsRenderSection }).first)
390+
XCTAssertEqual(declarationsSection.declarations.count, 1)
391+
let declarations = try XCTUnwrap(declarationsSection.declarations.first)
392+
393+
let otherDeclarations = try XCTUnwrap(declarations.otherDeclarations)
394+
XCTAssertEqual(otherDeclarations.declarations.count, 1)
395+
396+
XCTAssertEqual(otherDeclarations.declarations.first?.conformance?.constraints, [
397+
.codeVoice(code: "T"),
398+
.text(" conforms to "),
399+
.codeVoice(code: "Equatable"),
400+
.text("."),
401+
])
402+
}
359403
}
360404

361405
/// Render a list of declaration tokens as a plain-text decoration and as a plain-text rendering of which characters are highlighted.

0 commit comments

Comments
 (0)