Skip to content

Commit bad0aaa

Browse files
j-f1d-ronnqvist
andauthored
Add support for transforming Doxygen discussion/note tags (#798)
* Fix typo in doc comment * Add support for DoxygenDiscussion and DoxygenNote nodes * Update swift-markdown dependency * Add a basic test from @d-ronnqvist Co-Authored-By: David Rönnqvist <[email protected]> * Fix formatted output * Add links to the test * Update swift-markdown --------- Co-authored-by: David Rönnqvist <[email protected]>
1 parent f0a3d7a commit bad0aaa

File tree

4 files changed

+175
-2
lines changed

4 files changed

+175
-2
lines changed

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,29 @@ struct RenderContentCompiler: MarkupVisitor {
354354
return renderableDirective.render(blockDirective, with: &self)
355355
}
356356

357+
mutating func visitDoxygenDiscussion(_ doxygenDiscussion: DoxygenDiscussion) -> [RenderContent] {
358+
doxygenDiscussion.children.flatMap { self.visit($0) }
359+
}
360+
361+
mutating func visitDoxygenNote(_ doxygenNote: DoxygenNote) -> [RenderContent] {
362+
let content: [RenderBlockContent] = doxygenNote.children
363+
.flatMap { self.visit($0) }
364+
.map {
365+
switch $0 {
366+
case let inlineContent as RenderInlineContent:
367+
return .paragraph(.init(inlineContent: [inlineContent]))
368+
case let blockContent as RenderBlockContent:
369+
return blockContent
370+
default:
371+
fatalError("Unexpected content type in note: \(type(of: $0))")
372+
}
373+
}
374+
return [RenderBlockContent.aside(.init(
375+
style: .init(asideKind: .note),
376+
content: content
377+
))]
378+
}
379+
357380
func defaultVisit(_ markup: Markup) -> [RenderContent] {
358381
return []
359382
}

Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ fileprivate func removedLinkDestinationProblem(reference: ResolvedTopicReference
4040
}
4141

4242
/**
43-
Rewrites a ``Markup`` tree to resolve ``UnresolvedTopicReference`s using a ``DocumentationContext``.
43+
Rewrites a ``Markup`` tree to resolve ``UnresolvedTopicReference``s using a ``DocumentationContext``.
4444
*/
4545
struct MarkupReferenceResolver: MarkupRewriter {
4646
var context: DocumentationContext
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
13+
import XCTest
14+
@testable import SwiftDocC
15+
import SwiftDocCTestUtilities
16+
@testable import SymbolKit
17+
18+
class DoxygenTests: XCTestCase {
19+
func testDoxygenDiscussionAndNote() throws {
20+
let documentationLines: [SymbolGraph.LineList.Line] = """
21+
This is an abstract.
22+
23+
@discussion This is a discussion linking to ``AnotherClass`` and ``AnotherClass/prop``.
24+
25+
@note This is a note linking to ``Class3`` and ``Class3/prop2``.
26+
"""
27+
.splitByNewlines
28+
.enumerated()
29+
.map { index, line in
30+
SymbolGraph.LineList.Line(
31+
text: line,
32+
range: .init(start: .init(line: 1 + index, character: 1), end: .init(line: 1 + index, character: 1 + line.utf8.count))
33+
)
34+
}
35+
36+
let tempURL = try createTempFolder(content: [
37+
Folder(name: "unit-test.docc", content: [
38+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
39+
moduleName: "ModuleName",
40+
symbols: [
41+
SymbolGraph.Symbol(
42+
identifier: .init(precise: "some-class-id", interfaceLanguage: SourceLanguage.swift.id),
43+
names: .init(title: "SomeClass", navigator: nil, subHeading: nil, prose: nil),
44+
pathComponents: ["SomeClass"],
45+
docComment: .init(documentationLines),
46+
accessLevel: .public,
47+
kind: .init(parsedIdentifier: .class, displayName: "Kind Display Name"),
48+
mixins: [:]
49+
),
50+
SymbolGraph.Symbol(
51+
identifier: .init(precise: "another-class-id", interfaceLanguage: SourceLanguage.swift.id),
52+
names: .init(title: "AnotherClass", navigator: nil, subHeading: nil, prose: nil),
53+
pathComponents: ["AnotherClass"],
54+
docComment: nil,
55+
accessLevel: .public,
56+
kind: .init(parsedIdentifier: .class, displayName: "Kind Display Name"),
57+
mixins: [:]
58+
),
59+
SymbolGraph.Symbol(
60+
identifier: .init(precise: "another-class-prop-id", interfaceLanguage: SourceLanguage.swift.id),
61+
names: .init(title: "prop", navigator: nil, subHeading: nil, prose: nil),
62+
pathComponents: ["AnotherClass", "prop"],
63+
docComment: nil,
64+
accessLevel: .public,
65+
kind: .init(parsedIdentifier: .property, displayName: "Kind Display Name"),
66+
mixins: [:]
67+
),
68+
SymbolGraph.Symbol(
69+
identifier: .init(precise: "class3-id", interfaceLanguage: SourceLanguage.swift.id),
70+
names: .init(title: "Class3", navigator: nil, subHeading: nil, prose: nil),
71+
pathComponents: ["Class3"],
72+
docComment: nil,
73+
accessLevel: .public,
74+
kind: .init(parsedIdentifier: .class, displayName: "Kind Display Name"),
75+
mixins: [:]
76+
),
77+
SymbolGraph.Symbol(
78+
identifier: .init(precise: "class3-prop-id", interfaceLanguage: SourceLanguage.swift.id),
79+
names: .init(title: "prop", navigator: nil, subHeading: nil, prose: nil),
80+
pathComponents: ["Class3", "prop"],
81+
docComment: nil,
82+
accessLevel: .public,
83+
kind: .init(parsedIdentifier: .property, displayName: "Kind Display Name"),
84+
mixins: [:]
85+
),
86+
]
87+
)),
88+
])
89+
])
90+
91+
let (_, bundle, context) = try loadBundle(from: tempURL)
92+
let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName/SomeClass", sourceLanguage: .swift)
93+
94+
// Verify the expected content in the in-memory model
95+
let node = try context.entity(with: reference)
96+
let symbol = try XCTUnwrap(node.semantic as? Symbol)
97+
98+
XCTAssertEqual(symbol.abstract?.format(), "This is an abstract.")
99+
XCTAssertEqual(symbol.discussion?.content.map { $0.format() }, [
100+
#"\discussion This is a discussion linking to ``doc://unit-test/documentation/ModuleName/AnotherClass`` and ``doc://unit-test/documentation/ModuleName/AnotherClass/prop``."#,
101+
#"\note This is a note linking to ``doc://unit-test/documentation/ModuleName/Class3`` and ``Class3/prop2``."#
102+
])
103+
104+
// Verify the expected content in the render model
105+
var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference, source: nil)
106+
let renderNode = try XCTUnwrap(translator.visit(node.semantic) as? RenderNode)
107+
108+
XCTAssertEqual(renderNode.abstract, [.text("This is an abstract.")])
109+
XCTAssertEqual(renderNode.primaryContentSections.count, 1)
110+
111+
let overviewSection = try XCTUnwrap(renderNode.primaryContentSections.first as? ContentRenderSection)
112+
XCTAssertEqual(overviewSection.content.count, 3)
113+
XCTAssertEqual(overviewSection.content, [
114+
.heading(.init(level: 2, text: "Overview", anchor: "overview")),
115+
116+
.paragraph(.init(inlineContent: [
117+
.text("This is a discussion linking to "),
118+
.reference(
119+
identifier: .init("doc://unit-test/documentation/ModuleName/AnotherClass"),
120+
isActive: true,
121+
overridingTitle: nil,
122+
overridingTitleInlineContent: nil
123+
),
124+
.text(" and "),
125+
.reference(
126+
identifier: .init("doc://unit-test/documentation/ModuleName/AnotherClass/prop"),
127+
isActive: true,
128+
overridingTitle: nil,
129+
overridingTitleInlineContent: nil
130+
),
131+
.text(".")
132+
])),
133+
134+
.aside(.init(style: .init(asideKind: .note), content: [
135+
.paragraph(.init(inlineContent: [
136+
.text("This is a note linking to "),
137+
.reference(
138+
identifier: .init("doc://unit-test/documentation/ModuleName/Class3"),
139+
isActive: true,
140+
overridingTitle: nil,
141+
overridingTitleInlineContent: nil
142+
),
143+
.text(" and "),
144+
.codeVoice(code: "Class3/prop2"),
145+
.text(".")
146+
]))
147+
])),
148+
])
149+
}
150+
}

0 commit comments

Comments
 (0)